/** 
 *  HID driver for the multi-monitor USB mouse.
 *  
 *  Copyright (c) 1999 Andreas Gal
 *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
 *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
 *  Copyright (c) 2006-2007 Jiri Kosina
 *  Copyright (c) 2007 Paul Walmsley
 *  Copyright (c) 2008 Jiri Slaby
 *  Copyright (c) 2013-2014 Pete Arnold for Adder Technology Ltd.
 *
 *  0.7 Renamed hid-adder.c and reformed for generic Adder HID devices.
 *  0.6 Used Linux coding standards.
 *  0.5 Strip all non-simple-child code and correct the driver ready for
 *      pre-release testing.
 *  0.4 Can't make a simple entry, so return to the child items and look into
 *      initialisation etc.
 *  0.3 Return to simple system and try to reconfigure for a list of items.
 *  0.2 Introduce groups and child items to try to generalise the attribute
 *      functions.
 *  0.1 Simple configfs set-up based on childless items.
 */
/** 
 *  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/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>

#define ADDER_DRIVER_NAME	"hid-adder"
#define ADDER_DRIVER_VERSION	"0.7.25"

#define ADDER_VENDOR_ID		0x21d1
#define ADDER_VENDOR_NAME	"Adder"

/** Free-Flow mouse-specific parameters. */
#define ADDER_FF_ID		0x0001
#define ADDER_FF_NAME		"Free-Flow"
#define ADDER_FF_USAGE_XCOORD	0x00010030
#define ADDER_FF_USAGE_YCOORD	0x00010031
#define ADDER_FF_SCREENID	0xff000001

#define EXIT_OK			0
#define EXIT_NO_MORE_PROCESSING	1

#define DEBUG(x) { if (adder_debug > 0) printk x; }

/** Parameters (sysfs).
 */
int adder_debug = 0;
module_param_named(debug, adder_debug, int, 0600);
MODULE_PARM_DESC(debug, "Toggle HID-ADDER debugging messages.");

/** Use ConfigFS to maintain the monitor co-ordinates for all the monitors.
 *  Modified from the linux kernel Documentation example code, using the simple
 *  children model, in which each configfs directory represents a monitor and
 *  each monitor has a description file and a screen file in which the four co-
 *  ordinates left, top, right, bottom are specified.
 */
/** The monitor.
 */
static struct configfs_attribute
monitor_attr = {
	.ca_owner = THIS_MODULE,
	.ca_name = "monitor",
	.ca_mode = S_IRUGO | S_IWUGO
};

static struct configfs_attribute*
monitor_attrs[] = {
	&monitor_attr,
	NULL
};

struct monitor {
	struct config_item item;
	int bounds[4];
};

/** Get the monitor for the specified config_item, or return NULL if the item
 *  is NULL. */
static inline struct monitor *to_monitor(struct config_item *item)
{
	DEBUG((KERN_DEBUG "hid-adder configfs to_monitor (%p).\n", item));
	return item ? container_of(item, struct monitor, item) : NULL;
}

/** Show the monitor data when a user 'cat' command is used on the screen. */
static ssize_t monitor_show(
	struct config_item *item,
	struct configfs_attribute *attr,
	char *page
)
{
	struct monitor *m = to_monitor(item);
	return sprintf(page, "Monitor (%s): %d %d %d %d\n",
			item->ci_name, m->bounds[0], m->bounds[1], 
			m->bounds[2], m->bounds[3]);
}

/** Store the data when written by the user (e.g. via echo l t r b > monitor). 
 *  Make sure that a full set of data is specified. 
 *  Returns the size of the data stored or an error code. */
static ssize_t monitor_store(
	struct config_item *item,
	struct configfs_attribute *attr,
	const char *page,
	size_t count
)
{
	const int min_size = 0;
	const int max_size = 32767;

	size_t result = 0;
	struct monitor *m = to_monitor(item);
	int bounds[4] = {0, 0, 0, 0};	/* Temp. to be copied atomically. */
	int i = 0;
	unsigned long tmp;
	char *p = (char *)page;	 /* Non-constant copy. */

	for (i = 0; i < 4; i++) {
		tmp = simple_strtoul(p, &p, 10);
		if ((tmp < min_size) || (tmp > max_size))
		{	
			result = -ERANGE;
			goto exit;
		}
		bounds[i] = tmp;
		p++;
	}
	if ((*p != '\0') && (*p != '\n')) {
		result = -EINVAL;
		goto exit;
	}
	if ((bounds[2] <= bounds[0]) || (bounds[3] <= bounds[1])) {
		result = -EINVAL;
		goto exit;
	}
	for (i = 0; i < 4; i++)
		m->bounds[i] = bounds[i];
	result = count;
exit:
	return result;
}

