blob: 51c853a9d650df090f2967adbe2867f856d58479 [file] [log] [blame]
/*
* Goodix Touchscreen Driver
* Core layer of touchdriver architecture.
*
* Copyright (C) 2019 - 2020 Goodix, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be a reference
* to you, when you are integrating the GOODiX's CTP IC into your system,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/of_platform.h>
#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/of_irq.h>
#ifdef CONFIG_FB
#include <linux/notifier.h>
#include <linux/fb.h>
#endif
#include "goodix_ts_core.h"
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 38)
#include <linux/input/mt.h>
#define INPUT_TYPE_B_PROTOCOL
#endif
#define GOOIDX_INPUT_PHYS "goodix_ts/input0"
#define PINCTRL_STATE_ACTIVE "pmx_ts_active"
#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend"
static int goodix_ts_remove(struct platform_device *pdev);
int goodix_start_later_init(struct goodix_ts_core *ts_core);
void goodix_ts_dev_release(void);
void goodix_fw_module_preinit(struct goodix_ts_core *core_data);
struct goodix_module goodix_modules;
static enum goodix_panel_type panel_type = PANEL_TYPE_UNKNOWN;
/**
* __do_register_ext_module - register external module
* to register into touch core modules structure
*/
static void __do_register_ext_module(struct work_struct *work)
{
struct goodix_ext_module *module =
container_of(work, struct goodix_ext_module, work);
struct goodix_ext_module *ext_module, *next;
struct list_head *insert_point = &goodix_modules.head;
ts_info("__do_register_ext_module IN");
if (goodix_modules.core_data &&
!goodix_modules.core_data->initialized) {
ts_err("core layer has exit");
return;
}
if (!goodix_modules.core_data) {
/* waitting for core layer */
if (!wait_for_completion_timeout(&goodix_modules.core_comp,
25 * HZ)) {
ts_err("Module [%s] timeout", module->name);
return;
}
}
/* driver probe failed */
if (!goodix_modules.core_data ||
!goodix_modules.core_data->initialized) {
ts_err("Can't register ext_module core error");
return;
}
ts_info("start register ext_module");
/* prority level *must* be set */
if (module->priority == EXTMOD_PRIO_RESERVED) {
ts_err("Priority of module [%s] needs to be set",
module->name);
return;
}
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) {
ts_info("Module [%s] already exists",
module->name);
mutex_unlock(&goodix_modules.mutex);
return;
}
}
list_for_each_entry_safe(ext_module, next,
&goodix_modules.head, list) {
/* small value of priority have
* higher priority level
*/
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;
}
}
list_add(&module->list, insert_point->prev);
goodix_modules.count++;
mutex_unlock(&goodix_modules.mutex);
ts_info("Module [%s] registered,priority:%u",
module->name,
module->priority);
}
/**
* goodix_register_ext_module - interface for external module
* to register into touch core modules structure
*
* @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;
if (!goodix_modules.initilized) {
goodix_modules.initilized = true;
INIT_LIST_HEAD(&goodix_modules.head);
mutex_init(&goodix_modules.mutex);
init_completion(&goodix_modules.core_comp);
}
ts_info("goodix_register_ext_module IN");
INIT_WORK(&module->work, __do_register_ext_module);
schedule_work(&module->work);
ts_info("goodix_register_ext_module OUT");
return 0;
}
EXPORT_SYMBOL_GPL(goodix_register_ext_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.initilized)
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 -EFAULT;
}
if (!found) {
ts_err("Module [%s] never registed",
module->name);
mutex_unlock(&goodix_modules.mutex);
return -EFAULT;
}
list_del(&module->list);
goodix_modules.count--;
mutex_unlock(&goodix_modules.mutex);
if (module->funcs && module->funcs->exit)
module->funcs->exit(goodix_modules.core_data, module);
ts_info("Moudle [%s] unregistered",
module->name ? module->name : " ");
return 0;
}
EXPORT_SYMBOL_GPL(goodix_unregister_ext_module);
static void goodix_remove_all_ext_modules(void)
{
struct goodix_ext_module *ext_module, *next;
if (!goodix_modules.initilized || !goodix_modules.core_data)
return;
mutex_lock(&goodix_modules.mutex);
if (!list_empty(&goodix_modules.head)) {
list_for_each_entry_safe(ext_module, next,
&goodix_modules.head, list) {
list_del(&ext_module->list);
goodix_modules.count--;
if (ext_module->funcs && ext_module->funcs->exit)
ext_module->funcs->exit(goodix_modules.core_data,
ext_module);
}
}
mutex_unlock(&goodix_modules.mutex);
}
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;
}
EXPORT_SYMBOL_GPL(goodix_get_default_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;
}
EXPORT_SYMBOL_GPL(goodix_get_default_kobj);
/* debug fs */
struct debugfs_buf {
struct debugfs_blob_wrapper buf;
int pos;
struct dentry *dentry;
} goodix_dbg;
void goodix_msg_printf(const char *fmt, ...)
{
va_list args;
int r;
if (!goodix_dbg.dentry)
return;
if (goodix_dbg.pos < goodix_dbg.buf.size) {
va_start(args, fmt);
r = vscnprintf(goodix_dbg.buf.data + goodix_dbg.pos,
goodix_dbg.buf.size - 1, fmt, args);
goodix_dbg.pos += r;
va_end(args);
}
}
EXPORT_SYMBOL_GPL(goodix_msg_printf);
static int goodix_debugfs_init(void)
{
struct dentry *r_b;
goodix_dbg.buf.size = PAGE_SIZE;
goodix_dbg.pos = 0;
goodix_dbg.buf.data = kzalloc(goodix_dbg.buf.size, GFP_KERNEL);
if (goodix_dbg.buf.data == NULL) {
pr_err("Debugfs init failed\n");
goto exit;
}
r_b = debugfs_create_blob("goodix_ts", 0644, NULL, &goodix_dbg.buf);
if (!r_b) {
pr_err("Debugfs create failed\n");
return -ENOENT;
}
goodix_dbg.dentry = r_b;
exit:
return 0;
}
static void goodix_debugfs_exit(void)
{
debugfs_remove(goodix_dbg.dentry);
goodix_dbg.dentry = NULL;
pr_info("Debugfs module exit\n");
}
/* show external module infomation */
static ssize_t goodix_ts_extmod_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct goodix_ext_module *module, *next;
size_t offset = 0;
int r;
mutex_lock(&goodix_modules.mutex);
if (!list_empty(&goodix_modules.head)) {
list_for_each_entry_safe(module, next,
&goodix_modules.head, list) {
r = snprintf(&buf[offset], PAGE_SIZE,
"priority:%u module:%s\n",
module->priority, module->name);
if (r < 0) {
mutex_unlock(&goodix_modules.mutex);
return -EINVAL;
}
offset += r;
}
}
mutex_unlock(&goodix_modules.mutex);
return offset;
}
/* show driver infomation */
static ssize_t goodix_ts_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 goodix_ts_chip_info_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct goodix_ts_core *core_data =
dev_get_drvdata(dev);
struct goodix_ts_device *ts_dev = core_data->ts_dev;
struct goodix_ts_version chip_ver;
int r, cnt = 0;
cnt += snprintf(buf, PAGE_SIZE, "TouchDeviceName:%s\n", ts_dev->name);
if (ts_dev->hw_ops->read_version) {
r = ts_dev->hw_ops->read_version(ts_dev, &chip_ver);
if (!r && chip_ver.valid) {
cnt += snprintf(&buf[cnt], PAGE_SIZE,
"PID:%s\nVID:%02x%02x%02x%02x\nSensID:%02x\n",
chip_ver.pid, chip_ver.vid[0],
chip_ver.vid[1], chip_ver.vid[2],
chip_ver.vid[3], chip_ver.sensor_id);
}
}
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_device *ts_dev = core_data->ts_dev;
int en;
if (sscanf(buf, "%d", &en) != 1)
return -EINVAL;
if (en != 1)
return -EINVAL;
if (ts_dev->hw_ops->reset)
ts_dev->hw_ops->reset(ts_dev);
return count;
}
static ssize_t goodix_ts_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_device *ts_dev = core_data->ts_dev;
int ret, i, offset;
char *cfg_buf;
cfg_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!cfg_buf)
return -ENOMEM;
if (ts_dev->hw_ops->read_config)
ret = ts_dev->hw_ops->read_config(ts_dev, cfg_buf, 0);
else
ret = -EINVAL;
if (ret > 0) {
offset = 0;
for (i = 0; i < ret; i++) {
if (i != 0 && i % 20 == 0)
buf[offset++] = '\n';
offset += snprintf(&buf[offset], PAGE_SIZE - offset,
"%02x,", cfg_buf[i]);
}
}
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,
unsigned char *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\n", 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\n",
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;
}
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_device *ts_dev = core_data->ts_dev;
int en, r;
const struct firmware *cfg_img;
struct goodix_ts_config *config = NULL;
if (sscanf(buf, "%d", &en) != 1)
return -EINVAL;
if (en != 1)
return -EINVAL;
disable_irq(core_data->irq);
/*request configuration*/
r = request_firmware(&cfg_img, GOODIX_DEFAULT_CFG_NAME, dev);
if (r < 0) {
ts_err("cfg file [%s] not available,errno:%d",
GOODIX_DEFAULT_CFG_NAME, r);
goto exit;
} else
ts_info("cfg file [%s] is ready", GOODIX_DEFAULT_CFG_NAME);
config = kzalloc(sizeof(*config), GFP_KERNEL);
if (config == NULL)
goto exit;
/*parse cfg data*/
if (goodix_ts_convert_0x_data(cfg_img->data, cfg_img->size,
config->data, &config->length)) {
ts_err("convert config data FAILED");
goto exit;
}
config->reg_base = ts_dev->reg.cfg_addr;
mutex_init(&config->lock);
config->initialized = TS_CFG_STABLE;
if (ts_dev->hw_ops->send_config)
ts_dev->hw_ops->send_config(ts_dev, config);
exit:
enable_irq(core_data->irq);
kfree(config);
config = NULL;
if (cfg_img) {
release_firmware(cfg_img);
cfg_img = NULL;
}
return count;
}
/* show irq infomation */
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");
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);
int en;
if (sscanf(buf, "%d", &en) != 1)
return -EINVAL;
goodix_ts_irq_enable(core_data, en);
return count;
}
/*reg read/write */
static u16 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)
{
int ret;
struct goodix_ts_core *core_data = dev_get_drvdata(dev);
struct goodix_ts_device *ts_dev = core_data->ts_dev;
if (!rw_addr || !rw_len) {
ts_err("address(0x%x) and length(%d) cann't be null\n",
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 = ts_dev->hw_ops->read(ts_dev, rw_addr, show_buf, rw_len);
if (ret) {
ts_err("failed read addr(%x) length(%d)\n", 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_device *ts_dev = core_data->ts_dev;
char *pos = NULL, *token = NULL;
long result = 0;
int ret, i;
if (!buf || !count) {
ts_err("invalid params\n");
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'\n");
goto err_out;
}
/* get addr */
pos = (char *)buf;
pos += 2;
token = strsep(&pos, ":");
if (!token) {
ts_err("invalid address info\n");
goto err_out;
} else {
if (kstrtol(token, 16, &result)) {
ts_err("failed get addr info\n");
goto err_out;
}
rw_addr = (u16)result;
ts_info("rw addr is 0x%x\n", rw_addr);
}
/* get length */
token = strsep(&pos, ":");
if (!token) {
ts_err("invalid length info\n");
goto err_out;
} else {
if (kstrtol(token, 0, &result)) {
ts_err("failed get length info\n");
goto err_out;
}
rw_len = (u32)result;
ts_info("rw length info is %d\n", rw_len);
if (rw_len > sizeof(store_buf)) {
ts_err("data len > %lu\n", 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\n");
goto err_out;
} else {
if (kstrtol(token, 16, &result)) {
ts_err("failed get data[%d] info\n", i);
goto err_out;
}
store_buf[i] = (u8)result;
ts_info("get data[%d]=0x%x\n", i, store_buf[i]);
}
}
ret = ts_dev->hw_ops->write(ts_dev, rw_addr, store_buf, rw_len);
if (ret) {
ts_err("failed write addr(%x) data %*ph\n", rw_addr,
rw_len, store_buf);
goto err_out;
}
ts_info("%s write to addr (%x) with data %*ph\n",
"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;
}
static DEVICE_ATTR(extmod_info, S_IRUGO, goodix_ts_extmod_show, NULL);
static DEVICE_ATTR(driver_info, S_IRUGO, goodix_ts_driver_info_show, NULL);
static DEVICE_ATTR(chip_info, S_IRUGO, goodix_ts_chip_info_show, NULL);
static DEVICE_ATTR(reset, S_IWUSR | S_IWGRP, NULL, goodix_ts_reset_store);
static DEVICE_ATTR(send_cfg, S_IWUSR | S_IWGRP, NULL, goodix_ts_send_cfg_store);
static DEVICE_ATTR(read_cfg, S_IRUGO, goodix_ts_read_cfg_show, NULL);
static DEVICE_ATTR(irq_info, S_IRUGO | S_IWUSR | S_IWGRP,
goodix_ts_irq_info_show, goodix_ts_irq_info_store);
static DEVICE_ATTR(reg_rw, S_IRUGO | S_IWUSR | S_IWGRP,
goodix_ts_reg_rw_show, goodix_ts_reg_rw_store);
static struct attribute *sysfs_attrs[] = {
&dev_attr_extmod_info.attr,
&dev_attr_driver_info.attr,
&dev_attr_chip_info.attr,
&dev_attr_reset.attr,
&dev_attr_send_cfg.attr,
&dev_attr_read_cfg.attr,
&dev_attr_irq_info.attr,
&dev_attr_reg_rw.attr,
NULL,
};
static const struct attribute_group sysfs_group = {
.attrs = sysfs_attrs,
};
static ssize_t goodix_sysfs_config_write(struct file *file,
struct kobject *kobj, struct bin_attribute *attr,
char *buf, loff_t pos, size_t count)
{
struct platform_device *pdev = container_of(kobj_to_dev(kobj),
struct platform_device, dev);
struct goodix_ts_core *ts_core = platform_get_drvdata(pdev);
struct goodix_ts_device *ts_dev = ts_core->ts_dev;
struct goodix_ts_config *config = NULL;
int ret;
if (pos != 0 || count > GOODIX_CFG_MAX_SIZE) {
ts_info("pos(%d) != 0, cfg size %zu", (int)pos, count);
return -EINVAL;
}
config = kzalloc(sizeof(struct goodix_ts_config), GFP_KERNEL);
if (config == NULL)
return -ENOMEM;
memcpy(config->data, buf, count);
config->length = count;
config->reg_base = ts_dev->reg.cfg_addr;
mutex_init(&config->lock);
config->initialized = true;
ret = ts_dev->hw_ops->send_config(ts_dev, config);
if (ret) {
count = -EINVAL;
ts_err("send config failed %d", ret);
} else {
ts_info("send config success");
}
kfree(config);
return count;
}
static ssize_t goodix_sysfs_config_read(struct file *file,
struct kobject *kobj, struct bin_attribute *attr,
char *buf, loff_t pos, size_t size)
{
struct platform_device *pdev = container_of(kobj_to_dev(kobj),
struct platform_device, dev);
struct goodix_ts_core *ts_core = platform_get_drvdata(pdev);
struct goodix_ts_device *ts_dev = ts_core->ts_dev;
int ret;
ts_debug("pos = %d, size = %zu", (int)pos, size);
if (pos != 0)
return 0;
if (ts_dev->hw_ops->read_config)
ret = ts_dev->hw_ops->read_config(ts_dev, buf, 0);
else
ret = -EINVAL;
ts_debug("read config ret %d", ret);
return ret;
}
static struct bin_attribute goodix_config_bin_attr = {
.attr = {
.name = "config_bin",
.mode = S_IRUGO | S_IWUSR | S_IWGRP,
},
.size = GOODIX_CFG_MAX_SIZE,
.read = goodix_sysfs_config_read,
.write = goodix_sysfs_config_write,
};
static int goodix_ts_sysfs_init(struct goodix_ts_core *core_data)
{
int ret;
ret = sysfs_create_bin_file(&core_data->pdev->dev.kobj,
&goodix_config_bin_attr);
if (ret) {
ts_err("failed create config bin attr");
return ret;
}
ret = sysfs_create_group(&core_data->pdev->dev.kobj, &sysfs_group);
if (ret) {
ts_err("failed create core sysfs group");
sysfs_remove_bin_file(&core_data->pdev->dev.kobj,
&goodix_config_bin_attr);
return ret;
}
return ret;
}
static void goodix_ts_sysfs_exit(struct goodix_ts_core *core_data)
{
sysfs_remove_bin_file(&core_data->pdev->dev.kobj,
&goodix_config_bin_attr);
sysfs_remove_group(&core_data->pdev->dev.kobj, &sysfs_group);
}
/* 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);
}
EXPORT_SYMBOL(goodix_ts_register_notifier);
/**
* 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);
}
EXPORT_SYMBOL(goodix_ts_unregister_notifier);
/**
* 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;
}
EXPORT_SYMBOL_GPL(goodix_ts_blocking_notify);
static void goodix_ts_report_pen(struct input_dev *dev,
struct goodix_pen_data *pen_data)
{
int i;
if (pen_data->coords.status == TS_TOUCH) {
input_report_key(dev, BTN_TOUCH, 1);
input_report_key(dev, pen_data->coords.tool_type, 1);
} else if (pen_data->coords.status == TS_RELEASE) {
input_report_key(dev, BTN_TOUCH, 0);
input_report_key(dev, pen_data->coords.tool_type, 0);
}
if (pen_data->coords.status) {
input_report_abs(dev, ABS_X, pen_data->coords.x);
input_report_abs(dev, ABS_Y, pen_data->coords.y);
input_report_abs(dev, ABS_PRESSURE, pen_data->coords.p);
}
/* report pen button */
for (i = 0; i < GOODIX_MAX_PEN_KEY; i++) {
if (!pen_data->keys[i].status)
continue;
if (pen_data->keys[i].status == TS_TOUCH)
input_report_key(dev, pen_data->keys[i].code, 1);
else if (pen_data->keys[i].status == TS_RELEASE)
input_report_key(dev, pen_data->keys[i].code, 0);
}
input_sync(dev);
}
static void goodix_ts_report_finger(struct input_dev *dev,
struct goodix_touch_data *touch_data)
{
unsigned int touch_num = touch_data->touch_num;
static u32 pre_fin;
int i;
/*first touch down and last touch up condition*/
if (touch_num && !pre_fin)
input_report_key(dev, BTN_TOUCH, 1);
else if (!touch_num && pre_fin)
input_report_key(dev, BTN_TOUCH, 0);
pre_fin = touch_num;
for (i = 0; i < GOODIX_MAX_TOUCH; i++) {
if (!touch_data->coords[i].status)
continue;
if (touch_data->coords[i].status == TS_RELEASE) {
input_mt_slot(dev, i);
input_mt_report_slot_state(dev, MT_TOOL_FINGER, false);
continue;
}
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_X,
touch_data->coords[i].x);
input_report_abs(dev, ABS_MT_POSITION_Y,
touch_data->coords[i].y);
input_report_abs(dev, ABS_Y,
touch_data->coords[i].y);
input_report_abs(dev, ABS_MT_TOUCH_MAJOR,
touch_data->coords[i].w);
}
/* report panel key */
for (i = 0; i < GOODIX_MAX_TP_KEY; i++) {
if (!touch_data->keys[i].status)
continue;
if (touch_data->keys[i].status == TS_TOUCH)
input_report_key(dev, touch_data->keys[i].code, 1);
else if (touch_data->keys[i].status == TS_RELEASE)
input_report_key(dev, touch_data->keys[i].code, 0);
}
input_sync(dev);
}
/**
* goodix_ts_threadirq_func - Bottom half of interrupt
* This functions is excuted in thread context,
* sleep in this function is permit.
*
* @core_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_device *ts_dev = core_data->ts_dev;
struct goodix_ext_module *ext_module, *next;
struct goodix_ts_event *ts_event = &core_data->ts_event;
u8 irq_flag = 0;
int r;
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;
r = ext_module->funcs->irq_event(core_data, ext_module);
if (r == EVT_CANCEL_IRQEVT) {
mutex_unlock(&goodix_modules.mutex);
return IRQ_HANDLED;
}
}
mutex_unlock(&goodix_modules.mutex);
/* read touch data from touch device */
r = ts_dev->hw_ops->event_handler(ts_dev, ts_event);
if (likely(r >= 0)) {
if (ts_event->event_type == EVENT_TOUCH) {
/* report touch */
goodix_ts_report_finger(core_data->input_dev,
&ts_event->touch_data);
}
if (ts_dev->board_data.pen_enable &&
ts_event->event_type == EVENT_PEN) {
goodix_ts_report_pen(core_data->pen_dev,
&ts_event->pen_data);
}
}
/* clean irq flag */
irq_flag = 0;
ts_dev->hw_ops->write_trans(ts_dev, ts_dev->reg.coor, &irq_flag, 1);
return IRQ_HANDLED;
}
/**
* goodix_ts_init_irq - Requset interrput line from system
* @core_data: pointer to touch core data
* return: 0 ok, <0 failed
*/
int goodix_ts_irq_setup(struct goodix_ts_core *core_data)
{
const struct goodix_ts_board_data *ts_bdata = board_data(core_data);
int r;
/* if ts_bdata-> irq is invalid */
if (ts_bdata->irq <= 0)
core_data->irq = gpio_to_irq(ts_bdata->irq_gpio);
else
core_data->irq = ts_bdata->irq;
ts_info("IRQ:%u,flags:%d", core_data->irq, (int)ts_bdata->irq_flags);
r = devm_request_threaded_irq(&core_data->pdev->dev,
core_data->irq, NULL,
goodix_ts_threadirq_func,
ts_bdata->irq_flags | IRQF_ONESHOT,
GOODIX_CORE_DRIVER_NAME,
core_data);
if (r < 0)
ts_err("Failed to requeset threaded irq:%d", r);
else
atomic_set(&core_data->irq_enabled, 1);
return r;
}
/**
* goodix_ts_irq_enable - Enable/Disable a irq
* @core_data: pointer to touch core data
* enable: enable or disable irq
* return: 0 ok, <0 failed
*/
int goodix_ts_irq_enable(struct goodix_ts_core *core_data,
bool enable)
{
if (enable) {
if (!atomic_cmpxchg(&core_data->irq_enabled, 0, 1)) {
enable_irq(core_data->irq);
ts_debug("Irq enabled");
}
} else {
if (atomic_cmpxchg(&core_data->irq_enabled, 1, 0)) {
disable_irq(core_data->irq);
ts_debug("Irq disabled");
}
}
return 0;
}
EXPORT_SYMBOL(goodix_ts_irq_enable);
/**
* 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;
struct device *dev = NULL;
int r = 0;
ts_info("Power init");
/* dev:i2c client device or spi slave device*/
dev = core_data->ts_dev->dev;
ts_bdata = board_data(core_data);
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)) {
r = PTR_ERR(core_data->avdd);
ts_err("Failed to get regulator avdd:%d", r);
core_data->avdd = NULL;
return r;
}
} else {
ts_info("Avdd name is NULL[skip]");
}
return r;
}
/**
* 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 *core_data)
{
struct goodix_ts_board_data *ts_bdata = board_data(core_data);
int r;
ts_info("Device power on");
if (core_data->power_on)
return 0;
if (!core_data->avdd) {
core_data->power_on = 1;
return 0;
}
r = regulator_enable(core_data->avdd);
if (!r) {
ts_info("regulator enable SUCCESS");
if (ts_bdata->power_on_delay_us)
usleep_range(ts_bdata->power_on_delay_us,
ts_bdata->power_on_delay_us);
} else {
ts_err("Failed to enable analog power:%d", r);
return r;
}
core_data->power_on = 1;
return 0;
}
/**
* 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 *core_data)
{
struct goodix_ts_board_data *ts_bdata = board_data(core_data);
int r;
ts_info("Device power off");
if (!core_data->power_on)
return 0;
if (core_data->avdd) {
r = regulator_disable(core_data->avdd);
if (!r) {
ts_info("regulator disable SUCCESS");
if (ts_bdata->power_off_delay_us)
usleep_range(ts_bdata->power_off_delay_us,
ts_bdata->power_off_delay_us);
} else {
ts_err("Failed to disable analog power:%d", r);
return r;
}
}
core_data->power_on = 0;
return 0;
}
#ifdef CONFIG_PINCTRL
/**
* goodix_ts_pinctrl_init - Get pinctrl handler and pinctrl_state
* @core_data: pointer to touch core data
* return: 0 ok, <0 failed
*/
static int goodix_ts_pinctrl_init(struct goodix_ts_core *core_data)
{
int r = 0;
/* get pinctrl handler from of node */
core_data->pinctrl = devm_pinctrl_get(core_data->ts_dev->dev);
if (IS_ERR_OR_NULL(core_data->pinctrl)) {
ts_info("Failed to get pinctrl handler[need confirm]");
core_data->pinctrl = NULL;
return -EINVAL;
}
ts_debug("success get pinctrl");
/* active state */
core_data->pin_sta_active = pinctrl_lookup_state(core_data->pinctrl,
PINCTRL_STATE_ACTIVE);
if (IS_ERR_OR_NULL(core_data->pin_sta_active)) {
r = PTR_ERR(core_data->pin_sta_active);
ts_err("Failed to get pinctrl state:%s, r:%d",
PINCTRL_STATE_ACTIVE, r);
core_data->pin_sta_active = NULL;
goto exit_pinctrl_put;
}
ts_debug("success get avtive pinctrl state");
/* suspend state */
core_data->pin_sta_suspend = pinctrl_lookup_state(core_data->pinctrl,
PINCTRL_STATE_SUSPEND);
if (IS_ERR_OR_NULL(core_data->pin_sta_suspend)) {
r = PTR_ERR(core_data->pin_sta_suspend);
ts_err("Failed to get pinctrl state:%s, r:%d",
PINCTRL_STATE_SUSPEND, r);
core_data->pin_sta_suspend = NULL;
goto exit_pinctrl_put;
}
ts_debug("success get suspend pinctrl state");
return 0;
exit_pinctrl_put:
devm_pinctrl_put(core_data->pinctrl);
core_data->pinctrl = NULL;
return r;
}
#endif
/**
* goodix_ts_gpio_setup - Request gpio resources from GPIO subsysten
* reset_gpio and irq_gpio number are obtained from goodix_ts_device
* which created in hardware layer driver. e.g.goodix_xx_i2c.c
* A goodix_ts_device should set those two fileds to right value
* before registed to touch core driver.
* @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_HIGH, "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;
}
return 0;
}
/**
* goodix_input_set_params - set input parameters
*/
static void goodix_ts_set_input_params(struct input_dev *input_dev,
struct goodix_ts_board_data *ts_bdata)
{
int i;
if (ts_bdata->swap_axis)
swap(ts_bdata->panel_max_x, ts_bdata->panel_max_y);
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_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_Y,
0, ts_bdata->panel_max_y, 0, 0);
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
0, ts_bdata->panel_max_w, 0, 0);
if (ts_bdata->panel_max_key) {
for (i = 0; i < ts_bdata->panel_max_key; i++)
input_set_capability(input_dev, EV_KEY,
ts_bdata->panel_key_map[i]);
}
}
/**
* goodix_ts_input_dev_config - Requset and config a input device
* then register it to input sybsystem.
* NOTE that some hardware layer may provide a input device
* (ts_dev->input_dev not NULL).
* @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;
}
core_data->input_dev = input_dev;
input_set_drvdata(input_dev, core_data);
input_dev->name = GOODIX_CORE_DRIVER_NAME;
input_dev->phys = GOOIDX_INPUT_PHYS;
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);
#ifdef INPUT_PROP_DIRECT
__set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
#endif
/* set input parameters */
goodix_ts_set_input_params(input_dev, ts_bdata);
#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);
r = input_register_device(input_dev);
if (r < 0) {
ts_err("Unable to register input device");
input_free_device(input_dev);
return r;
}
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;
}
core_data->pen_dev = pen_dev;
input_set_drvdata(pen_dev, core_data);
pen_dev->name = GOODIX_PEN_DRIVER_NAME;
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(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,
GOODIX_PEN_MAX_PRESSURE, 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;
}
return 0;
}
void goodix_ts_input_dev_remove(struct goodix_ts_core *core_data)
{
input_unregister_device(core_data->input_dev);
input_free_device(core_data->input_dev);
core_data->input_dev = NULL;
}
void goodix_ts_pen_dev_remove(struct goodix_ts_core *core_data)
{
input_unregister_device(core_data->pen_dev);
input_free_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 *core = container_of(ts_esd,
struct goodix_ts_core, ts_esd);
const struct goodix_ts_hw_ops *hw_ops = ts_hw_ops(core);
u8 data = GOODIX_ESD_TICK_WRITE_DATA;
int r = 0;
if (!atomic_read(&ts_esd->esd_on))
return;
if (hw_ops->check_hw)
r = hw_ops->check_hw(core->ts_dev);
if (r < 0) {
goodix_ts_power_off(core);
goodix_ts_power_on(core);
if (hw_ops->reset)
hw_ops->reset(core->ts_dev);
/*init static esd*/
if (core->ts_dev->ic_type == IC_TYPE_NANJING) {
r = hw_ops->write(core->ts_dev, 0x8043, &data, 1);
if (r < 0)
ts_err("failed init static esd");
}
/*init dynamic esd*/
r = hw_ops->write_trans(core->ts_dev, core->ts_dev->reg.esd,
&data, 1);
if (r < 0)
ts_err("failed init dynamic esd");
} else {
/*init dynamic esd*/
r = hw_ops->write_trans(core->ts_dev,
core->ts_dev->reg.esd,
&data, 1);
if (r < 0)
ts_err("failed init watch dog");
}
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 *core)
{
struct goodix_ts_esd *ts_esd = &core->ts_esd;
if (core->ts_dev->reg.esd == 0)
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 *core)
{
struct goodix_ts_esd *ts_esd = &core->ts_esd;
int ret;
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 *core)
{
struct goodix_ts_esd *ts_esd = &core->ts_esd;
struct goodix_ts_device *dev = core->ts_dev;
u8 data = GOODIX_ESD_TICK_WRITE_DATA;
int r;
if (!dev->hw_ops->check_hw || !dev->reg.esd) {
ts_info("key parameters unset for esd check");
return 0;
}
INIT_DELAYED_WORK(&ts_esd->esd_work, goodix_ts_esd_work);
ts_esd->ts_core = core;
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);
/*init static esd*/
if (dev->ic_type == IC_TYPE_NANJING) {
r = dev->hw_ops->write_trans(core->ts_dev, 0x8043, &data, 1);
if (r < 0)
ts_err("failed init static esd[ignore]");
}
/*init dynamic esd*/
r = dev->hw_ops->write_trans(core->ts_dev, core->ts_dev->reg.esd,
&data, 1);
if (r < 0)
ts_err("failed init dynamic esd[ignore]");
goodix_ts_esd_on(core);
return 0;
}
/**
* 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_device *ts_dev = core_data->ts_dev;
int r;
ts_info("Suspend start");
/*
* 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;
r = ext_module->funcs->before_suspend(core_data,
ext_module);
if (r == EVT_CANCEL_SUSPEND) {
mutex_unlock(&goodix_modules.mutex);
ts_info("Canceled by module:%s",
ext_module->name);
goto out;
}
}
}
mutex_unlock(&goodix_modules.mutex);
/* disable irq */
goodix_ts_irq_enable(core_data, false);
/* let touch ic work in sleep mode */
if (ts_dev && ts_dev->hw_ops->suspend)
ts_dev->hw_ops->suspend(ts_dev);
atomic_set(&core_data->suspended, 1);
#ifdef CONFIG_PINCTRL
if (core_data->pinctrl) {
r = pinctrl_select_state(core_data->pinctrl,
core_data->pin_sta_suspend);
if (r < 0)
ts_err("Failed to select active pinstate, r:%d", r);
}
#endif
/* 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;
r = ext_module->funcs->after_suspend(core_data,
ext_module);
if (r == EVT_CANCEL_SUSPEND) {
mutex_unlock(&goodix_modules.mutex);
ts_info("Canceled by module:%s",
ext_module->name);
goto out;
}
}
}
mutex_unlock(&goodix_modules.mutex);
out:
ts_info("Suspend end");
return 0;
}
/**
* 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_device *ts_dev =
core_data->ts_dev;
int r;
ts_info("Resume start");
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;
r = ext_module->funcs->before_resume(core_data,
ext_module);
if (r == EVT_CANCEL_RESUME) {
mutex_unlock(&goodix_modules.mutex);
ts_info("Canceled by module:%s",
ext_module->name);
goto out;
}
}
}
mutex_unlock(&goodix_modules.mutex);
#ifdef CONFIG_PINCTRL
if (core_data->pinctrl) {
r = pinctrl_select_state(core_data->pinctrl,
core_data->pin_sta_active);
if (r < 0)
ts_err("Failed to select active pinstate, r:%d", r);
}
#endif
atomic_set(&core_data->suspended, 0);
/* resume device */
if (ts_dev && ts_dev->hw_ops->resume)
ts_dev->hw_ops->resume(ts_dev);
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;
r = ext_module->funcs->after_resume(core_data,
ext_module);
if (r == EVT_CANCEL_RESUME) {
mutex_unlock(&goodix_modules.mutex);
ts_info("Canceled by module:%s",
ext_module->name);
goto out;
}
}
}
mutex_unlock(&goodix_modules.mutex);
goodix_ts_irq_enable(core_data, true);
/*
* notify resume event, inform the esd protector
* and charger detector to turn on the work
*/
ts_info("try notify resume");
goodix_ts_blocking_notify(NOTIFY_RESUME, NULL);
out:
ts_debug("Resume end");
return 0;
}
#ifdef 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_EARLY_EVENT_BLANK) {
/* before fb blank */
} else 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
#ifdef CONFIG_HAS_EARLYSUSPEND
/**
* goodix_ts_earlysuspend - Early suspend function
* Called by kernel during system suspend phrase
*/
static void goodix_ts_earlysuspend(struct early_suspend *h)
{
struct goodix_ts_core *core_data =
container_of(h, struct goodix_ts_core,
early_suspend);
goodix_ts_suspend(core_data);
}
/**
* goodix_ts_lateresume - Late resume function
* Called by kernel during system wakeup
*/
static void goodix_ts_lateresume(struct early_suspend *h)
{
struct goodix_ts_core *core_data =
container_of(h, struct goodix_ts_core,
early_suspend);
goodix_ts_resume(core_data);
}
#endif
#ifdef CONFIG_PM
#if !defined(CONFIG_FB) && !defined(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 *ts_core = container_of(self,
struct goodix_ts_core, ts_notifier);
struct goodix_ts_device *ts_dev = ts_device(ts_core);
const struct goodix_ts_hw_ops *hw_ops = ts_hw_ops(ts_core);
int r;
ts_info("notify event type 0x%x", (unsigned int)action);
switch (action) {
case NOTIFY_FWUPDATE_SUCCESS:
case NOTIFY_FWUPDATE_FAILED:
r = hw_ops->read_version(ts_dev, &ts_dev->chip_version);
if (r < 0)
ts_info("failed read fw version info[ignore]");
break;
default:
break;
}
return 0;
}
int goodix_ts_stage2_init(struct goodix_ts_core *core_data)
{
int r;
struct goodix_ts_device *ts_dev = ts_device(core_data);
r = ts_dev->hw_ops->init(ts_dev);
if (r)
return r;
/* send normal-cfg to firmware */
r = ts_dev->hw_ops->send_config(ts_dev, &(ts_dev->normal_cfg));
if (r < 0) {
ts_info("failed send normal config[ignore]");
}
r = ts_dev->hw_ops->read_version(ts_dev, &ts_dev->chip_version);
if (r < 0)
ts_info("failed read fw version info[ignore]");
/* alloc/config/register input device */
r = goodix_ts_input_dev_config(core_data);
if (r < 0) {
ts_err("failed set input device");
return r;
}
if (ts_dev->board_data.pen_enable) {
r = goodix_ts_pen_dev_config(core_data);
if (r < 0) {
ts_err("failed set pen device");
goto err_finger;
}
}
/* request irq line */
r = goodix_ts_irq_setup(core_data);
if (r < 0) {
ts_info("failed set irq");
goto exit;
}
ts_info("success register irq");
#ifdef CONFIG_FB
core_data->fb_notifier.notifier_call = goodix_ts_fb_notifier_callback;
if (fb_register_client(&core_data->fb_notifier))
ts_err("Failed to register fb notifier client:%d", r);
#elif defined(CONFIG_HAS_EARLYSUSPEND)
core_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
core_data->early_suspend.resume = goodix_ts_lateresume;
core_data->early_suspend.suspend = goodix_ts_earlysuspend;
register_early_suspend(&core_data->early_suspend);
#endif
/*create sysfs files*/
goodix_ts_sysfs_init(core_data);
/* esd protector */
goodix_ts_esd_init(core_data);
return 0;
exit:
if (ts_dev->board_data.pen_enable) {
goodix_ts_pen_dev_remove(core_data);
}
err_finger:
goodix_ts_input_dev_remove(core_data);
return r;
}
/**
* goodix_ts_probe - called by kernel when a Goodix touch
* platform driver is added.
*/
static int goodix_ts_probe(struct platform_device *pdev)
{
struct goodix_ts_core *core_data = NULL;
struct goodix_ts_device *ts_device;
int r;
ts_info("goodix_ts_probe IN");
ts_device = pdev->dev.platform_data;
if (!ts_device || !ts_device->hw_ops) {
ts_err("Invalid touch device");
return -ENODEV;
}
core_data = devm_kzalloc(&pdev->dev, sizeof(struct goodix_ts_core),
GFP_KERNEL);
if (!core_data) {
ts_err("Failed to allocate memory for core data");
return -ENOMEM;
}
/* touch core layer is a platform driver */
core_data->pdev = pdev;
core_data->ts_dev = ts_device;
platform_set_drvdata(pdev, core_data);
core_data->cfg_group_parsed = false;
core_data->need_release = false;
core_data->panel_type = panel_type;
r = goodix_ts_power_init(core_data);
if (r < 0)
goto out;
r = goodix_ts_power_on(core_data);
if (r < 0)
goto out;
#ifdef CONFIG_PINCTRL
/* Pinctrl handle is optional. */
r = goodix_ts_pinctrl_init(core_data);
if (!r && core_data->pinctrl) {
r = pinctrl_select_state(core_data->pinctrl,
core_data->pin_sta_active);
if (r < 0)
ts_err("Failed to select active pinstate, r:%d", r);
}
#endif
/* get GPIO resource */
r = goodix_ts_gpio_setup(core_data);
if (r < 0)
goto out;
/* confirm it's goodix touch dev or not */
r = ts_device->hw_ops->dev_confirm(ts_device);
if (r) {
ts_err("goodix device confirm failed");
goto out;
}
msleep(100);
goodix_fw_module_preinit(core_data);
/* Try start a thread to get config-bin info */
r = goodix_start_later_init(core_data);
if (r) {
ts_info("Failed start cfg_bin_proc");
goto out;
}
/* generic notifier callback */
core_data->ts_notifier.notifier_call = goodix_generic_noti_callback;
goodix_ts_register_notifier(&core_data->ts_notifier);
out:
if (r)
core_data->initialized = 0;
else
core_data->initialized = 1;
goodix_modules.core_data = core_data;
ts_info("goodix_ts_probe OUT, r:%d", r);
/* wakeup ext module register work */
complete_all(&goodix_modules.core_comp);
return r;
}
static int goodix_ts_remove(struct platform_device *pdev)
{
struct goodix_ts_core *core_data = platform_get_drvdata(pdev);
core_data->initialized = 0;
if (atomic_read(&core_data->ts_esd.esd_on))
goodix_ts_esd_off(core_data);
goodix_remove_all_ext_modules();
goodix_ts_power_off(core_data);
goodix_debugfs_exit();
goodix_ts_sysfs_exit(core_data);
// can't free the memory for tools or gesture module
//kfree(core_data);
return 0;
}
#ifdef CONFIG_PM
static const struct dev_pm_ops dev_pm_ops = {
#if !defined(CONFIG_FB) && !defined(CONFIG_HAS_EARLYSUSPEND)
.suspend = goodix_ts_pm_suspend,
.resume = goodix_ts_pm_resume,
#endif
};
#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,
#ifdef CONFIG_PM
.pm = &dev_pm_ops,
#endif
},
.probe = goodix_ts_probe,
.remove = goodix_ts_remove,
.id_table = ts_core_ids,
};
static int __init get_panel_type(char *panel_arg)
{
if (*panel_arg == 0) {
ts_err("No panel_type argument found");
panel_type = PANEL_TYPE_UNKNOWN;
return 0;
}
if (strcmp(panel_arg, GOODIX_BOE_FITI9364) == 0) {
ts_info("Using a BOE + Fiti9364 panel");
panel_type = PANEL_TYPE_BOE_FITI9364;
return 0;
} else if (strcmp(panel_arg, GOODIX_BOE_FITI9365) == 0) {
ts_info("Using a BOE + Fiti9365 panel");
panel_type = PANEL_TYPE_BOE_FITI9365;
return 0;
} else if (strcmp(panel_arg, GOODIX_BOE_SIT7703) == 0) {
ts_info("Using a BOE + SIT7703 panel");
panel_type = PANEL_TYPE_BOE_SIT7703;
return 0;
} else if (strcmp(panel_arg, GOODIX_KD_FITI9364) == 0) {
ts_info("Using a KD + Fiti9364 panel");
panel_type = PANEL_TYPE_KD_FITI9364;
return 0;
} else if (strcmp(panel_arg, GOODIX_KD_FITI9365) == 0) {
ts_info("Using a KD + Fiti9365 panel");
panel_type = PANEL_TYPE_KD_FITI9365;
return 0;
} else if (strcmp(panel_arg, GOODIX_INX_FITI9364) == 0) {
ts_info("Using a INX + Fiti9364 panel");
panel_type = PANEL_TYPE_INX_FITI9364;
return 0;
}
ts_err("Using an unknown panel, panel_type=%s", panel_arg);
panel_type = PANEL_TYPE_UNKNOWN;
return 0;
}
__setup("panel_type=", get_panel_type);
int goodix_ts_core_init(void)
{
ts_info("Core layer init");
if (!goodix_modules.initilized) {
/* this may init by outer modules register event */
ts_info("init modules struct");
goodix_modules.initilized = true;
INIT_LIST_HEAD(&goodix_modules.head);
mutex_init(&goodix_modules.mutex);
init_completion(&goodix_modules.core_comp);
}
goodix_debugfs_init();
return platform_driver_register(&goodix_ts_driver);
}
/* uninit module manually */
int goodix_ts_core_release(struct goodix_ts_core *core_data)
{
ts_info("goodix core module removed");
platform_driver_unregister(&goodix_ts_driver);
goodix_ts_dev_release();
return 0;
}