/*
 *  HID driver for N-Trig touchscreens
 *
 *  Copyright (c) 2008 Rafi Rubin
 *  Copyright (c) 2009 Stephane Chatty
 *
 */

/*
 * 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.
 */

#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/list.h>

#include "hid-ids.h"

#define NTRIG_DUPLICATE_USAGES	0x001
#define NTRIG_MAX_CONTACTS			10

#define nt_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
					EV_KEY, (c))

/* to be used soon for caching so that we 
 * can unjumble fingers */
struct ntrig_contact {
	char active;
	__s8 logical_id;
	__s32 x, y;
	__s32 confidence;

	/* height and width transformed */
	char orientation;
	__s32 touch_major;
	__s32 touch_minor;
};

struct ntrig_data {
	__s32 x, y;

	/* Touch values */
	__s32 id, w, h;
	__s32 confidence;

	int max_width;
	int max_height;

	/* used to determine when enough groups have been supressed */
	unsigned int groups_suppressed;

	/* and for the end of activity */
	unsigned int touch_end_count;

	unsigned char reading_mt;

	/* Collected state for 2 full sets of contacts */
	struct ntrig_contact contacts[NTRIG_MAX_CONTACTS];
	struct ntrig_contact prev_contacts[NTRIG_MAX_CONTACTS];
	__u8 contact_count;
	__u8 prev_contact_count;
	__s8 contact_map[NTRIG_MAX_CONTACTS];

	__u8 mt_footer[4];
	__u8 mt_foot_count;

	/* pen state */
	__u32 pen_current_tool;
	__u32 tip, barrel, eraser;
	unsigned char inverted;
	unsigned char inrange;

	/* options */
	unsigned char emit_ghosts;
	unsigned int touch_suppress;
	unsigned int touch_end_slack;
};

static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi,
			       struct hid_field *field, struct hid_usage *usage,
			       unsigned long **bit, int *max)
{
	switch (usage->hid & HID_USAGE_PAGE) {

	case HID_UP_GENDESK:
		switch (usage->hid) {
		case HID_GD_X:
			hid_map_usage(hi, usage, bit, max,
				      EV_ABS, ABS_MT_POSITION_X);
			input_set_abs_params(hi->input, ABS_X,
					     field->logical_minimum,
					     field->logical_maximum, 0, 0);
			return 1;
		case HID_GD_Y:
			hid_map_usage(hi, usage, bit, max,
				      EV_ABS, ABS_MT_POSITION_Y);
			input_set_abs_params(hi->input, ABS_Y,
					     field->logical_minimum,
					     field->logical_maximum, 0, 0);
			return 1;
		}
		return 0;

	case HID_UP_DIGITIZER:
		return 0;

	case 0xff000000:
		/* we do not want to map these: no input-oriented meaning */
		return -1;
	}

	return 0;
}

static int ntrig_input_mapped(struct hid_device *hdev, struct hid_input *hi,
			      struct hid_field *field, struct hid_usage *usage,
			      unsigned long **bit, int *max)
{
	if (usage->type == EV_KEY || usage->type == EV_REL
	    || usage->type == EV_ABS)
		clear_bit(usage->code, *bit);

	return 0;
}

static int ntrig_pen_event(struct hid_device *hid, struct hid_field *field,
			   struct hid_usage *usage, __s32 value)
{
	struct input_dev *input = field->hidinput->input;
	struct ntrig_data *nd = hid_get_drvdata(hid);

	if (hid->claimed & HID_CLAIMED_INPUT) {
		switch (usage->hid) {
		case HID_DG_INRANGE:
			nd->inrange = value;
			return 0;
		case HID_DG_TIPSWITCH:
			nd->tip = value;
			break;
		case HID_DG_BARRELSWITCH:
			nd->barrel = value;
			break;
		case HID_DG_INVERT:
			nd->inverted = value;
			break;
		case HID_DG_ERASER:
			nd->eraser = value;
			if (nd->inverted) {
				input_report_key(input, BTN_TOOL_PEN, 0);
				input_report_key(input, BTN_TOOL_RUBBER, 1);
				input_report_key(input, BTN_TOUCH, nd->eraser);
				input_report_key(input, BTN_2, nd->eraser);
			} else if (nd->inrange) {
				input_report_key(input, BTN_TOOL_RUBBER, 0);
				input_report_key(input, BTN_TOOL_PEN, 1);
				input_report_key(input, BTN_TOUCH, nd->tip);
				input_report_key(input, BTN_0, nd->tip);
				input_report_key(input, BTN_STYLUS, nd->barrel);
				input_report_key(input, BTN_1, nd->barrel);
			} else {
				input_report_key(input, BTN_TOUCH, 0);
				input_report_key(input, BTN_0, 0);
				input_report_key(input, BTN_STYLUS, 0);
				input_report_key(input, BTN_1, 0);
				input_report_key(input, BTN_2, 0);
				input_report_key(input, BTN_TOOL_PEN, 0);
				input_report_key(input, BTN_TOOL_RUBBER, 0);
			}
			break;

		case HID_GD_X:
			nd->x = value;
			input_event(input, EV_ABS, ABS_X, nd->x);
			break;
		case HID_GD_Y:
			nd->y = value;
			input_event(input, EV_ABS, ABS_Y, nd->y);
			break;

		case HID_DG_TIPPRESSURE:
		default:
			return 0;
		}
	}