/** Release the data when deleted by the user. */
static void monitor_release(struct config_item *item)
{
	DEBUG((KERN_DEBUG "hid-adder configfs monitor_release (%p).\n", item));
	kfree(to_monitor(item));
}

static struct configfs_item_operations monitor_ops = {
	.release		= monitor_release,
	.show_attribute		= monitor_show,
	.store_attribute	= monitor_store,
};

static struct config_item_type monitor_type = {
	.ct_item_ops	= &monitor_ops,
	.ct_attrs	= monitor_attrs,
	.ct_owner	= THIS_MODULE,
};

/** The containing & identifying directory: mumo_layout.
 */
struct mumo_layout {
	struct config_group group;
};

static struct configfs_attribute
mumo_layout_attr_description = {
	.ca_owner = THIS_MODULE,
	.ca_name = "description",
	.ca_mode = S_IRUGO | S_IWUGO,
};

static struct configfs_attribute*
mumo_layout_attrs[] = {
	&mumo_layout_attr_description,
	NULL
};

/** Get the monitor for the specified config_item, or return NULL if the item
 *  is NULL. */
static inline struct mumo_layout *to_mumo_layout(
	struct config_item *item
)
{
	DEBUG((KERN_DEBUG "hid-adder configfs to_mumo_layout (%p).\n", item));
	return item ? container_of(to_config_group(item),
				   struct mumo_layout, group) : NULL;
}

/** Release the data when deleted by the user. */
static void mumo_layout_release
(
	struct config_item *item
)
{
	DEBUG((KERN_DEBUG "hid-adder configfs mumo_layout_release (%p).\n", item));
	kfree(to_mumo_layout(item));
}

/** Display the description text when the user reads the description file. */
static ssize_t mumo_layout_attr_show(
	struct config_item *item,
	struct configfs_attribute *attr,
	char *page
)
{
	return sprintf(page,
		"[Multi-monitor Layout]\n"
		"The %s configfs directory represents a monitor layout.\n"
		"Each layout consists of a set of monitor directories, each of\n"
		"which has a monitor file in which the four co-ordinates left,\n"
		"top, right, bottom are specified.\n", ADDER_FF_NAME);
}

/* Create a directory (monitor) and default file (screen) on mkdir. */
static struct config_item *mumo_layout_make_item(
	struct config_group *group,
	const char *name
)
{
	struct monitor *m;

	DEBUG((KERN_DEBUG "hid-adder configfs mumo_layout_make_item (%s).\n", name));
	m = kzalloc(sizeof(struct monitor), GFP_KERNEL);
	if (!m) return NULL;
	config_item_init_type_name(&m->item, name, &monitor_type);
	m->bounds[0] = m->bounds[1] = m->bounds[2] = m->bounds[3] = 0;
	DEBUG((KERN_DEBUG "hid-adder configfs mumo_layout_make_item (%p).\n", &m->item));
	return &m->item;
}

static struct configfs_item_operations
mumo_layout_item_ops = {
	.release	= mumo_layout_release,
	.show_attribute = mumo_layout_attr_show,
};

static struct configfs_group_operations
mumo_layout_group_ops = {
	.make_item	= mumo_layout_make_item,
	/** Note that, since no extra work is required on ->drop_item(),
	 *  no ->drop_item() is provided.
	 */
};

static struct config_item_type
mumo_layout_type = {
	.ct_item_ops	= &mumo_layout_item_ops,
	.ct_group_ops	= &mumo_layout_group_ops,
	.ct_attrs	= mumo_layout_attrs,
	.ct_owner	= THIS_MODULE,
};

static struct configfs_subsystem
mumo_layout_subsys = {
	.su_group = {
		.cg_item = {
			.ci_namebuf = ADDER_FF_NAME,
			.ci_type = &mumo_layout_type
		}
	}
};

/** Unregister the configfs for multi-monitor. */
static void unregister_configfs(void)
{
	configfs_unregister_subsystem(&mumo_layout_subsys);
}

