blob: 0f8570a075651732d4d2fdb80a001b2549de4356 [file] [log] [blame]
/*
* Synaptics TCM touchscreen driver
*
* Copyright (C) 2017-2019 Synaptics Incorporated. All rights reserved.
*
* Copyright (C) 2017-2019 Scott Lin <scott.lin@tw.synaptics.com>
*
* 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.
*/
#include <linux/sched/signal.h>
#include "synaptics_tcm_core.h"
#define SYSFS_DIR_NAME "diagnostics"
enum pingpong_state {
PING = 0,
PONG = 1,
};
struct diag_hcd {
pid_t pid;
unsigned char report_type;
enum pingpong_state state;
struct kobject *sysfs_dir;
struct siginfo sigio;
struct task_struct *task;
struct syna_tcm_buffer ping;
struct syna_tcm_buffer pong;
struct syna_tcm_hcd *tcm_hcd;
};
DECLARE_COMPLETION(diag_remove_complete);
static struct diag_hcd *diag_hcd;
STORE_PROTOTYPE(diag, pid);
SHOW_PROTOTYPE(diag, size);
STORE_PROTOTYPE(diag, type);
SHOW_PROTOTYPE(diag, rows);
SHOW_PROTOTYPE(diag, cols);
SHOW_PROTOTYPE(diag, hybrid);
SHOW_PROTOTYPE(diag, buttons);
static struct device_attribute *attrs[] = {
ATTRIFY(pid),
ATTRIFY(size),
ATTRIFY(type),
ATTRIFY(rows),
ATTRIFY(cols),
ATTRIFY(hybrid),
ATTRIFY(buttons),
};
static ssize_t diag_sysfs_data_show(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count);
static struct bin_attribute bin_attr = {
.attr = {
.name = "data",
.mode = 0444,
},
.size = 0,
.read = diag_sysfs_data_show,
};
static ssize_t diag_sysfs_pid_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int retval;
unsigned int input;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
if (kstrtouint(buf, 10, &input))
return -EINVAL;
mutex_lock(&tcm_hcd->extif_mutex);
diag_hcd->pid = input;
if (diag_hcd->pid) {
diag_hcd->task = pid_task(find_vpid(diag_hcd->pid),
PIDTYPE_PID);
if (!diag_hcd->task) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to locate task\n");
retval = -EINVAL;
goto exit;
}
}
retval = count;
exit:
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_size_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
if (diag_hcd->state == PING) {
LOCK_BUFFER(diag_hcd->ping);
retval = snprintf(buf, PAGE_SIZE,
"%u\n",
diag_hcd->ping.data_length);
UNLOCK_BUFFER(diag_hcd->ping);
} else {
LOCK_BUFFER(diag_hcd->pong);
retval = snprintf(buf, PAGE_SIZE,
"%u\n",
diag_hcd->pong.data_length);
UNLOCK_BUFFER(diag_hcd->pong);
}
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_type_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int input;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
if (kstrtouint(buf, 10, &input))
return -EINVAL;
mutex_lock(&tcm_hcd->extif_mutex);
diag_hcd->report_type = (unsigned char)input;
mutex_unlock(&tcm_hcd->extif_mutex);
return count;
}
static ssize_t diag_sysfs_rows_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval;
unsigned int rows;
struct syna_tcm_app_info *app_info;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
tcm_hcd->app_status != APP_STATUS_OK) {
retval = -ENODEV;
goto exit;
}
app_info = &tcm_hcd->app_info;
rows = le2_to_uint(app_info->num_of_image_rows);
retval = snprintf(buf, PAGE_SIZE, "%u\n", rows);
exit:
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_cols_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval;
unsigned int cols;
struct syna_tcm_app_info *app_info;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
tcm_hcd->app_status != APP_STATUS_OK) {
retval = -ENODEV;
goto exit;
}
app_info = &tcm_hcd->app_info;
cols = le2_to_uint(app_info->num_of_image_cols);
retval = snprintf(buf, PAGE_SIZE, "%u\n", cols);
exit:
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_hybrid_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval;
unsigned int hybrid;
struct syna_tcm_app_info *app_info;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
tcm_hcd->app_status != APP_STATUS_OK) {
retval = -ENODEV;
goto exit;
}
app_info = &tcm_hcd->app_info;
hybrid = le2_to_uint(app_info->has_hybrid_data);
retval = snprintf(buf, PAGE_SIZE, "%u\n", hybrid);
exit:
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_buttons_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval;
unsigned int buttons;
struct syna_tcm_app_info *app_info;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
if (tcm_hcd->id_info.mode != MODE_APPLICATION ||
tcm_hcd->app_status != APP_STATUS_OK) {
retval = -ENODEV;
goto exit;
}
app_info = &tcm_hcd->app_info;
buttons = le2_to_uint(app_info->num_of_buttons);
retval = snprintf(buf, PAGE_SIZE, "%u\n", buttons);
exit:
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static ssize_t diag_sysfs_data_show(struct file *data_file,
struct kobject *kobj, struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count)
{
int retval;
unsigned int readlen;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
mutex_lock(&tcm_hcd->extif_mutex);
retval = 0;
if (diag_hcd->state == PING) {
LOCK_BUFFER(diag_hcd->ping);
if (diag_hcd->ping.data_length == 0) {
readlen = 0;
goto exit;
}
readlen = MIN(count, diag_hcd->ping.data_length - pos);
if (diag_hcd->ping.data_length) {
retval = secure_memcpy(buf,
count,
&diag_hcd->ping.buf[pos],
diag_hcd->ping.buf_size - pos,
readlen);
}
UNLOCK_BUFFER(diag_hcd->ping);
} else {
LOCK_BUFFER(diag_hcd->pong);
if (diag_hcd->pong.data_length == 0) {
readlen = 0;
goto exit;
}
readlen = MIN(count, diag_hcd->pong.data_length - pos);
if (diag_hcd->pong.data_length) {
retval = secure_memcpy(buf,
count,
&diag_hcd->pong.buf[pos],
diag_hcd->pong.buf_size - pos,
readlen);
}
UNLOCK_BUFFER(diag_hcd->pong);
}
exit:
if (retval < 0) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to copy report data\n");
} else {
retval = readlen;
}
mutex_unlock(&tcm_hcd->extif_mutex);
return retval;
}
static void diag_report(void)
{
int retval;
static enum pingpong_state state = PING;
struct syna_tcm_hcd *tcm_hcd = diag_hcd->tcm_hcd;
if (state == PING) {
LOCK_BUFFER(diag_hcd->ping);
retval = syna_tcm_alloc_mem(tcm_hcd,
&diag_hcd->ping,
tcm_hcd->report.buffer.data_length);
if (retval < 0) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for ping.buf\n");
UNLOCK_BUFFER(diag_hcd->ping);
return;
}
retval = secure_memcpy(diag_hcd->ping.buf,
diag_hcd->ping.buf_size,
tcm_hcd->report.buffer.buf,
tcm_hcd->report.buffer.buf_size,
tcm_hcd->report.buffer.data_length);
if (retval < 0) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to copy report data\n");
UNLOCK_BUFFER(diag_hcd->ping);
return;
}
diag_hcd->ping.data_length = tcm_hcd->report.buffer.data_length;
UNLOCK_BUFFER(diag_hcd->ping);
diag_hcd->state = state;
state = PONG;
} else {
LOCK_BUFFER(diag_hcd->pong);
retval = syna_tcm_alloc_mem(tcm_hcd,
&diag_hcd->pong,
tcm_hcd->report.buffer.data_length);
if (retval < 0) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for pong.buf\n");
UNLOCK_BUFFER(diag_hcd->pong);
return;
}
retval = secure_memcpy(diag_hcd->pong.buf,
diag_hcd->pong.buf_size,
tcm_hcd->report.buffer.buf,
tcm_hcd->report.buffer.buf_size,
tcm_hcd->report.buffer.data_length);
if (retval < 0) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to copy report data\n");
UNLOCK_BUFFER(diag_hcd->pong);
return;
}
diag_hcd->pong.data_length = tcm_hcd->report.buffer.data_length;
UNLOCK_BUFFER(diag_hcd->pong);
diag_hcd->state = state;
state = PING;
}
if (diag_hcd->pid)
send_sig_info(SIGIO, &diag_hcd->sigio, diag_hcd->task);
}
static int diag_init(struct syna_tcm_hcd *tcm_hcd)
{
int retval;
int idx;
diag_hcd = kzalloc(sizeof(*diag_hcd), GFP_KERNEL);
if (!diag_hcd) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to allocate memory for diag_hcd\n");
return -ENOMEM;
}
diag_hcd->tcm_hcd = tcm_hcd;
diag_hcd->state = PING;
INIT_BUFFER(diag_hcd->ping, false);
INIT_BUFFER(diag_hcd->pong, false);
memset(&diag_hcd->sigio, 0x00, sizeof(diag_hcd->sigio));
diag_hcd->sigio.si_signo = SIGIO;
diag_hcd->sigio.si_code = SI_USER;
diag_hcd->sysfs_dir = kobject_create_and_add(SYSFS_DIR_NAME,
tcm_hcd->sysfs_dir);
if (!diag_hcd->sysfs_dir) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to create sysfs directory\n");
retval = -EINVAL;
goto err_sysfs_create_dir;
}
for (idx = 0; idx < ARRAY_SIZE(attrs); idx++) {
retval = sysfs_create_file(diag_hcd->sysfs_dir,
&(*attrs[idx]).attr);
if (retval < 0) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to create sysfs file\n");
goto err_sysfs_create_file;
}
}
retval = sysfs_create_bin_file(diag_hcd->sysfs_dir, &bin_attr);
if (retval < 0) {
LOGE(tcm_hcd->pdev->dev.parent,
"Failed to create sysfs bin file\n");
goto err_sysfs_create_bin_file;
}
return 0;
err_sysfs_create_bin_file:
err_sysfs_create_file:
for (idx--; idx >= 0; idx--)
sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr);
kobject_put(diag_hcd->sysfs_dir);
err_sysfs_create_dir:
RELEASE_BUFFER(diag_hcd->pong);
RELEASE_BUFFER(diag_hcd->ping);
kfree(diag_hcd);
diag_hcd = NULL;
return retval;
}
static int diag_remove(struct syna_tcm_hcd *tcm_hcd)
{
int idx;
if (!diag_hcd)
goto exit;
sysfs_remove_bin_file(diag_hcd->sysfs_dir, &bin_attr);
for (idx = 0; idx < ARRAY_SIZE(attrs); idx++)
sysfs_remove_file(diag_hcd->sysfs_dir, &(*attrs[idx]).attr);
kobject_put(diag_hcd->sysfs_dir);
RELEASE_BUFFER(diag_hcd->pong);
RELEASE_BUFFER(diag_hcd->ping);
kfree(diag_hcd);
diag_hcd = NULL;
exit:
complete(&diag_remove_complete);
return 0;
}
static int diag_syncbox(struct syna_tcm_hcd *tcm_hcd)
{
if (!diag_hcd)
return 0;
if (tcm_hcd->report.id == diag_hcd->report_type)
diag_report();
return 0;
}
static int diag_reset(struct syna_tcm_hcd *tcm_hcd)
{
int retval;
if (!diag_hcd) {
retval = diag_init(tcm_hcd);
return retval;
}
return 0;
}
static struct syna_tcm_module_cb diag_module = {
.type = TCM_DIAGNOSTICS,
.init = diag_init,
.remove = diag_remove,
.syncbox = diag_syncbox,
.asyncbox = NULL,
.reset = diag_reset,
.suspend = NULL,
.resume = NULL,
.early_suspend = NULL,
};
static int __init diag_module_init(void)
{
return syna_tcm_add_module(&diag_module, true);
}
static void __exit diag_module_exit(void)
{
syna_tcm_add_module(&diag_module, false);
wait_for_completion(&diag_remove_complete);
}
module_init(diag_module_init);
module_exit(diag_module_exit);
MODULE_AUTHOR("Synaptics, Inc.");
MODULE_DESCRIPTION("Synaptics TCM Diagnostics Module");
MODULE_LICENSE("GPL v2");