blob: f442f557723bb65a8c3b3c26b2fe4c4b6a9e0822 [file] [edit]
// 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 <linux/string.h>
#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);
syna_hc_dump(tcm);
syna_debug_dump(tcm);
} 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, 0644, 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, 0644, 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, 0644, 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, 0644, 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, 0644, syna_sysfs_mf_mode_show,
syna_sysfs_mf_mode_store);
/**
* syna_sysfs_compression_threshold_show()
*
* Attribute get the heatmap compression threshold.
*
* @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_compression_threshold_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->hw_if->compression_threhsold);
syna_pal_mutex_unlock(&g_extif_mutex);
return retval;
}
/**
* syna_sysfs_compression_threshold_store()
*
* Attribute set the heatmap compression threshold.
*
* @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_compression_threshold_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, 10, &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->hw_if->compression_threhsold = input;
syna_tcm_set_dynamic_config(tcm->tcm_dev,
DC_COMPRESSION_THRESHOLD,
input,
RESP_IN_ATTN);
LOGI("Set the heatmap compression threshold as %u.\n",
tcm->hw_if->compression_threhsold);
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_compression_threshold =
__ATTR(compression_threshold, 0644, syna_sysfs_compression_threshold_show,
syna_sysfs_compression_threshold_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,
&kobj_attr_compression_threshold.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;
}