/** Register the configfs for multi-monitor. */
static int register_configfs(void)
{
	int result;

	DEBUG((KERN_DEBUG "hid-adder configfs register.\n"));
	config_group_init(&mumo_layout_subsys.su_group);
	mutex_init(&mumo_layout_subsys.su_mutex);
	result = configfs_register_subsystem(&mumo_layout_subsys);
	if (result) {
		DEBUG((KERN_ERR "Error %d while registering subsystem %s\n",
			result,
			mumo_layout_subsys.su_group.cg_item.ci_namebuf));
		/* ??? Unregistering in case it may be partially registered. */
		unregister_configfs();
		return result;
	}
	return EXIT_OK;
}

/** The Free-Flow driver.
 *  The driver is based on the HID quirks drivers in the Linux kernel. It
 *  provides a probe, event and remove handler so that we can convert from the
 *  co-ordinate system provided by the device (monitor-id, x (0-32767) on the
 *  monitor, y (0-32767) on the monitor) into multi-monitor co-ordinates
 *  x (0-32767) over all the monitors, y (0-32767) over all the monitors.
 */

struct freeflow_state {
/** Since the id, x and y values are passed in via separate calls to the event
 *  handler, we need to maintain all of these between calls. */
	unsigned int monitor_id;	/* Device-defined screen ID. */
	unsigned int x;			/* X-co-ordinate on screen. */
	unsigned int y;			/* Y-co-ordinate on screen. */
	unsigned int probed;		/* Flag to show probing is complete. */
};

/** Probe is called when the device is detected. Create any device-specific
 *  data and start the hardware. */
static int freeflow_probe(
	struct hid_device *hdev, 
	const struct hid_device_id *id
)
{
	int result = EXIT_OK;
	unsigned int connect_mask = HID_CONNECT_DEFAULT;
	struct freeflow_state *state;

	DEBUG((KERN_DEBUG "hid-adder FF probe.\n"));

	/* Maintain some state for the device. */
	state = kzalloc(sizeof(struct freeflow_state), GFP_KERNEL);
	if (state == NULL) {
		DEBUG((KERN_DEBUG "hid-adder FF probe, state allocation failed.\n"));
			result = -ENOMEM;
		goto exit;
	} else {
		DEBUG((KERN_DEBUG "hid-adder FF probe, state allocation (%ld bytes) OK.\n",
			sizeof(struct freeflow_state)));
	}
	state->monitor_id = state->x = state->y = state->probed = 0;
	hid_set_drvdata(hdev, state);
	/* Call hid_parse() and ensure that this is OK. */
	result = hid_parse(hdev);
	if (result != EXIT_OK) {
		DEBUG((KERN_DEBUG "hid-adder FF probe, parse failed: %08x.\n",
			result));
	} else {
		DEBUG((KERN_DEBUG "hid-adder FF probe, parse OK."));
	}
	/* Start the hardware. */
	result = hid_hw_start(hdev, connect_mask);
	if (result != EXIT_OK) {
		DEBUG((KERN_DEBUG "hid-adder FF probe, hw_start failed: %08x.\n",
			result));
		goto exit;
	} else {
		DEBUG((KERN_DEBUG "hid-adder FF probe, hw_start OK."));
	}
exit:
	if (result == EXIT_OK)
		state->probed = 1;
	else
		kfree(state);
	DEBUG((KERN_DEBUG "hid-adder FF probe, returning: %08x.\n", result));
	return result;
}

/** Get the required screen data from the configfs subsystem.
 *  Note that this is the only place (other than the registration functions)
 *  which uses the configfs structures. */
static void freeflow_get_monitor_data
(
	int monitor_id,
	int *max_x,
	int *max_y,
	int **monitor_layout
)
{
	int count;
	int defaultMonitor[4] = {0, 0, 0, 0};
	struct config_group *group;
	struct config_item *item;
	struct monitor *m;
	struct list_head *pos;

	/* Note that under Debian Squeeze, the mutex causes a problem (always
	 * on the Y-COORD) and so the locks are currently commented out.
	 * mutex_lock(&mumo_layout_subsys.su_mutex);
	 */
	/* Search through all the configured screens.
	 * We refer to the screens numerically so that the user
	 * can define any screen names they like. The hardware
	 * will refer to screens 0, 1 etc. We will associate
	 * screen 0 with the first item returned in the list,
	 * screen 1 with the second etc. Tests indicated that
	 * the list is ordered in the order of creation. This
	 * may be iplementation specific or inconvenient, so
	 * it might be worth creating an ordered list first.
	 */
	count = monitor_id;
	*monitor_layout = defaultMonitor;
	*max_x = *max_y = 0;
	group = &mumo_layout_subsys.su_group;
	config_group_get(group);
	list_for_each(pos, &group->cg_children) {
		item = container_of(pos, struct config_item, ci_entry);
		config_item_get(item);
		m = to_monitor(item);
		DEBUG((KERN_DEBUG "hid-adder FF get-monitor-data from configfs, found item %s at"
			" (%d, %d) - (%d, %d).\n", item->ci_name, m->bounds[0],
			m->bounds[1], m->bounds[2], m->bounds[3]));
		if (m->bounds[2] > *max_x)
			*max_x = m->bounds[2];
		if (m->bounds[3] > *max_y)
			*max_y = m->bounds[3];
		if (count-- == 0)
			*monitor_layout = m->bounds;
		config_item_put(item);
	}
	config_group_put(group);
	/* See above.
	 * mutex_unlock(&mumo_layout_subsys.su_mutex);
	 */
}