	/* we have handled the hidinput part, now remains hiddev */
	if (hid->claimed & HID_CLAIMED_HIDDEV && hid->hiddev_hid_event)
		hid->hiddev_hid_event(hid, field, usage, value);

	return 1;
}

static void ntrig_single_touch_emit(struct input_dev *input,
				    struct ntrig_data *nd)
{
	if (nd->confidence) {
		switch (nd->contact_count) {
		case 0:	/* for single touch devices */
		case 1:
			input_report_key(input, BTN_TOOL_DOUBLETAP, 1);
			input_report_key(input, BTN_0, 1);
			break;
		case 2:
			input_report_key(input, BTN_TOOL_TRIPLETAP, 1);
			input_report_key(input, BTN_1, 1);
			break;
		case 3:
		default:
			input_report_key(input, BTN_TOOL_QUADTAP, 1);
			input_report_key(input, BTN_2, 1);
		}
		input_report_key(input, BTN_TOUCH, 1);
	} else {
		/* No active fingers, clear all state */
		input_report_key(input, BTN_TOUCH, 0);
		input_report_key(input, BTN_TOOL_DOUBLETAP, 0);
		input_report_key(input, BTN_TOOL_TRIPLETAP, 0);
		input_report_key(input, BTN_TOOL_QUADTAP, 0);
		input_report_key(input, BTN_0, 0);
		input_report_key(input, BTN_1, 0);
		input_report_key(input, BTN_2, 0);
	}
	input_event(input, EV_ABS, ABS_X, nd->x);
	input_event(input, EV_ABS, ABS_Y, nd->y);
	input_sync(input);
}

/* 
 * Spatial comparison of two points.  If the difference
 * is within the given thresholds they are treated as the
 * same point.
 */
#define nt_same_point(a, b, max_dx, max_dy) ( \
		(abs(a.x - b.x) <= max_dx) && \
		(abs(a.y - b.y) <= max_dy))

/*
 * To verify a new contact matches a contact in the previous
 * group, ensure both are valid then check spatial correlation.
 */
#define nt_match_points(nd, new, old) (nd->contacts[new].confidence && \
		nd->prev_contacts[old].confidence && \
		nt_same_point(nd->contacts[new], nd->prev_contacts[old], \
			nd->max_width, nd->max_height))

/*
 * After an older contact is identified as a match nt_map_match updates
 * the newer point as well as the contact map
 */
#define nt_map_match(nd, contact_map, new, old) \
	nd->contacts[new].logical_id = nd->prev_contacts[old].logical_id; \
	contact_map[nd->contacts[new].logical_id] = new;

