888 lines
25 KiB
C
Executable file
888 lines
25 KiB
C
Executable file
/*
|
|
*
|
|
* FocalTech TouchScreen driver.
|
|
*
|
|
* Copyright (c) 2012-2020, FocalTech Systems, Ltd., all rights reserved.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
/*****************************************************************************
|
|
*
|
|
* File Name: focaltech_hp.c
|
|
*
|
|
* Author: Focaltech Driver Team
|
|
*
|
|
* Created: 2023-12-12
|
|
*
|
|
* Abstract: Host Processing
|
|
*
|
|
* Version: V1.0
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
* Included header files
|
|
*****************************************************************************/
|
|
#include "focaltech_core.h"
|
|
|
|
/*****************************************************************************
|
|
* Private constant and macro definitions using #define
|
|
*****************************************************************************/
|
|
#define FTS_FHP_NAME "fhp_ft"
|
|
#define FTS_FHPI_DEV_NAME "fhp_input"
|
|
|
|
#define DEFAULT_IOCTL_SPI_BUFSIZE 256
|
|
#define MAX_QUEUE_BUFSIZE (32768)
|
|
|
|
#define FHP_CMD_GET_FRAME 0x01
|
|
|
|
struct fhp_spi_sync_data {
|
|
char *tx;
|
|
char *rx;
|
|
unsigned int size;
|
|
};
|
|
|
|
struct fhp_input_report_data {
|
|
char *buffer;
|
|
unsigned int size;
|
|
};
|
|
|
|
#define FHP_IOCTL_DEVICE 0xC5
|
|
#define FHP_IOCTL_RESET _IOW(FHP_IOCTL_DEVICE, 0x01, long)
|
|
#define FHP_IOCTL_SPI_SYNC _IOWR(FHP_IOCTL_DEVICE, 0x02, struct fhp_spi_sync_data)
|
|
#define FHP_IOCTL_SET_IRQ _IOW(FHP_IOCTL_DEVICE, 0x03, long)
|
|
#define FHP_IOCTL_SET_RAW_SIZE _IOW(FHP_IOCTL_DEVICE, 0x04, long)
|
|
#define FHP_IOCTL_SET_SPI_SPEED _IOW(FHP_IOCTL_DEVICE, 0x05, long)
|
|
#define FHP_IOCTL_GET_CHIP_INIT_DONE _IOR(FHP_IOCTL_DEVICE, 0x06, long)
|
|
#define FHP_IOCTL_GET_FRAME _IOR(FHP_IOCTL_DEVICE, 0x07, void *)
|
|
#define FHP_IOCTL_CLEAR_FRAME _IOW(FHP_IOCTL_DEVICE, 0x08, long)
|
|
#define FHP_IOCTL_SET_TIMEOUT _IOW(FHP_IOCTL_DEVICE, 0x09, long)
|
|
#define FHP_IOCTL_REPORT _IOW(FHP_IOCTL_DEVICE, 0x0A, struct fhp_input_report_data)
|
|
|
|
#define FHP_INPUT_IOCTL_DEVICE 0xC6
|
|
#define FHP_INPUT_IOCTL_REPORT _IOW(FHP_INPUT_IOCTL_DEVICE, 0x01, struct fhp_input_report_data)
|
|
|
|
/*****************************************************************************
|
|
* Private enumerations, structures and unions using typedef
|
|
*****************************************************************************/
|
|
typedef enum {
|
|
FHP_FRAME_WAITQ_DEFAULT,
|
|
FHP_FRAME_WAITQ_WAIT,
|
|
FHP_FRAME_WAITQ_WAIT_POLL,
|
|
FHP_FRAME_WAITQ_WAKEUP,
|
|
} FHP_FRAME_WAITQ_FLAG;
|
|
|
|
struct fhp_frame {
|
|
u64 tv;
|
|
unsigned char value[0];
|
|
};
|
|
|
|
struct fhp_queue {
|
|
int head;
|
|
int tail;
|
|
int count;
|
|
int max_count;
|
|
int elem_size;
|
|
u8 *buffer;
|
|
struct mutex mutexq;
|
|
};
|
|
|
|
struct fhp_core_data {
|
|
struct fts_ts_data *ts_data;
|
|
struct miscdevice fhp_dev;
|
|
struct miscdevice fhpi_dev;
|
|
wait_queue_head_t frame_waitq;
|
|
struct fhp_queue q;
|
|
int fhp_dev_open_cnt;
|
|
int timeout; /*0:no wait, <0 wait infinitely, >0 wait timeout*/
|
|
int frame_waitq_flag;
|
|
int touch_size_bak;
|
|
};
|
|
|
|
/*****************************************************************************
|
|
* Global variable or extern global variabls/functions
|
|
*****************************************************************************/
|
|
static struct fhp_core_data *fts_fhp_data;
|
|
static long fhp_input_report(struct fhp_core_data *fhp_data, unsigned long arg);
|
|
|
|
/*****************************************************************************
|
|
* Static function prototypes
|
|
*****************************************************************************/
|
|
static u64 fhp_get_timestamp(void)
|
|
{
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0))
|
|
ktime_t tv;
|
|
tv = ktime_get_real() / 1000;
|
|
return (u64)tv;
|
|
#else
|
|
struct timeval tv;
|
|
do_gettimeofday(&tv);
|
|
return (u64)(((u64)tv.tv_sec * 1000000) + tv.tv_usec);
|
|
#endif
|
|
}
|
|
|
|
static int fhp_spi_sync(struct fhp_core_data *fhp_data, u8 *tx_buf, u8 *rx_buf, u32 len)
|
|
{
|
|
int ret = 0;
|
|
u8 *tbuf = NULL;
|
|
u8 *rbuf = NULL;
|
|
struct spi_message msg;
|
|
struct spi_transfer xfer = { 0 };
|
|
|
|
if (!fhp_data || !fhp_data->ts_data || !fhp_data->ts_data->spi) {
|
|
FTS_ERROR("fhp/ts data/spi is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!fhp_data->ts_data->bus_tx_buf || !fhp_data->ts_data->bus_rx_buf || !tx_buf || !rx_buf || !len) {
|
|
FTS_ERROR("tx_buf/rx_buf is null/len(%d) invalid", len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&fhp_data->ts_data->bus_lock);
|
|
if (len > FTS_MAX_BUS_BUF) {
|
|
tbuf = kzalloc(len, GFP_KERNEL);
|
|
if (NULL == tbuf) {
|
|
FTS_ERROR("txbuf malloc fail");
|
|
ret = -ENOMEM;
|
|
goto err_spi_sync;
|
|
}
|
|
|
|
rbuf = kzalloc(len, GFP_KERNEL);
|
|
if (NULL == rbuf) {
|
|
FTS_ERROR("rxbuf malloc fail");
|
|
ret = -ENOMEM;
|
|
goto err_spi_sync;
|
|
}
|
|
} else {
|
|
tbuf = fhp_data->ts_data->bus_tx_buf;
|
|
rbuf = fhp_data->ts_data->bus_rx_buf;
|
|
memset(tbuf, 0x0, FTS_MAX_BUS_BUF);
|
|
memset(rbuf, 0x0, FTS_MAX_BUS_BUF);
|
|
}
|
|
|
|
memcpy(tbuf, tx_buf, len);
|
|
xfer.tx_buf = tbuf;
|
|
xfer.rx_buf = rbuf;
|
|
xfer.len = len;
|
|
spi_message_init(&msg);
|
|
spi_message_add_tail(&xfer, &msg);
|
|
ret = spi_sync(fhp_data->ts_data->spi, &msg);
|
|
memcpy(rx_buf, rbuf, len);
|
|
if (ret) {
|
|
FTS_ERROR("spi_sync fail,addr:%x,status:%x,ret:%d", tbuf[0], rbuf[3], ret);
|
|
goto err_spi_sync;
|
|
}
|
|
|
|
ret = 0;
|
|
err_spi_sync:
|
|
if (len > FTS_MAX_BUS_BUF) {
|
|
if (tbuf) {
|
|
kfree(tbuf);
|
|
tbuf = NULL;
|
|
}
|
|
|
|
if (rbuf) {
|
|
kfree(rbuf);
|
|
rbuf = NULL;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&fhp_data->ts_data->bus_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int fhpq_open(struct fhp_queue *q)
|
|
{
|
|
if (!q) {
|
|
FTS_ERROR("q is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
FTS_FUNC_ENTER();
|
|
mutex_lock(&(q->mutexq));
|
|
q->head = q->tail = q->count = 0;
|
|
q->buffer = kzalloc(MAX_QUEUE_BUFSIZE, GFP_KERNEL);
|
|
if (!q->buffer) {
|
|
FTS_ERROR("malloc queue buffer failed");
|
|
mutex_unlock(&(q->mutexq));
|
|
return -ENOMEM;
|
|
}
|
|
mutex_unlock(&(q->mutexq));
|
|
FTS_FUNC_EXIT();
|
|
return 0;
|
|
}
|
|
|
|
static int fhpq_set_element_size(struct fhp_queue *q, int elem_size)
|
|
{
|
|
int max_size = FTS_MAX_TOUCH_BUF - sizeof(struct fhp_frame);
|
|
if (!q || !elem_size || (elem_size > max_size)) {
|
|
FTS_ERROR("q is null/elem_size(%d) is invalid", elem_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
FTS_FUNC_ENTER();
|
|
mutex_lock(&(q->mutexq));
|
|
q->elem_size = elem_size;
|
|
q->max_count = MAX_QUEUE_BUFSIZE / elem_size;
|
|
FTS_INFO("set queue elem_size=%d,max_count=%d", q->elem_size, q->max_count);
|
|
mutex_unlock(&(q->mutexq));
|
|
FTS_FUNC_EXIT();
|
|
return 0;
|
|
}
|
|
|
|
static bool fhpq_full(struct fhp_queue *q)
|
|
{
|
|
return q->count == q->max_count;
|
|
}
|
|
|
|
static bool fhpq_empty(struct fhp_queue *q)
|
|
{
|
|
return q->count == 0;
|
|
}
|
|
|
|
static void fhpq_clear(struct fhp_queue *q)
|
|
{
|
|
mutex_lock(&(q->mutexq));
|
|
q->head = q->tail = q->count = 0;
|
|
mutex_unlock(&(q->mutexq));
|
|
}
|
|
|
|
static int fhpq_enqueue(struct fhp_queue *q, u8 *val, u64 timestamp)
|
|
{
|
|
struct fhp_frame *tail_elem = NULL;
|
|
|
|
if (!q || !val || !q->buffer || !q->elem_size || !q->max_count) {
|
|
FTS_ERROR("q/val/buffer is null/size is 0");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&(q->mutexq));
|
|
tail_elem = (struct fhp_frame *)&q->buffer[q->tail * q->elem_size];
|
|
tail_elem->tv = timestamp;
|
|
memcpy(tail_elem->value, val, q->elem_size);
|
|
q->tail = (q->tail + 1) % q->max_count;
|
|
if (fhpq_full(q)) {
|
|
q->head = (q->head + 1) % q->max_count;
|
|
} else {
|
|
q->count++;
|
|
}
|
|
mutex_unlock(&(q->mutexq));
|
|
return 0;
|
|
}
|
|
|
|
static int fhpq_dequeue_userspace(struct fhp_queue *q, void __user *buff)
|
|
{
|
|
if (!q || !buff || !q->buffer) {
|
|
FTS_ERROR("q/buff/buffer is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&(q->mutexq));
|
|
if (fhpq_empty(q)) {
|
|
mutex_unlock(&(q->mutexq));
|
|
return -ENODATA;
|
|
}
|
|
if (copy_to_user(buff, &q->buffer[q->head * q->elem_size], q->elem_size)) {
|
|
FTS_ERROR("copy frame data to user failed");
|
|
mutex_unlock(&(q->mutexq));
|
|
return -EFAULT;
|
|
}
|
|
q->head = (q->head + 1) % q->max_count;
|
|
q->count--;
|
|
mutex_unlock(&(q->mutexq));
|
|
return 0;
|
|
}
|
|
|
|
static int fhpq_release(struct fhp_queue *q)
|
|
{
|
|
if (q) {
|
|
mutex_lock(&(q->mutexq));
|
|
q->head = q->tail = q->count = 0;
|
|
q->max_count = q->elem_size = 0;
|
|
if (q->buffer) {
|
|
kfree(q->buffer);
|
|
q->buffer = NULL;
|
|
}
|
|
mutex_unlock(&(q->mutexq));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int fhp_ioctl_reset(struct fhp_core_data *fhp_data, unsigned long high)
|
|
{
|
|
if (!fhp_data || !fhp_data->ts_data) {
|
|
FTS_ERROR("fhp/ts data is null");
|
|
return -EINVAL;
|
|
}
|
|
return fts_set_reset(fhp_data->ts_data, (int)high);
|
|
}
|
|
|
|
static int fhp_ioctl_set_irq(struct fhp_core_data *fhp_data, unsigned long enable)
|
|
{
|
|
if (!fhp_data || !fhp_data->ts_data) {
|
|
FTS_ERROR("fhp/ts data is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
FTS_INFO("set irq to %s", enable ? "enable" : "disable");
|
|
if (enable)
|
|
fts_irq_enable();
|
|
else
|
|
fts_irq_disable();
|
|
return 0;
|
|
}
|
|
|
|
static int fhp_ioctl_spi_sync(struct fhp_core_data *fhp_data, unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
struct fhp_spi_sync_data spi_data;
|
|
u8 txbuf_temp[DEFAULT_IOCTL_SPI_BUFSIZE] = { 0 };
|
|
u8 rxbuf_temp[DEFAULT_IOCTL_SPI_BUFSIZE] = { 0 };
|
|
u8 *txbuf = txbuf_temp;
|
|
u8 *rxbuf = rxbuf_temp;
|
|
|
|
if (!fhp_data || !fhp_data->ts_data) {
|
|
FTS_ERROR("fhp/ts data is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (copy_from_user(&spi_data, (void *)arg, sizeof(struct fhp_spi_sync_data))) {
|
|
FTS_ERROR("copy spi_sync data from userspace fail");
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (!spi_data.tx || !spi_data.rx || !spi_data.size) {
|
|
FTS_ERROR("tx/rx/size(%d) from userspace are invalid", spi_data.size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (spi_data.size > DEFAULT_IOCTL_SPI_BUFSIZE) {
|
|
txbuf = kmalloc(spi_data.size, GFP_KERNEL);
|
|
rxbuf = kmalloc(spi_data.size, GFP_KERNEL);
|
|
if (!txbuf || !rxbuf) {
|
|
FTS_ERROR("kzalloc memory(size:%d) for spi tx/rx buffer fail", spi_data.size);
|
|
ret = -ENOMEM;
|
|
goto spi_sync_err;
|
|
}
|
|
}
|
|
memset(txbuf, 0, spi_data.size);
|
|
memset(rxbuf, 0, spi_data.size);
|
|
|
|
if (copy_from_user(txbuf, spi_data.tx, spi_data.size)) {
|
|
FTS_ERROR("copy spi tx data from userspace fail");
|
|
ret = -EFAULT;
|
|
goto spi_sync_err;
|
|
}
|
|
|
|
ret = fhp_spi_sync(fhp_data, txbuf, rxbuf, spi_data.size);
|
|
if (ret) {
|
|
FTS_ERROR("spi sync fail");
|
|
ret = -EIO;
|
|
goto spi_sync_err;
|
|
}
|
|
|
|
if (copy_to_user(spi_data.rx, rxbuf, spi_data.size)) {
|
|
FTS_ERROR("copy spi rx data to userspace fail");
|
|
ret = -EFAULT;
|
|
goto spi_sync_err;
|
|
}
|
|
|
|
return 0;
|
|
spi_sync_err:
|
|
if (spi_data.size > DEFAULT_IOCTL_SPI_BUFSIZE) {
|
|
kfree(txbuf);
|
|
kfree(rxbuf);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int fhp_ioctl_set_frame_size(struct fhp_core_data *fhp_data, unsigned long size)
|
|
{
|
|
int ret = 0;
|
|
int chip_frame_size = (int)size;
|
|
int frame_size = 0;
|
|
int max_size = FTS_MAX_TOUCH_BUF - sizeof(struct fhp_frame);
|
|
|
|
if (!fhp_data || !fhp_data->ts_data || (chip_frame_size <= 0) || (chip_frame_size > max_size)) {
|
|
FTS_ERROR("fhp/ts data is null/size(%d) is invalid", chip_frame_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
FTS_INFO("set frame size to %d", chip_frame_size);
|
|
frame_size = chip_frame_size + sizeof(struct fhp_frame);
|
|
ret = fhpq_set_element_size(&fhp_data->q, frame_size);
|
|
if (ret) {
|
|
FTS_ERROR("set queue element size failed,ret=%d", ret);
|
|
return ret;
|
|
}
|
|
fhp_data->touch_size_bak = fhp_data->ts_data->touch_size;
|
|
fhp_data->ts_data->touch_size = chip_frame_size;
|
|
return 0;
|
|
}
|
|
|
|
static int fhp_ioctl_set_spi_speed(struct fhp_core_data *fhp_data, unsigned long speed)
|
|
{
|
|
int ret = 0;
|
|
if (!fhp_data || !fhp_data->ts_data || !fhp_data->ts_data->spi) {
|
|
FTS_ERROR("fhp/ts data/spi is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (fhp_data->ts_data->spi->max_speed_hz == speed) {
|
|
FTS_INFO("spi speed == max_speed_hz");
|
|
return 0;
|
|
}
|
|
|
|
FTS_INFO("set spi speed to %ld", speed);
|
|
mutex_lock(&fhp_data->ts_data->bus_lock);
|
|
fhp_data->ts_data->spi->max_speed_hz = speed;
|
|
ret = spi_setup(fhp_data->ts_data->spi);
|
|
if (ret < 0) {
|
|
FTS_ERROR("spi setup fail,ret:%d", ret);
|
|
}
|
|
mutex_unlock(&fhp_data->ts_data->bus_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int fhp_ioctl_get_chip_init_done(struct fhp_core_data *fhp_data, unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
u8 status = 0;
|
|
if (!fhp_data || !fhp_data->ts_data) {
|
|
FTS_ERROR("fhp/ts data is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* 1:init done, 0:not done, others:reserved */
|
|
status = fhp_data->ts_data->fw_loading ? 0 : 1;
|
|
FTS_INFO("chip init flag:%d", status);
|
|
if (copy_to_user((void *)arg, (u8 *)&status, sizeof(u8))) {
|
|
FTS_ERROR("copy chip_init_done flag data to userspace fail");
|
|
ret = -EFAULT;
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fhp_ioctl_get_frame(struct fhp_core_data *fhp_data, unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
if (!fhp_data || !fhp_data->ts_data || !fhp_data->ts_data->touch_buf) {
|
|
FTS_ERROR("fhp/ts data is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (fhpq_empty(&fhp_data->q) && (fhp_data->frame_waitq_flag != FHP_FRAME_WAITQ_WAIT_POLL)) {
|
|
fhp_data->frame_waitq_flag = FHP_FRAME_WAITQ_WAIT;
|
|
if (fhp_data->timeout == 0) {
|
|
FTS_INFO("No frame data, and not wait");
|
|
return -ENODATA;
|
|
} else if (fhp_data->timeout < 0) {
|
|
wait_event_interruptible(fhp_data->frame_waitq,
|
|
fhp_data->frame_waitq_flag == FHP_FRAME_WAITQ_WAKEUP);
|
|
} else if (fhp_data->timeout > 0) {
|
|
ret = wait_event_interruptible_timeout(fhp_data->frame_waitq,
|
|
fhp_data->frame_waitq_flag == FHP_FRAME_WAITQ_WAKEUP,
|
|
msecs_to_jiffies(fhp_data->timeout));
|
|
if (ret == 0) return -ETIMEDOUT;
|
|
}
|
|
}
|
|
|
|
ret = fhpq_dequeue_userspace(&fhp_data->q, (void *)arg);
|
|
if (ret < 0) {
|
|
FTS_ERROR("dequeue frame failed");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fhp_ioctl_clear_frame(struct fhp_core_data *fhp_data, unsigned long arg)
|
|
{
|
|
if (fhp_data) {
|
|
FTS_INFO("clear queue frame");
|
|
fhpq_clear(&fhp_data->q);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int fhp_ioctl_set_timeout(struct fhp_core_data *fhp_data, unsigned long arg)
|
|
{
|
|
int timeout = (int)arg;
|
|
if (!fhp_data) {
|
|
FTS_ERROR("fhp data is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (fhp_data->timeout != timeout) {
|
|
FTS_INFO("set timeout:%d->%d", fhp_data->timeout, timeout);
|
|
fhp_data->timeout = timeout;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int fhp_open(struct inode *inode, struct file *file)
|
|
{
|
|
int ret = 0;
|
|
struct fhp_core_data *fhp_data = container_of(file->private_data, struct fhp_core_data, fhp_dev);
|
|
FTS_FUNC_ENTER();
|
|
if (!fhp_data && !fhp_data->ts_data) {
|
|
FTS_ERROR("fhp_data/ts_data is null");
|
|
return -ENODATA;
|
|
}
|
|
|
|
if (fhp_data->fhp_dev_open_cnt >= 1) {
|
|
FTS_ERROR("open fail(cnt:%d)", fhp_data->fhp_dev_open_cnt);
|
|
return -EBUSY;
|
|
}
|
|
|
|
fts_irq_disable();
|
|
ret = fhpq_open(&fhp_data->q);
|
|
if (ret) {
|
|
FTS_ERROR("queue open failed");
|
|
return ret;
|
|
}
|
|
|
|
fhp_data->frame_waitq_flag = FHP_FRAME_WAITQ_DEFAULT;
|
|
fhp_data->timeout = 0;
|
|
fhp_data->fhp_dev_open_cnt++;
|
|
fhp_data->ts_data->fhp_mode = ENABLE;
|
|
FTS_INFO("open successfully(cnt:%d)", fhp_data->fhp_dev_open_cnt);
|
|
FTS_FUNC_EXIT();
|
|
return 0;
|
|
}
|
|
|
|
static int fhp_close(struct inode *inode, struct file *file)
|
|
{
|
|
struct fhp_core_data *fhp_data = container_of(file->private_data, struct fhp_core_data, fhp_dev);
|
|
|
|
FTS_FUNC_ENTER();
|
|
if (!fhp_data && !fhp_data->ts_data) {
|
|
FTS_ERROR("fhp_data/ts_data is null");
|
|
return -ENODATA;
|
|
}
|
|
fhpq_release(&fhp_data->q);
|
|
fhp_data->fhp_dev_open_cnt--;
|
|
fhp_data->ts_data->fhp_mode = DISABLE;
|
|
fhp_data->ts_data->touch_size = fhp_data->touch_size_bak;
|
|
fts_irq_enable();
|
|
FTS_FUNC_EXIT();
|
|
return 0;
|
|
}
|
|
|
|
static long fhp_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
struct fhp_core_data *fhp_data = container_of(filp->private_data, struct fhp_core_data, fhp_dev);
|
|
|
|
if (!fhp_data) {
|
|
FTS_ERROR("fhp_data is null");
|
|
return -ENODATA;
|
|
}
|
|
|
|
//FTS_DEBUG("ioctl cmd:%x,arg:%lx", cmd, arg);
|
|
switch (cmd) {
|
|
case FHP_IOCTL_GET_FRAME:
|
|
ret = fhp_ioctl_get_frame(fhp_data, arg);
|
|
break;
|
|
case FHP_IOCTL_SPI_SYNC:
|
|
ret = fhp_ioctl_spi_sync(fhp_data, arg);
|
|
break;
|
|
case FHP_IOCTL_RESET:
|
|
ret = fhp_ioctl_reset(fhp_data, arg);
|
|
break;
|
|
case FHP_IOCTL_SET_IRQ:
|
|
ret = fhp_ioctl_set_irq(fhp_data, arg);
|
|
break;
|
|
case FHP_IOCTL_SET_RAW_SIZE:
|
|
fhp_ioctl_set_frame_size(fhp_data, arg);
|
|
break;
|
|
case FHP_IOCTL_SET_SPI_SPEED:
|
|
ret = fhp_ioctl_set_spi_speed(fhp_data, arg);
|
|
break;
|
|
case FHP_IOCTL_GET_CHIP_INIT_DONE:
|
|
ret = fhp_ioctl_get_chip_init_done(fhp_data, arg);
|
|
break;
|
|
case FHP_IOCTL_CLEAR_FRAME:
|
|
ret = fhp_ioctl_clear_frame(fhp_data, arg);
|
|
break;
|
|
case FHP_IOCTL_SET_TIMEOUT:
|
|
ret = fhp_ioctl_set_timeout(fhp_data, arg);
|
|
break;
|
|
case FHP_IOCTL_REPORT:
|
|
ret = fhp_input_report(fhp_data, arg);
|
|
break;
|
|
default:
|
|
FTS_ERROR("unkown ioctl cmd(0x%x)", (int)cmd);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int fhp_poll(struct file *filp, struct poll_table_struct *wait)
|
|
{
|
|
unsigned int mask = 0;
|
|
struct fhp_core_data *fhp_data = container_of(filp->private_data, struct fhp_core_data, fhp_dev);
|
|
|
|
if (!fhp_data) {
|
|
FTS_ERROR("fhp_data is null");
|
|
return -ENODATA;
|
|
}
|
|
|
|
poll_wait(filp, &fhp_data->frame_waitq, wait);
|
|
if (fhp_data->frame_waitq_flag == FHP_FRAME_WAITQ_WAKEUP)
|
|
mask |= POLLIN | POLLRDNORM;
|
|
|
|
fhp_data->frame_waitq_flag = FHP_FRAME_WAITQ_WAIT_POLL;
|
|
return mask;
|
|
}
|
|
|
|
static struct file_operations fhp_fops = {
|
|
.open = fhp_open,
|
|
.release = fhp_close,
|
|
.unlocked_ioctl = fhp_ioctl,
|
|
.poll = fhp_poll,
|
|
};
|
|
|
|
static int fhp_miscdev_init(struct fhp_core_data *fhp_data)
|
|
{
|
|
int ret = 0;
|
|
|
|
fhp_data->fhp_dev.minor = MISC_DYNAMIC_MINOR;
|
|
fhp_data->fhp_dev.name = FTS_FHP_NAME;
|
|
fhp_data->fhp_dev.fops = &fhp_fops;
|
|
ret = misc_register(&fhp_data->fhp_dev);
|
|
if (ret) {
|
|
FTS_ERROR("misc_register(fhp_dev) fail");
|
|
return ret;
|
|
}
|
|
|
|
FTS_INFO("misc_register(fhp_dev) success");
|
|
return 0;
|
|
}
|
|
|
|
static long fhp_input_report(struct fhp_core_data *fhp_data, unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
u8 report_buf[FTS_TOUCH_DATA_LEN_V2] = { 0 };
|
|
struct fhp_input_report_data report;
|
|
|
|
if (!fhp_data || !fhp_data->ts_data) {
|
|
FTS_ERROR("fhp_data/ts_data is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (copy_from_user(&report, (void *)arg, sizeof(struct fhp_input_report_data))) {
|
|
FTS_ERROR("copy input touch report data from userspace fail");
|
|
return -EFAULT;
|
|
}
|
|
|
|
if ((report.size <= 0) || (report.size > FTS_TOUCH_DATA_LEN_V2)) {
|
|
FTS_ERROR("report size(%d) is invalid", report.size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(report_buf, 0xFF, FTS_TOUCH_DATA_LEN_V2);
|
|
if (copy_from_user(report_buf, report.buffer , report.size)) {
|
|
FTS_ERROR("copy input touch report buffer from userspace fail");
|
|
return -EFAULT;
|
|
}
|
|
|
|
if ((report_buf[1] == 0xFF) && (report_buf[2] == 0xFF) && (report_buf[3] == 0xFF) && (report_buf[4] == 0xFF)) {
|
|
fts_release_all_finger();
|
|
fts_tp_state_recovery(fhp_data->ts_data);
|
|
return 0;
|
|
}
|
|
|
|
ret = fts_input_report_buffer(fhp_data->ts_data, report_buf);
|
|
if (ret < 0) {
|
|
FTS_ERROR("report buffer failed");
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int fhp_input_open(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int fhp_input_close(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static long fhp_input_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
struct fhp_core_data *fhp_data = container_of(filp->private_data, struct fhp_core_data, fhpi_dev);
|
|
|
|
if (!fhp_data) {
|
|
FTS_ERROR("fhp_data is null");
|
|
return -ENODATA;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case FHP_INPUT_IOCTL_REPORT:
|
|
ret = fhp_input_report(fhp_data, arg);
|
|
break;
|
|
default:
|
|
FTS_ERROR("unkown ioctl cmd(0x%x)", (int)cmd);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
/*
|
|
* On certain platforms, it is necessary to initialize the open function in order to initialize the private_data of the current device.
|
|
* Otherwise, there may be a situation where using container_of to retrieve the device fails.
|
|
*/
|
|
static struct file_operations fhp_input_fops = {
|
|
.open = fhp_input_open,
|
|
.release = fhp_input_close,
|
|
.unlocked_ioctl = fhp_input_ioctl,
|
|
};
|
|
|
|
static int fhp_input_miscdev_init(struct fhp_core_data *fhp_data)
|
|
{
|
|
int ret = 0;
|
|
|
|
fhp_data->fhpi_dev.minor = MISC_DYNAMIC_MINOR;
|
|
fhp_data->fhpi_dev.name = FTS_FHPI_DEV_NAME;
|
|
fhp_data->fhpi_dev.fops = &fhp_input_fops;
|
|
ret = misc_register(&fhp_data->fhpi_dev);
|
|
if (ret) {
|
|
FTS_ERROR("misc_register(input misc device) fail");
|
|
return ret;
|
|
}
|
|
|
|
FTS_INFO("misc_register(input misc device) success");
|
|
return 0;
|
|
}
|
|
|
|
static int fhp_get_frame(u8 *touch_buf, u32 touch_size)
|
|
{
|
|
struct fhp_core_data *fhp_data = fts_fhp_data;
|
|
if (!touch_buf || !touch_size) {
|
|
FTS_ERROR("touch_buf is null/touch_size is 0");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (fhp_data->q.elem_size != (sizeof(struct fhp_frame) + touch_size)) {
|
|
FTS_ERROR("touch size(%u,%d,%lu) is invalid", touch_size, fhp_data->q.elem_size, sizeof(struct fhp_frame));
|
|
return -EINVAL;
|
|
}
|
|
|
|
fhpq_enqueue(&fhp_data->q, touch_buf, fhp_get_timestamp());
|
|
|
|
if ((fhp_data->frame_waitq_flag == FHP_FRAME_WAITQ_WAIT)
|
|
|| (fhp_data->frame_waitq_flag == FHP_FRAME_WAITQ_WAIT_POLL)) {
|
|
fhp_data->frame_waitq_flag = FHP_FRAME_WAITQ_WAKEUP;
|
|
wake_up_interruptible(&fhp_data->frame_waitq);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fts_fhp_irq_handler(struct fts_ts_data *ts_data)
|
|
{
|
|
int ret = 0;
|
|
u8 cmd = FHP_CMD_GET_FRAME;
|
|
|
|
if (!ts_data || !ts_data->touch_buf) {
|
|
FTS_ERROR("ts_data/touch_buf is null");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ts_data->touch_size || (ts_data->touch_size > FTS_MAX_TOUCH_BUF)) {
|
|
FTS_ERROR("touch_size(%d) is invalid", ts_data->touch_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ts_data->suspended && ts_data->gesture_support) {
|
|
fts_gesture_readdata(ts_data, NULL);
|
|
return 0;
|
|
}
|
|
|
|
ret = fts_read(&cmd, 1, ts_data->touch_buf, ts_data->touch_size);
|
|
if (ret < 0) {
|
|
FTS_ERROR("read frame data failed");
|
|
return ret;
|
|
}
|
|
|
|
ret = fhp_get_frame(ts_data->touch_buf, ts_data->touch_size);
|
|
if (ret < 0) {
|
|
FTS_ERROR("get frame failed");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fts_fhp_init(struct fts_ts_data *ts_data)
|
|
{
|
|
int ret = 0;
|
|
struct fhp_core_data *fhp_data = NULL;
|
|
|
|
FTS_FUNC_ENTER();
|
|
fhp_data = kzalloc(sizeof(struct fhp_core_data), GFP_KERNEL);
|
|
if (!fhp_data) {
|
|
FTS_ERROR("allocate memory for fhp_data failed");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
fts_fhp_data = fhp_data;
|
|
fhp_data->ts_data = ts_data;
|
|
fhp_data->fhp_dev_open_cnt = 0;
|
|
fhp_data->frame_waitq_flag = FHP_FRAME_WAITQ_DEFAULT;
|
|
init_waitqueue_head(&fhp_data->frame_waitq);
|
|
mutex_init(&fhp_data->q.mutexq);
|
|
|
|
/*register /dev/fhp_ft*/
|
|
ret = fhp_miscdev_init(fhp_data);
|
|
if (ret) {
|
|
FTS_ERROR("fhp_dev miscdev register fail");
|
|
goto err_miscdev_init;
|
|
}
|
|
|
|
ret = fhp_input_miscdev_init(fhp_data);
|
|
if (ret) {
|
|
FTS_ERROR("input misc device register fail");
|
|
goto err_input_miscdev_init;
|
|
}
|
|
|
|
FTS_FUNC_EXIT();
|
|
return 0;
|
|
|
|
err_input_miscdev_init:
|
|
misc_deregister(&fhp_data->fhp_dev);
|
|
err_miscdev_init:
|
|
kfree(fhp_data);
|
|
fhp_data = NULL;
|
|
return ret;
|
|
}
|
|
|
|
void fts_fhp_exit(struct fts_ts_data *ts_data)
|
|
{
|
|
struct fhp_core_data *fhp_data = fts_fhp_data;
|
|
FTS_FUNC_ENTER();
|
|
if (fhp_data) {
|
|
misc_deregister(&fhp_data->fhpi_dev);
|
|
misc_deregister(&fhp_data->fhp_dev);
|
|
kfree(fhp_data);
|
|
fhp_data = NULL;
|
|
}
|
|
FTS_FUNC_EXIT();
|
|
}
|