// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2012-2013, 2016-2017 The Linux Foundation. All rights reserved.
 */

#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/input/mt.h>
#include <linux/syscalls.h>
#include "usfcdev.h"

#define UNDEF_ID    0xffffffff
#define SLOT_CMD_ID 0
#define MAX_RETRIES 10

enum usdev_event_status {
	USFCDEV_EVENT_ENABLED,
	USFCDEV_EVENT_DISABLING,
	USFCDEV_EVENT_DISABLED,
};

struct usfcdev_event {
	bool (*match_cb)(uint16_t, struct input_dev *dev);
	bool registered_event;
	bool interleaved;
	enum usdev_event_status event_status;
};
static struct usfcdev_event s_usfcdev_events[MAX_EVENT_TYPE_NUM];

struct usfcdev_input_command {
	unsigned int type;
	unsigned int code;
	unsigned int value;
};

static long  s_usf_pid;

static bool usfcdev_filter(struct input_handle *handle,
			 unsigned int type, unsigned int code, int value);
static bool usfcdev_match(struct input_handler *handler,
				struct input_dev *dev);
static int usfcdev_connect(struct input_handler *handler,
				struct input_dev *dev,
				const struct input_device_id *id);
static void usfcdev_disconnect(struct input_handle *handle);

static const struct input_device_id usfc_tsc_ids[] = {
	{
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
			INPUT_DEVICE_ID_MATCH_KEYBIT |
			INPUT_DEVICE_ID_MATCH_ABSBIT,
		.evbit = { BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY) },
		.keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
		/* assumption: ABS_X & ABS_Y are in the same long */
		.absbit = { [BIT_WORD(ABS_X)] = BIT_MASK(ABS_X) |
						BIT_MASK(ABS_Y) },
	},
	{
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
			INPUT_DEVICE_ID_MATCH_KEYBIT |
			INPUT_DEVICE_ID_MATCH_ABSBIT,
		.evbit = { BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY) },
		.keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
		/* assumption: MT_.._X & MT_.._Y are in the same long */
		.absbit = { [BIT_WORD(ABS_MT_POSITION_X)] =
			BIT_MASK(ABS_MT_POSITION_X) |
			BIT_MASK(ABS_MT_POSITION_Y) },
	},
	{ } /* Terminating entry */
};

MODULE_DEVICE_TABLE(input, usfc_tsc_ids);

static struct input_handler s_usfc_handlers[MAX_EVENT_TYPE_NUM] = {
	{ /* TSC handler */
		.filter         = usfcdev_filter,
		.match          = usfcdev_match,
		.connect        = usfcdev_connect,
		.disconnect     = usfcdev_disconnect,
		/* .minor can be used as index in the container, */
		/*  because .fops isn't supported */
		.minor          = TSC_EVENT_TYPE_IND,
		.name           = "usfc_tsc_handler",
		.id_table       = usfc_tsc_ids,
	},
};

/*
 * For each event type, there are a number conflicting devices (handles)
 * The first registered device (primary) is real TSC device; it's mandatory
 * Optionally, later registered devices are simulated ones.
 * They are dynamically managed
 * The primary device's handles are stored in the below static array
 */
static struct input_handle s_usfc_primary_handles[MAX_EVENT_TYPE_NUM] = {
	{ /* TSC handle */
		.handler	= &s_usfc_handlers[TSC_EVENT_TYPE_IND],
		.name		= "usfc_tsc_handle",
	},
};

static struct usfcdev_input_command initial_clear_cmds[] = {
	{EV_ABS, ABS_PRESSURE,               0},
	{EV_KEY, BTN_TOUCH,                  0},
};

static struct usfcdev_input_command slot_clear_cmds[] = {
	{EV_ABS, ABS_MT_SLOT,               0},
	{EV_ABS, ABS_MT_TRACKING_ID, UNDEF_ID},
};

static struct usfcdev_input_command no_filter_cmds[] = {
	{EV_ABS, ABS_MT_SLOT,               0},
	{EV_ABS, ABS_MT_TRACKING_ID, UNDEF_ID},
	{EV_SYN, SYN_REPORT,                0},
};

static bool usfcdev_match(struct input_handler *handler, struct input_dev *dev)
{
	bool rc = false;
	int ind = handler->minor;

	pr_debug("%s: name=[%s]; ind=%d\n", __func__, dev->name, ind);

	if (s_usfcdev_events[ind].registered_event &&
		s_usfcdev_events[ind].match_cb) {
		rc = (*s_usfcdev_events[ind].match_cb)((uint16_t)ind, dev);
		pr_debug("%s: [%s]; rc=%d\n", __func__, dev->name, rc);
	}
	return rc;
}