static void ntrig_conclude_mt(struct input_dev *input, struct ntrig_data *nd)
{
	__s8 contact_map[NTRIG_MAX_CONTACTS];
	int i, j, k;
	int matched = 0;
	int first_free_id = 0;
	int count = nd->contact_count;
	int prev_count = nd->prev_contact_count;

	/* If the previous state is corrupted, discard it. */
	if (nd->prev_contact_count >= NTRIG_MAX_CONTACTS) {
		printk(KERN_ERR
		       "N-Trig previous state corrupted, discarding\n");
		nd->prev_contact_count = 0;
		prev_count = 0;
	}

	/* Under some circumstances an empty group is emitted with an invalid
	 * contact 0 and contact count of 1. */
	if (count && (!nd->contacts[0].confidence)) {
		count = 0;
		nd->contact_count = 0;
	}

	/*
	 * The sensor sometimes sends a garbage empty group.  The real end
	 * of activity results in a several empty groups (7 or 8).
	 * Discarding groups up to a threshold helps reduce tracking loss.
	 *
	 * Pen activity results in slightly different signal that should
	 * trigger the threshold immediately.
	 */
	if (!count) {
		if (nd->touch_end_count < nd->touch_end_slack) {
			nd->touch_end_count++;
			return;
		}
	} else			/* Still active, reset the counter */
		nd->touch_end_count = 0;

	/* Initialize and empty logical id map */
	for (i = 0; i < NTRIG_MAX_CONTACTS; i++) {
		contact_map[i] = -1;
	}

	/* 
	 * Phase 1: Identify which contacts seem to match
	 * those with the same physical id from the previous group.
	 * This should be the most common case during long touch
	 * action. */
	for (i = 0; i < count && i < prev_count; i++) {
		if (nt_match_points(nd, i, i)) {
			nt_map_match(nd, contact_map, i, i);
			matched++;
		} else
			nd->contacts[i].logical_id = -1;
	}

	/* 
	 * Phase 2: Find corresponding contacts when the incoming
	 * order has changed.
	 */
	for (i = 0; i < count && matched < count; i++) {
		for (j = 0; j < nd->prev_contact_count &&
		     (nd->contacts[i].logical_id < 0); j++) {

			/* Check the map to avoid reusing an old contact
			 * for multiple current contacts */
			if ((contact_map[nd->prev_contacts[j].logical_id] < 0)
			    && nt_match_points(nd, i, j)) {
				nt_map_match(nd, contact_map, i, j);
				matched++;
			}
		}
	}

	/*
	 * Phase 3: New or unidentied contacts are assigned logical ids.
	 */
	for (i = 0; i < count && matched < count; i++) {
		/* Ignore points that are already mapped */
		if ((nd->contacts[i].confidence
		     && nd->contacts[i].logical_id < 0)) {
			/* find the first available logical id */
			while (contact_map[first_free_id] >= 0
			       && first_free_id < NTRIG_MAX_CONTACTS)
				first_free_id++;
			if (first_free_id >= NTRIG_MAX_CONTACTS) {
				printk(KERN_ERR
				       "hid-ntrig: exceeded contacts limit\n");
				break;
			}
			nd->contacts[i].logical_id = first_free_id;
			contact_map[first_free_id++] = i;
		}
	}

	k = -1;			/* Lowest id contact */
	j = -1;			/* Highest id contact */
	for (i = 0; i < NTRIG_MAX_CONTACTS; i++)
		if (contact_map[i] >= 0) {
			j = i;
			if (k < 0)
				k = i;
		}

	/* Update the classic touchscreen state */
	if (count) {
		if (nd->groups_suppressed >= nd->touch_suppress) {
			nd->x = nd->contacts[contact_map[k]].x;
			nd->y = nd->contacts[contact_map[k]].y;
			nd->confidence =
			    nd->contacts[contact_map[k]].confidence;
			ntrig_single_touch_emit(input, nd);
		} else
			nd->groups_suppressed++;
	} else if (prev_count) {
		/* Hit the end of activity, clear state */
		for (i = 0; i < NTRIG_MAX_CONTACTS; i++)
			if (nd->contact_map[i] >= 0) {
				k = nd->contact_map[i];
				nd->x = nd->prev_contacts[k].x;
				nd->y = nd->prev_contacts[k].y;
			}
		nd->confidence = 0;
		nd->groups_suppressed = 0;
		ntrig_single_touch_emit(input, nd);
	}

	/* If we have two empty groups of events don't update */
	/* Take this oportunity to update the saved mapping */
	for (i = 0; i <= j && i < NTRIG_MAX_CONTACTS; i++)
		nd->contact_map[i] = contact_map[i];

	/* Emit multitouch events */
	for (i = 0; i <= j && i < NTRIG_MAX_CONTACTS; i++) {
		/* Valid contact, send real values */
		if (contact_map[i] >= 0
		    && nd->contacts[contact_map[i]].confidence) {
			struct ntrig_contact *contact =
			    &nd->contacts[contact_map[i]];
			input_event(input, EV_ABS, ABS_MT_TRACKING_ID, i);
			input_event(input, EV_ABS, ABS_MT_POSITION_X,
				    contact->x);
			input_event(input, EV_ABS, ABS_MT_POSITION_Y,
				    contact->y);
			input_event(input, EV_ABS, ABS_MT_ORIENTATION,
				    contact->orientation);
			input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR,
				    contact->touch_major);
			input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR,
				    contact->touch_minor);
			input_mt_sync(input);
		} else if (nd->emit_ghosts) {
			/* emit filler points if so desired */
			input_event(input, EV_ABS, ABS_MT_TRACKING_ID, i);
			input_event(input, EV_ABS, ABS_MT_POSITION_X, 0);
			input_event(input, EV_ABS, ABS_MT_POSITION_Y, 0);
			input_event(input, EV_ABS, ABS_MT_ORIENTATION, 0);
			input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, 0);
			input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, 0);
			input_mt_sync(input);
		}
	}

	/* Age the current state to previous. */
	for (i = 0; i < nd->contact_count && i < NTRIG_MAX_CONTACTS; i++) {
		nd->prev_contacts[i] = nd->contacts[i];
	}
	nd->prev_contact_count = nd->contact_count;
}

