Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 896b76e6 authored by Abir Ghosh's avatar Abir Ghosh
Browse files

qbt: Add support for touch events from input framework



Add changes to get touch events from input framework
and perform AoI and radius filtering. AoI info is passed
by userspace  using ioctl.

Change-Id: Iabedc175d06f123d540d64806cbfb2ce31c3acfe
Signed-off-by: default avatarAbir Ghosh <abirg@codeaurora.org>
parent a6ae9f82
Loading
Loading
Loading
Loading
+300 −123
Original line number Diff line number Diff line
@@ -27,15 +27,17 @@
#include <linux/of_gpio.h>
#include <linux/kfifo.h>
#include <linux/poll.h>
#include <linux/input.h>
#include <uapi/linux/qbt_handler.h>
#include <linux/input/touch_event_notify.h>

#define QBT_DEV "qbt"
#define MAX_FW_EVENTS 128
#define MT_MAX_FINGERS 10
#define MINOR_NUM_FD 0
#define MINOR_NUM_IPC 1
#define QBT_INPUT_DEV_NAME "qbt_key_input"
#define QBT_INPUT_DEV_VERSION 0x0100
#define QBT_TOUCH_FD_VERSION 2

struct finger_detect_gpio {
	int gpio;
@@ -60,6 +62,23 @@ struct fd_event {
	bool touch_valid;
};

struct touch_event {
	int X;
	int Y;
	int id;
	bool updated;
};

struct finger_detect_touch {
	struct qbt_touch_config_v2 config;
	struct work_struct work;
	struct touch_event current_events[MT_MAX_FINGERS];
	struct touch_event last_events[MT_MAX_FINGERS];
	int delta_X[MT_MAX_FINGERS];
	int delta_Y[MT_MAX_FINGERS];
	int current_slot;
};

struct fd_userspace_buf {
	uint32_t num_events;
	struct fd_event fd_events[MAX_FW_EVENTS];
@@ -86,127 +105,246 @@ struct qbt_drvdata {
	struct mutex	ipc_events_mutex;
	struct fw_ipc_info	fw_ipc;
	struct finger_detect_gpio fd_gpio;
	struct finger_detect_touch fd_touch;
	DECLARE_KFIFO(fd_events, struct fd_event, MAX_FW_EVENTS);
	DECLARE_KFIFO(ipc_events, struct ipc_event, MAX_FW_EVENTS);
	wait_queue_head_t read_wait_queue_fd;
	wait_queue_head_t read_wait_queue_ipc;
	bool is_wuhb_connected;
	struct qbt_touch_config touch_config;
	struct fd_userspace_buf scrath_buf;
	atomic_t wakelock_acquired;
};

static struct qbt_drvdata *drvdata_g;

static void qbt_add_touch_event(struct touch_event *evt)
static void qbt_fd_report_event(struct qbt_drvdata *drvdata,
		struct fd_event *event)
{
	struct qbt_drvdata *drvdata = drvdata_g;
	struct fd_event event;
	mutex_lock(&drvdata->fd_events_mutex);

	memset(&event, 0, sizeof(event));
	memcpy(&event.timestamp, &evt->time, sizeof(struct timeval));
	event.X = evt->x;
	event.Y = evt->y;
	event.id = evt->fid;
	event.touch_valid = true;
	switch (evt->type) {
	case 'D':
		event.state = QBT_EVENT_FINGER_DOWN;
		break;
	case 'U':
		event.state = QBT_EVENT_FINGER_UP;
		break;
	case 'M':
		event.state = QBT_EVENT_FINGER_MOVE;
		break;
	default:
		pr_err("Invalid touch event type\n");
	}
	pr_debug("Adding event id: %d state: %d x: %d y: %d\n",
			event.id, event.state, event.X, event.Y);
	pr_debug("timestamp: %ld.%06ld\n", event.timestamp.tv_sec,
			event.timestamp.tv_usec);
	if (!kfifo_put(&drvdata->fd_events, event))
	if (!kfifo_put(&drvdata->fd_events, *event)) {
		pr_err("FD events fifo: error adding item\n");
	} else {
		pr_debug("FD event %d at slot %d queued at time %lu uS\n",
				event->state, event->id,
				(unsigned long)ktime_to_us(ktime_get()));
	}
	mutex_unlock(&drvdata->fd_events_mutex);
	wake_up_interruptible(&drvdata->read_wait_queue_fd);
}

static void qbt_radius_filter(struct touch_event *evt)
static int qbt_touch_connect(struct input_handler *handler,
	struct input_dev *dev, const struct input_device_id *id)
{
	struct qbt_drvdata *drvdata = drvdata_g;
	struct fd_event event;
	int fifo_len = 0, last_x = 0, last_y = 0,
			last_state = QBT_EVENT_FINGER_UP,
			delta_x = 0, delta_y = 0, i = 0;

