461 lines
13 KiB
C
461 lines
13 KiB
C
/*
|
|
* Goodix Gesture Module
|
|
*
|
|
* 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/spinlock.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/input.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/version.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/atomic.h>
|
|
#include "goodix_ts_core.h"
|
|
|
|
#define GSX_GESTURE_CMD 0x08
|
|
|
|
#define QUERYBIT(longlong, bit) (!!(longlong[bit/8] & (1 << bit%8)))
|
|
|
|
#define GSX_MAX_KEY_DATA_LEN 64
|
|
#define GSX_KEY_DATA_LEN 37
|
|
#define GSX_KEY_DATA_LEN_YS 41
|
|
#define GSX_GESTURE_TYPE_LEN 32
|
|
|
|
/*
|
|
* struct gesture_module - gesture module data
|
|
* @registered: module register state
|
|
* @sysfs_node_created: sysfs node state
|
|
* @gesture_type: store valid gesture type,each bit stand for a gesture
|
|
* @gesture_data: gesture data
|
|
* @gesture_ts_cmd: gesture command data
|
|
*/
|
|
struct gesture_module {
|
|
atomic_t registered;
|
|
unsigned int kobj_initialized;
|
|
rwlock_t rwlock;
|
|
unsigned char gesture_type[GSX_GESTURE_TYPE_LEN];
|
|
unsigned char gesture_data[GSX_MAX_KEY_DATA_LEN];
|
|
struct goodix_ext_module module;
|
|
struct goodix_ts_cmd cmd;
|
|
};
|
|
|
|
static int gsx_enter_gesture_mode(struct goodix_ts_device *ts_dev);
|
|
static struct gesture_module *gsx_gesture; /*allocated in gesture init module*/
|
|
|
|
/**
|
|
* gsx_gesture_type_show - show valid gesture type
|
|
*
|
|
* @module: pointer to goodix_ext_module struct
|
|
* @buf: pointer to output buffer
|
|
* Returns >=0 - succeed,< 0 - failed
|
|
*/
|
|
static ssize_t gsx_gesture_type_show(struct goodix_ext_module *module,
|
|
char *buf)
|
|
{
|
|
int count = 0, i, ret = 0;
|
|
unsigned char *type;
|
|
|
|
if (atomic_read(&gsx_gesture->registered) != 1) {
|
|
ts_info("Gesture module not register!");
|
|
return -EPERM;
|
|
}
|
|
type = kzalloc(256, GFP_KERNEL);
|
|
if (!type)
|
|
return -ENOMEM;
|
|
read_lock(&gsx_gesture->rwlock);
|
|
for (i = 0; i < 256; i++) {
|
|
if (QUERYBIT(gsx_gesture->gesture_type, i)) {
|
|
type[count] = i;
|
|
count++;
|
|
}
|
|
}
|
|
type[count] = '\0';
|
|
if (count > 0)
|
|
ret = scnprintf(buf, PAGE_SIZE, "%s", type);
|
|
read_unlock(&gsx_gesture->rwlock);
|
|
|
|
kfree(type);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gsx_gesture_type_store - set vailed gesture
|
|
*
|
|
* @module: pointer to goodix_ext_module struct
|
|
* @buf: pointer to valid gesture type
|
|
* @count: length of buf
|
|
* Returns >0 - valid gestures, < 0 - failed
|
|
*/
|
|
static ssize_t gsx_gesture_type_store(struct goodix_ext_module *module,
|
|
const char *buf, size_t count)
|
|
{
|
|
int i;
|
|
|
|
if (count <= 0 || count > 256 || buf == NULL) {
|
|
ts_err("Parameter error");
|
|
return -EINVAL;
|
|
}
|
|
|
|
write_lock(&gsx_gesture->rwlock);
|
|
memset(gsx_gesture->gesture_type, 0, GSX_GESTURE_TYPE_LEN);
|
|
for (i = 0; i < count; i++)
|
|
gsx_gesture->gesture_type[buf[i]/8] |= (0x1 << buf[i]%8);
|
|
write_unlock(&gsx_gesture->rwlock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t gsx_gesture_enable_show(struct goodix_ext_module *module,
|
|
char *buf)
|
|
{
|
|
return scnprintf(buf, PAGE_SIZE, "%d\n",
|
|
atomic_read(&gsx_gesture->registered));
|
|
}
|
|
|
|
static ssize_t gsx_gesture_enable_store(struct goodix_ext_module *module,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned int tmp;
|
|
int ret;
|
|
|
|
if (sscanf(buf, "%u", &tmp) != 1) {
|
|
ts_info("Parameter illegal");
|
|
return -EINVAL;
|
|
}
|
|
ts_debug("Tmp value =%d", tmp);
|
|
|
|
if (tmp == 1) {
|
|
if (atomic_read(&gsx_gesture->registered)) {
|
|
ts_debug("Gesture module has aready registered");
|
|
return count;
|
|
}
|
|
ret = goodix_register_ext_module(&gsx_gesture->module);
|
|
if (!ret) {
|
|
ts_info("Gesture module registered!");
|
|
atomic_set(&gsx_gesture->registered, 1);
|
|
} else {
|
|
atomic_set(&gsx_gesture->registered, 0);
|
|
ts_err("Gesture module register failed");
|
|
}
|
|
} else if (tmp == 0) {
|
|
if (!atomic_read(&gsx_gesture->registered)) {
|
|
ts_debug("Gesture module has aready unregistered");
|
|
return count;
|
|
}
|
|
ts_debug("Start unregistered gesture module");
|
|
ret = goodix_unregister_ext_module(&gsx_gesture->module);
|
|
if (!ret) {
|
|
atomic_set(&gsx_gesture->registered, 0);
|
|
ts_info("Gesture module unregistered success");
|
|
} else {
|
|
atomic_set(&gsx_gesture->registered, 1);
|
|
ts_info("Gesture module unregistered failed");
|
|
}
|
|
} else {
|
|
ts_err("Parameter error!");
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t gsx_gesture_data_show(struct goodix_ext_module *module,
|
|
char *buf)
|
|
{
|
|
ssize_t count;
|
|
|
|
if (atomic_read(&gsx_gesture->registered) != 1) {
|
|
ts_info("Gesture module not register!");
|
|
return -EPERM;
|
|
}
|
|
if (!buf || !gsx_gesture->gesture_data) {
|
|
ts_info("Parameter error!");
|
|
return -EPERM;
|
|
}
|
|
read_lock(&gsx_gesture->rwlock);
|
|
|
|
count = scnprintf(buf, PAGE_SIZE, "Previous gesture type:0x%x\n",
|
|
gsx_gesture->gesture_data[2]);
|
|
read_unlock(&gsx_gesture->rwlock);
|
|
|
|
return count;
|
|
}
|
|
|
|
const struct goodix_ext_attribute gesture_attrs[] = {
|
|
__EXTMOD_ATTR(type, 0666, gsx_gesture_type_show,
|
|
gsx_gesture_type_store),
|
|
__EXTMOD_ATTR(enable, 0666, gsx_gesture_enable_show,
|
|
gsx_gesture_enable_store),
|
|
__EXTMOD_ATTR(data, 0444, gsx_gesture_data_show, NULL)
|
|
};
|
|
|
|
static int gsx_enter_gesture_mode(struct goodix_ts_device *ts_dev)
|
|
{
|
|
if (!gsx_gesture->cmd.initialized) {
|
|
if (!ts_dev->reg.command) {
|
|
ts_err("command reg can not be null");
|
|
return -EINVAL;
|
|
}
|
|
if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE) {
|
|
gsx_gesture->cmd.cmd_reg = ts_dev->reg.command;
|
|
gsx_gesture->cmd.length = 5;
|
|
gsx_gesture->cmd.cmds[0] = GSX_GESTURE_CMD;
|
|
gsx_gesture->cmd.cmds[1] = 0x0;
|
|
gsx_gesture->cmd.cmds[2] = 0x0;
|
|
gsx_gesture->cmd.cmds[3] = 0x0;
|
|
gsx_gesture->cmd.cmds[4] = GSX_GESTURE_CMD;
|
|
gsx_gesture->cmd.initialized = 1;
|
|
|
|
} else {
|
|
gsx_gesture->cmd.cmd_reg = ts_dev->reg.command;
|
|
gsx_gesture->cmd.length = 3;
|
|
gsx_gesture->cmd.cmds[0] = GSX_GESTURE_CMD;
|
|
gsx_gesture->cmd.cmds[1] = 0x0;
|
|
gsx_gesture->cmd.cmds[2] = 0 - GSX_GESTURE_CMD;
|
|
gsx_gesture->cmd.initialized = 1;
|
|
}
|
|
}
|
|
|
|
return ts_dev->hw_ops->send_cmd(ts_dev, &gsx_gesture->cmd);
|
|
}
|
|
|
|
static int gsx_gesture_init(struct goodix_ts_core *core_data,
|
|
struct goodix_ext_module *module)
|
|
{
|
|
int i, ret = -EINVAL;
|
|
struct goodix_ts_device *ts_dev = core_data->ts_dev;
|
|
|
|
if (!core_data || !ts_dev->hw_ops->write || !ts_dev->hw_ops->read) {
|
|
ts_err("Register gesture module failed, ts_core unsupported");
|
|
goto exit_gesture_init;
|
|
}
|
|
|
|
memset(gsx_gesture->gesture_type, 0, GSX_GESTURE_TYPE_LEN);
|
|
memset(gsx_gesture->gesture_data, 0xff,
|
|
sizeof(gsx_gesture->gesture_data));
|
|
|
|
ts_debug("Set gesture type manually");
|
|
/* set all bit to 1 to enable all gesture wakeup */
|
|
memset(gsx_gesture->gesture_type, 0xff, GSX_GESTURE_TYPE_LEN);
|
|
|
|
if (gsx_gesture->kobj_initialized) {
|
|
ret = 0;
|
|
goto exit_gesture_init;
|
|
}
|
|
|
|
ret = kobject_init_and_add(&module->kobj, goodix_get_default_ktype(),
|
|
&core_data->pdev->dev.kobj, "gesture");
|
|
|
|
if (ret) {
|
|
ts_err("Create gesture sysfs node error!");
|
|
goto exit_gesture_init;
|
|
}
|
|
|
|
ret = 0;
|
|
for (i = 0; i < ARRAY_SIZE(gesture_attrs) && !ret; i++)
|
|
ret = sysfs_create_file(&module->kobj, &gesture_attrs[i].attr);
|
|
if (ret) {
|
|
ts_err("failed create gst sysfs files");
|
|
while (--i >= 0)
|
|
sysfs_remove_file(&module->kobj, &gesture_attrs[i].attr);
|
|
|
|
kobject_put(&module->kobj);
|
|
goto exit_gesture_init;
|
|
}
|
|
|
|
gsx_gesture->kobj_initialized = 1;
|
|
|
|
exit_gesture_init:
|
|
return ret;
|
|
}
|
|
|
|
static int gsx_gesture_exit(struct goodix_ts_core *core_data,
|
|
struct goodix_ext_module *module)
|
|
{
|
|
atomic_set(&gsx_gesture->registered, 0);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* gsx_gesture_ist - Gesture Irq handle
|
|
* This functions is excuted when interrupt happended and
|
|
* ic in doze mode.
|
|
*
|
|
* @core_data: pointer to touch core data
|
|
* @module: pointer to goodix_ext_module struct
|
|
* return: 0 goon execute, EVT_CANCEL_IRQEVT stop execute
|
|
*/
|
|
static int gsx_gesture_ist(struct goodix_ts_core *core_data,
|
|
struct goodix_ext_module *module)
|
|
{
|
|
int ret;
|
|
int key_data_len = 0;
|
|
u8 clear_reg = 0, checksum = 0, gsx_type = 0;
|
|
u8 temp_data[GSX_MAX_KEY_DATA_LEN];
|
|
struct goodix_ts_device *ts_dev = core_data->ts_dev;
|
|
|
|
if (atomic_read(&core_data->suspended) == 0)
|
|
return EVT_CONTINUE;
|
|
|
|
if (!ts_dev->reg.gesture) {
|
|
ts_err("gesture reg can't be null");
|
|
return EVT_CONTINUE;
|
|
}
|
|
/* get ic gesture state*/
|
|
if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE)
|
|
key_data_len = GSX_KEY_DATA_LEN_YS;
|
|
else
|
|
key_data_len = GSX_KEY_DATA_LEN;
|
|
|
|
ret = ts_dev->hw_ops->read_trans(ts_dev, ts_dev->reg.gesture,
|
|
temp_data, key_data_len);
|
|
if (ret < 0 || ((temp_data[0] & GOODIX_GESTURE_EVENT) == 0)) {
|
|
ts_debug("invalid gesture event, ret=%d, temp_data[0]=0x%x",
|
|
ret, temp_data[0]);
|
|
goto re_send_ges_cmd;
|
|
}
|
|
|
|
if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE)
|
|
checksum = checksum_u8_ys(temp_data, key_data_len);
|
|
else
|
|
checksum = checksum_u8(temp_data, key_data_len);
|
|
if (checksum) {
|
|
ts_err("Gesture data checksum error:0x%x", checksum);
|
|
ts_info("Gesture data %*ph",
|
|
(int)sizeof(temp_data), temp_data);
|
|
goto re_send_ges_cmd;
|
|
}
|
|
|
|
ts_debug("Gesture data:");
|
|
ts_debug("data[0-4]0x%x, 0x%x, 0x%x, 0x%x", temp_data[0], temp_data[1],
|
|
temp_data[2], temp_data[3]);
|
|
|
|
write_lock(&gsx_gesture->rwlock);
|
|
memcpy(gsx_gesture->gesture_data, temp_data, key_data_len);
|
|
write_unlock(&gsx_gesture->rwlock);
|
|
|
|
if (ts_dev->ic_type == IC_TYPE_YELLOWSTONE)
|
|
gsx_type = temp_data[3];
|
|
else
|
|
gsx_type = temp_data[2];
|
|
|
|
if (QUERYBIT(gsx_gesture->gesture_type, gsx_type)) {
|
|
/* do resume routine */
|
|
ts_info("Gesture match success, resume IC");
|
|
input_report_key(core_data->input_dev, KEY_POWER, 1);
|
|
input_sync(core_data->input_dev);
|
|
input_report_key(core_data->input_dev, KEY_POWER, 0);
|
|
input_sync(core_data->input_dev);
|
|
goto gesture_ist_exit;
|
|
} else {
|
|
ts_info("Unsupported gesture:%x", temp_data[2]);
|
|
}
|
|
|
|
re_send_ges_cmd:
|
|
if (gsx_enter_gesture_mode(core_data->ts_dev))
|
|
ts_info("warning: failed re_send gesture cmd\n");
|
|
gesture_ist_exit:
|
|
ts_dev->hw_ops->write_trans(ts_dev, ts_dev->reg.gesture,
|
|
&clear_reg, 1);
|
|
return EVT_CANCEL_IRQEVT;
|
|
}
|
|
|
|
/**
|
|
* gsx_gesture_before_suspend - execute gesture suspend routine
|
|
* This functions is excuted to set ic into doze mode
|
|
*
|
|
* @core_data: pointer to touch core data
|
|
* @module: pointer to goodix_ext_module struct
|
|
* return: 0 goon execute, EVT_IRQCANCLED stop execute
|
|
*/
|
|
static int gsx_gesture_before_suspend(struct goodix_ts_core *core_data,
|
|
struct goodix_ext_module *module)
|
|
{
|
|
int ret;
|
|
const struct goodix_ts_hw_ops *hw_ops = core_data->ts_dev->hw_ops;
|
|
struct goodix_ts_cmd *gesture_cmd = &gsx_gesture->cmd;
|
|
|
|
if (!gesture_cmd->initialized || hw_ops == NULL) {
|
|
ts_err("Uninitialized doze command or hw_ops");
|
|
return EVT_CONTINUE;
|
|
}
|
|
|
|
atomic_set(&core_data->suspended, 1);
|
|
ret = gsx_enter_gesture_mode(core_data->ts_dev);
|
|
if (ret != 0)
|
|
ts_err("failed enter gesture mode");
|
|
else
|
|
ts_info("Set IC in gesture mode");
|
|
|
|
return EVT_CANCEL_SUSPEND;
|
|
}
|
|
|
|
static struct goodix_ext_module_funcs gsx_gesture_funcs = {
|
|
.irq_event = gsx_gesture_ist,
|
|
.init = gsx_gesture_init,
|
|
.exit = gsx_gesture_exit,
|
|
.before_suspend = gsx_gesture_before_suspend
|
|
};
|
|
|
|
static int __init goodix_gsx_gesture_init(void)
|
|
{
|
|
/* initialize core_data->ts_dev->gesture_cmd */
|
|
int result;
|
|
ts_info("gesture module init");
|
|
gsx_gesture = kzalloc(sizeof(struct gesture_module), GFP_KERNEL);
|
|
if (!gsx_gesture)
|
|
result = -ENOMEM;
|
|
gsx_gesture->module.funcs = &gsx_gesture_funcs;
|
|
gsx_gesture->module.priority = EXTMOD_PRIO_GESTURE;
|
|
gsx_gesture->module.name = "Goodix_gsx_gesture";
|
|
gsx_gesture->module.priv_data = gsx_gesture;
|
|
gsx_gesture->kobj_initialized = 0;
|
|
atomic_set(&gsx_gesture->registered, 0);
|
|
rwlock_init(&gsx_gesture->rwlock);
|
|
result = goodix_register_ext_module(&(gsx_gesture->module));
|
|
if (result == 0)
|
|
atomic_set(&gsx_gesture->registered, 1);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void __exit goodix_gsx_gesture_exit(void)
|
|
{
|
|
int i, ret;
|
|
ts_info("gesture module exit");
|
|
if (atomic_read(&gsx_gesture->registered)) {
|
|
ret = goodix_unregister_ext_module(&gsx_gesture->module);
|
|
atomic_set(&gsx_gesture->registered, 0);
|
|
}
|
|
if (gsx_gesture->kobj_initialized) {
|
|
for (i = 0; i < ARRAY_SIZE(gesture_attrs); i++)
|
|
sysfs_remove_file(&gsx_gesture->module.kobj,
|
|
&gesture_attrs[i].attr);
|
|
|
|
kobject_put(&gsx_gesture->module.kobj);
|
|
}
|
|
|
|
kfree(gsx_gesture);
|
|
}
|
|
|
|
module_init(goodix_gsx_gesture_init);
|
|
module_exit(goodix_gsx_gesture_exit);
|
|
|
|
MODULE_DESCRIPTION("Goodix gsx Touchscreen Gesture Module");
|
|
MODULE_AUTHOR("Goodix, Inc.");
|
|
MODULE_LICENSE("GPL v2");
|