6db4831e98
Android 14
680 lines
14 KiB
C
680 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/cdev.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/uaccess.h>
|
|
#include "synaptics_tcm_core.h"
|
|
|
|
#define CHAR_DEVICE_NAME "tcm"
|
|
|
|
#define CONCURRENT true
|
|
|
|
#define DEVICE_IOC_MAGIC 's'
|
|
#define DEVICE_IOC_RESET _IO(DEVICE_IOC_MAGIC, 0) /* 0x00007300 */
|
|
#define DEVICE_IOC_IRQ _IOW(DEVICE_IOC_MAGIC, 1, int) /* 0x40047301 */
|
|
#define DEVICE_IOC_RAW _IOW(DEVICE_IOC_MAGIC, 2, int) /* 0x40047302 */
|
|
#define DEVICE_IOC_CONCURRENT _IOW(DEVICE_IOC_MAGIC, 3, int) /* 0x40047303 */
|
|
|
|
struct device_hcd {
|
|
dev_t dev_num;
|
|
bool raw_mode;
|
|
bool concurrent;
|
|
unsigned int ref_count;
|
|
struct cdev char_dev;
|
|
struct class *class;
|
|
struct device *device;
|
|
struct syna_tcm_buffer out;
|
|
struct syna_tcm_buffer resp;
|
|
struct syna_tcm_buffer report;
|
|
struct syna_tcm_hcd *tcm_hcd;
|
|
};
|
|
|
|
DECLARE_COMPLETION(device_remove_complete);
|
|
|
|
static struct device_hcd *device_hcd;
|
|
|
|
static int rmidev_major_num;
|
|
|
|
static void device_capture_touch_report(unsigned int count)
|
|
{
|
|
int retval;
|
|
unsigned char id;
|
|
unsigned int idx;
|
|
unsigned int size;
|
|
unsigned char *data;
|
|
struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
|
|
static bool report;
|
|
static unsigned int offset;
|
|
static unsigned int remaining_size;
|
|
|
|
if (count < 2)
|
|
return;
|
|
|
|
data = &device_hcd->resp.buf[0];
|
|
|
|
if (data[0] != MESSAGE_MARKER)
|
|
return;
|
|
|
|
id = data[1];
|
|
|
|
size = 0;
|
|
|
|
LOCK_BUFFER(device_hcd->report);
|
|
|
|
switch (id) {
|
|
case REPORT_TOUCH:
|
|
if (count >= 4) {
|
|
remaining_size = le2_to_uint(&data[2]);
|
|
} else {
|
|
report = false;
|
|
goto exit;
|
|
}
|
|
retval = syna_tcm_alloc_mem(tcm_hcd,
|
|
&device_hcd->report,
|
|
remaining_size);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to allocate memory for device_hcd->report.buf\n");
|
|
report = false;
|
|
goto exit;
|
|
}
|
|
idx = 4;
|
|
size = count - idx;
|
|
offset = 0;
|
|
report = true;
|
|
break;
|
|
case STATUS_CONTINUED_READ:
|
|
if (report == false)
|
|
goto exit;
|
|
if (count >= 2) {
|
|
idx = 2;
|
|
size = count - idx;
|
|
}
|
|
break;
|
|
default:
|
|
goto exit;
|
|
}
|
|
|
|
if (size) {
|
|
size = MIN(size, remaining_size);
|
|
retval = secure_memcpy(&device_hcd->report.buf[offset],
|
|
device_hcd->report.buf_size - offset,
|
|
&data[idx],
|
|
count - idx,
|
|
size);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to copy touch report data\n");
|
|
report = false;
|
|
goto exit;
|
|
} else {
|
|
offset += size;
|
|
remaining_size -= size;
|
|
device_hcd->report.data_length += size;
|
|
}
|
|
}
|
|
|
|
if (remaining_size)
|
|
goto exit;
|
|
|
|
LOCK_BUFFER(tcm_hcd->report.buffer);
|
|
|
|
tcm_hcd->report.buffer.buf = device_hcd->report.buf;
|
|
tcm_hcd->report.buffer.buf_size = device_hcd->report.buf_size;
|
|
tcm_hcd->report.buffer.data_length = device_hcd->report.data_length;
|
|
|
|
tcm_hcd->report_touch();
|
|
|
|
UNLOCK_BUFFER(tcm_hcd->report.buffer);
|
|
|
|
report = false;
|
|
|
|
exit:
|
|
UNLOCK_BUFFER(device_hcd->report);
|
|
}
|
|
|
|
static int device_capture_touch_report_config(unsigned int count)
|
|
{
|
|
int retval;
|
|
unsigned int size;
|
|
unsigned int buf_size;
|
|
unsigned char *data;
|
|
struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
|
|
|
|
if (device_hcd->raw_mode) {
|
|
if (count < 3) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Invalid write data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
size = le2_to_uint(&device_hcd->out.buf[1]);
|
|
|
|
if (count - 3 < size) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Incomplete write data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!size)
|
|
return 0;
|
|
|
|
data = &device_hcd->out.buf[3];
|
|
buf_size = device_hcd->out.buf_size - 3;
|
|
} else {
|
|
size = count - 1;
|
|
|
|
if (!size)
|
|
return 0;
|
|
|
|
data = &device_hcd->out.buf[1];
|
|
buf_size = device_hcd->out.buf_size - 1;
|
|
}
|
|
|
|
LOCK_BUFFER(tcm_hcd->config);
|
|
|
|
retval = syna_tcm_alloc_mem(tcm_hcd,
|
|
&tcm_hcd->config,
|
|
size);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to allocate memory for tcm_hcd->config.buf\n");
|
|
UNLOCK_BUFFER(tcm_hcd->config);
|
|
return retval;
|
|
}
|
|
|
|
retval = secure_memcpy(tcm_hcd->config.buf,
|
|
tcm_hcd->config.buf_size,
|
|
data,
|
|
buf_size,
|
|
size);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to copy touch report config data\n");
|
|
UNLOCK_BUFFER(tcm_hcd->config);
|
|
return retval;
|
|
}
|
|
|
|
tcm_hcd->config.data_length = size;
|
|
|
|
UNLOCK_BUFFER(tcm_hcd->config);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAVE_UNLOCKED_IOCTL
|
|
static long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
#else
|
|
static int device_ioctl(struct inode *inp, struct file *filp, unsigned int cmd,
|
|
unsigned long arg)
|
|
#endif
|
|
{
|
|
int retval;
|
|
struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
|
|
|
|
mutex_lock(&tcm_hcd->extif_mutex);
|
|
|
|
retval = 0;
|
|
|
|
switch (cmd) {
|
|
case DEVICE_IOC_RESET:
|
|
retval = tcm_hcd->reset(tcm_hcd, false, true);
|
|
break;
|
|
case DEVICE_IOC_IRQ:
|
|
if (arg == 0)
|
|
retval = tcm_hcd->enable_irq(tcm_hcd, false, false);
|
|
else if (arg == 1)
|
|
retval = tcm_hcd->enable_irq(tcm_hcd, true, NULL);
|
|
break;
|
|
case DEVICE_IOC_RAW:
|
|
if (arg == 0) {
|
|
device_hcd->raw_mode = false;
|
|
tcm_hcd->update_watchdog(tcm_hcd, true);
|
|
} else if (arg == 1) {
|
|
device_hcd->raw_mode = true;
|
|
tcm_hcd->update_watchdog(tcm_hcd, false);
|
|
}
|
|
break;
|
|
case DEVICE_IOC_CONCURRENT:
|
|
if (arg == 0)
|
|
device_hcd->concurrent = false;
|
|
else if (arg == 1)
|
|
device_hcd->concurrent = true;
|
|
break;
|
|
default:
|
|
retval = -ENOTTY;
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&tcm_hcd->extif_mutex);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static loff_t device_llseek(struct file *filp, loff_t off, int whence)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static ssize_t device_read(struct file *filp, char __user *buf,
|
|
size_t count, loff_t *f_pos)
|
|
{
|
|
int retval;
|
|
struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
|
|
|
|
if (count == 0)
|
|
return 0;
|
|
|
|
mutex_lock(&tcm_hcd->extif_mutex);
|
|
|
|
LOCK_BUFFER(device_hcd->resp);
|
|
|
|
if (device_hcd->raw_mode) {
|
|
retval = syna_tcm_alloc_mem(tcm_hcd,
|
|
&device_hcd->resp,
|
|
count);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to allocate memory for device_hcd->resp.buf\n");
|
|
UNLOCK_BUFFER(device_hcd->resp);
|
|
goto exit;
|
|
}
|
|
retval = tcm_hcd->read_message(tcm_hcd,
|
|
device_hcd->resp.buf,
|
|
count);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to read message\n");
|
|
UNLOCK_BUFFER(device_hcd->resp);
|
|
goto exit;
|
|
}
|
|
} else {
|
|
if (count != device_hcd->resp.data_length) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Invalid length information\n");
|
|
UNLOCK_BUFFER(device_hcd->resp);
|
|
retval = -EINVAL;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
if (copy_to_user(buf, device_hcd->resp.buf, count)) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to copy data to user space\n");
|
|
UNLOCK_BUFFER(device_hcd->resp);
|
|
retval = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (!device_hcd->concurrent)
|
|
goto skip_concurrent;
|
|
|
|
if (tcm_hcd->report_touch == NULL) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Unable to report touch\n");
|
|
device_hcd->concurrent = false;
|
|
}
|
|
|
|
if (device_hcd->raw_mode)
|
|
device_capture_touch_report(count);
|
|
|
|
skip_concurrent:
|
|
UNLOCK_BUFFER(device_hcd->resp);
|
|
|
|
retval = count;
|
|
|
|
exit:
|
|
mutex_unlock(&tcm_hcd->extif_mutex);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static ssize_t device_write(struct file *filp, const char __user *buf,
|
|
size_t count, loff_t *f_pos)
|
|
{
|
|
int retval;
|
|
struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
|
|
|
|
if (count == 0)
|
|
return 0;
|
|
|
|
mutex_lock(&tcm_hcd->extif_mutex);
|
|
|
|
LOCK_BUFFER(device_hcd->out);
|
|
|
|
retval = syna_tcm_alloc_mem(tcm_hcd,
|
|
&device_hcd->out,
|
|
count == 1 ? count + 1 : count);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to allocate memory for device_hcd->out.buf\n");
|
|
UNLOCK_BUFFER(device_hcd->out);
|
|
goto exit;
|
|
}
|
|
|
|
if (copy_from_user(device_hcd->out.buf, buf, count)) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to copy data from user space\n");
|
|
UNLOCK_BUFFER(device_hcd->out);
|
|
retval = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
LOCK_BUFFER(device_hcd->resp);
|
|
|
|
if (device_hcd->raw_mode) {
|
|
retval = tcm_hcd->write_message(tcm_hcd,
|
|
device_hcd->out.buf[0],
|
|
&device_hcd->out.buf[1],
|
|
count - 1,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
0);
|
|
} else {
|
|
mutex_lock(&tcm_hcd->reset_mutex);
|
|
retval = tcm_hcd->write_message(tcm_hcd,
|
|
device_hcd->out.buf[0],
|
|
&device_hcd->out.buf[1],
|
|
count - 1,
|
|
&device_hcd->resp.buf,
|
|
&device_hcd->resp.buf_size,
|
|
&device_hcd->resp.data_length,
|
|
NULL,
|
|
0);
|
|
mutex_unlock(&tcm_hcd->reset_mutex);
|
|
}
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to write command 0x%02x\n",
|
|
device_hcd->out.buf[0]);
|
|
UNLOCK_BUFFER(device_hcd->resp);
|
|
UNLOCK_BUFFER(device_hcd->out);
|
|
goto exit;
|
|
}
|
|
|
|
if (count && device_hcd->out.buf[0] == CMD_SET_TOUCH_REPORT_CONFIG) {
|
|
retval = device_capture_touch_report_config(count);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to capture touch report config\n");
|
|
}
|
|
}
|
|
|
|
UNLOCK_BUFFER(device_hcd->out);
|
|
|
|
if (device_hcd->raw_mode)
|
|
retval = count;
|
|
else
|
|
retval = device_hcd->resp.data_length;
|
|
|
|
UNLOCK_BUFFER(device_hcd->resp);
|
|
|
|
exit:
|
|
mutex_unlock(&tcm_hcd->extif_mutex);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int device_open(struct inode *inp, struct file *filp)
|
|
{
|
|
int retval;
|
|
struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
|
|
|
|
mutex_lock(&tcm_hcd->extif_mutex);
|
|
|
|
if (device_hcd->ref_count < 1) {
|
|
device_hcd->ref_count++;
|
|
retval = 0;
|
|
} else {
|
|
retval = -EACCES;
|
|
}
|
|
|
|
mutex_unlock(&tcm_hcd->extif_mutex);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int device_release(struct inode *inp, struct file *filp)
|
|
{
|
|
struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
|
|
|
|
mutex_lock(&tcm_hcd->extif_mutex);
|
|
|
|
if (device_hcd->ref_count)
|
|
device_hcd->ref_count--;
|
|
|
|
mutex_unlock(&tcm_hcd->extif_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char *device_devnode(struct device *dev, umode_t *mode)
|
|
{
|
|
if (!mode)
|
|
return NULL;
|
|
|
|
*mode = 0666;
|
|
|
|
return kasprintf(GFP_KERNEL, "%s/%s", PLATFORM_DRIVER_NAME,
|
|
dev_name(dev));
|
|
}
|
|
|
|
static int device_create_class(void)
|
|
{
|
|
struct syna_tcm_hcd *tcm_hcd = device_hcd->tcm_hcd;
|
|
|
|
if (device_hcd->class != NULL)
|
|
return 0;
|
|
|
|
device_hcd->class = class_create(THIS_MODULE, PLATFORM_DRIVER_NAME);
|
|
|
|
if (IS_ERR(device_hcd->class)) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to create class\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
device_hcd->class->devnode = device_devnode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations device_fops = {
|
|
.owner = THIS_MODULE,
|
|
#ifdef HAVE_UNLOCKED_IOCTL
|
|
.unlocked_ioctl = device_ioctl,
|
|
#ifdef HAVE_COMPAT_IOCTL
|
|
.compat_ioctl = device_ioctl,
|
|
#endif
|
|
#else
|
|
.ioctl = device_ioctl,
|
|
#endif
|
|
.llseek = device_llseek,
|
|
.read = device_read,
|
|
.write = device_write,
|
|
.open = device_open,
|
|
.release = device_release,
|
|
};
|
|
|
|
static int device_init(struct syna_tcm_hcd *tcm_hcd)
|
|
{
|
|
int retval;
|
|
dev_t dev_num;
|
|
const struct syna_tcm_board_data *bdata = tcm_hcd->hw_if->bdata;
|
|
|
|
device_hcd = kzalloc(sizeof(*device_hcd), GFP_KERNEL);
|
|
if (!device_hcd) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to allocate memory for device_hcd\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
device_hcd->tcm_hcd = tcm_hcd;
|
|
|
|
device_hcd->concurrent = CONCURRENT;
|
|
|
|
INIT_BUFFER(device_hcd->out, false);
|
|
INIT_BUFFER(device_hcd->resp, false);
|
|
INIT_BUFFER(device_hcd->report, false);
|
|
|
|
if (rmidev_major_num) {
|
|
dev_num = MKDEV(rmidev_major_num, 0);
|
|
retval = register_chrdev_region(dev_num, 1,
|
|
PLATFORM_DRIVER_NAME);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to register char device\n");
|
|
goto err_register_chrdev_region;
|
|
}
|
|
} else {
|
|
retval = alloc_chrdev_region(&dev_num, 0, 1,
|
|
PLATFORM_DRIVER_NAME);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to allocate char device\n");
|
|
goto err_alloc_chrdev_region;
|
|
}
|
|
|
|
rmidev_major_num = MAJOR(dev_num);
|
|
}
|
|
|
|
device_hcd->dev_num = dev_num;
|
|
|
|
cdev_init(&device_hcd->char_dev, &device_fops);
|
|
|
|
retval = cdev_add(&device_hcd->char_dev, dev_num, 1);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to add char device\n");
|
|
goto err_add_chardev;
|
|
}
|
|
|
|
retval = device_create_class();
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to create class\n");
|
|
goto err_create_class;
|
|
}
|
|
|
|
device_hcd->device = device_create(device_hcd->class, NULL,
|
|
device_hcd->dev_num, NULL, CHAR_DEVICE_NAME"%d",
|
|
MINOR(device_hcd->dev_num));
|
|
if (IS_ERR(device_hcd->device)) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to create device\n");
|
|
retval = -ENODEV;
|
|
goto err_create_device;
|
|
}
|
|
|
|
if (bdata->irq_gpio >= 0) {
|
|
retval = gpio_export(bdata->irq_gpio, false);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to export GPIO\n");
|
|
} else {
|
|
retval = gpio_export_link(&tcm_hcd->pdev->dev,
|
|
"attn", bdata->irq_gpio);
|
|
if (retval < 0) {
|
|
LOG_ERR(tcm_hcd->pdev->dev.parent,
|
|
"Failed to export GPIO link\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_create_device:
|
|
class_destroy(device_hcd->class);
|
|
|
|
err_create_class:
|
|
cdev_del(&device_hcd->char_dev);
|
|
|
|
err_add_chardev:
|
|
unregister_chrdev_region(dev_num, 1);
|
|
|
|
err_alloc_chrdev_region:
|
|
err_register_chrdev_region:
|
|
RELEASE_BUFFER(device_hcd->report);
|
|
RELEASE_BUFFER(device_hcd->resp);
|
|
RELEASE_BUFFER(device_hcd->out);
|
|
|
|
kfree(device_hcd);
|
|
device_hcd = NULL;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int device_remove(struct syna_tcm_hcd *tcm_hcd)
|
|
{
|
|
if (!device_hcd)
|
|
goto exit;
|
|
|
|
device_destroy(device_hcd->class, device_hcd->dev_num);
|
|
|
|
class_destroy(device_hcd->class);
|
|
|
|
cdev_del(&device_hcd->char_dev);
|
|
|
|
unregister_chrdev_region(device_hcd->dev_num, 1);
|
|
|
|
RELEASE_BUFFER(device_hcd->report);
|
|
RELEASE_BUFFER(device_hcd->resp);
|
|
RELEASE_BUFFER(device_hcd->out);
|
|
|
|
kfree(device_hcd);
|
|
device_hcd = NULL;
|
|
|
|
exit:
|
|
complete(&device_remove_complete);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int device_reset(struct syna_tcm_hcd *tcm_hcd)
|
|
{
|
|
int retval;
|
|
|
|
if (!device_hcd) {
|
|
retval = device_init(tcm_hcd);
|
|
return retval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct syna_tcm_module_cb device_module = {
|
|
.type = TCM_DEVICE,
|
|
.init = device_init,
|
|
.remove = device_remove,
|
|
.syncbox = NULL,
|
|
.asyncbox = NULL,
|
|
.reset = device_reset,
|
|
.suspend = NULL,
|
|
.resume = NULL,
|
|
.early_suspend = NULL,
|
|
};
|
|
|
|
static int __init device_module_init(void)
|
|
{
|
|
return syna_tcm_add_module(&device_module, true);
|
|
}
|
|
|
|
static void __exit device_module_exit(void)
|
|
{
|
|
syna_tcm_add_module(&device_module, false);
|
|
|
|
wait_for_completion(&device_remove_complete);
|
|
}
|
|
|
|
module_init(device_module_init);
|
|
module_exit(device_module_exit);
|
|
|
|
MODULE_AUTHOR("Synaptics, Inc.");
|
|
MODULE_DESCRIPTION("Synaptics TCM Device Module");
|
|
MODULE_LICENSE("GPL v2");
|