// SPDX-License-Identifier: GPL-2.0 /* * Synaptics TouchCom touchscreen driver * * Copyright (C) 2017-2020 Synaptics Incorporated. All rights reserved. * * 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 useful, * 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. * * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. * DOLLARS. */ /** * @file syna_tcm2_sysfs.c * * This file implements cdev and ioctl interface in the reference driver. */ #include #include "syna_tcm2.h" #include "synaptics_touchcom_core_dev.h" #include "synaptics_touchcom_func_base.h" #include "synaptics_touchcom_func_touch.h" #ifdef HAS_TESTING_FEATURE #include "syna_tcm2_testing.h" #endif #if (KERNEL_VERSION(5, 9, 0) <= LINUX_VERSION_CODE) || \ defined(HAVE_UNLOCKED_IOCTL) #define USE_UNLOCKED_IOCTL #endif #if defined(CONFIG_COMPAT) && defined(HAVE_COMPAT_IOCTL) #define USE_COMPAT_IOCTL #endif #if (KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE) #define REPLACE_KTIME #endif /* #define ENABLE_PID_TASK */ #define SIG_ATTN (46) /* structure for IOCTLs */ struct syna_ioctl_data { unsigned int data_length; unsigned int buf_size; unsigned char __user *buf; }; #ifdef USE_COMPAT_IOCTL struct syna_tcm_ioctl_data_compat { unsigned int data_length; unsigned int buf_size; compat_uptr_t __user *buf; }; #endif /* defines the IOCTLs supported */ #define IOCTL_MAGIC 's' /* Previous IOCTLs in early driver */ #define OLD_RESET_ID (0x00) #define OLD_SET_IRQ_MODE_ID (0x01) #define OLD_SET_RAW_MODE_ID (0x02) #define OLD_CONCURRENT_ID (0x03) #define IOCTL_OLD_RESET \ _IO(IOCTL_MAGIC, OLD_RESET_ID) #define IOCTL_OLD_SET_IRQ_MODE \ _IOW(IOCTL_MAGIC, OLD_SET_IRQ_MODE_ID, int) #define IOCTL_OLD_SET_RAW_MODE \ _IOW(IOCTL_MAGIC, OLD_SET_RAW_MODE_ID, int) #define IOCTL_OLD_CONCURRENT \ _IOW(IOCTL_MAGIC, OLD_CONCURRENT_ID, int) /* Standard IOCTLs in TCM2 driver */ #define STD_IOCTL_BEGIN (0x10) #define STD_SET_PID_ID (0x11) #define STD_ENABLE_IRQ_ID (0x12) #define STD_RAW_READ_ID (0x13) #define STD_RAW_WRITE_ID (0x14) #define STD_GET_FRAME_ID (0x15) #define STD_SEND_MESSAGE_ID (0x16) #define STD_SET_REPORTS_ID (0x17) #define STD_CHECK_FRAMES_ID (0x18) #define STD_CLEAN_OUT_FRAMES_ID (0x19) #define IOCTL_STD_IOCTL_BEGIN \ _IOR(IOCTL_MAGIC, STD_IOCTL_BEGIN) #define IOCTL_STD_SET_PID \ _IOW(IOCTL_MAGIC, STD_SET_PID_ID, struct syna_ioctl_data *) #define IOCTL_STD_ENABLE_IRQ \ _IOW(IOCTL_MAGIC, STD_ENABLE_IRQ_ID, struct syna_ioctl_data *) #define IOCTL_STD_RAW_READ \ _IOR(IOCTL_MAGIC, STD_RAW_READ_ID, struct syna_ioctl_data *) #define IOCTL_STD_RAW_WRITE \ _IOW(IOCTL_MAGIC, STD_RAW_WRITE_ID, struct syna_ioctl_data *) #define IOCTL_STD_GET_FRAME \ _IOWR(IOCTL_MAGIC, STD_GET_FRAME_ID, struct syna_ioctl_data *) #define IOCTL_STD_SEND_MESSAGE \ _IOWR(IOCTL_MAGIC, STD_SEND_MESSAGE_ID, struct syna_ioctl_data *) #define IOCTL_STD_SET_REPORT_TYPES \ _IOW(IOCTL_MAGIC, STD_SET_REPORTS_ID, struct syna_ioctl_data *) #define IOCTL_STD_CHECK_FRAMES \ _IOWR(IOCTL_MAGIC, STD_CHECK_FRAMES_ID, struct syna_ioctl_data *) #define IOCTL_STD_CLEAN_OUT_FRAMES \ _IOWR(IOCTL_MAGIC, STD_CLEAN_OUT_FRAMES_ID, struct syna_ioctl_data *) /* g_sysfs_dir represents the root directory of sysfs nodes being created */ static struct kobject *g_sysfs_dir; /* g_extif_mutex is used to protect the access from the userspace application */ static syna_pal_mutex_t g_extif_mutex; /* g_cdev_buf is a temporary buffer storing the data from userspace */ static struct tcm_buffer g_cdev_cbuf; /* g_fifo_queue_mutex is used to protect the access from * the userspace application */ static syna_pal_mutex_t g_fifo_queue_mutex; /* The g_sysfs_io_polling_interval is used to set the polling interval * for syna_tcm_send_command from syna_cdev_ioctl_send_message. * It will set to the mode SYSFS_FULL_INTERRUPT for using the full * interrupt mode. The way to update this variable is through the * syna_cdev_ioctl_enable_irq. */ unsigned int g_sysfs_io_polling_interval; /* a buffer to record the streaming report * considering touch report and another reports may be co-enabled * at the same time, give a little buffer here (3 sec x 300 fps) */ #define FIFO_QUEUE_MAX_FRAMES (1200) #define SEND_MESSAGE_HEADER_LENGTH (3) /* Indicate the interrupt status especially for sysfs using */ #define SYSFS_DISABLED_INTERRUPT (0) #define SYSFS_ENABLED_INTERRUPT (1) /*Define a data structure that contains a list_head*/ struct fifo_queue { struct list_head next; unsigned char *fifo_data; unsigned int data_length; #ifdef REPLACE_KTIME struct timespec64 timestamp; #else struct timeval timestamp; #endif }; /** * syna_sysfs_info_show() * * Attribute to show the device and driver information to the console. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [out] buf: string buffer shown on console * * @return * on success, number of characters being output; * otherwise, negative value on error. */ static ssize_t syna_sysfs_info_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int retval; unsigned int count; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; struct tcm_dev *tcm_dev; int i; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); tcm_dev = tcm->tcm_dev; syna_pal_mutex_lock(&g_extif_mutex); count = 0; retval = scnprintf(buf, PAGE_SIZE - count, "Driver version: %d.%s\n", SYNAPTICS_TCM_DRIVER_VERSION, SYNAPTICS_TCM_DRIVER_SUBVER); if (retval < 0) goto exit; buf += retval; count += retval; retval = scnprintf(buf, PAGE_SIZE - count, "Core lib version: %d.%02d\n\n", (unsigned char)(SYNA_TCM_CORE_LIB_VERSION >> 8), (unsigned char)SYNA_TCM_CORE_LIB_VERSION); if (retval < 0) goto exit; buf += retval; count += retval; if (!tcm->is_connected) { retval = scnprintf(buf, PAGE_SIZE - count, "Device is NOT connected\n"); goto exit; } retval = scnprintf(buf, PAGE_SIZE - count, "TouchComm version: %d\n", tcm_dev->id_info.version); if (retval < 0) goto exit; buf += retval; count += retval; switch (tcm_dev->id_info.mode) { case MODE_APPLICATION_FIRMWARE: retval = scnprintf(buf, PAGE_SIZE - count, "Firmware mode: Application Firmware, 0x%02x\n", tcm_dev->id_info.mode); if (retval < 0) goto exit; break; case MODE_BOOTLOADER: retval = scnprintf(buf, PAGE_SIZE - count, "Firmware mode: Bootloader, 0x%02x\n", tcm_dev->id_info.mode); if (retval < 0) goto exit; break; case MODE_ROMBOOTLOADER: retval = scnprintf(buf, PAGE_SIZE - count, "Firmware mode: Rom Bootloader, 0x%02x\n", tcm_dev->id_info.mode); if (retval < 0) goto exit; break; default: retval = scnprintf(buf, PAGE_SIZE - count, "Firmware mode: Mode 0x%02x\n", tcm_dev->id_info.mode); if (retval < 0) goto exit; break; } buf += retval; count += retval; retval = scnprintf(buf, PAGE_SIZE - count, "Part number: "); if (retval < 0) goto exit; buf += retval; count += retval; retval = syna_pal_mem_cpy(buf, PAGE_SIZE - count, tcm_dev->id_info.part_number, sizeof(tcm_dev->id_info.part_number), sizeof(tcm_dev->id_info.part_number)); if (retval < 0) { LOGE("Fail to copy part number string\n"); goto exit; } buf += sizeof(tcm_dev->id_info.part_number); count += sizeof(tcm_dev->id_info.part_number); retval = scnprintf(buf, PAGE_SIZE - count, "\n"); if (retval < 0) goto exit; buf += retval; count += retval; retval = scnprintf(buf, PAGE_SIZE - count, "Packrat number: %d\n\n", tcm_dev->packrat_number); if (retval < 0) goto exit; buf += retval; count += retval; if (tcm_dev->id_info.mode != MODE_APPLICATION_FIRMWARE) { retval = count; goto exit; } retval = scnprintf(buf, PAGE_SIZE - count, "Config ID: "); if (retval < 0) goto exit; buf += retval; count += retval; for (i = 0; i < MAX_SIZE_CONFIG_ID; i++) { retval = scnprintf(buf, PAGE_SIZE - count, "0x%2x ", tcm_dev->config_id[i]); if (retval < 0) goto exit; buf += retval; count += retval; } retval = scnprintf(buf, PAGE_SIZE - count, "\n"); if (retval < 0) goto exit; buf += retval; count += retval; retval = scnprintf(buf, PAGE_SIZE - count, "Max X & Y: %d, %d\n", tcm_dev->max_x, tcm_dev->max_y); if (retval < 0) goto exit; buf += retval; count += retval; retval = scnprintf(buf, PAGE_SIZE - count, "Num of objects: %d\n", tcm_dev->max_objects); if (retval < 0) goto exit; buf += retval; count += retval; retval = scnprintf(buf, PAGE_SIZE - count, "Num of cols & rows: %d, %d\n", tcm_dev->cols, tcm_dev->rows); if (retval < 0) goto exit; buf += retval; count += retval; retval = count; exit: syna_pal_mutex_unlock(&g_extif_mutex); return retval; } static struct kobj_attribute kobj_attr_info = __ATTR(info, 0444, syna_sysfs_info_show, NULL); /** * syna_sysfs_irq_en_store() * * Attribute to disable/enable the irq * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [ in] buf: string buffer input * [ in] count: size of buffer input * * @return * on success, return count; otherwise, return error code */ static ssize_t syna_sysfs_irq_en_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int retval = 0; unsigned int input; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); if (kstrtouint(buf, 10, &input)) return -EINVAL; if (!tcm->hw_if->ops_enable_irq) return 0; if (!tcm->is_connected) { LOGW("Device is NOT connected\n"); return count; } syna_pal_mutex_lock(&g_extif_mutex); /* disable the interrupt line */ if (input == 0) { retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, false); if (retval < 0) { LOGE("Fail to disable interrupt\n"); goto exit; } } else if (input == 1) { /* enable the interrupt line */ retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, true); if (retval < 0) { LOGE("Fail to enable interrupt\n"); goto exit; } } else { LOGW("Unknown option %d (0:disable / 1:enable)\n", input); retval = -EINVAL; goto exit; } retval = count; exit: syna_pal_mutex_unlock(&g_extif_mutex); return retval; } static struct kobj_attribute kobj_attr_irq_en = __ATTR(irq_en, 0220, NULL, syna_sysfs_irq_en_store); /** * syna_sysfs_reset_store() * * Attribute to issue a reset. * "1" for a sw reset; "2" for a hardware reset * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [ in] buf: string buffer input * [ in] count: size of buffer input * * @return * on success, return count; otherwise, return error code */ static ssize_t syna_sysfs_reset_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int retval = 0; unsigned int input; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); if (kstrtouint(buf, 10, &input)) return -EINVAL; if (!tcm->is_connected) { LOGW("Device is NOT connected\n"); return count; } syna_pal_mutex_lock(&g_extif_mutex); syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, true); if (input == 1) { retval = syna_tcm_reset(tcm->tcm_dev); if (retval < 0) { LOGE("Fail to do reset\n"); goto exit; } } else if (input == 2) { if (!tcm->hw_if->ops_hw_reset) { LOGE("No hardware reset support\n"); goto exit; } tcm->hw_if->ops_hw_reset(tcm->hw_if); /* enable the interrupt to process the identify report * after the hardware reset. */ if (!tcm->hw_if->bdata_attn.irq_enabled) { tcm->hw_if->ops_enable_irq(tcm->hw_if, true); /* disable it and back to original status */ syna_pal_sleep_ms(100); tcm->hw_if->ops_enable_irq(tcm->hw_if, false); } } else { LOGW("Unknown option %d (1:sw / 2:hw)\n", input); retval = -EINVAL; goto exit; } /* check the fw setup in case the settings is changed */ if (IS_APP_FW_MODE(tcm->tcm_dev->dev_mode)) { retval = tcm->dev_set_up_app_fw(tcm); if (retval < 0) { LOGE("Fail to set up app fw\n"); goto exit; } } retval = count; exit: syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, false); syna_pal_mutex_unlock(&g_extif_mutex); return retval; } static struct kobj_attribute kobj_attr_reset = __ATTR(reset, 0220, NULL, syna_sysfs_reset_store); /** * syna_sysfs_scan_mode_store() * * Attribute to set different scan mode. * 0 - Lock Normal Mode Active Mode. * 1 - Lock Normal Mode Doze Mode. * 2 - Lock Low Power Gesture Mode Active Mode. * 3 - Lock Low Power Gesture Mode Doze Mode. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [ in] buf: string buffer input * [ in] count: size of buffer input * * @return * on success, return count; otherwise, return error code */ static ssize_t syna_sysfs_scan_mode_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int retval = 0; unsigned int input; unsigned char command = 0; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; struct syna_hw_interface *hw_if; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); hw_if = tcm->hw_if; if (kstrtouint(buf, 10, &input)) return -EINVAL; if (!tcm->is_connected) { LOGW("Device is NOT connected\n"); retval = count; goto exit; } syna_pal_mutex_lock(&g_extif_mutex); syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, true); if (hw_if->ops_hw_reset) { hw_if->ops_hw_reset(hw_if); } else { retval = syna_tcm_reset(tcm->tcm_dev); if (retval < 0) { LOGE("Fail to do reset\n"); goto exit; } } if (input == 0 || input == 2) { command = DC_DISABLE_DOZE; } else if (input == 1 || input == 3) { command = DC_FORCE_DOZE_MODE; } else { LOGW("Unsupport command %u\n", input); goto exit; } if (input == 2 || input == 3) { retval = syna_tcm_set_dynamic_config(tcm->tcm_dev, DC_ENABLE_WAKEUP_GESTURE_MODE, 1, RESP_IN_ATTN); if (retval < 0) { LOGE("Fail to enable wakeup gesture via DC command\n"); goto exit; } } retval = syna_tcm_set_dynamic_config(tcm->tcm_dev, command, 1, RESP_IN_ATTN); if (retval < 0) { LOGE("Fail to set DC command %d\n", command); goto exit; } retval = count; exit: syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, false); syna_pal_mutex_unlock(&g_extif_mutex); return retval; } static struct kobj_attribute kobj_attr_scan_mode = __ATTR(scan_mode, 0220, NULL, syna_sysfs_scan_mode_store); /** * syna_sysfs_force_active_store() * * Attribute to set different scan mode. * 0x10 - Set SYNA_BUS_REF_FORCE_ACTIVE bit 0. * 0x11 - Set SYNA_BUS_REF_FORCE_ACTIVE bit 1. * 0x20 - Set SYNA_BUS_REF_BUGREPORT bit 0. * 0x21 - Set SYNA_BUS_REF_BUGREPORT bit 1. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [ in] buf: string buffer input * [ in] count: size of buffer input * * @return * on success, return count; otherwise, return error code */ static ssize_t syna_sysfs_force_active_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int retval = 0; unsigned char input; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; bool active; u32 ref = 0; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); if (kstrtou8(buf, 16, &input)) return -EINVAL; if (!tcm->is_connected) { LOGW("Device is NOT connected\n"); retval = count; goto exit; } syna_pal_mutex_lock(&g_extif_mutex); switch (input) { case 0x10: ref = SYNA_BUS_REF_FORCE_ACTIVE; active = false; break; case 0x11: ref = SYNA_BUS_REF_FORCE_ACTIVE; active = true; break; case 0x20: ref = SYNA_BUS_REF_BUGREPORT; active = false; tcm->bugreport_ktime_start = 0; break; case 0x21: ref = SYNA_BUS_REF_BUGREPORT; active = true; tcm->bugreport_ktime_start = ktime_get(); break; default: LOGE("Invalid input %#x.\n", input); retval = -EINVAL; goto exit; } LOGI("Set bus reference bit %#x %s.", ref, active ? "enable" : "disable"); if (active) pm_stay_awake(&tcm->pdev->dev); else pm_relax(&tcm->pdev->dev); retval = syna_set_bus_ref(tcm, ref, active); if (retval < 0) { LOGE("Set bus reference bit %#x %s failed.", ref, active ? "enable" : "disable"); goto exit; } retval = count; exit: syna_pal_mutex_unlock(&g_extif_mutex); return retval; } static struct kobj_attribute kobj_attr_force_active = __ATTR(force_active, 0220, NULL, syna_sysfs_force_active_store); /** * syna_sysfs_get_raw_data_show() * * Attribute to show the rawdata. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [out] buf: string buffer shown on console * * @return * on success, number of characters being output; * otherwise, negative value on error. */ static ssize_t syna_sysfs_get_raw_data_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int retval = 0; unsigned int count = 0; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; struct tcm_dev *tcm_dev; int i, j, mutual_length; bool is_signed; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); tcm_dev = tcm->tcm_dev; mutual_length = tcm_dev->cols * tcm_dev->rows; is_signed = (tcm->raw_data_report_code == REPORT_DELTA); syna_pal_mutex_lock(&g_extif_mutex); if (wait_for_completion_timeout(&tcm->raw_data_completion, msecs_to_jiffies(500)) == 0) { complete_all(&tcm->raw_data_completion); count += scnprintf(buf + count, PAGE_SIZE - count, "Timeout\n"); goto exit; } if (!tcm->raw_data_buffer) { count += scnprintf(buf + count, PAGE_SIZE - count, "Raw data buffer is NULL.\n"); goto exit; } /* Mutual raw. */ count += scnprintf(buf + count, PAGE_SIZE - count, "Mutual\n"); for (i = 0; i < tcm_dev->rows; i++) { for (j = 0; j < tcm_dev->cols; j++) { count += scnprintf(buf + count, PAGE_SIZE - count, (is_signed) ? "%d " : "%u ", (is_signed) ? tcm->raw_data_buffer[i * tcm_dev->cols + j] : (u16) (tcm->raw_data_buffer[i * tcm_dev->cols + j])); } count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); } /* Self raw. */ count += scnprintf(buf + count, PAGE_SIZE - count, "Self\n"); for (i = 0; i < tcm_dev->cols; i++) { count += scnprintf(buf + count, PAGE_SIZE - count, (is_signed) ? "%d " : "%u ", (is_signed) ? tcm->raw_data_buffer[mutual_length + i] : (u16) (tcm->raw_data_buffer[mutual_length + i])); } count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); for (j = 0; j < tcm_dev->rows; j++) { count += scnprintf(buf + count, PAGE_SIZE - count, (is_signed) ? "%d " : "%u ", (is_signed) ? tcm->raw_data_buffer[mutual_length + i + j] : (u16) (tcm->raw_data_buffer[mutual_length + i + j])); } count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); LOGI("Got raw data, report code %#x\n", tcm->raw_data_report_code); exit: retval = count; syna_tcm_set_dynamic_config(tcm->tcm_dev, DC_DISABLE_DOZE, 0, RESP_IN_ATTN); syna_tcm_enable_report(tcm_dev, tcm->raw_data_report_code, false); syna_pal_mutex_unlock(&g_extif_mutex); syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, false); return retval; } /** * syna_sysfs_get_raw_data_store() * * Attribute to enable the rawdata report type. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [ in] buf: string buffer input * [ in] count: size of buffer input * * @return * on success, return count; otherwise, return error code */ static ssize_t syna_sysfs_get_raw_data_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int retval = count; unsigned char input; unsigned char report_code; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); if (kstrtou8(buf, 16, &input)) return -EINVAL; syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, true); syna_pal_mutex_lock(&g_extif_mutex); switch (input) { case REPORT_DELTA: report_code = REPORT_DELTA; break; case REPORT_RAW: report_code = REPORT_RAW; break; case REPORT_BASELINE: report_code = REPORT_BASELINE; break; default: LOGE("Invalid input %#x.\n", input); retval = -EINVAL; goto exit; } LOGI("Enable raw data, report code %#x\n", report_code); syna_tcm_set_dynamic_config(tcm->tcm_dev, DC_DISABLE_DOZE, 1, RESP_IN_ATTN); tcm->raw_data_report_code = report_code; syna_tcm_enable_report(tcm->tcm_dev, report_code, true); reinit_completion(&tcm->raw_data_completion); exit: syna_pal_mutex_unlock(&g_extif_mutex); return retval; } static struct kobj_attribute kobj_attr_get_raw_data = __ATTR(get_raw_data, 0664, syna_sysfs_get_raw_data_show, syna_sysfs_get_raw_data_store); /** * syna_sysfs_high_sensitivity_show() * * Attribute to show current sensitivity mode. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [out] buf: string buffer shown on console * * @return * on success, number of characters being output; * otherwise, negative value on error. */ static ssize_t syna_sysfs_high_sensitivity_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int retval = 0; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); syna_pal_mutex_lock(&g_extif_mutex); retval = scnprintf(buf, PAGE_SIZE, "%d\n", tcm->high_sensitivity_mode); syna_pal_mutex_unlock(&g_extif_mutex); return retval; } /** * syna_sysfs_high_sensitivity_store() * * Attribute to set high sensitivity mode. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [ in] buf: string buffer input * [ in] count: size of buffer input * * @return * on success, return count; otherwise, return error code */ static ssize_t syna_sysfs_high_sensitivity_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int retval = count; bool input; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); if (kstrtobool(buf, &input)) { LOGE("Invalid input %s", buf); return -EINVAL; } syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, true); syna_pal_mutex_lock(&g_extif_mutex); tcm->high_sensitivity_mode = input; retval = syna_tcm_set_dynamic_config(tcm->tcm_dev, DC_HIGH_SENSITIVIRY_MODE, input, RESP_IN_ATTN); LOGI("%s high sensitivity mode.\n", tcm->high_sensitivity_mode ? "Enable" : "Disable"); retval = count; syna_pal_mutex_unlock(&g_extif_mutex); syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, false); return retval; } static struct kobj_attribute kobj_attr_high_sensitivity = __ATTR(high_sensitivity, 0664, syna_sysfs_high_sensitivity_show, syna_sysfs_high_sensitivity_store); /** * syna_sysfs_fw_grip_show() * * Attribute to show current grip suppression mode. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [out] buf: string buffer shown on console * * @return * on success, number of characters being output; * otherwise, negative value on error. */ static ssize_t syna_sysfs_fw_grip_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int retval = 0; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); syna_pal_mutex_lock(&g_extif_mutex); retval = scnprintf(buf, PAGE_SIZE, "%u\n", tcm->enable_fw_grip); syna_pal_mutex_unlock(&g_extif_mutex); return retval; } /** * syna_sysfs_fw_grip_store() * * Attribute to set grip suppression mode. * 0 - Disable fw grip suppression. * 1 - Enable fw grip suppression. * 2 - Force disable fw grip suppression. * 3 - Force enable fw grip suppression. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [ in] buf: string buffer input * [ in] count: size of buffer input * * @return * on success, return count; otherwise, return error code */ static ssize_t syna_sysfs_fw_grip_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int retval = count; u8 input; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); if (kstrtou8(buf, 16, &input)) { LOGE("Invalid input %s", buf); return -EINVAL; } syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, true); syna_pal_mutex_lock(&g_extif_mutex); tcm->enable_fw_grip = input; retval = syna_tcm_set_dynamic_config(tcm->tcm_dev, DC_ENABLE_GRIP_SUPPRESSION, (input & 0x01), RESP_IN_ATTN); LOGI("Set fw grip suppression mode %u.\n", tcm->enable_fw_grip); retval = count; syna_pal_mutex_unlock(&g_extif_mutex); syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, false); return retval; } static struct kobj_attribute kobj_attr_fw_grip = __ATTR(fw_grip, 0664, syna_sysfs_fw_grip_show, syna_sysfs_fw_grip_store); /** * syna_sysfs_fw_palm_show() * * Attribute to show current palm rejection mode. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [out] buf: string buffer shown on console * * @return * on success, number of characters being output; * otherwise, negative value on error. */ static ssize_t syna_sysfs_fw_palm_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int retval = 0; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); syna_pal_mutex_lock(&g_extif_mutex); retval = scnprintf(buf, PAGE_SIZE, "%u\n", tcm->enable_fw_palm); syna_pal_mutex_unlock(&g_extif_mutex); return retval; } /** * syna_sysfs_fw_palm_store() * * Attribute to set palm rejection mode. * 0 - Disable fw palm rejection. * 1 - Enable fw palm rejection. * 2 - Force disable fw palm rejection. * 3 - Force enable fw palm rejection. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [ in] buf: string buffer input * [ in] count: size of buffer input * * @return * on success, return count; otherwise, return error code */ static ssize_t syna_sysfs_fw_palm_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int retval = count; u8 input; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); if (kstrtou8(buf, 16, &input)) { LOGE("Invalid input %s", buf); return -EINVAL; } syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, true); syna_pal_mutex_lock(&g_extif_mutex); tcm->enable_fw_palm = input; retval = syna_tcm_set_dynamic_config(tcm->tcm_dev, DC_ENABLE_PALM_REJECTION, (input & 0x01), RESP_IN_ATTN); LOGI("Set fw palm rejection mode %u.\n", tcm->enable_fw_palm); retval = count; syna_pal_mutex_unlock(&g_extif_mutex); syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, false); return retval; } static struct kobj_attribute kobj_attr_fw_palm = __ATTR(fw_palm, 0664, syna_sysfs_fw_palm_show, syna_sysfs_fw_palm_store); /** * syna_sysfs_default_mf_show() * * Attribute to show motion filter mode. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [out] buf: string buffer shown on console * * @return * on success, number of characters being output; * otherwise, negative value on error. */ static ssize_t syna_sysfs_mf_mode_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int retval = 0; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); syna_pal_mutex_lock(&g_extif_mutex); retval = scnprintf(buf, PAGE_SIZE, "%d\n", tcm->mf_mode); syna_pal_mutex_unlock(&g_extif_mutex); return retval; } /** * syna_sysfs_mf_mode_store() * * Attribute to set motion filter mode. * 0 = Always unfilter. * 1 = Dynamic change motion filter. * 2 = Always filter by touch FW. * * @param * [ in] kobj: an instance of kobj * [ in] attr: an instance of kobj attribute structure * [ in] buf: string buffer input * [ in] count: size of buffer input * * @return * on success, return count; otherwise, return error code */ static ssize_t syna_sysfs_mf_mode_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int retval = count; u8 input; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); if (kstrtou8(buf, 16, &input)) { LOGE("Invalid input %s", buf); return -EINVAL; } syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, true); syna_pal_mutex_lock(&g_extif_mutex); tcm->mf_mode = input; LOGI("Set motion filer mode %d.\n", tcm->mf_mode); syna_pal_mutex_unlock(&g_extif_mutex); syna_set_bus_ref(tcm, SYNA_BUS_REF_SYSFS, false); return retval; } static struct kobj_attribute kobj_attr_mf_mode = __ATTR(mf_mode, 0664, syna_sysfs_mf_mode_show, syna_sysfs_mf_mode_store); /** * declaration of sysfs attributes */ static struct attribute *attrs[] = { &kobj_attr_info.attr, &kobj_attr_irq_en.attr, &kobj_attr_reset.attr, &kobj_attr_scan_mode.attr, &kobj_attr_force_active.attr, &kobj_attr_get_raw_data.attr, &kobj_attr_high_sensitivity.attr, &kobj_attr_fw_grip.attr, &kobj_attr_fw_palm.attr, &kobj_attr_mf_mode.attr, NULL, }; static struct attribute_group attr_group = { .attrs = attrs, }; /** * syna_sysfs_create_dir() * * Create a directory and register it with sysfs. * Then, create all defined sysfs files. * * @param * [ in] tcm: the driver handle * [ in] pdev: an instance of platform device * * @return * on success, 0; otherwise, negative value on error. */ int syna_sysfs_create_dir(struct syna_tcm *tcm, struct platform_device *pdev) { int retval = 0; g_sysfs_dir = kobject_create_and_add("sysfs", &pdev->dev.kobj); if (!g_sysfs_dir) { LOGE("Fail to create sysfs directory\n"); return -ENOTDIR; } tcm->sysfs_dir = g_sysfs_dir; retval = sysfs_create_group(g_sysfs_dir, &attr_group); if (retval < 0) { LOGE("Fail to create sysfs group\n"); kobject_put(tcm->sysfs_dir); return retval; } #ifdef HAS_TESTING_FEATURE retval = syna_testing_create_dir(tcm, g_sysfs_dir); if (retval < 0) { LOGE("Fail to create testing sysfs\n"); sysfs_remove_group(tcm->sysfs_dir, &attr_group); kobject_put(tcm->sysfs_dir); return retval; } #endif return 0; } /** * syna_sysfs_remove_dir() * * Remove the allocate sysfs directory * * @param * [ in] tcm: the driver handle * * @return * on success, 0; otherwise, negative value on error. */ void syna_sysfs_remove_dir(struct syna_tcm *tcm) { if (!tcm) { LOGE("Invalid tcm device handle\n"); return; } if (tcm->sysfs_dir) { #ifdef HAS_TESTING_FEATURE syna_testing_remove_dir(); #endif sysfs_remove_group(tcm->sysfs_dir, &attr_group); kobject_put(tcm->sysfs_dir); } } #ifdef ENABLE_EXTERNAL_FRAME_PROCESS /** * syna_cdev_insert_fifo() * * Insert/Push the data to the queue. * * This function is called by syna_cdev_update_report_queue(), * where the event data will be placed as the below format in byte * and use this function to store the data in queue. * [0 ] : status / report code * [1 : 2 ] : length of data frame * [3 : N + 3] : N bytes of data payload * * @param * [ in] tcm: the driver handle * [ in] buf_ptr: points to a data going to push * [ in] length: data length * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_insert_fifo(struct syna_tcm *tcm, unsigned char *buf_ptr, unsigned int length) { int retval = 0; struct fifo_queue *pfifo_data; struct fifo_queue *pfifo_data_temp; static int pre_remaining_frames = -1; syna_pal_mutex_lock(&g_fifo_queue_mutex); /* check queue buffer limit */ if (tcm->fifo_remaining_frame >= FIFO_QUEUE_MAX_FRAMES) { if (tcm->fifo_remaining_frame != pre_remaining_frames) LOGI("Reached %d and drop FIFO first frame\n", tcm->fifo_remaining_frame); pfifo_data_temp = list_first_entry(&tcm->frame_fifo_queue, struct fifo_queue, next); list_del(&pfifo_data_temp->next); kfree(pfifo_data_temp->fifo_data); kfree(pfifo_data_temp); pre_remaining_frames = tcm->fifo_remaining_frame; tcm->fifo_remaining_frame--; } else if (pre_remaining_frames >= FIFO_QUEUE_MAX_FRAMES) { LOGI("Reached limit, dropped oldest frame, remaining:%d\n", tcm->fifo_remaining_frame); pre_remaining_frames = tcm->fifo_remaining_frame; } pfifo_data = kmalloc(sizeof(*pfifo_data), GFP_KERNEL); if (!(pfifo_data)) { LOGE("Failed to allocate memory\n"); LOGE("Allocation size = %zu\n", (sizeof(*pfifo_data))); retval = -ENOMEM; goto exit; } pfifo_data->fifo_data = kmalloc(length, GFP_KERNEL); if (!(pfifo_data->fifo_data)) { LOGE("Failed to allocate memory, size = %d\n", length); retval = -ENOMEM; goto exit; } pfifo_data->data_length = length; memcpy((void *)pfifo_data->fifo_data, (void *)buf_ptr, length); #ifdef REPLACE_KTIME ktime_get_real_ts64(&(pfifo_data->timestamp)); #else do_gettimeofday(&(pfifo_data->timestamp)); #endif /* append the data to the tail for FIFO queueing */ list_add_tail(&pfifo_data->next, &tcm->frame_fifo_queue); tcm->fifo_remaining_frame++; retval = 0; exit: syna_pal_mutex_unlock(&g_fifo_queue_mutex); return retval; } #endif /** * syna_cdev_ioctl_check_frame() * * Check the queuing status and wait for the data if it's empty. * * @param * [ in] tcm: the driver handle * [ in/out] ubuf_ptr: points to a memory space from userspace * [ in] buf_size: size of given space * [ in] data_size: timeout value for queue waiting * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_ioctl_check_frame(struct syna_tcm *tcm, const unsigned char *ubuf_ptr, unsigned int buf_size, unsigned int data_size) { int retval = 0; int result = 0; unsigned int timeout = 0; unsigned int frames = 0; unsigned char data[4] = {0}; if (!tcm->is_connected) { LOGE("Not connected\n"); return _EINVAL; } if (buf_size < sizeof(data) || data_size < sizeof(data)) { LOGE("Invalid sync data size, buf_size: %u\n", buf_size); retval = -EINVAL; goto exit; } result = copy_from_user(data, ubuf_ptr, sizeof(data)); if (result) { LOGE("Fail to copy data from user space\n"); retval = -EBADE; goto exit; } /* Parese the waiting duration length */ timeout = syna_pal_le4_to_uint(&data[0]); LOGD("Time out: %d\n", timeout); if (list_empty(&tcm->frame_fifo_queue)) { LOGD("The queue is empty, wait for the frames\n"); result = wait_event_interruptible_timeout(tcm->wait_frame, (tcm->fifo_remaining_frame > 0), msecs_to_jiffies(timeout)); if (result == 0) { LOGD("Queue waiting timed out after %dms\n", timeout); retval = -ETIMEDOUT; goto exit; } LOGD("Data queued\n"); retval = data_size; } else { LOGD("Queue is not empty\n"); retval = data_size; } exit: if (retval > 0) { frames = tcm->fifo_remaining_frame; data[0] = (unsigned char)(frames & 0xff); data[1] = (unsigned char)((frames >> 8) & 0xff); data[2] = (unsigned char)((frames >> 16) & 0xff); data[3] = (unsigned char)((frames >> 24) & 0xff); result = copy_to_user((void *)ubuf_ptr, data, sizeof(data)); if (result) { LOGE("Fail to copy data to user space\n"); retval = -EBADE; } } return retval; } /** * syna_cdev_clean_queue() * * Clean the data queue. * All data in the queue will be cleaned up in every time of device * open and close. * * @param * [ in] tcm: the driver handle * * @return * void. */ static void syna_cdev_clean_queue(struct syna_tcm *tcm) { struct fifo_queue *pfifo_data; syna_pal_mutex_lock(&g_fifo_queue_mutex); while (!list_empty(&tcm->frame_fifo_queue)) { pfifo_data = list_first_entry(&tcm->frame_fifo_queue, struct fifo_queue, next); list_del(&pfifo_data->next); kfree(pfifo_data->fifo_data); kfree(pfifo_data); if (tcm->fifo_remaining_frame != 0) tcm->fifo_remaining_frame--; } LOGD("Queue cleaned, frame: %d\n", tcm->fifo_remaining_frame); syna_pal_mutex_unlock(&g_fifo_queue_mutex); } /** * syna_cdev_ioctl_get_frame() * * Read the data from the queue and return to userspace if data is * copied or the specified timeout is expired. * * Please be noted that the retried data is formatted as follows. * [0 ] : status / report code * [1 : 2 ] : length of data frame * [3 : N + 3] : N bytes of data payload * * @param * [ in] tcm: the driver handle * [in/out] ubuf_ptr: points to a memory space from userspace * [ in] buf_size: size of given space * [out] frame_size: frame size returned * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_ioctl_get_frame(struct syna_tcm *tcm, const unsigned char *ubuf_ptr, unsigned int buf_size, unsigned int *frame_size) { int retval = 0; int timeout = 0; unsigned char timeout_data[4] = {0}; struct fifo_queue *pfifo_data; if (!tcm->is_connected) { LOGE("Not connected\n"); return _EINVAL; } if (buf_size < sizeof(timeout_data)) { LOGE("Invalid sync data size, buf_size:%d\n", buf_size); retval = -EINVAL; goto exit; } #if !defined(ENABLE_EXTERNAL_FRAME_PROCESS) LOGE("ENABLE_EXTERNAL_FRAME_PROCESS is not enabled\n"); return -EFAULT; #endif retval = copy_from_user(timeout_data, ubuf_ptr, sizeof(timeout_data)); if (retval) { LOGE("Fail to copy data from user space, size:%d\n", retval); retval = -EBADE; goto exit; } /* get the waiting duration */ timeout = syna_pal_le4_to_uint(&timeout_data[0]); LOGD("Wait time: %dms\n", timeout); if (list_empty(&tcm->frame_fifo_queue)) { LOGD("The queue is empty, wait for the frame\n"); retval = wait_event_interruptible_timeout(tcm->wait_frame, (tcm->fifo_remaining_frame > 0), msecs_to_jiffies(timeout)); if (retval == 0) { LOGD("Queue waiting timed out after %dms\n", timeout); retval = -ETIMEDOUT; *frame_size = 0; goto exit; } LOGD("Data queued\n"); } /* confirm the queue status */ if (list_empty(&tcm->frame_fifo_queue)) { LOGD("Is queue empty? The remaining frame = %d\n", tcm->fifo_remaining_frame); retval = -ENODATA; goto exit; } syna_pal_mutex_lock(&g_fifo_queue_mutex); pfifo_data = list_first_entry(&tcm->frame_fifo_queue, struct fifo_queue, next); LOGD("Pop data from the queue, data length = %d\n", pfifo_data->data_length); if (buf_size >= pfifo_data->data_length) { retval = copy_to_user((void *)ubuf_ptr, pfifo_data->fifo_data, pfifo_data->data_length); if (retval) { LOGE("Fail to copy data to user space, size:%d\n", retval); retval = -EBADE; } *frame_size = pfifo_data->data_length; } else { LOGE("No enough space for data copy, buf_size:%d data:%d\n", buf_size, pfifo_data->data_length); retval = -EOVERFLOW; goto exit; } LOGD("From FIFO: (0x%02x, 0x%02x, 0x%02x, 0x%02x)\n", pfifo_data->fifo_data[0], pfifo_data->fifo_data[1], pfifo_data->fifo_data[2], pfifo_data->fifo_data[3]); list_del(&pfifo_data->next); if (retval >= 0) retval = pfifo_data->data_length; kfree(pfifo_data->fifo_data); kfree(pfifo_data); if (tcm->fifo_remaining_frame != 0) tcm->fifo_remaining_frame--; syna_pal_mutex_unlock(&g_fifo_queue_mutex); exit: return retval; } /** * syna_cdev_ioctl_set_reports() * * Assign the report types for queuing. The enabled reports will be queued * into the FIFO queue. * * @param * [ in] tcm: the driver handle * [ in] ubuf_ptr: points to a memory space from userspace * [ in] buf_size: size of given space * [ in] report_size: report types data size * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_ioctl_set_reports(struct syna_tcm *tcm, const unsigned char *ubuf_ptr, unsigned int buf_size, unsigned int report_size) { int retval = 0; unsigned char data[REPORT_TYPES] = {0}; unsigned int reports = 0; unsigned int report_set = 0; if (buf_size < sizeof(data)) { LOGE("Invalid sync data size, buf_size:%d, expected:%d\n", buf_size, (unsigned int)sizeof(data)); return -EINVAL; } #if !defined(ENABLE_EXTERNAL_FRAME_PROCESS) LOGE("ENABLE_EXTERNAL_FRAME_PROCESS is not enabled\n"); return -EINVAL; #endif if (report_size == 0) { LOGE("Invalid written size\n"); return -EINVAL; } retval = copy_from_user(data, ubuf_ptr, report_size); if (retval) { LOGE("Fail to copy data from user space, size:%d\n", retval); retval = -EBADE; goto exit; } retval = syna_pal_mem_cpy(tcm->report_to_queue, REPORT_TYPES, data, sizeof(data), REPORT_TYPES); for (reports = 0 ; reports < REPORT_TYPES ; reports++) { if (tcm->report_to_queue[reports] == EFP_ENABLE) { report_set++; LOGD("Set report 0x%02x for queue\n", reports); } } LOGI("Forward %d types of reports to the Queue.\n", report_set); retval = report_set; exit: return retval; } /** * syna_cdev_ioctl_send_message() * * Send the command/message from userspace. * * For updating the g_sysfs_io_polling_interval, it need to be configured * by syna_cdev_ioctl_enable_irq from userspace. * * @param * [ in] tcm: the driver handle * [ in/out] ubuf_ptr: points to a memory space from userspace * [ in] buf_size: size of given space * [ in/out] msg_size: size of message * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_ioctl_send_message(struct syna_tcm *tcm, const unsigned char *ubuf_ptr, unsigned int buf_size, unsigned int *msg_size) { int retval = 0; unsigned char *data = NULL; unsigned char resp_code = 0; unsigned int payload_length = 0; unsigned int delay_ms_resp = RESP_IN_POLLING; struct tcm_buffer resp_data_buf; if (!tcm->is_connected) { LOGE("Not connected\n"); return -ENXIO; } if (buf_size < SEND_MESSAGE_HEADER_LENGTH) { LOGE("Invalid sync data size, buf_size:%d\n", buf_size); return -EINVAL; } if (*msg_size == 0) { LOGE("Invalid message length, msg size: 0\n"); return -EINVAL; } syna_tcm_buf_lock(&g_cdev_cbuf); retval = syna_tcm_buf_alloc(&g_cdev_cbuf, buf_size); if (retval < 0) { LOGE("Fail to allocate memory for g_cdev_cbuf, size: %d\n", buf_size); goto exit; } data = g_cdev_cbuf.buf; retval = copy_from_user(data, ubuf_ptr, *msg_size); if (retval) { LOGE("Fail to copy data from user space, size:%d\n", *msg_size); retval = -EBADE; goto exit; } payload_length = syna_pal_le2_to_uint(&data[1]); LOGD("Command = 0x%02x, payload length = %d\n", data[0], payload_length); /* init a buffer for the response data */ syna_tcm_buf_init(&resp_data_buf); if (g_sysfs_io_polling_interval == RESP_IN_ATTN) delay_ms_resp = RESP_IN_ATTN; else delay_ms_resp = g_sysfs_io_polling_interval; retval = syna_tcm_send_command(tcm->tcm_dev, data[0], &data[3], payload_length, &resp_code, &resp_data_buf, delay_ms_resp); if (retval < 0) { LOGE("Fail to send command:%d\n", retval); goto exit; } syna_pal_mem_set(data, 0, buf_size); /* status code */ data[0] = resp_code; /* the length for response data */ data[1] = (unsigned char)(resp_data_buf.data_length & 0xff); data[2] = (unsigned char)((resp_data_buf.data_length >> 8) & 0xff); /* response data */ if (resp_data_buf.data_length > 0) { retval = syna_pal_mem_cpy(&g_cdev_cbuf.buf[3], (g_cdev_cbuf.buf_size - SEND_MESSAGE_HEADER_LENGTH), resp_data_buf.buf, resp_data_buf.buf_size, resp_data_buf.data_length); if (retval < 0) { LOGE("Fail to copy resp data\n"); goto exit; } } #ifdef ENABLE_EXTERNAL_FRAME_PROCESS /* It's for queuing the data when user is polling the command * response for the selected responses. The response will not be * queued if the user doesn't set the report/response types through * syna_cdev_ioctl_set_reports. */ if (delay_ms_resp != RESP_IN_ATTN) { if (tcm->report_to_queue[resp_code] == EFP_ENABLE) { syna_cdev_update_report_queue(tcm, resp_code, &resp_data_buf); } } #endif if (buf_size < resp_data_buf.data_length) { LOGE("No enough space for data copy, buf_size:%d data:%d\n", buf_size, resp_data_buf.data_length); retval = -EOVERFLOW; goto exit; } retval = copy_to_user((void *)ubuf_ptr, data, resp_data_buf.data_length); if (retval) { LOGE("Fail to copy data to user space\n"); retval = -EBADE; goto exit; } *msg_size = resp_data_buf.data_length + 3; retval = *msg_size; exit: syna_tcm_buf_unlock(&g_cdev_cbuf); syna_tcm_buf_release(&resp_data_buf); return retval; } /** * syna_cdev_ioctl_enable_irq() * * Enable or disable the irq via IOCTL. * * Expect to get 4 bytes unsigned int parameter from userspace: * 0: disable the irq. * 1: enable the irq and set g_sysfs_io_polling_interval * to RESP_IN_ATTN * otherwise: enable the irq and also assign the polling interval * to a specific time, which will be used when calling * syna_cdev_ioctl_send_message. * the min. polling time is RESP_IN_POLLING * * @param * [ in] tcm: the driver handle * [ in] ubuf_ptr: points to a memory space from userspace * [ in] buf_size: size of given space * [ in] data_size: size of actual data * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_ioctl_enable_irq(struct syna_tcm *tcm, const unsigned char *ubuf_ptr, unsigned int buf_size, unsigned int data_size) { int retval = 0; unsigned int data; if (!tcm->is_connected) { LOGE("Not connected\n"); return -ENXIO; } if ((buf_size < sizeof(data)) || (data_size < sizeof(data))) { LOGE("Invalid sync data size, buf_size:%d, data_size:%d\n", buf_size, data_size); return -EINVAL; } if (!tcm->hw_if->ops_enable_irq) { LOGW("Not support irq control\n"); return -EFAULT; } retval = copy_from_user(&data, ubuf_ptr, buf_size); if (retval) { LOGE("Fail to copy data from user space, size:%d\n", retval); return -EBADE; } switch (data) { case SYSFS_DISABLED_INTERRUPT: retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, false); if (retval < 0) { LOGE("Fail to disable interrupt\n"); return retval; } g_sysfs_io_polling_interval = tcm->tcm_dev->msg_data.default_resp_reading; LOGI("IRQ is disabled by userspace application\n"); break; case SYSFS_ENABLED_INTERRUPT: retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, true); if (retval < 0) { LOGE("Fail to enable interrupt\n"); return retval; } g_sysfs_io_polling_interval = RESP_IN_ATTN; LOGI("IRQ is enabled by userspace application\n"); break; default: /* recover the interrupt and also assing the polling interval */ retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, true); if (retval < 0) { LOGE("Fail to enable interrupt\n"); return retval; } g_sysfs_io_polling_interval = data; if (g_sysfs_io_polling_interval < RESP_IN_POLLING) g_sysfs_io_polling_interval = RESP_IN_POLLING; LOGI("IRQ is enabled by userspace application\n"); LOGI("Polling interval is set to %d ms\n", g_sysfs_io_polling_interval); break; } return 0; } /** * syna_cdev_ioctl_store_pid() * * Save PID through IOCTL interface * * @param * [ in] tcm: the driver handle * [ in] ubuf_ptr: points to a memory space from userspace * [ in] buf_size: size of given space * [ in] data_size: size of actual data * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_ioctl_store_pid(struct syna_tcm *tcm, const unsigned char *ubuf_ptr, unsigned int buf_size, unsigned int data_size) { int retval = 0; unsigned char *data = NULL; if (!tcm->is_connected) { LOGE("Not connected\n"); return -ENXIO; } if (buf_size < 4) { LOGE("Invalid sync data size, buf_size:%d\n", buf_size); return -EINVAL; } if (data_size < 4) { LOGE("Invalid data_size\n"); return -EINVAL; } syna_tcm_buf_lock(&g_cdev_cbuf); retval = syna_tcm_buf_alloc(&g_cdev_cbuf, buf_size); if (retval < 0) { LOGE("Fail to allocate memory for g_cdev_buf, size: %d\n", buf_size); goto exit; } data = g_cdev_cbuf.buf; retval = copy_from_user(data, ubuf_ptr, data_size); if (retval) { LOGE("Fail to copy data from user space, size:%d\n", retval); retval = -EBADE; goto exit; } tcm->proc_pid = syna_pal_le4_to_uint(&data[0]); LOGD("PID: %d\n", (unsigned int)tcm->proc_pid); #ifdef ENABLE_PID_TASK if (tcm->proc_pid) { tcm->proc_task = pid_task( find_vpid(tcm->proc_pid), PIDTYPE_PID); if (!tcm->proc_task) { LOGE("Fail to locate task, pid: %d\n", (unsigned int)tcm->proc_pid); retval = -ESRCH; goto exit; } } #endif exit: syna_tcm_buf_unlock(&g_cdev_cbuf); return retval; } /** * syna_cdev_ioctl_raw_read() * * Read the data from device directly without routing to command wrapper * interface. * * @param * [ in] tcm: the driver handle * [in/out] ubuf_ptr: ubuf_ptr: points to a memory space from userspace * [ in] buf_size: size of given space * [ in] rd_size: reading size * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_ioctl_raw_read(struct syna_tcm *tcm, const unsigned char *ubuf_ptr, unsigned int buf_size, unsigned int rd_size) { int retval = 0; unsigned char *data = NULL; if (!tcm->is_connected) { LOGE("Not connected\n"); return -ENXIO; } if ((buf_size < 0) || (rd_size > buf_size)) { LOGE("Invalid sync data size, buf_size:%d, rd_size:%d\n", buf_size, rd_size); return -EINVAL; } if (rd_size == 0) { LOGE("The read length is 0\n"); return 0; } syna_tcm_buf_lock(&g_cdev_cbuf); retval = syna_tcm_buf_alloc(&g_cdev_cbuf, rd_size); if (retval < 0) { LOGE("Fail to allocate memory for g_cdev_cbuf, size: %d\n", rd_size); goto exit; } data = g_cdev_cbuf.buf; retval = syna_tcm_read(tcm->tcm_dev, data, rd_size); if (retval < 0) { LOGE("Fail to read raw data, size: %d\n", rd_size); goto exit; } if (copy_to_user((void *)ubuf_ptr, data, rd_size)) { LOGE("Fail to copy data to user space\n"); retval = -EBADE; goto exit; } retval = rd_size; exit: syna_tcm_buf_unlock(&g_cdev_cbuf); return retval; } /** * syna_cdev_ioctl_raw_write() * * Write the given data to device directly without routing to command wrapper * interface. * * @param * [ in] tcm: the driver handle * [ in] ubuf_ptr: points to a memory space from userspace * [ in] buf_size: size of given space * [ in] wr_size: size to write * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_ioctl_raw_write(struct syna_tcm *tcm, const unsigned char *ubuf_ptr, unsigned int buf_size, unsigned int wr_size) { int retval = 0; unsigned char *data = NULL; if (!tcm->is_connected) { LOGE("Not connected\n"); return -ENXIO; } if ((buf_size < 0) || (wr_size > buf_size)) { LOGE("Invalid sync data size, buf_size:%d, wr_size:%d\n", buf_size, wr_size); return -EINVAL; } if (wr_size == 0) { LOGE("Invalid written size\n"); return -EINVAL; } syna_tcm_buf_lock(&g_cdev_cbuf); retval = syna_tcm_buf_alloc(&g_cdev_cbuf, wr_size); if (retval < 0) { LOGE("Fail to allocate memory for g_cdev_cbuf, size: %d\n", wr_size); goto exit; } data = g_cdev_cbuf.buf; retval = copy_from_user(data, ubuf_ptr, wr_size); if (retval) { LOGE("Fail to copy data from user space, size:%d\n", retval); retval = -EBADE; goto exit; } LOGD("Write command: 0x%02x, legnth: 0x%02x, 0x%02x (size:%d)\n", data[0], data[1], data[2], wr_size); retval = syna_tcm_write(tcm->tcm_dev, data, wr_size); if (retval < 0) { LOGE("Fail to write raw data, size: %d\n", wr_size); goto exit; } retval = wr_size; exit: syna_tcm_buf_unlock(&g_cdev_cbuf); return retval; } /** * syna_cdev_ioctl_dispatch() * * Dispatch the IOCTLs operation based on the given code * * @param * [ in] tcm: the driver handle * [ in] code: code for the target operation * [ in] ubuf_ptr: points to a memory space from userspace * [ in] ubuf_size: size of given space * [ in] wr_size: written data size * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_ioctl_dispatch(struct syna_tcm *tcm, unsigned int code, const unsigned char *ubuf_ptr, unsigned int ubuf_size, unsigned int *data_size) { int retval = 0; switch (code) { case STD_SET_PID_ID: retval = syna_cdev_ioctl_store_pid(tcm, ubuf_ptr, ubuf_size, *data_size); break; case STD_ENABLE_IRQ_ID: retval = syna_cdev_ioctl_enable_irq(tcm, ubuf_ptr, ubuf_size, *data_size); break; case STD_RAW_WRITE_ID: retval = syna_cdev_ioctl_raw_write(tcm, ubuf_ptr, ubuf_size, *data_size); break; case STD_RAW_READ_ID: retval = syna_cdev_ioctl_raw_read(tcm, ubuf_ptr, ubuf_size, *data_size); break; case STD_GET_FRAME_ID: retval = syna_cdev_ioctl_get_frame(tcm, ubuf_ptr, ubuf_size, data_size); break; case STD_SEND_MESSAGE_ID: retval = syna_cdev_ioctl_send_message(tcm, ubuf_ptr, ubuf_size, data_size); break; case STD_SET_REPORTS_ID: retval = syna_cdev_ioctl_set_reports(tcm, ubuf_ptr, ubuf_size, *data_size); break; case STD_CHECK_FRAMES_ID: retval = syna_cdev_ioctl_check_frame(tcm, ubuf_ptr, ubuf_size, *data_size); break; case STD_CLEAN_OUT_FRAMES_ID: LOGD("STD_CLEAN_OUT_FRAMES_ID called\n"); syna_cdev_clean_queue(tcm); retval = 0; break; default: LOGE("Unknown ioctl code: 0x%x\n", code); return -EINVAL; } return retval; } /** * syna_cdev_ioctl_old_dispatch() * * Dispatch the old IOCTLs operation based on the given code * * @param * [ in] tcm: the driver handle * [ in] code: code for the target operation * [ in] arg: argument passed from user-space * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_ioctl_old_dispatch(struct syna_tcm *tcm, unsigned int code, unsigned long arg) { int retval = 0; switch (code) { case OLD_RESET_ID: retval = syna_tcm_reset(tcm->tcm_dev); if (retval < 0) { LOGE("Fail to do reset\n"); break; } retval = tcm->dev_set_up_app_fw(tcm); if (retval < 0) { LOGE("Fail to set up app fw\n"); break; } break; case OLD_SET_IRQ_MODE_ID: if (!tcm->hw_if->ops_enable_irq) { retval = -EFAULT; break; } if (arg == 0) retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, false); else if (arg == 1) retval = tcm->hw_if->ops_enable_irq(tcm->hw_if, true); break; case OLD_SET_RAW_MODE_ID: if (arg == 0) tcm->is_attn_redirecting = false; else if (arg == 1) tcm->is_attn_redirecting = true; break; case OLD_CONCURRENT_ID: retval = 0; break; default: LOGE("Unknown ioctl code: 0x%x\n", code); retval = -EINVAL; break; } return retval; } /** * syna_cdev_ioctls() * * Used to implements the IOCTL operations * * @param * [ in] filp: represents the file descriptor * [ in] cmd: command code sent from userspace * [ in] arg: arguments sent from userspace * * @return * on success, 0; otherwise, negative value on error. */ #ifdef USE_UNLOCKED_IOCTL static long syna_cdev_ioctls(struct file *filp, unsigned int cmd, unsigned long arg) #else static int syna_cdev_ioctls(struct inode *inp, struct file *filp, unsigned int cmd, unsigned long arg) #endif { int retval = 0; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; struct syna_ioctl_data ioc_data; unsigned char *ptr = NULL; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); syna_pal_mutex_lock(&g_extif_mutex); retval = 0; /* handle the old IOCTLs */ if ((_IOC_NR(cmd)) < STD_IOCTL_BEGIN) { retval = syna_cdev_ioctl_old_dispatch(tcm, (unsigned int)_IOC_NR(cmd), arg); goto exit; } else if ((_IOC_NR(cmd)) == STD_IOCTL_BEGIN) { retval = 1; goto exit; } retval = copy_from_user(&ioc_data, (void __user *) arg, sizeof(struct syna_ioctl_data)); if (retval) { LOGE("Fail to copy ioctl_data from user space, size:%d\n", retval); retval = -EBADE; goto exit; } ptr = ioc_data.buf; retval = syna_cdev_ioctl_dispatch(tcm, (unsigned int)_IOC_NR(cmd), (const unsigned char *)ptr, ioc_data.buf_size, &ioc_data.data_length); if (retval < 0) goto exit; retval = copy_to_user((void __user *) arg, &ioc_data, sizeof(struct syna_ioctl_data)); if (retval) { LOGE("Fail to update ioctl_data to user space, size:%d\n", retval); retval = -EBADE; goto exit; } exit: syna_pal_mutex_unlock(&g_extif_mutex); return retval; } #ifdef USE_COMPAT_IOCTL /** * syna_cdev_compat_ioctls() * * Used to implements the IOCTL compatible operations * * @param * [ in] filp: represents the file descriptor * [ in] cmd: command code sent from userspace * [ in] arg: arguments sent from userspace * * @return * on success, 0; otherwise, negative value on error. */ static long syna_cdev_compat_ioctls(struct file *filp, unsigned int cmd, unsigned long arg) { int retval = 0; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; struct syna_tcm_ioctl_data_compat ioc_data; unsigned char *ptr = NULL; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); syna_pal_mutex_lock(&g_extif_mutex); retval = 0; /* handle the old IOCTLs */ if ((_IOC_NR(cmd)) < STD_IOCTL_BEGIN) { retval = syna_cdev_ioctl_old_dispatch(tcm, (unsigned int)_IOC_NR(cmd), arg); goto exit; } else if ((_IOC_NR(cmd)) == STD_IOCTL_BEGIN) { retval = 1; goto exit; } retval = copy_from_user(&ioc_data, (struct syna_tcm_ioctl_data_compat __user *) compat_ptr(arg), sizeof(struct syna_tcm_ioctl_data_compat)); if (retval) { LOGE("Fail to copy ioctl_data from user space, size:%d\n", retval); retval = -EBADE; goto exit; } ptr = compat_ptr((unsigned long)ioc_data.buf); retval = syna_cdev_ioctl_dispatch(tcm, (unsigned int)_IOC_NR(cmd), (const unsigned char *)ptr, ioc_data.buf_size, &ioc_data.data_length); if (retval < 0) goto exit; retval = copy_to_user(compat_ptr(arg), &ioc_data, sizeof(struct syna_tcm_ioctl_data_compat)); if (retval) { LOGE("Fail to update ioctl_data to user space, size:%d\n", retval); retval = -EBADE; goto exit; } exit: syna_pal_mutex_unlock(&g_extif_mutex); return retval; } #endif /** * syna_cdev_llseek() * * Used to change the current position in a file. * * @param * [ in] filp: represents the file descriptor * [ in] off: the file position * [ in] whence: flag for seeking * * @return * not support */ static loff_t syna_cdev_llseek(struct file *filp, loff_t off, int whence) { return -EFAULT; } /** * syna_cdev_read() * * Used to read data through the device file. * Function will use raw write approach. * * @param * [ in] filp: represents the file descriptor * [out] buf: given buffer from userspace * [ in] count: size of buffer * [ in] f_pos: the file position * * @return * on success, 0; otherwise, negative value on error. */ static ssize_t syna_cdev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { int retval = 0; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); if (count == 0) return 0; syna_pal_mutex_lock(&g_extif_mutex); retval = syna_cdev_ioctl_raw_read(tcm, (const unsigned char *)buf, count, count); if (retval != count) { LOGE("Invalid read operation, request:%d, return:%d\n", (unsigned int)count, retval); } syna_pal_mutex_unlock(&g_extif_mutex); return retval; } /** * syna_cdev_write() * * Used to send data to device through the device file. * Function will use raw write approach. * * @param * [ in] filp: represents the file descriptor * [ in] buf: given buffer from userspace * [ in] count: size of buffer * [ in] f_pos: the file position * * @return * on success, 0; otherwise, negative value on error. */ static ssize_t syna_cdev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { int retval = 0; struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); if (count == 0) return 0; syna_pal_mutex_lock(&g_extif_mutex); retval = syna_cdev_ioctl_raw_write(tcm, (const unsigned char *)buf, count, count); if (retval != count) { LOGE("Invalid write operation, request:%d, return:%d\n", (unsigned int)count, retval); } syna_pal_mutex_unlock(&g_extif_mutex); return retval; } /** * syna_cdev_open() * * Invoked when the device file is being open, which should be * always the first operation performed on the device file * * @param * [ in] inp: represents a file in rootfs * [ in] filp: represents the file descriptor * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_open(struct inode *inp, struct file *filp) { struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); syna_pal_mutex_lock(&g_extif_mutex); if (tcm->char_dev_ref_count != 0) { LOGN("cdev already open, %d\n", tcm->char_dev_ref_count); return -EBUSY; } tcm->char_dev_ref_count++; g_sysfs_io_polling_interval = 0; #ifdef ENABLE_EXTERNAL_FRAME_PROCESS syna_cdev_clean_queue(tcm); #endif syna_pal_mutex_unlock(&g_extif_mutex); LOGI("cdev open\n"); return 0; } /** * syna_cdev_release() * * Invoked when the device file is being released * * @param * [ in] inp: represents a file in rootfs * [ in] filp: represents the file descriptor * * @return * on success, 0; otherwise, negative value on error. */ static int syna_cdev_release(struct inode *inp, struct file *filp) { struct device *p_dev; struct kobject *p_kobj; struct syna_tcm *tcm; p_kobj = g_sysfs_dir->parent; p_dev = container_of(p_kobj, struct device, kobj); tcm = dev_get_drvdata(p_dev); syna_pal_mutex_lock(&g_extif_mutex); if (tcm->char_dev_ref_count <= 0) { LOGN("cdev already closed, %d\n", tcm->char_dev_ref_count); return 0; } tcm->char_dev_ref_count--; tcm->is_attn_redirecting = false; syna_pal_mem_set(tcm->report_to_queue, 0, REPORT_TYPES); #ifdef ENABLE_EXTERNAL_FRAME_PROCESS syna_cdev_clean_queue(tcm); #endif syna_pal_mutex_unlock(&g_extif_mutex); g_sysfs_io_polling_interval = 0; LOGI("cdev close\n"); return 0; } /** * Declare the operations of TouchCom device file */ static const struct file_operations device_fops = { .owner = THIS_MODULE, #ifdef USE_UNLOCKED_IOCTL .unlocked_ioctl = syna_cdev_ioctls, #ifdef USE_COMPAT_IOCTL .compat_ioctl = syna_cdev_compat_ioctls, #endif #else .ioctl = syna_cdev_ioctls, #endif .llseek = syna_cdev_llseek, .read = syna_cdev_read, .write = syna_cdev_write, .open = syna_cdev_open, .release = syna_cdev_release, }; /** * syna_cdev_redirect_attn() * * Expose the status of ATTN signal to userspace * * @param * [ in] tcm: the driver handle * * @return * none. */ void syna_cdev_redirect_attn(struct syna_tcm *tcm) { if (tcm->proc_pid) return; } #ifdef ENABLE_EXTERNAL_FRAME_PROCESS /** * syna_cdev_update_report_queue() * * Push the selected data to the queue. * * @param * [ in] tcm: the driver handle * [ in] code: report type * [ in] pevent_data: report payload * * @return * none. */ void syna_cdev_update_report_queue(struct syna_tcm *tcm, unsigned char code, struct tcm_buffer *pevent_data) { int retval; unsigned char *frame_buffer = NULL; unsigned int frame_length = 0; if (pevent_data == NULL) { LOGE("Returned, invalid event data pointer\n"); return; } frame_length = pevent_data->data_length + 3; LOGD("The overall queuing data length = %d\n", frame_length); frame_buffer = (unsigned char *)syna_pal_mem_alloc(frame_length, sizeof(unsigned char)); if (!frame_buffer) { LOGE("Fail to allocate buffer, size: %d, data_length: %d\n", pevent_data->data_length + 3, pevent_data->data_length); return; } frame_buffer[0] = code; frame_buffer[1] = (unsigned char)pevent_data->data_length; frame_buffer[2] = (unsigned char)(pevent_data->data_length >> 8); if (pevent_data->data_length > 0) { retval = syna_pal_mem_cpy(&frame_buffer[3], (frame_length - 3), pevent_data->buf, pevent_data->data_length, pevent_data->data_length); if (retval < 0) { LOGE("Fail to copy data to buffer, size: %d\n", pevent_data->data_length); goto exit; } } retval = syna_cdev_insert_fifo(tcm, frame_buffer, frame_length); if (retval < 0) { LOGE("Fail to insert data to fifo\n"); goto exit; } wake_up_interruptible(&(tcm->wait_frame)); exit: syna_pal_mem_free((void *)frame_buffer); } #endif /** * syna_cdev_devnode() * * Provide the declaration of devtmpfs * * @param * [ in] dev: an instance of device * [ in] mode: mode of created node * * @return * the string of devtmpfs */ static char *syna_cdev_devnode(struct device *dev, umode_t *mode) { if (!mode) return NULL; /* S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH */ *mode = CHAR_DEVICE_MODE; return kasprintf(GFP_KERNEL, "%s", dev_name(dev)); } /** * syna_cdev_create_sysfs() * * Create a device node and register it with sysfs. * * @param * [ in] tcm: the driver handle * [ in] pdev: an instance of platform device * * @return * on success, 0; otherwise, negative value on error. */ int syna_cdev_create_sysfs(struct syna_tcm *tcm, struct platform_device *pdev) { int retval = 0; struct class *device_class = NULL; struct device *device = NULL; static int cdev_major_num; tcm->device_class = NULL; tcm->device = NULL; tcm->is_attn_redirecting = false; syna_pal_mutex_alloc(&g_extif_mutex); #ifdef ENABLE_EXTERNAL_FRAME_PROCESS syna_pal_mutex_alloc(&g_fifo_queue_mutex); #endif syna_tcm_buf_init(&g_cdev_cbuf); if (cdev_major_num) { tcm->char_dev_num = MKDEV(cdev_major_num, 0); retval = register_chrdev_region(tcm->char_dev_num, 1, PLATFORM_DRIVER_NAME); if (retval < 0) { LOGE("Fail to register char device\n"); goto err_register_chrdev_region; } } else { retval = alloc_chrdev_region(&tcm->char_dev_num, 0, 1, PLATFORM_DRIVER_NAME); if (retval < 0) { LOGE("Fail to allocate char device\n"); goto err_alloc_chrdev_region; } cdev_major_num = MAJOR(tcm->char_dev_num); } cdev_init(&tcm->char_dev, &device_fops); tcm->char_dev.owner = THIS_MODULE; retval = cdev_add(&tcm->char_dev, tcm->char_dev_num, 1); if (retval < 0) { LOGE("Fail to add cdev_add\n"); goto err_add_chardev; } device_class = class_create(THIS_MODULE, PLATFORM_DRIVER_NAME); if (IS_ERR(device_class)) { LOGE("Fail to create device class\n"); retval = PTR_ERR(device_class); goto err_create_class; } device_class->devnode = syna_cdev_devnode; device = device_create(device_class, NULL, tcm->char_dev_num, NULL, CHAR_DEVICE_NAME"%d", MINOR(tcm->char_dev_num)); if (IS_ERR(tcm->device)) { LOGE("Fail to create character device\n"); retval = -ENOENT; goto err_create_device; } tcm->device_class = device_class; tcm->device = device; tcm->char_dev_ref_count = 0; tcm->proc_pid = 0; #ifdef ENABLE_EXTERNAL_FRAME_PROCESS INIT_LIST_HEAD(&tcm->frame_fifo_queue); init_waitqueue_head(&tcm->wait_frame); #endif syna_pal_mem_set(tcm->report_to_queue, 0, REPORT_TYPES); retval = syna_sysfs_create_dir(tcm, pdev); if (retval < 0) { LOGE("Fail to create sysfs dir\n"); retval = -ENOTDIR; goto err_create_dir; } return 0; err_create_dir: device_destroy(device_class, tcm->char_dev_num); err_create_device: class_destroy(device_class); err_create_class: cdev_del(&tcm->char_dev); err_add_chardev: unregister_chrdev_region(tcm->char_dev_num, 1); err_alloc_chrdev_region: err_register_chrdev_region: return retval; } /** * syna_cdev_remove_sysfs() * * Remove the allocate cdev device node and release the resource * * @param * [ in] tcm: the driver handle * * @return * none. */ void syna_cdev_remove_sysfs(struct syna_tcm *tcm) { if (!tcm) { LOGE("Invalid tcm driver handle\n"); return; } syna_sysfs_remove_dir(tcm); syna_pal_mem_set(tcm->report_to_queue, 0, REPORT_TYPES); syna_cdev_clean_queue(tcm); syna_pal_mutex_free(&g_fifo_queue_mutex); tcm->char_dev_ref_count = 0; tcm->proc_pid = 0; if (tcm->device) { device_destroy(tcm->device_class, tcm->char_dev_num); class_destroy(tcm->device_class); cdev_del(&tcm->char_dev); unregister_chrdev_region(tcm->char_dev_num, 1); } syna_tcm_buf_release(&g_cdev_cbuf); syna_pal_mutex_free(&g_extif_mutex); tcm->device_class = NULL; tcm->device = NULL; }