| /* |
| * 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 "goodix_ts_core.h" |
| #include <linux/atomic.h> |
| #include <linux/delay.h> |
| #include <linux/init.h> |
| #include <linux/input.h> |
| #include <linux/input/mt.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/string.h> |
| #include <linux/version.h> |
| /* |
| * [GOOG] |
| * Move GOODIX_GESTURE_* define to goodix_ts_core.h. |
| */ |
| |
| static ssize_t gsx_double_type_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct device *device = |
| container_of(((struct kobject *)dev)->parent, struct device, kobj); |
| struct goodix_ts_core *cd = dev_get_drvdata(device); |
| uint32_t type = cd->gesture_type; |
| |
| return sprintf(buf, "%s\n", |
| (type & GESTURE_DOUBLE_TAP) ? "enable" : "disable"); |
| } |
| |
| static ssize_t gsx_double_type_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct device *device = |
| container_of(((struct kobject *)dev)->parent, struct device, kobj); |
| struct goodix_ts_core *cd = dev_get_drvdata(device); |
| |
| if (buf[0] == '1') { |
| ts_info("enable double tap"); |
| cd->gesture_type |= GESTURE_DOUBLE_TAP; |
| } else if (buf[0] == '0') { |
| ts_info("disable double tap"); |
| cd->gesture_type &= ~GESTURE_DOUBLE_TAP; |
| } else |
| ts_err("invalid cmd[%d]", buf[0]); |
| |
| return count; |
| } |
| |
| static ssize_t gsx_single_type_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct device *device = |
| container_of(((struct kobject *)dev)->parent, struct device, kobj); |
| struct goodix_ts_core *cd = dev_get_drvdata(device); |
| uint32_t type = cd->gesture_type; |
| |
| return sprintf(buf, "%s\n", |
| (type & GESTURE_SINGLE_TAP) ? "enable" : "disable"); |
| } |
| |
| static ssize_t gsx_single_type_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct device *device = |
| container_of(((struct kobject *)dev)->parent, struct device, kobj); |
| struct goodix_ts_core *cd = dev_get_drvdata(device); |
| |
| if (buf[0] == '1') { |
| ts_info("enable single tap"); |
| cd->gesture_type |= GESTURE_SINGLE_TAP; |
| } else if (buf[0] == '0') { |
| ts_info("disable single tap"); |
| cd->gesture_type &= ~GESTURE_SINGLE_TAP; |
| } else |
| ts_err("invalid cmd[%d]", buf[0]); |
| |
| return count; |
| } |
| |
| static ssize_t gsx_fod_type_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct device *device = |
| container_of(((struct kobject *)dev)->parent, struct device, kobj); |
| struct goodix_ts_core *cd = dev_get_drvdata(device); |
| uint32_t type = cd->gesture_type; |
| |
| return sprintf( |
| buf, "%s\n", (type & GESTURE_FOD_PRESS) ? "enable" : "disable"); |
| } |
| |
| static ssize_t gsx_fod_type_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct device *device = |
| container_of(((struct kobject *)dev)->parent, struct device, kobj); |
| struct goodix_ts_core *cd = dev_get_drvdata(device); |
| |
| if (buf[0] == '1') { |
| ts_info("enable fod"); |
| cd->gesture_type |= GESTURE_FOD_PRESS; |
| } else if (buf[0] == '0') { |
| ts_info("disable fod"); |
| cd->gesture_type &= ~GESTURE_FOD_PRESS; |
| } else |
| ts_err("invalid cmd[%d]", buf[0]); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(double_type, 0664, gsx_double_type_show, gsx_double_type_store); |
| static DEVICE_ATTR(single_type, 0664, gsx_single_type_show, gsx_single_type_store); |
| static DEVICE_ATTR(fod_type, 0664, gsx_fod_type_show, gsx_fod_type_store); |
| |
| static struct attribute *gesture_attrs[] = { |
| &dev_attr_double_type.attr, |
| &dev_attr_single_type.attr, |
| &dev_attr_fod_type.attr, |
| NULL, |
| }; |
| |
| const static struct attribute_group gesture_sysfs_group = { |
| .attrs = gesture_attrs, |
| }; |
| |
| /* [GOOG] |
| * Use cd->gesture_data to avoid data racing issue. |
| */ |
| int goodix_ts_report_gesture(struct goodix_ts_core *cd) |
| { |
| int coor_x, coor_y, coor_size, coor_press; |
| int major, minor, orientation; |
| unsigned char event_type = GOODIX_GESTURE_UNKNOWN; |
| |
| mutex_lock(&cd->gesture_data_lock); |
| |
| coor_x = le16_to_cpup((__le16 *)cd->gesture_data.data); |
| coor_y = le16_to_cpup((__le16 *)(cd->gesture_data.data + 2)); |
| coor_size = le16_to_cpup((__le16 *)(cd->gesture_data.data + 4)); |
| coor_press = cd->gesture_data.data[6]; |
| major = le16_to_cpup((__le16 *)(cd->gesture_data.data + 7)); |
| minor = le16_to_cpup((__le16 *)(cd->gesture_data.data + 9)); |
| orientation = (s8)cd->gesture_data.data[11]; |
| event_type = cd->gesture_data.event_type; |
| |
| mutex_unlock(&cd->gesture_data_lock); |
| |
| switch (event_type) { |
| case GOODIX_GESTURE_SINGLE_TAP: |
| if (cd->gesture_type & GESTURE_SINGLE_TAP) { |
| ts_info("get SINGLE-TAP gesture"); |
| ts_debug( |
| "fodx:%d fody:%d size:%d press:%d maj:%d min:%d ori:%d", |
| coor_x, coor_y, coor_size, coor_press, major, |
| minor, orientation); |
| input_report_key(cd->input_dev, BTN_TOUCH, 1); |
| input_mt_slot(cd->input_dev, 0); |
| input_mt_report_slot_state( |
| cd->input_dev, MT_TOOL_FINGER, 1); |
| input_report_abs( |
| cd->input_dev, ABS_MT_POSITION_X, coor_x); |
| input_report_abs( |
| cd->input_dev, ABS_MT_POSITION_Y, coor_y); |
| input_report_abs( |
| cd->input_dev, ABS_MT_PRESSURE, coor_press); |
| input_report_abs( |
| cd->input_dev, ABS_MT_TOUCH_MAJOR, major); |
| input_report_abs( |
| cd->input_dev, ABS_MT_TOUCH_MINOR, minor); |
| input_report_key(cd->input_dev, KEY_WAKEUP, 1); |
| input_sync(cd->input_dev); |
| input_report_key(cd->input_dev, BTN_TOUCH, 0); |
| input_mt_slot(cd->input_dev, 0); |
| input_mt_report_slot_state( |
| cd->input_dev, MT_TOOL_FINGER, 0); |
| input_report_key(cd->input_dev, KEY_WAKEUP, 0); |
| input_sync(cd->input_dev); |
| } else { |
| ts_debug("not enable SINGLE-TAP"); |
| } |
| break; |
| case GOODIX_GESTURE_DOUBLE_TAP: |
| if (cd->gesture_type & GESTURE_DOUBLE_TAP) { |
| ts_info("get DOUBLE-TAP gesture"); |
| input_report_key(cd->input_dev, KEY_WAKEUP, 1); |
| input_sync(cd->input_dev); |
| input_report_key(cd->input_dev, KEY_WAKEUP, 0); |
| input_sync(cd->input_dev); |
| } else { |
| ts_debug("not enable DOUBLE-TAP"); |
| } |
| break; |
| case GOODIX_GESTURE_FOD_DOWN: |
| if (cd->gesture_type & GESTURE_FOD_PRESS) { |
| ts_info("get FOD-DOWN gesture"); |
| ts_debug( |
| "fodx:%d fody:%d size:%d press:%d maj:%d min:%d ori:%d", |
| coor_x, coor_y, coor_size, coor_press, major, |
| minor, orientation); |
| input_report_key(cd->input_dev, BTN_TOUCH, 1); |
| input_mt_slot(cd->input_dev, 0); |
| input_mt_report_slot_state( |
| cd->input_dev, MT_TOOL_FINGER, 1); |
| input_report_abs( |
| cd->input_dev, ABS_MT_POSITION_X, coor_x); |
| input_report_abs( |
| cd->input_dev, ABS_MT_POSITION_Y, coor_y); |
| input_report_abs( |
| cd->input_dev, ABS_MT_PRESSURE, coor_press); |
| input_report_abs( |
| cd->input_dev, ABS_MT_TOUCH_MAJOR, major); |
| input_report_abs( |
| cd->input_dev, ABS_MT_TOUCH_MINOR, minor); |
| input_sync(cd->input_dev); |
| } else { |
| ts_debug("not enable FOD-DOWN"); |
| } |
| break; |
| case GOODIX_GESTURE_FOD_UP: |
| if (cd->gesture_type & GESTURE_FOD_PRESS) { |
| ts_info("get FOD-UP gesture"); |
| input_report_key(cd->input_dev, BTN_TOUCH, 0); |
| input_mt_slot(cd->input_dev, 0); |
| input_mt_report_slot_state( |
| cd->input_dev, MT_TOOL_FINGER, 0); |
| input_sync(cd->input_dev); |
| } else { |
| ts_debug("not enable FOD-UP"); |
| } |
| break; |
| default: |
| ts_err("not support gesture type[%02X]", event_type); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| int gesture_module_init(struct goodix_ts_core *core_data) |
| { |
| int ret = 0; |
| struct kobject *parent = &core_data->pdev->dev.kobj; |
| |
| /* gesture sysfs init */ |
| core_data->gesture_kobj = kobject_create_and_add("gesture", parent); |
| if (!core_data->gesture_kobj) { |
| ts_err("failed create gesture sysfs node!"); |
| ret = -ENOENT; /* [GOOG] */ |
| goto err_out; |
| } |
| |
| ret = sysfs_create_group(core_data->gesture_kobj, &gesture_sysfs_group); |
| if (ret) { |
| ts_err("failed create gesture sysfs files"); |
| kobject_put(core_data->gesture_kobj); |
| goto err_out; |
| } |
| |
| ts_info("gesture module init success"); |
| return 0; |
| |
| err_out: |
| ts_err("gesture module init failed!"); |
| return ret; |
| } |
| |
| void gesture_module_exit(struct goodix_ts_core *core_data) |
| { |
| ts_info("gesture module exit"); |
| |
| sysfs_remove_group(core_data->gesture_kobj, &gesture_sysfs_group); |
| kobject_put(core_data->gesture_kobj); |
| } |