static int usfcdev_connect(struct input_handler *handler, struct input_dev *dev,
				const struct input_device_id *id)
{
	int ret = 0;
	uint16_t ind = handler->minor;
	struct input_handle *usfc_handle = NULL;

	if (s_usfc_primary_handles[ind].dev == NULL) {
		pr_debug("%s: primary device; ind=%d\n",
			__func__,
			ind);
		usfc_handle = &s_usfc_primary_handles[ind];
	} else {
		pr_debug("%s: secondary device; ind=%d\n",
			__func__,
			ind);
		usfc_handle = kzalloc(sizeof(struct input_handle),
					GFP_KERNEL);
		if (!usfc_handle)
			return -ENOMEM;

		usfc_handle->handler = &s_usfc_handlers[ind];
		usfc_handle->name = s_usfc_primary_handles[ind].name;
	}
	usfc_handle->dev = dev;
	ret = input_register_handle(usfc_handle);
	pr_debug("%s: name=[%s]; ind=%d; dev=0x%pK\n",
		 __func__,
		dev->name,
		ind,
		usfc_handle->dev);
	if (ret)
		pr_err("%s: input_register_handle[%d] failed: ret=%d\n",
			__func__,
			ind,
			ret);
	else {
		ret = input_open_device(usfc_handle);
		if (ret) {
			pr_err("%s: input_open_device[%d] failed: ret=%d\n",
				__func__,
				ind,
				ret);
			input_unregister_handle(usfc_handle);
		} else
			pr_debug("%s: device[%d] is opened\n",
				__func__,
				ind);
	}

	return ret;
}

static void usfcdev_disconnect(struct input_handle *handle)
{
	int ind = handle->handler->minor;

	input_close_device(handle);
	input_unregister_handle(handle);
	pr_debug("%s: handle[%d], name=[%s] is disconnected\n",
		__func__,
		ind,
		handle->dev->name);
	if (s_usfc_primary_handles[ind].dev == handle->dev)
		s_usfc_primary_handles[ind].dev = NULL;
	else
		kfree(handle);
}

static bool usfcdev_filter(struct input_handle *handle,
			unsigned int type, unsigned int code, int value)
{
	uint16_t i = 0;
	uint16_t ind = (uint16_t)handle->handler->minor;
	bool rc = (s_usfcdev_events[ind].event_status != USFCDEV_EVENT_ENABLED);

	if (s_usf_pid == current->pid) {
		/* Pass events from usfcdev driver */
		rc = false;
		pr_debug("%s: event_type=%d; type=%d; code=%d; val=%d",
			__func__,
			ind,
			type,
			code,
			value);
	} else if (s_usfcdev_events[ind].event_status ==
						USFCDEV_EVENT_DISABLING) {
		uint32_t u_value = value;

		s_usfcdev_events[ind].interleaved = true;
		/* Pass events for freeing slots from TSC driver */
		for (i = 0; i < ARRAY_SIZE(no_filter_cmds); ++i) {
			if ((no_filter_cmds[i].type == type) &&
			    (no_filter_cmds[i].code == code) &&
			    (no_filter_cmds[i].value <= u_value)) {
				rc = false;
				pr_debug("%s: no_filter_cmds[%d]; %d",
					__func__,
					i,
					no_filter_cmds[i].value);
				break;
			}
		}
	}

	return rc;
}

bool usfcdev_register(
	uint16_t event_type_ind,
	bool (*match_cb)(uint16_t, struct input_dev *dev))
{
	int ret = 0;
	bool rc = false;

	if ((event_type_ind >= MAX_EVENT_TYPE_NUM) || !match_cb) {
		pr_err("%s: wrong input: event_type_ind=%d; match_cb=0x%pK\n",
			__func__,
			event_type_ind,
			match_cb);
		return false;
	}

	if (s_usfcdev_events[event_type_ind].registered_event) {
		pr_info("%s: handler[%d] was already registered\n",
			__func__,
			event_type_ind);
		return true;
	}

	s_usfcdev_events[event_type_ind].registered_event = true;
	s_usfcdev_events[event_type_ind].match_cb = match_cb;
	s_usfcdev_events[event_type_ind].event_status = USFCDEV_EVENT_ENABLED;
	ret = input_register_handler(&s_usfc_handlers[event_type_ind]);
	if (!ret) {
		rc = true;
		pr_debug("%s: handler[%d] was registered\n",
			__func__,
			event_type_ind);
	} else {
		s_usfcdev_events[event_type_ind].registered_event = false;
		s_usfcdev_events[event_type_ind].match_cb = NULL;
		pr_err("%s: handler[%d] registration failed: ret=%d\n",
			__func__,
			event_type_ind,
			ret);
	}

	return rc;
}