/** A mouse event has occurred for the Free-Flow mouse, process and update
 *  the state. */
static int freeflow_event(
	struct hid_device *hdev,
	struct hid_field *field,
	struct hid_usage *usage,
	__s32 value
)
{
	unsigned hid_usage = usage->hid;
	struct freeflow_state *state;
	int *monitor;
	int max_x = 0;
	int max_y = 0;

	state = hid_get_drvdata(hdev);
	DEBUG((KERN_DEBUG "hid-adder FF event.\n"));
	if (state->probed == 0)
	{
		DEBUG((KERN_DEBUG "hid-adder FF event before probe --- exiting.\n"));
		return EXIT_NO_MORE_PROCESSING; 
	}
	switch (hid_usage) {
	case ADDER_FF_USAGE_XCOORD:
		freeflow_get_monitor_data(state->monitor_id, &max_x,
					  &max_y, &monitor);
		state->x = (unsigned int)value;
		/* If the screen maximum is indeterminate, then we just
		   apply the co-ordinates to the screen (value = value).
		   This will have the effect of making the cursor
		   traverse all the monitors as many times as there are
		   monitors horizontally, leaving the mouse usable. */
		if (max_x > 0)
			value = ((monitor[0] * 32768) + 
				((monitor[2] - monitor[0]) * state->x)) / 
				max_x;
		DEBUG((KERN_DEBUG "hid-adder FF event [X-COORD] calculation is "
			"(%d * 32768) / %d + ((%d - %d) * %d)) / %d = %d.\n",
			monitor[0], max_x, monitor[2], monitor[0],
			state->x, max_x, value));
		break;
	case ADDER_FF_USAGE_YCOORD:
		freeflow_get_monitor_data(state->monitor_id, &max_x,
					 &max_y, &monitor);
		state->y = (unsigned int)value;
		/* If the screen maximum is indeterminate, then we just
		   apply the co-ordinates to the screen (value = value).
		   This will have the effect of making the cursor
		   traverse all the monitors as many times as there are
		   monitors vertically, leaving the mouse usable. */
		if (max_y > 0)
			value = ((monitor[1] * 32768) +
				((monitor[3] - monitor[1]) * state->y)) /
				max_y;
		DEBUG((KERN_DEBUG "hid-adder FF event [Y-COORD] calculation is "
			"(%d * 32768) / %d + ((%d - %d) * %d)) / %d = %d.\n",
			monitor[1], max_y, monitor[3], monitor[1],
			state->y, max_y, value));
		break;
	case ADDER_FF_SCREENID:
		state->monitor_id = (unsigned int)value;
		DEBUG((KERN_DEBUG "hid-adder FF event [SCREEN ] %d.", value));
		break;
	default:
		DEBUG((KERN_DEBUG "hid-adder FF event [UNKNOWN]."));
		break;
	}
	DEBUG((KERN_DEBUG "hid-adder FF event state at exit: screen = %01d, "
		"position = (%5d, %5d).\n",
		state->monitor_id, state->x, state->y));
	input_event(field->hidinput->input, usage->type, usage->code, value);
	return EXIT_NO_MORE_PROCESSING; 
}

/** The Adder HID device drivers.
 */
static const struct hid_device_id adder_devices[] = {
	{ HID_USB_DEVICE(ADDER_VENDOR_ID, ADDER_FF_ID), },
	{ }
};

/** Probe is called when the device is detected. Create any device-specific
 *  data and start the hardware. */