	fifo_len = kfifo_len(&drvdata->fd_events);
	for (i = 0; i < fifo_len; i++) {
		if (!kfifo_get(&drvdata->fd_events, &event))
			pr_err("FD events fifo: error removing item\n");
		else {
			if (event.id == evt->fid) {
				last_state = event.state;
				last_x = event.X;
				last_y = event.Y;
	struct input_handle *handle;
	int ret;

	handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
	if (!handle)
		return -ENOMEM;

	handle->dev = dev;
	handle->handler = handler;
	handle->name = "qbt_touch";

	ret = input_register_handle(handle);
	if (ret) {
		pr_err("Failed to register to input handle: %d\n", ret);
		kfree(handle);
		return ret;
	}
			kfifo_put(&drvdata->fd_events, event);

	ret = input_open_device(handle);
	if (ret) {
		pr_err("Failed to open to input handle: %d\n", ret);
		input_unregister_handle(handle);
		kfree(handle);
		return ret;
	}

	pr_info("Connected device: %s\n", dev_name(&dev->dev));

	return ret;
}
	if (last_state == QBT_EVENT_FINGER_DOWN ||
			last_state == QBT_EVENT_FINGER_MOVE) {
		delta_x = abs(last_x - evt->x);
		delta_y = abs(last_y - evt->y);
		if (delta_x > drvdata->touch_config.rad_x ||
				delta_y > drvdata->touch_config.rad_y)
			qbt_add_touch_event(evt);
	} else
		qbt_add_touch_event(evt);

static void qbt_touch_disconnect(struct input_handle *handle)
{
	pr_info("Disconnected device: %s\n", dev_name(&handle->dev->dev));

	input_close_device(handle);
	input_unregister_handle(handle);
	kfree(handle);
}

static void qbt_filter_touch_event(struct touch_event *evt)
static void qbt_touch_report_event(struct input_handle *handle,
	unsigned int type, unsigned int code, int value)
{
	struct qbt_drvdata *drvdata = drvdata_g;
	struct qbt_drvdata *drvdata = handle->handler->private;
	struct finger_detect_touch *fd_touch = &drvdata->fd_touch;
	struct touch_event *event =
		&fd_touch->current_events[fd_touch->current_slot];
	static bool report_event = true;

	pr_debug("Received event id: %d type: %c x: %d y: %d\n",
			evt->fid, evt->type, evt->x, evt->y);
	pr_debug("timestamp: %ld.%06ld\n", evt->time.tv_sec,
			evt->time.tv_usec);
	if (!fd_touch->config.touch_fd_enable || !drvdata->fd_gpio.irq_enabled)
		return;

	mutex_lock(&drvdata->fd_events_mutex);
	switch (evt->type) {
	case 'D':
	case 'U':
		qbt_add_touch_event(evt);
	if (type != EV_SYN && type != EV_ABS)
		return;

	switch (code) {
	case ABS_MT_SLOT:
		fd_touch->current_slot = value;
		if (!report_event)
			event->updated = true;
		report_event = false;
		break;
	case 'M':
		if (drvdata->touch_config.rad_filter_enable)
			qbt_radius_filter(evt);
		else
			qbt_add_touch_event(evt);
	case ABS_MT_TRACKING_ID:
		event->id = value;
		report_event = false;
		break;
	case ABS_MT_POSITION_X:
		event->X = abs(value);
		report_event = false;
		break;
	case ABS_MT_POSITION_Y:
		event->Y = abs(value);
		report_event = false;
		break;
	case SYN_REPORT:
		event->updated = true;
		report_event = true;
		break;
	default:
		pr_err("Invalid touch event type\n");
		break;
	}
	mutex_unlock(&drvdata->fd_events_mutex);
	wake_up_interruptible(&drvdata->read_wait_queue_fd);

	if (report_event) {
		pm_stay_awake(drvdata->dev);
		schedule_work(&drvdata->fd_touch.work);
	}
static int qfp_touch_event_notify(struct notifier_block *self,
			unsigned long action, void *data)
}

static const struct input_device_id qbt_touch_ids[] = {
	{
	int i = 0;
	struct touch_event *event = (struct touch_event *)data;
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
		.evbit = {BIT_MASK(EV_ABS)},
	},
	{},
};
MODULE_DEVICE_TABLE(input, qbt_touch_ids);
static struct input_handler qbt_touch_handler = {
	.event = qbt_touch_report_event,
	.connect = qbt_touch_connect,
	.disconnect = qbt_touch_disconnect,
	.name =	"qbt_touch",
	.id_table = qbt_touch_ids
};

static bool qbt_touch_filter_aoi_region(struct touch_event *event,
		struct qbt_touch_config_v2 *config)
{
	if (event->X < config->left ||
			event->X > config->right ||
			event->Y < config->top ||
			event->Y > config->bottom)
		return false;
	else
		return true;
}

	while (action > 0 && i < sizeof(action)) {
		if (__test_and_clear_bit(i, &action))
			qbt_filter_touch_event(event);
		i++;
		event++;
static bool qbt_touch_filter_by_radius(
		struct qbt_drvdata *drvdata,
		struct touch_event *current_event,
		struct touch_event *last_event,
		int slot)
{
	unsigned int del_X = 0, del_Y = 0;
	struct qbt_touch_config_v2 *config = &drvdata->fd_touch.config;

	drvdata->fd_touch.delta_X[slot] +=
			current_event->X - last_event->X;
	drvdata->fd_touch.delta_Y[slot] +=
			current_event->Y - last_event->Y;

	del_X = abs(drvdata->fd_touch.delta_X[slot]);
	del_Y = abs(drvdata->fd_touch.delta_Y[slot]);
	if (!config->rad_filter_enable ||
			del_X > config->rad_x ||
			del_Y > config->rad_y) {
		drvdata->fd_touch.delta_X[slot] = 0;
		drvdata->fd_touch.delta_Y[slot] = 0;
		return true;
	} else
		return false;
}
	return NOTIFY_OK;

static void qbt_touch_work_func(struct work_struct *work)
{
	struct qbt_drvdata *drvdata = NULL;
	struct qbt_touch_config_v2 *config;
	struct finger_detect_touch *fd_touch = NULL;
	struct touch_event current_event, last_event;
	struct fd_event finger_event;
	struct timespec timestamp;
	int slot = 0;

	if (!work) {
		pr_err("NULL pointer passed\n");
		return;
	}

static struct notifier_block _input_event_notifier = {
	.notifier_call = qfp_touch_event_notify,
};
	drvdata = container_of(work, struct qbt_drvdata, fd_touch.work);
	fd_touch = &drvdata->fd_touch;
	config = &fd_touch->config;
	finger_event.touch_valid = true;
	for (slot = 0; slot < MT_MAX_FINGERS; slot++) {
		memcpy(&current_event, &fd_touch->current_events[slot],
				sizeof(current_event));
		fd_touch->current_events[slot].updated = false;

		if (!current_event.updated)
			continue;

		memcpy(&last_event, &fd_touch->last_events[slot],
				sizeof(last_event));
		memcpy(&fd_touch->last_events[slot], &current_event,
				sizeof(current_event));

		if (current_event.id < 0)
			finger_event.state = QBT_EVENT_FINGER_UP;
		else if (last_event.id < 0)
			finger_event.state = QBT_EVENT_FINGER_DOWN;
		else if (last_event.id == current_event.id)
			finger_event.state = QBT_EVENT_FINGER_MOVE;
		else {
			pr_warn("finger up got missed, reporting finger down\n");
			finger_event.state = QBT_EVENT_FINGER_DOWN;
		}

		if (!qbt_touch_filter_aoi_region(&current_event, config))
			if (qbt_touch_filter_aoi_region(&last_event, config) &&
					last_event.id >= 0)
				finger_event.state = QBT_EVENT_FINGER_UP;
			else
				continue;
		else if (!qbt_touch_filter_aoi_region(&last_event, config))
			finger_event.state = QBT_EVENT_FINGER_DOWN;

		if (finger_event.state == QBT_EVENT_FINGER_MOVE &&
				!qbt_touch_filter_by_radius(drvdata,
				&current_event,	&last_event, slot))
			continue;

		getnstimeofday(&timestamp);
		finger_event.timestamp.tv_sec = timestamp.tv_sec;
		finger_event.timestamp.tv_usec = timestamp.tv_nsec / 1000;

		finger_event.id = slot;
		finger_event.X = current_event.X;
		finger_event.Y = current_event.Y;

		qbt_fd_report_event(drvdata, &finger_event);
	}
	pm_relax(drvdata->dev);
}

/**
 * qbt_open() - Function called when user space opens device.
@@ -407,18 +545,8 @@ static long qbt_ioctl(
	}
	case QBT_CONFIGURE_TOUCH_FD:
	{
		if (copy_from_user(&drvdata->touch_config, priv_arg,
			sizeof(drvdata->touch_config))
				!= 0) {
			rc = -EFAULT;
			pr_err("failed copy from user space %d\n", rc);
			goto end;
		}
		pr_debug("Touch FD Radius Filter enable: %d\n",
			drvdata->touch_config.rad_filter_enable);
		pr_debug("rad_x: %d rad_y: %d\n",
			drvdata->touch_config.rad_x,
			drvdata->touch_config.rad_y);
		pr_debug("unsupported version\n");
		rc = -EINVAL;
		break;
	}
	case QBT_ACQUIRE_WAKELOCK:
@@ -440,6 +568,60 @@ static long qbt_ioctl(
		}
		break;
	}
	case QBT_GET_TOUCH_FD_VERSION:
	{
		struct qbt_touch_fd_version version;

		version.version = QBT_TOUCH_FD_VERSION;
		rc = copy_to_user((void __user *)priv_arg,
				&version, sizeof(version));

		if (rc != 0) {
			pr_err("Failed to copy touch FD version: %d\n", rc);
			rc = -EFAULT;
			goto end;
		}

		break;
	}
	case QBT_CONFIGURE_TOUCH_FD_V2:
	{
		if (copy_from_user(&drvdata->fd_touch.config.version,
				priv_arg,
				sizeof(drvdata->fd_touch.config.version))
				!= 0) {
			rc = -EFAULT;
			pr_err("failed copy from user space %d\n", rc);
			goto end;
		}
		if (drvdata->fd_touch.config.version.version
				!= QBT_TOUCH_FD_VERSION) {
			rc = -EINVAL;
			pr_err("unsupported version %d\n",
					drvdata->fd_touch.config.version);
			goto end;
		}
		if (copy_from_user(&drvdata->fd_touch.config,
				priv_arg,
				sizeof(drvdata->fd_touch.config)) != 0) {
			rc = -EFAULT;
			pr_err("failed copy from user space %d\n", rc);
			goto end;
		}
		pr_debug("Touch FD enable: %d\n",
			drvdata->fd_touch.config.touch_fd_enable);
		pr_debug("left: %d right: %d top: %d bottom: %d\n",
			drvdata->fd_touch.config.left,
			drvdata->fd_touch.config.right,
			drvdata->fd_touch.config.top,
			drvdata->fd_touch.config.bottom);
		pr_debug("Radius Filter enable: %d\n",
			drvdata->fd_touch.config.rad_filter_enable);
		pr_debug("rad_x: %d rad_y: %d\n",
			drvdata->fd_touch.config.rad_x,
			drvdata->fd_touch.config.rad_y);
		break;
	}
	default:
		pr_err("invalid cmd %d\n", cmd);
		rc = -ENOIOCTLCMD;
@@ -692,7 +874,7 @@ static int qbt_dev_register(struct qbt_drvdata *drvdata)
}

/**
 * qbt1000_create_input_device() - Function allocates an input
 * qbt_create_input_device() - Function allocates an input
 * device, configures it for key events and registers it
 *
 * @drvdata:	ptr to driver data
@@ -750,7 +932,7 @@ static int qbt_create_input_device(struct qbt_drvdata *drvdata)
	return rc;
}

static void qbt_fd_report_event(struct qbt_drvdata *drvdata, int state)
static void qbt_gpio_report_event(struct qbt_drvdata *drvdata, int state)
{
	struct fd_event event;
	struct timespec timestamp;
@@ -778,17 +960,7 @@ static void qbt_fd_report_event(struct qbt_drvdata *drvdata, int state)
	getnstimeofday(&timestamp);
	event.timestamp.tv_sec = timestamp.tv_sec;
	event.timestamp.tv_usec = timestamp.tv_nsec / 1000;

	mutex_lock(&drvdata->fd_events_mutex);

	if (!kfifo_put(&drvdata->fd_events, event)) {
		pr_err("FD events fifo: error adding item\n");
	} else {
		pr_debug("FD event %d queued at time %lu uS\n", event.id,
				(unsigned long)ktime_to_us(ktime_get()));
	}
	mutex_unlock(&drvdata->fd_events_mutex);
	wake_up_interruptible(&drvdata->read_wait_queue_fd);
	qbt_fd_report_event(drvdata, &event);
}

static void qbt_gpio_work_func(struct work_struct *work)
@@ -807,7 +979,7 @@ static void qbt_gpio_work_func(struct work_struct *work)
			QBT_EVENT_FINGER_DOWN : QBT_EVENT_FINGER_UP)
			^ drvdata->fd_gpio.active_low;

	qbt_fd_report_event(drvdata, state);
	qbt_gpio_report_event(drvdata, state);
	pm_relax(drvdata->dev);
}

@@ -1036,6 +1208,7 @@ static int qbt_probe(struct platform_device *pdev)
	struct device *dev = &pdev->dev;
	struct qbt_drvdata *drvdata;
	int rc = 0;
	int slot = 0;

	pr_debug("entry\n");
	drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
@@ -1084,10 +1257,15 @@ static int qbt_probe(struct platform_device *pdev)
	if (rc < 0)
		goto end;

	rc = touch_event_register_notifier(&_input_event_notifier);
	qbt_touch_handler.private = drvdata;
	INIT_WORK(&drvdata->fd_touch.work, qbt_touch_work_func);
	for (slot = 0; slot < MT_MAX_FINGERS; slot++) {
		drvdata->fd_touch.current_events[slot].id = -1;
		drvdata->fd_touch.last_events[slot].id = -1;
	}
	rc = input_register_handler(&qbt_touch_handler);
	if (rc < 0)
		pr_err("Touch Event Registration failed: %d\n", rc);
	drvdata_g = drvdata;
		pr_err("Failed to register input handler: %d\n", rc);

end:
	pr_debug("exit : %d\n", rc);
@@ -1112,8 +1290,7 @@ static int qbt_remove(struct platform_device *pdev)
	unregister_chrdev_region(drvdata->qbt_ipc_cdev.dev, 1);

	device_init_wakeup(&pdev->dev, 0);
	touch_event_unregister_notifier(&_input_event_notifier);
	drvdata_g = NULL;
	input_unregister_handler(&qbt_touch_handler);

	return 0;
}
+37 −0
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@
#define QBT_ACQUIRE_WAKELOCK     107
#define QBT_RELEASE_WAKELOCK     108

#define QBT_GET_TOUCH_FD_VERSION  109
#define QBT_CONFIGURE_TOUCH_FD_V2 110

/*
 * enum qbt_finger_events -
 *      enumeration of qbt finger events
@@ -77,4 +80,38 @@ struct qbt_touch_config {
	int rad_y;
};

/*
 * struct qbt_touch_fd_version -
 *		used to get touch finger detect version
 * @version: version number
 */
struct qbt_touch_fd_version {
	int version;
};

/*
 * struct qbt_touch_config_v2 -
 *		used to configure touch finger detect
 * @version - touch FD version
 * @touch_fd_enable - flag to enable/disable touch finger detect
 * @rad_filter_enable - flag to enable/disable radius based filtering
 * @left - x-coordinate of top left corner of AOI
 * @top - y-coordinate of top left corner of AOI
 * @right - x-coordinate of bottom right corner of AOI
 * @bottom - y--coordinate of bottom right corner of AOI
 * @rad_x: movement radius in x direction
 * @rad_y: movement radius in y direction
 */
struct qbt_touch_config_v2 {
	struct qbt_touch_fd_version version;
	_Bool touch_fd_enable;
	_Bool rad_filter_enable;
	int left;
	int top;
	int right;
	int bottom;
	int rad_x;
	int rad_y;
};

#endif /* _UAPI_QBT_HANDLER_H_ */