void usfcdev_unregister(uint16_t event_type_ind)
{
	if (event_type_ind >= MAX_EVENT_TYPE_NUM) {
		pr_err("%s: wrong input: event_type_ind=%d\n",
			__func__,
			event_type_ind);
		return;
	}
	if (s_usfcdev_events[event_type_ind].registered_event) {
		input_unregister_handler(&s_usfc_handlers[event_type_ind]);
		pr_debug("%s: handler[%d] was unregistered\n",
			__func__,
			event_type_ind);
		s_usfcdev_events[event_type_ind].registered_event = false;
		s_usfcdev_events[event_type_ind].match_cb = NULL;
		s_usfcdev_events[event_type_ind].event_status =
							USFCDEV_EVENT_ENABLED;

	}
}

static inline void usfcdev_send_cmd(
	struct input_dev *dev,
	struct usfcdev_input_command cmd)
{
	input_event(dev, cmd.type, cmd.code, cmd.value);
}

static void usfcdev_clean_dev(uint16_t event_type_ind)
{
	struct input_dev *dev = NULL;
	int i;
	int j;
	int retries = 0;

	if (event_type_ind >= MAX_EVENT_TYPE_NUM) {
		pr_err("%s: wrong input: event_type_ind=%d\n",
			__func__,
			event_type_ind);
		return;
	}
	/* Only primary device must exist */
	dev = s_usfc_primary_handles[event_type_ind].dev;
	if (dev == NULL) {
		pr_err("%s: NULL primary device\n",
		__func__);
		return;
	}

	for (i = 0; i < ARRAY_SIZE(initial_clear_cmds); i++)
		usfcdev_send_cmd(dev, initial_clear_cmds[i]);
	input_sync(dev);

	/* Send commands to free all slots */
	for (i = 0; i < dev->mt->num_slots; i++) {
		s_usfcdev_events[event_type_ind].interleaved = false;
		if (input_mt_get_value(&dev->mt->slots[i],
					ABS_MT_TRACKING_ID) < 0) {
			pr_debug("%s: skipping slot %d",
				__func__, i);
			continue;
		}
		slot_clear_cmds[SLOT_CMD_ID].value = i;
		for (j = 0; j < ARRAY_SIZE(slot_clear_cmds); j++)
			usfcdev_send_cmd(dev, slot_clear_cmds[j]);

		if (s_usfcdev_events[event_type_ind].interleaved) {
			pr_debug("%s: interleaved(%d): slot(%d)",
				__func__, i, dev->mt->slot);
			if (retries++ < MAX_RETRIES) {
				--i;
				continue;
			}
			pr_warn("%s: index(%d) reached max retires",
				__func__, i);
		}

		retries = 0;
		input_sync(dev);
	}
}

bool usfcdev_set_filter(uint16_t event_type_ind, bool filter)
{
	bool rc = true;

	if (event_type_ind >= MAX_EVENT_TYPE_NUM) {
		pr_err("%s: wrong input: event_type_ind=%d\n",
			__func__,
			event_type_ind);
		return false;
	}

	if (s_usfcdev_events[event_type_ind].registered_event) {

		pr_debug("%s: event_type[%d]; filter=%d\n",
			__func__,
			event_type_ind,
			filter
			);
		if (filter) {
			s_usfcdev_events[event_type_ind].event_status =
						USFCDEV_EVENT_DISABLING;
			s_usf_pid = current->pid;
			usfcdev_clean_dev(event_type_ind);
			s_usfcdev_events[event_type_ind].event_status =
						USFCDEV_EVENT_DISABLED;
		} else
			s_usfcdev_events[event_type_ind].event_status =
						USFCDEV_EVENT_ENABLED;
	} else {
		pr_err("%s: event_type[%d] isn't registered\n",
			__func__,
			event_type_ind);
		rc = false;
	}

	return rc;
}