static int adder_probe(
	struct hid_device *hdev,
	const struct hid_device_id *id
)
{
	DEBUG((KERN_DEBUG "hid-adder (%04X/%04X) probe, data = %08lx.\n",
		hdev->vendor, hdev->product, id->driver_data));
	switch (hdev->product) {
	case ADDER_FF_ID:
		return freeflow_probe(hdev, id);
	default:
		break;
	}
	DEBUG((KERN_DEBUG "hid-adder probe, returning from unhandled product ID.\n"));
	return EXIT_OK;
}

/** A mouse event has occurred for the Free-Flow mouse, process and update
 *  the state. */
static int adder_event(
	struct hid_device *hdev,
	struct hid_field *field,
	struct hid_usage *usage,
	__s32 value
)
{
	DEBUG((KERN_DEBUG "hid-adder event, usage = %04x, value = %d.\n",
		usage->hid, value));
	switch (hdev->product) {
	case ADDER_FF_ID:
		return freeflow_event(hdev, field, usage, value);
	default:
		break;
	}
	DEBUG((KERN_DEBUG "hid-adder event, returning from unhandled product ID.\n"));
	return EXIT_OK;
}

/** Device removed - clean up. */
static void adder_remove(
	struct hid_device *hdev
)
{
	DEBUG((KERN_DEBUG "hid-adder remove.\n"));
	hid_hw_stop(hdev);
	kfree(hid_get_drvdata(hdev));
}

/**
 * struct hid_driver
 * @name: driver name (e.g. "Footech_bar-wheel")
 * @id_table: which devices is this driver for (must be non-NULL for probe
 * 	      to be called)
 * @dyn_list: list of dynamically added device ids
 * @dyn_lock: lock protecting @dyn_list
 * @probe: new device inserted
 * @remove: device removed (NULL if not a hot-plug capable driver)
 * @report_table: on which reports to call raw_event (NULL means all)
 * @raw_event: if report in report_table, this hook is called (NULL means nop)
 * @usage_table: on which events to call event (NULL means all)
 * @event: if usage in usage_table, this hook is called (NULL means nop)
 * @report_fixup: called before report descriptor parsing (NULL means nop)
 * @input_mapping: invoked on input registering before mapping an usage
 * @input_mapped: invoked on input registering after mapping an usage
 *
 * raw_event and event should return 0 on no action performed, 1 when no
 * further processing should be done and negative on error
 *
 * input_mapping shall return a negative value to completely ignore this usage
 * (e.g. doubled or invalid usage), zero to continue with parsing of this
 * usage by generic code (no special handling needed) or positive to skip
 * generic parsing (needed special handling which was done in the hook already)
 * input_mapped shall return negative to inform the layer that this usage
 * should not be considered for further processing or zero to notify that
 * no processing was performed and should be done in a generic manner
 * Both these functions may be NULL which means the same behavior as returning
 * zero from them.
 */
static struct hid_driver adder_driver = {
	.name = ADDER_DRIVER_NAME,
	.id_table = adder_devices,
	.probe = adder_probe,
	.event = adder_event,
	.remove = adder_remove
};

static int __init adder_init(void)
{
	int result = EXIT_OK;

	result = hid_register_driver(&adder_driver);
	if (result == EXIT_OK) {
		DEBUG((KERN_DEBUG "hid-adder init (V%s) "
			"OK (%08x).\n", ADDER_DRIVER_VERSION, result));
		result = register_configfs();
		if (result != EXIT_OK) {
			DEBUG((KERN_DEBUG "hid-adder init, failed to register " 
				"configfs, returning: %08x.\n", result));
		}
	} else {
		DEBUG((KERN_DEBUG "hid-adder init, failed to register the "
			"driver, returning: %08x.\n", result));
	}
	return result;
}

static void __exit adder_exit(void)
{
	DEBUG((KERN_DEBUG "hid-adder exit.\n"));
	hid_unregister_driver(&adder_driver);
	unregister_configfs();
}

module_init(adder_init);
module_exit(adder_exit);
/* It doesn't seem necessary to explicitly define this line. A hid:21d1:0001
line appears in modules.alias anyway. */
//MODULE_ALIAS("usb:v21D1p0001d*dc*dsc*dp*ic*isc*ip*");
//MODULE_ALIAS("hid:b0003v000021D1p00000001");
MODULE_ALIAS("input:b0003v21D1p0001e*");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pete Arnold at Adder Technology Ltd");
MODULE_DESCRIPTION("Converts Screen+Absolute Screen Co-ordinate mouse data "
		   "from the Free-Flow mouse to Display absolute mouse data.");
MODULE_VERSION(ADDER_DRIVER_VERSION);
MODULE_DEVICE_TABLE(hid, adder_devices);