/*
 * this function is called upon all reports
 * so that we can filter contact point information,
 * decide whether we are in multi or single touch mode
 * and call input_mt_sync after each point if necessary
 */
static int ntrig_touchscreen_event(struct hid_device *hid,
				   struct hid_field *field,
				   struct hid_usage *usage, __s32 value)
{
	struct input_dev *input = field->hidinput->input;
	struct ntrig_data *nd = hid_get_drvdata(hid);

	if (hid->claimed & HID_CLAIMED_INPUT) {
		switch (usage->hid) {

		case 0xff000001:
			/* Tag indicating the start of a multitouch group */
			nd->reading_mt = 1;
			break;
		case HID_DG_CONFIDENCE:
			nd->confidence = value;
			break;
		case HID_GD_X:
			nd->x = value;
			nd->mt_foot_count = 0;
			break;
		case HID_GD_Y:
			nd->y = value;
			break;
		case HID_DG_CONTACTID:
			nd->id = value;
			break;
		case HID_DG_WIDTH:
			nd->w = value;
			break;
		case HID_DG_HEIGHT:
			nd->h = value;
			/*
			 * when in single touch mode, this is the last
			 * report received in a finger event. We want
			 * to emit a normal (X, Y) position
			 */
			if (!nd->reading_mt) {
				if (!nd->confidence) {
					nd->x = nd->contacts[0].x;
					nd->y = nd->contacts[0].y;
				} else {
					nd->contacts[0].x = nd->x;
					nd->contacts[0].y = nd->y;
				}
				ntrig_single_touch_emit(input, nd);
			}

			break;
		case 0xff000002:
			/*
			 * Conclusion of a single multitouch contact.
			 * We receive this when the device is in multitouch
			 * mode. The first of the three values tagged with
			 * this usage tells if the contact point is real
			 * or a placeholder and if its the last contact
			 * of the set.
			 */

			/* Shouldn't get more than 4 footer packets, so skip */
			if (nd->mt_foot_count >= 4)
				break;

			nd->mt_footer[nd->mt_foot_count++] = value;

			/* if the footer isn't complete break */
			if (nd->mt_foot_count != 4)
				break;

			/* Pen activity signal, trigger end of touch. */
			if (nd->mt_footer[2]) {
				nd->touch_end_count = nd->touch_end_slack;
				nd->contacts[nd->id].x = 0;
				nd->contacts[nd->id].y = 0;
				nd->contacts[nd->id].confidence = 0;
				break;
			}

			/* If the contact was invalid */
			if (!(nd->confidence && nd->mt_footer[0])) {
				nd->contacts[nd->id].x = 0;
				nd->contacts[nd->id].y = 0;
				nd->contacts[nd->id].confidence = 0;
				break;
			}

			nd->contacts[nd->id].logical_id = -1;
			nd->contacts[nd->id].confidence = nd->confidence;
			nd->contacts[nd->id].x = nd->x;
			nd->contacts[nd->id].y = nd->y;

			if (nd->w > nd->h) {
				nd->contacts[nd->id].orientation = 1;
				nd->contacts[nd->id].touch_major = nd->w;
				nd->contacts[nd->id].touch_minor = nd->h;
			} else {
				nd->contacts[nd->id].orientation = 0;
				nd->contacts[nd->id].touch_major = nd->h;
				nd->contacts[nd->id].touch_minor = nd->w;
			}
			break;

		case HID_DG_CONTACTCOUNT:
			/* This marks the end of the multitouch group */
			nd->contact_count = value;
			nd->reading_mt = 0;
			ntrig_conclude_mt(input, nd);
			break;

		default:
			/* fallback to the generic hidinput handling */
			return 0;
		}
	}

