blob: b4a8c7f5c28ac83c9e74e4a9c40a72185d316617 [file] [log] [blame]
/*
* 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"
#define GOOIDX_INPUT_PHYS "goodix_ts/input0"
struct goodix_module goodix_modules;
int core_module_prob_sate = CORE_MODULE_UNPROBED;
static const struct dev_pm_ops dev_pm_ops;
static int goodix_send_ic_config(struct goodix_ts_core *cd, int type);
/**
* __do_register_ext_module - register external module
* to register into touch core modules structure
* return 0 on success, otherwise return < 0
*/
static int __do_register_ext_module(struct goodix_ext_module *module)
{
struct goodix_ext_module *ext_module, *next;
struct list_head *insert_point = &goodix_modules.head;
/* prority level *must* be set */
if (module->priority == EXTMOD_PRIO_RESERVED) {
ts_err("Priority of module [%s] needs to be set", module->name);
return -EINVAL;
}
mutex_lock(&goodix_modules.mutex);
/* find insert point for the specified priority */
if (!list_empty(&goodix_modules.head)) {
list_for_each_entry_safe(
ext_module, next, &goodix_modules.head, list)
{
if (ext_module == module) {
ts_info("Module [%s] already exists",
module->name);
mutex_unlock(&goodix_modules.mutex);
return 0;
}
}
/* smaller priority value with higher priority level */
list_for_each_entry_safe(
ext_module, next, &goodix_modules.head, list)
{
if (ext_module->priority >= module->priority) {
insert_point = &ext_module->list;
break;
}
}
}
if (module->funcs && module->funcs->init) {
if (module->funcs->init(goodix_modules.core_data, module) < 0) {
ts_err("Module [%s] init error",
module->name ? module->name : " ");
mutex_unlock(&goodix_modules.mutex);
return -EFAULT;
}
}
list_add(&module->list, insert_point->prev);
mutex_unlock(&goodix_modules.mutex);
return 0;
}
static void goodix_register_ext_module_work(struct work_struct *work)
{
struct goodix_ext_module *module =
container_of(work, struct goodix_ext_module, work);
ts_info("module register work IN");
/* driver probe failed */
if (core_module_prob_sate != CORE_MODULE_PROB_SUCCESS) {
ts_err("Can't register ext_module core error");
return;
}
if (__do_register_ext_module(module))
ts_err("failed register module: %s", module->name);
else
ts_info("success register module: %s", module->name);
}
static void goodix_core_module_init(void)
{
if (goodix_modules.initialized)
return;
goodix_modules.initialized = true;
INIT_LIST_HEAD(&goodix_modules.head);
mutex_init(&goodix_modules.mutex);
}
/**
* goodix_register_ext_module - interface for register external module
* to the core. This will create a workqueue to finish the real register
* work and return immediately. The user need to check the final result
* to make sure registe is success or fail.
*
* @module: pointer to external module to be register
* return: 0 ok, <0 failed
*/
int goodix_register_ext_module(struct goodix_ext_module *module)
{
if (!module)
return -EINVAL;
ts_info("IN");
goodix_core_module_init();
INIT_WORK(&module->work, goodix_register_ext_module_work);
schedule_work(&module->work);
ts_info("OUT");
return 0;
}
/**
* goodix_register_ext_module_no_wait
* return: 0 ok, <0 failed
*/
int goodix_register_ext_module_no_wait(struct goodix_ext_module *module)
{
if (!module)
return -EINVAL;
ts_info("IN");
goodix_core_module_init();
/* driver probe failed */
if (core_module_prob_sate != CORE_MODULE_PROB_SUCCESS) {
ts_err("Can't register ext_module core error");
return -EINVAL;
}
return __do_register_ext_module(module);
}
/**
* goodix_unregister_ext_module - interface for external module
* to unregister external modules
*
* @module: pointer to external module
* return: 0 ok, <0 failed
*/
int goodix_unregister_ext_module(struct goodix_ext_module *module)
{
struct goodix_ext_module *ext_module, *next;
bool found = false;
if (!module)
return -EINVAL;
if (!goodix_modules.initialized)
return -EINVAL;
if (!goodix_modules.core_data)
return -ENODEV;
mutex_lock(&goodix_modules.mutex);
if (!list_empty(&goodix_modules.head)) {
list_for_each_entry_safe(
ext_module, next, &goodix_modules.head, list)
{
if (ext_module == module) {
found = true;
break;
}
}
} else {
mutex_unlock(&goodix_modules.mutex);
return 0;
}
if (!found) {
ts_debug("Module [%s] never registed", module->name);
mutex_unlock(&goodix_modules.mutex);
return 0;
}
list_del(&module->list);
mutex_unlock(&goodix_modules.mutex);
if (module->funcs && module->funcs->exit)
module->funcs->exit(goodix_modules.core_data, module);
ts_info("Module [%s] unregistered", module->name ? module->name : " ");
return 0;
}
static void goodix_ext_sysfs_release(struct kobject *kobj)
{
ts_info("Kobject released!");
}
#define to_ext_module(kobj) container_of(kobj, struct goodix_ext_module, kobj)
#define to_ext_attr(attr) container_of(attr, struct goodix_ext_attribute, attr)
static ssize_t goodix_ext_sysfs_show(
struct kobject *kobj, struct attribute *attr, char *buf)
{
struct goodix_ext_module *module = to_ext_module(kobj);
struct goodix_ext_attribute *ext_attr = to_ext_attr(attr);
if (ext_attr->show)
return ext_attr->show(module, buf);
return -EIO;
}
static ssize_t goodix_ext_sysfs_store(struct kobject *kobj,
struct attribute *attr, const char *buf, size_t count)
{
struct goodix_ext_module *module = to_ext_module(kobj);
struct goodix_ext_attribute *ext_attr = to_ext_attr(attr);
if (ext_attr->store)
return ext_attr->store(module, buf, count);
return -EIO;
}
static const struct sysfs_ops goodix_ext_ops = { .show = goodix_ext_sysfs_show,
.store = goodix_ext_sysfs_store };
static struct kobj_type goodix_ext_ktype = {
.release = goodix_ext_sysfs_release,
.sysfs_ops = &goodix_ext_ops,
};
struct kobj_type *goodix_get_default_ktype(void)
{
return &goodix_ext_ktype;
}
struct kobject *goodix_get_default_kobj(void)
{
struct kobject *kobj = NULL;
if (goodix_modules.core_data && goodix_modules.core_data->pdev)
kobj = &goodix_modules.core_data->pdev->dev.kobj;
return kobj;
}
/* 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;
}
/* 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)
{
if (!buf || count <= 0)
return -EINVAL;
if (buf[0] != '0')
goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
else
goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
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;
}
#define GOODIX_MAX_PEN_FREQ_DATA_LEN 16
#define GOODIX_HOGP_INFO_LEN 3
#pragma pack(1)
struct goodix_hid_hogp {
u16 pressure;
u8 key;
};
#pragma pack()
struct goodix_ble_data {
u8 freq[GOODIX_MAX_PEN_FREQ_DATA_LEN];
u8 hogp[GOODIX_HOGP_INFO_LEN];
int hogp_ready;
int freq_ready;
struct mutex lock;
} goodix_ble_data;
int goodix_update_pen_freq(struct goodix_ts_core *cd, u8 *data, int len)
{
if (len > sizeof(goodix_ble_data.freq)) {
ts_err("pen freq data exceed limit");
return -EINVAL;
}
mutex_lock(&goodix_ble_data.lock);
memset(goodix_ble_data.freq, 0, sizeof(goodix_ble_data.freq));
memcpy(goodix_ble_data.freq, data, len);
goodix_ble_data.freq_ready = 1;
mutex_unlock(&goodix_ble_data.lock);
sysfs_notify(&cd->pdev->dev.kobj, NULL, "pen_freq");
ts_debug("send pen freq hop event");
return 0;
}
/* debug level show */
static ssize_t goodix_ts_pen_freq_show(
struct device *dev, struct device_attribute *attr, char *buf)
{
mutex_lock(&goodix_ble_data.lock);
memcpy(buf, goodix_ble_data.freq, sizeof(goodix_ble_data.freq));
goodix_ble_data.freq_ready = 0;
mutex_unlock(&goodix_ble_data.lock);
return sizeof(goodix_ble_data.freq);
}
/* debug level store */
static ssize_t goodix_ts_pen_debug_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int pen_freq;
struct goodix_ts_core *core_data = dev_get_drvdata(dev);
sscanf(buf, "%d", &pen_freq);
ts_debug("set new pen_freq %d", pen_freq);
goodix_ble_data.freq[0] = 0xC0;
goodix_ble_data.freq[1] = 1;
goodix_ble_data.freq[2] = pen_freq & 0xFF;
sysfs_notify(&core_data->pdev->dev.kobj, NULL, "pen_freq");
return count;
}
static ssize_t goodix_ts_pen_hogp_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct goodix_hid_hogp *tmp_prs;
if (count < sizeof(goodix_ble_data.hogp)) {
ts_err("data count to short");
return -EINVAL;
}
mutex_lock(&goodix_ble_data.lock);
memcpy(goodix_ble_data.hogp, buf, sizeof(goodix_ble_data.hogp));
goodix_ble_data.hogp_ready = 1;
mutex_unlock(&goodix_ble_data.lock);
tmp_prs = (struct goodix_hid_hogp *)goodix_ble_data.hogp;
ts_debug("set ble pen data: %d, key %x", tmp_prs->pressure,
tmp_prs->key);
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(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_freq, 0440, goodix_ts_pen_freq_show, NULL);
static DEVICE_ATTR(pen_debug, 0220, NULL, goodix_ts_pen_debug_store);
static DEVICE_ATTR(pen_hogp, 0220, NULL, goodix_ts_pen_hogp_store);
static struct attribute *sysfs_attrs[] = {
&dev_attr_driver_info.attr,
&dev_attr_chip_info.attr,
&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_freq.attr,
&dev_attr_pen_debug.attr,
&dev_attr_pen_hogp.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);
}
#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_NORMAL_RESET_DELAY_MS);
}
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_blocking_notify(NOTIFY_ESD_ON, NULL);
ts_info("set sense ON");
} else {
goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
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 {
/* close esd */
goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
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_blocking_notify(NOTIFY_ESD_ON, NULL);
}
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 irq & close esd */
cd->hw_ops->irq_enable(cd, false);
goodix_ts_blocking_notify(NOTIFY_ESD_OFF, NULL);
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 irq & esd */
cd->hw_ops->irq_enable(cd, true);
goodix_ts_blocking_notify(NOTIFY_ESD_ON, NULL);
}
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_NORMAL_RESET_DELAY_MS);
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_selftest(void *private_data, struct gti_selftest_cmd *cmd)
{
cmd->result = GTI_SELFTEST_RESULT_DONE;
return driver_test_selftest(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
/* 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)
{
struct proc_dir_entry *proc_entry;
int ret = 0;
proc_entry = proc_mkdir("goodix_ts", NULL);
if (proc_entry == NULL) {
ts_err("failed to create proc entry: goodix_ts");
return -ENOMEM;
}
proc_entry = proc_create_data("goodix_ts/tp_capacitance_data", 0664,
NULL, &rawdata_proc_fops, core_data);
if (proc_entry == NULL) {
ts_err("failed to create proc entry: goodix_ts/tp_capacitance_data");
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/driver_test");
ret = -ENOMEM;
goto err_create_driver;
}
return ret;
err_create_driver:
remove_proc_entry("goodix_ts/tp_capacitance_data", NULL);
err_create_data:
remove_proc_entry("goodix_ts", NULL);
return ret;
}
static void goodix_ts_procfs_exit(struct goodix_ts_core *core_data)
{
driver_test_proc_remove();
remove_proc_entry("goodix_ts/tp_capacitance_data", NULL);
remove_proc_entry("goodix_ts", NULL);
}
/* event notifier */
static BLOCKING_NOTIFIER_HEAD(ts_notifier_list);
/**
* goodix_ts_register_client - register a client notifier
* @nb: notifier block to callback on events
* see enum ts_notify_event in goodix_ts_core.h
*/
int goodix_ts_register_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&ts_notifier_list, nb);
}
/**
* goodix_ts_unregister_client - unregister a client notifier
* @nb: notifier block to callback on events
* see enum ts_notify_event in goodix_ts_core.h
*/
int goodix_ts_unregister_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&ts_notifier_list, nb);
}
/**
* fb_notifier_call_chain - notify clients of fb_events
* see enum ts_notify_event in goodix_ts_core.h
*/
int goodix_ts_blocking_notify(enum ts_notify_event evt, void *v)
{
int ret;
ret = blocking_notifier_call_chain(
&ts_notifier_list, (unsigned long)evt, v);
return ret;
}
#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 (!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 (of_property_read_bool(node, "goodix,panel_map")) {
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));
}
}
/* use default test limits name */
ts_info("use default test limits: %s", 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_err("failed to get udfps-x");
return r;
}
r = of_property_read_u32(node, "goodix,udfps-y", &board_data->udfps_y);
if (r) {
ts_err("failed to get udfps-y");
return r;
}
/* 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;
static unsigned int pen_pressure;
struct goodix_hid_hogp *hogp;
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);
input_report_key(dev, BTN_TOUCH, 1);
input_report_key(dev, pen_data->coords.tool_type, 1);
input_report_abs(dev, ABS_X, pen_data->coords.x);
input_report_abs(dev, ABS_Y, pen_data->coords.y);
mutex_lock(&goodix_ble_data.lock);
if (goodix_ble_data.hogp_ready) {
hogp = (struct goodix_hid_hogp *)goodix_ble_data.hogp;
pen_pressure = hogp->pressure;
ts_debug("update pen pressure from ble %d",
pen_pressure);
}
goodix_ble_data.hogp_ready = 0;
mutex_unlock(&goodix_ble_data.lock);
if (pen_data->coords.p && pen_pressure)
pen_data->coords.p = 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);
} 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);
pen_pressure = 0;
input_report_key(dev, BTN_TOUCH, 0);
input_report_key(dev, pen_data->coords.tool_type, 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_NORMAL_RESET_DELAY_MS);
else if (ts_event->request_code == REQUEST_TYPE_UPDATE)
ret = goodix_do_fw_update(
NULL, UPDATE_MODE_FORCE | UPDATE_MODE_BLOCK |
UPDATE_MODE_SRC_REQUEST);
else if (ts_event->request_code == REQUEST_PEN_FREQ_HOP)
ret = goodix_update_pen_freq(cd, ts_event->request_data,
sizeof(ts_event->request_data));
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_ext_module *ext_module, *next;
struct goodix_ts_event *ts_event = &core_data->ts_event;
struct goodix_ts_esd *ts_esd = &core_data->ts_esd;
int ret;
/*
* 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++;
/* inform external module */
mutex_lock(&goodix_modules.mutex);
list_for_each_entry_safe(ext_module, next, &goodix_modules.head, list)
{
if (!ext_module->funcs->irq_event)
continue;
ret = ext_module->funcs->irq_event(core_data, ext_module);
if (ret == EVT_CANCEL_IRQEVT) {
mutex_unlock(&goodix_modules.mutex);
return IRQ_HANDLED;
}
}
mutex_unlock(&goodix_modules.mutex);
/* 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;
}
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);
}
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);
/* read done */
hw_ops->after_event_handler(core_data);
}
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 r;
input_dev = input_allocate_device();
if (!input_dev) {
ts_err("Failed to allocated input device");
return -ENOMEM;
}
input_dev->name = GOODIX_CORE_DRIVER_NAME;
input_dev->phys = GOOIDX_INPUT_PHYS;
input_dev->uniq = "goodix_ts";
input_dev->id.product = 0xDEAD;
input_dev->id.vendor = 0xBEEF;
input_dev->id.version = 10427;
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);
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 r;
pen_dev = input_allocate_device();
if (!pen_dev) {
ts_err("Failed to allocated pen device");
return -ENOMEM;
}
pen_dev->name = GOODIX_PEN_DRIVER_NAME;
pen_dev->phys = "goodix_ts,pen/input0";
pen_dev->uniq = "goodix_ts,pen";
pen_dev->id.product = 0xDEAD;
pen_dev->id.vendor = 0xBEEF;
pen_dev->id.version = 10427;
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(&goodix_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
*/
static 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
*/
static 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_esd_notifier_callback - notification callback
* under certain condition, we need to turn off/on the esd
* protector, we use kernel notify call chain to achieve this.
*
* for example: before firmware update we need to turn off the
* esd protector and after firmware update finished, we should
* turn on the esd protector.
*/
static int goodix_esd_notifier_callback(
struct notifier_block *nb, unsigned long action, void *data)
{
struct goodix_ts_esd *ts_esd =
container_of(nb, struct goodix_ts_esd, esd_notifier);
switch (action) {
case NOTIFY_FWUPDATE_START:
case NOTIFY_SUSPEND:
case NOTIFY_ESD_OFF:
goodix_ts_esd_off(ts_esd->ts_core);
break;
case NOTIFY_FWUPDATE_FAILED:
case NOTIFY_FWUPDATE_SUCCESS:
case NOTIFY_RESUME:
case NOTIFY_ESD_ON:
goodix_ts_esd_on(ts_esd->ts_core);
break;
default:
break;
}
return 0;
}
/**
* 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);
ts_esd->esd_notifier.notifier_call = goodix_esd_notifier_callback;
goodix_ts_register_notifier(&ts_esd->esd_notifier);
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);
goodix_ts_unregister_notifier(&ts_esd->esd_notifier);
}
#if !IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
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_ext_module *ext_module, *next;
struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
int ret;
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);
/*
* notify suspend event, inform the esd protector
* and charger detector to turn off the work
*/
goodix_ts_blocking_notify(NOTIFY_SUSPEND, NULL);
/* inform external module */
mutex_lock(&goodix_modules.mutex);
if (!list_empty(&goodix_modules.head)) {
list_for_each_entry_safe(
ext_module, next, &goodix_modules.head, list)
{
if (!ext_module->funcs->before_suspend)
continue;
ret = ext_module->funcs->before_suspend(
core_data, ext_module);
if (ret == EVT_CANCEL_SUSPEND) {
mutex_unlock(&goodix_modules.mutex);
ts_info("Canceled by module:%s",
ext_module->name);
goto out;
}
}
}
mutex_unlock(&goodix_modules.mutex);
/* 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);
/* inform exteranl modules */
mutex_lock(&goodix_modules.mutex);
if (!list_empty(&goodix_modules.head)) {
list_for_each_entry_safe(
ext_module, next, &goodix_modules.head, list)
{
if (!ext_module->funcs->after_suspend)
continue;
ret = ext_module->funcs->after_suspend(
core_data, ext_module);
if (ret == EVT_CANCEL_SUSPEND) {
mutex_unlock(&goodix_modules.mutex);
ts_info("Canceled by module:%s",
ext_module->name);
goto out;
}
}
}
mutex_unlock(&goodix_modules.mutex);
goodix_set_pinctrl_state(core_data, PINCTRL_MODE_SUSPEND);
out:
#if !IS_ENABLED(CONFIG_GOOG_TOUCH_INTERFACE)
goodix_ts_release_connects(core_data);
#endif
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->ts_event.gesture_data;
unsigned char gesture_type = gesture_data->gesture_type;
ktime_t now = ktime_get();
bool timeout = gesture_type == GOODIX_GESTURE_FOD_DOWN ?
now >= cd->gesture_up_timeout : now >= cd->gesture_down_timeout;
if (gesture_type != GOODIX_GESTURE_FOD_UP && !timeout) {
queue_delayed_work(cd->event_wq, &cd->monitor_gesture_work,
msecs_to_jiffies(5));
return;
}
if (gesture_type == GOODIX_GESTURE_FOD_UP ||
gesture_type == GOODIX_GESTURE_UNKNOWN) {
if (gesture_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_NORMAL_RESET_DELAY_MS);
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_ext_module *ext_module, *next;
struct goodix_ts_hw_ops *hw_ops = core_data->hw_ops;
struct goodix_gesture_data* gesture_data = &core_data->ts_event.gesture_data;
int ret;
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);
atomic_set(&core_data->suspended, 0);
hw_ops->irq_enable(core_data, false);
mutex_lock(&goodix_modules.mutex);
if (!list_empty(&goodix_modules.head)) {
list_for_each_entry_safe(
ext_module, next, &goodix_modules.head, list)
{
if (!ext_module->funcs->before_resume)
continue;
ret = ext_module->funcs->before_resume(
core_data, ext_module);
if (ret == EVT_CANCEL_RESUME) {
mutex_unlock(&goodix_modules.mutex);
ts_info("Canceled by module:%s",
ext_module->name);
goto out;
}
}
}
mutex_unlock(&goodix_modules.mutex);
if (check_gesture_mode(core_data)) {
gesture_data->gesture_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 {
/* reset device or power on*/
if (core_data->board_data.sleep_enable)
hw_ops->reset(core_data, GOODIX_NORMAL_RESET_DELAY_MS);
else
goodix_ts_power_on(core_data);
}
mutex_lock(&goodix_modules.mutex);
if (!list_empty(&goodix_modules.head)) {
list_for_each_entry_safe(
ext_module, next, &goodix_modules.head, list)
{
if (!ext_module->funcs->after_resume)
continue;
ret = ext_module->funcs->after_resume(
core_data, ext_module);
if (ret == EVT_CANCEL_RESUME) {
mutex_unlock(&goodix_modules.mutex);
ts_info("Canceled by module:%s",
ext_module->name);
goto out;
}
}
}
mutex_unlock(&goodix_modules.mutex);
out:
/* enable irq */
hw_ops->irq_enable(core_data, true);
/* open esd */
goodix_ts_blocking_notify(NOTIFY_RESUME, NULL);
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
/**
* goodix_generic_noti_callback - generic notifier callback
* for goodix touch notification event.
*/
static int goodix_generic_noti_callback(
struct notifier_block *self, unsigned long action, void *data)
{
struct goodix_ts_core *cd =
container_of(self, struct goodix_ts_core, ts_notifier);
const struct goodix_ts_hw_ops *hw_ops = cd->hw_ops;
if (cd->init_stage < CORE_INIT_STAGE2)
return 0;
ts_info("notify event type 0x%x", (unsigned int)action);
switch (action) {
case NOTIFY_FWUPDATE_START:
hw_ops->irq_enable(cd, 0);
break;
case NOTIFY_FWUPDATE_SUCCESS:
case NOTIFY_FWUPDATE_FAILED:
if (hw_ops->read_version(cd, &cd->fw_version))
ts_info("failed read fw version info[ignore]");
hw_ops->irq_enable(cd, 1);
break;
default:
break;
}
return 0;
}
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(&goodix_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->selftest = gti_selftest;
options->get_context_driver = gti_get_context_driver;
options->set_report_rate = gti_set_report_rate;
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();
if (ret < 0) {
ts_err("failed set init gesture");
goto err_init_gesture;
}
#endif
/* inspect init */
ret = inspect_module_init();
if (ret < 0) {
ts_err("failed set init inspect");
goto err_init_inspect;
}
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;
}
/* 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();
err_init_inspect:
#if IS_ENABLED(CONFIG_GOODIX_GESTURE)
gesture_module_exit();
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;
/* 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;
goto upgrade;
}
/* step 2: 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");
upgrade:
/* step 3: 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;
}
ts_info("update flag: 0x%X", update_flag);
ret = goodix_do_fw_update(
cd->ic_configs[CONFIG_TYPE_NORMAL], update_flag);
if (ret)
ts_err("failed do fw update");
/* step 4: get fw version and ic_info
* at this step we believe that the ic is in normal mode,
* if the version info is invalid there must have some
* problem we cann't cover so exit init directly.
*/
ret = hw_ops->read_version(cd, &cd->fw_version);
if (ret) {
ts_err("invalid fw version, abort");
goto uninit_fw;
}
ret = hw_ops->get_ic_info(cd, &cd->ic_info);
if (ret) {
ts_err("invalid ic info, abort");
goto uninit_fw;
}
/* 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;
return 0;
uninit_fw:
goodix_fw_update_uninit();
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_ts_core *core_data = NULL;
struct goodix_bus_interface *bus_interface;
int ret;
ts_info("IN");
bus_interface = pdev->dev.platform_data;
if (!bus_interface) {
ts_err("Invalid touch device");
core_module_prob_sate = CORE_MODULE_PROB_FAILED;
return -ENODEV;
}
core_data = devm_kzalloc(
&pdev->dev, sizeof(struct goodix_ts_core), GFP_KERNEL);
if (!core_data) {
core_module_prob_sate = CORE_MODULE_PROB_FAILED;
return -ENOMEM;
}
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");
core_module_prob_sate = CORE_MODULE_PROB_FAILED;
return -EINVAL;
}
mutex_init(&core_data->cmd_lock);
goodix_core_module_init();
/* 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;
}
/* generic notifier callback */
core_data->ts_notifier.notifier_call = goodix_generic_noti_callback;
goodix_ts_register_notifier(&core_data->ts_notifier);
/* debug node init */
ret = goodix_tools_init();
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;
goodix_modules.core_data = core_data;
core_module_prob_sate = CORE_MODULE_PROB_SUCCESS;
/* 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("goodix_ts_core probe success");
return 0;
err_start_late_init:
goodix_tools_exit();
err_init_tools:
goodix_ts_unregister_notifier(&core_data->ts_notifier);
goodix_ts_power_off(core_data);
err_setup_gpio:
goodix_set_pinctrl_state(core_data, PINCTRL_MODE_SUSPEND);
err_out:
mutex_destroy(&core_data->cmd_lock);
core_data->init_stage = CORE_INIT_FAIL;
core_module_prob_sate = CORE_MODULE_PROB_FAILED;
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;
if (core_data->init_stage >= CORE_INIT_STAGE2) {
hw_ops->irq_enable(core_data, false);
inspect_module_exit();
#if IS_ENABLED(CONFIG_GOODIX_GESTURE)
gesture_module_exit();
#endif
core_module_prob_sate = CORE_MODULE_REMOVED;
goodix_ts_esd_uninit(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
destroy_workqueue(core_data->event_wq);
touch_apis_deinit(&core_data->pdev->dev);
goog_touch_interface_remove(core_data->gti);
goodix_ts_sysfs_exit(core_data);
#endif
#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_fw_update_uninit();
}
goodix_tools_exit();
goodix_ts_unregister_notifier(&core_data->ts_notifier);
goodix_ts_power_off(core_data);
goodix_set_pinctrl_state(core_data, PINCTRL_MODE_SUSPEND);
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);
#ifdef CONFIG_TOUCHSCREEN_GOODIX_BRL_SPI
ret = goodix_spi_bus_init();
#else
ret = goodix_i2c_bus_init();
#endif
if (ret) {
ts_err("failed add bus driver");
return ret;
}
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();
#else
goodix_i2c_bus_exit();
#endif
}
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");