	/* we have handled the hidinput part, now remains hiddev */
	if (hid->claimed & HID_CLAIMED_HIDDEV && hid->hiddev_hid_event)
		hid->hiddev_hid_event(hid, field, usage, value);

	return 1;
}

static int ntrig_event(struct hid_device *hid, struct hid_field *field,
		       struct hid_usage *usage, __s32 value)
{
	switch (field->application) {
	case HID_DG_PEN:
		return ntrig_pen_event(hid, field, usage, value);
	case HID_DG_TOUCHSCREEN:
		return ntrig_touchscreen_event(hid, field, usage, value);
	}
	return -1;
}

static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
	int ret;
	struct ntrig_data *nd;
	struct hid_input *hidinput;
	struct input_dev *input;

	if (id->driver_data)
		hdev->quirks |= HID_QUIRK_MULTI_INPUT;

	nd = kmalloc(sizeof(struct ntrig_data), GFP_KERNEL);
	if (!nd) {
		dev_err(&hdev->dev, "cannot allocate N-Trig data\n");
		return -ENOMEM;
	}
	nd->id = 0;
	nd->reading_mt = 0;
	nd->contact_count = 0;
	nd->prev_contact_count = 0;
	nd->max_width = 0x500;
	nd->max_height = 0x500;
	nd->groups_suppressed = 0;
	nd->touch_suppress = 1;
	nd->touch_end_slack = 4;
	nd->touch_end_count = 0;
	nd->emit_ghosts = 0;

	hid_set_drvdata(hdev, nd);

	ret = hid_parse(hdev);
	if (!ret)
		ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);

	if (ret) {
		kfree(nd);
		return ret;
	}

	list_for_each_entry(hidinput, &hdev->inputs, list) {
		input = hidinput->input;

		input->absfuzz[ABS_X] = 4;
		input->absfuzz[ABS_Y] = 4;

		switch (hidinput->report->field[0]->application) {
		case HID_DG_PEN:
			input->name = "N-Trig Pen";
			set_bit(BTN_STYLUS, input->keybit);
			set_bit(BTN_TOUCH, input->keybit);
			set_bit(BTN_0, input->keybit);
			set_bit(BTN_1, input->keybit);
			set_bit(BTN_2, input->keybit);
			break;
		case HID_DG_TOUCHSCREEN:
			/* Multitouch has many more fields than the single
			 * touch input.  Use that to determine the name. */
			input->name = (hidinput->report->maxfield > 10)
			    ? "N-Trig MultiTouch" :
			    "N-Trig Touchscreen";
			set_bit(BTN_TOOL_DOUBLETAP, input->keybit);
			set_bit(BTN_TOOL_TRIPLETAP, input->keybit);
			set_bit(BTN_TOOL_QUADTAP, input->keybit);
			set_bit(BTN_TOUCH, input->keybit);
			set_bit(ABS_MT_TRACKING_ID, input->absbit);
			set_bit(ABS_MT_ORIENTATION, input->absbit);
			set_bit(ABS_MT_TOUCH_MAJOR, input->absbit);
			set_bit(ABS_MT_TOUCH_MINOR, input->absbit);
			set_bit(BTN_0, input->keybit);
			set_bit(BTN_1, input->keybit);
			set_bit(BTN_2, input->keybit);
			break;
		}
	}

	return ret;
}

static void ntrig_remove(struct hid_device *hdev)
{
	hid_hw_stop(hdev);
	kfree(hid_get_drvdata(hdev));
}

static const struct hid_device_id ntrig_devices[] = {

	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN),
	  .driver_data = NTRIG_DUPLICATE_USAGES },
	{ }
};

MODULE_DEVICE_TABLE(hid, ntrig_devices);

static const struct hid_usage_id ntrig_grabbed_usages[] = {
	{ HID_ANY_ID, HID_ANY_ID, HID_ANY_ID },
	{ HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1 }
};

static struct hid_driver ntrig_driver = {
	.name = "ntrig",
	.id_table = ntrig_devices,
	.probe = ntrig_probe,
	.remove = ntrig_remove,
	.input_mapping = ntrig_input_mapping,
	.input_mapped = ntrig_input_mapped,
	.usage_table = ntrig_grabbed_usages,
	.event = ntrig_event,
};

static int __init ntrig_init(void)
{
	return hid_register_driver(&ntrig_driver);
}

static void __exit ntrig_exit(void)
{
	hid_unregister_driver(&ntrig_driver);
}

module_init(ntrig_init);
module_exit(ntrig_exit);
MODULE_LICENSE("GPL");
