/* 
 * Copyright (c) 1998  Metro Link Incorporated
 *
 * Template driver used: Copyright (c) 1998  Metro Link Incorporated
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, cpy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Except as contained in this notice, the name of the Metro Link shall not be
 * used in advertising or otherwise to promote the sale, use or other dealings
 * in this Software without prior written authorization from Metro Link.
 *
 */
/* $XFree86: xc/programs/Xserver/hw/xfree86/input/elo/elo.c,v 1.3 2000/08/11 19:10:43 dawes Exp $ */

#define _ELOUSB_C_

#include <linux/input.h>

/* keithp - a hack to avoid redefinitions of these in xf86str.h */
#ifdef BUS_PCI
#undef BUS_PCI
#endif
#ifdef BUS_ISA
#undef BUS_ISA
#endif

#include <misc.h>
#include <xf86.h>
#define NEED_XF86_TYPES	
#include <xf86_ansic.h>
#include <xf86_OSproc.h>
#include <xf86Xinput.h>
#include <xisb.h>
#include <exevents.h>

/*****************************************************************************
 *	Local Headers
 ****************************************************************************/
#include "elousb.h"
#include "elo_memory.h"

/*****************************************************************************
 *	Variables without includable headers
 ****************************************************************************/

/*****************************************************************************
 *	Local Variables
 ****************************************************************************/
InputDriverRec ELOUSB = {
        1,
        "elousb",
        NULL,
        EloPreInit,
        /*EloUnInit*/NULL,
        NULL,
        0
};        



#ifdef XFree86LOADER

static XF86ModuleVersionInfo VersionRec =
{
	"elousb",
	MODULEVENDORSTRING,
	MODINFOSTRING1,
	MODINFOSTRING2,
	XF86_VERSION_CURRENT,
	1, 0, 0,
	ABI_CLASS_XINPUT,
	ABI_XINPUT_VERSION,
	MOD_CLASS_XINPUT,
	{0, 0, 0, 0}				/* signature, to be patched into the file by
								 * a tool */
};


static pointer
EloSetupProc(	pointer module,
			pointer options,
			int *errmaj,
			int *errmin )
{
	xf86AddInputDriver(&ELOUSB, module, 0);
	return (pointer) 1;
}

XF86ModuleData elousbModuleData = { &VersionRec, EloSetupProc, NULL };


#endif /* XFree86LOADER */


/*****************************************************************************
 *	Function Definitions
 ****************************************************************************/


static InputInfoPtr
EloPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
{              
	InputInfoPtr local;
   	EloPrivatePtr priv = xcalloc (1, sizeof (EloPrivateRec));
	char *s;

	if (!priv)
		return NULL;

	local = xf86AllocateInput(drv, 0);
	if (!local) {
		xfree(priv);
		return NULL;
	}
  
	local->name = xf86SetStrOption( local->options, "DeviceName", "Elo USB TouchScreen");
	local->type_name = XI_TOUCHSCREEN;
	local->device_control = DeviceControl;
	local->read_input = ReadInput;
	local->control_proc = ControlProc;
	local->close_proc = CloseProc;
	local->switch_mode = SwitchMode;
	local->conversion_proc = ConvertProc;
	local->dev = NULL;
	local->private = priv;
	local->private_flags = 0;
	local->flags = XI86_POINTER_CAPABLE | XI86_SEND_DRAG_EVENTS;
	local->conf_idev = dev;

	xf86CollectInputOptions(local, NULL, NULL);

	local->fd = xf86OpenSerial (local->options);
	xf86OptionListReport( local->options );
	if (local->fd == -1)
	{
	  ErrorF("%s: driver unable to open device.\n", local->name);
		goto SetupProc_fail;
	}
	xf86ErrorFVerb( 3, "%s: port opened successfully.\n", local->name );

	/* 
	 * Process the options for your device like this
	 */
	priv->min_x = xf86SetIntOption( local->options, "MinX", 0 );
	priv->max_x = xf86SetIntOption( local->options, "MaxX", 4095 );
	priv->min_y = xf86SetIntOption( local->options, "MinY", 4095 );
	priv->max_y = xf86SetIntOption( local->options, "MaxY", 0 );
  priv->swap_xy = xf86SetBoolOption( local->options, "SwapAxes", 0);
	priv->screen_num = xf86SetIntOption( local->options, "ScreenNumber", 0 );
	priv->button_number = xf86SetIntOption( local->options, "ButtonNumber", 1 );
	priv->button_threshold = xf86SetIntOption( local->options, "ButtonThreshold", -1 );
  priv->screen_width = -1;
  priv->screen_height = -1;
	priv->x_offset = -1;
	priv->y_offset = -1;
	priv->cal_threshold = -1;
	priv->button_down = FALSE;
	
	s = xf86FindOptionValue (local->options, "ReportingMode");
	if ((s) && (xf86NameCmp (s, "raw") == 0))
		priv->reporting_mode = TS_Raw;
	else
		priv->reporting_mode = TS_Scaled;

	priv->t_mode = NORMAL;
	if ((s = xf86FindOptionValue (local->options, "TwinMode")))
	{
		if (xf86NameCmp (s, "LeftOf") == 0)
			priv->t_mode = LEFTOF;
		else if (xf86NameCmp (s, "RightOf") == 0)
			priv->t_mode = RIGHTOF;
		else if (xf86NameCmp (s, "Above") == 0)
			priv->t_mode = ABOVE;
		else if (xf86NameCmp (s, "Below") == 0)
			priv->t_mode = BELOW;
		else if (xf86NameCmp (s, "Clone") == 0)
			priv->t_mode = CLONE;
	}

	if (QueryHardware (local) != Success)
	{
	  ErrorF("%s: unable to query/initialize hardware.\n", local->name);
		goto SetupProc_fail;
	}

	local->history_size = xf86SetIntOption( local->options, "HistorySize", 0 );
	xf86ProcessCommonOptions(local, local->options);
	local->flags |= XI86_CONFIGURED;

	if (local->fd != -1)
	{ 
		RemoveEnabledDevice (local->fd);
		xf86CloseSerial(local->fd);
		local->fd = -1;
	}
	return (local);

  SetupProc_fail:

	ErrorF ("%s: driver work in silence mode.\n", local->name);

	local->device_control = DeviceControlSilence;
	local->read_input = NULL;
	local->control_proc = NULL;
	local->close_proc = NULL;
	local->switch_mode = NULL;
	local->conversion_proc = NULL;
	local->flags |= XI86_CONFIGURED;

	if (local->fd != -1)
	{
		xf86CloseSerial (local->fd);
		local->fd = -1;
	}
	if (priv)
		xfree (priv);
	return (local);
}

static Bool
DeviceControl (DeviceIntPtr dev, int mode)
{
	InputInfoPtr local = dev->public.devicePrivate;
	EloPrivatePtr priv = (EloPrivatePtr) (local->private);
	unsigned char map[] =	{0, 1};

	switch (mode)
	{
	case DEVICE_INIT:

		/*
		 * these have to be here instead of in the SetupProc, because when the
		 * SetupProc is run at server startup, screenInfo is not setup yet
		 */
    if (priv->screen_num >= screenInfo.numScreens ||
	     priv->screen_num < 0)
    {
	    ErrorF("%s: invalid screen number %d, resetting to 0\n",
		   local->name, priv->screen_num);
	    priv->screen_num = 0;
  	}
		priv->screen_width = screenInfo.screens[priv->screen_num]->width;
		priv->screen_height = screenInfo.screens[priv->screen_num]->height;
		priv->x_offset = 0;
		priv->y_offset = 0;
		
		switch(priv->t_mode)
		{
		case LEFTOF:
			priv->screen_width = priv->screen_width/2;
			break;

		case RIGHTOF:
			priv->x_offset = priv->screen_width/2;
			MemoryOffset = SCREEN_STATE_SIZE;
			break;

		case ABOVE:
			priv->screen_height = priv->screen_height/2;
			break;

		case BELOW:
			priv->y_offset = priv->screen_height/2;
			MemoryOffset = SCREEN_STATE_SIZE;
			break;
			
		case CLONE:
			MemoryOffset = SCREEN_STATE_SIZE;
			break;

		default:
			if (priv->screen_num == 1)
				MemoryOffset = SCREEN_STATE_SIZE;
			else
				MemoryOffset = 0;
		}

		if (!MemoryOpen(STATE_MEMORY_KEY, STATE_MEMORY_SIZE))
			if (!MemoryCreate(STATE_MEMORY_KEY, STATE_MEMORY_SIZE))
			{
				ErrorF("Can't open shared memory : %d\n", STATE_MEMORY_KEY);
				return !Success;
			}
		xf86ErrorFVerb (4, "Shared memory opened : %d\n", STATE_MEMORY_KEY);

		/*
		 * Device reports button press for 1 button.
		 */
		if (InitButtonClassDeviceStruct (dev, 1, map) == FALSE)
			{
				ErrorF ("Unable to allocate Elo ButtonClassDeviceStruct\n");
				return !Success;
			}
		
		/*
		 * Device reports motions on 2 axes in absolute coordinates.
		 * Axes min and max values are reported in raw coordinates.
		 */
		if (InitValuatorClassDeviceStruct (dev, 2, xf86GetMotionEvents,
						   local->history_size, Absolute) == FALSE)
			{
				ErrorF ("Unable to allocate Elo ValuatorClassDeviceStruct\n");
				return !Success;
			}
		else
			{
				InitValuatorAxisStruct (dev, 0, priv->min_x, priv->max_x,
							9500,
							0 /* min_res */ ,
							9500 /* max_res */ );
				InitValuatorAxisStruct (dev, 1, priv->min_y, priv->max_y,
							10500,
							0 /* min_res */ ,
							10500 /* max_res */ );
			}
		
		if (InitProximityClassDeviceStruct (dev) == FALSE)
			{
				ErrorF ("Unable to allocate Elo ProximityClassDeviceStruct\n");
				return !Success;
			}
		
		/* 
		 * Allocate the motion events buffer.
		 */
		xf86MotionHistoryAllocate (local);
		return (Success);
		
	case DEVICE_ON:
		local->fd = xf86OpenSerial(local->options);
		if (local->fd == -1)
			{
				xf86Msg(X_WARNING, "%s: cannot open input device\n", local->name);
				return (!Success);
			}
		else 
			{
				priv->buffer = XisbNew(local->fd,
        	   NUMEVENTS * sizeof(struct input_event));
				if (!priv->buffer) 
					{
						xf86CloseSerial(local->fd);
						local->fd = -1;
						return (!Success);
					}
			}
		
		xf86FlushInput(local->fd);
		AddEnabledDevice (local->fd);
		dev->public.on = TRUE;
		state_memory[SCREEN_READY_ADDRESS+MemoryOffset] = 0;
		state_memory[SCREEN_WAIT_ADDRESS+MemoryOffset] = 0;
		state_memory[SCREEN_UNLOCK_ADDRESS+MemoryOffset] = 1;
		priv->cal_threshold = 0;
		return (Success);
		
	case DEVICE_OFF:
	case DEVICE_CLOSE:
		memset( state_memory, 0, SCREEN_STATE_SIZE );
		if (local->fd != -1)
			{ 
				RemoveEnabledDevice (local->fd);
				if (priv->buffer)
					{
						XisbFree(priv->buffer);
						priv->buffer = NULL;
					}
				xf86CloseSerial(local->fd);
				local->fd = -1;
 			}
		dev->public.on = FALSE;
		return (Success);
	default:
		return (BadValue);
	}

}

static Bool
DeviceControlSilence (DeviceIntPtr dev, int mode)
{
	return (Success);
}

/* 
 * The ReadInput function will have to be tailored to your device
 */
static void
ReadInput (InputInfoPtr local)
{
	static int cur_x, cur_y, cur_z;
  static struct input_event eventList[NUMEVENTS];
  int n, c; 
  struct input_event *event = eventList;
  unsigned char *pBuf;

	EloPrivatePtr priv = (EloPrivatePtr) (local->private);

	if ((state_memory[SCREEN_UNLOCK_ADDRESS+MemoryOffset] == 1) &&
				(state_memory[SCREEN_READY_ADDRESS+MemoryOffset] == 1))
	{
		int v0 = 0;
		int v1 = 0;
		int v2 = 0;
		int v3 = 0;
		state_memory[SCREEN_WAIT_ADDRESS+MemoryOffset] = 0;
		state_memory[SCREEN_READY_ADDRESS+MemoryOffset] = 0;
		priv->cal_threshold = 0;
		memcpy(&v0, state_memory+SCREEN_MIN_X_ADDRESS+MemoryOffset, SCREEN_MIN_X_SIZE);
		memcpy(&v1, state_memory+SCREEN_MAX_X_ADDRESS+MemoryOffset, SCREEN_MAX_X_SIZE);
		memcpy(&v2, state_memory+SCREEN_MIN_Y_ADDRESS+MemoryOffset, SCREEN_MIN_Y_SIZE);
		memcpy(&v3, state_memory+SCREEN_MAX_Y_ADDRESS+MemoryOffset, SCREEN_MAX_Y_SIZE);
		if ((v0 != v1) && (v2 != v3))
		{
			priv->min_x=v0;
			priv->max_x=v1;
			priv->min_y=v2;
			priv->max_y=v3;
		  xf86ErrorFVerb (5, "Calibration: MinX(%d), MaxX(%d), MinY(%d), MaxY(%d)\n",	v0,	v1,	v2,	v3);
		}
	}

	/* 
	 * set blocking to -1 on the first call because we know there is data to
	 * read. Xisb automatically clears it after one successful read so that
	 * succeeding reads are preceeded buy a select with a 0 timeout to prevent
	 * read from blocking indefinately.
	 */
  XisbBlockDuration(priv->buffer, -1);
  pBuf = (unsigned char *)eventList;
  n = 0;
  while ((c = XisbRead(priv->buffer)) >= 0 && n < sizeof(eventList))
	  pBuf[n++] = (unsigned char)c;

  if (n == 0)
	  return;

  n /= sizeof(struct input_event);
  while( n-- )
  {
    int x, y;
    int state = 0;
		xf86ErrorFVerb (5, "ReadInput: type(%d), code(%d), value(%d)\n",
							event->type, event->code, event->value);
		if (event->type == 3) 
		{
			if (event->code == 1) 
			{
				if (priv->swap_xy)
				  cur_y = event->value;
				else
					cur_x = event->value;
			}
			else if (event->code == 2) 
			{
				if (priv->swap_xy)
				  cur_x = event->value;
				else
		 		  cur_y = event->value;
			}
      else if (event->code == 11)
        cur_z = event->value;
		}
		else if ((event->type == 1) && (event->code == 272)) 
		{
			if (event->value == 1) 
				state = ELO_PRESS;
			else
				state = ELO_RELEASE;
		}
		  
    x = cur_x;
    y = 4095 - cur_y;
    
			if ((state_memory[SCREEN_UNLOCK_ADDRESS+MemoryOffset] == 1) &&
					(state_memory[SCREEN_WAIT_ADDRESS+MemoryOffset] == 1))
				if (priv->cal_threshold < EVENTS_LIMIT)
				{
					memcpy(state_memory+SCREEN_X_ADDRESS+MemoryOffset, &x, SCREEN_X_SIZE);
					memcpy(state_memory+SCREEN_Y_ADDRESS+MemoryOffset, &y, SCREEN_Y_SIZE);
					priv->cal_threshold++;
				}
				else
				{
					state_memory[SCREEN_WAIT_ADDRESS+MemoryOffset] = 0;
					priv->cal_threshold = 0;
				}
    	if (priv->reporting_mode == TS_Scaled)
			{	
				x = xf86ScaleAxis (x, priv->x_offset, priv->screen_width, priv->min_x,
								   priv->max_x);
				y = xf86ScaleAxis (y, priv->y_offset, priv->screen_height, priv->min_y,
								   priv->max_y);
			}
			xf86XInputSetScreen (local, priv->screen_num, x, y);
			if (state == ELO_PRESS)
				xf86PostProximityEvent (local->dev, 1, 0, 2, x, y);

			/* 
			 * Send events.
			 *
			 * We *must* generate a motion before a button change if pointer
			 * location has changed as DIX assumes this. This is why we always
			 * emit a motion, regardless of the kind of packet processed.
			 */
			xf86PostMotionEvent (local->dev, TRUE, 0, 2, x, y);

			/* 
			 * Emit a button press or release.
			 */
			if (((cur_z > priv->button_threshold) && (state == ELO_PRESS))
				&& (!priv->button_down))
			{
				xf86PostButtonEvent (local->dev, TRUE,
									 priv->button_number, 1, 0, 2, x, y);
				priv->button_down = TRUE;
			}
			/* 
			 * if button_threshold is 0, there may never be a STREAMING
			 * event with a z <= 0 before there is an untouch (RELEASE)
			 * event
			 */
			if (((cur_z <= priv->button_threshold) || (state == ELO_RELEASE))
				&& (priv->button_down))
			{
				xf86PostButtonEvent (local->dev, TRUE,
									 priv->button_number, 0, 0, 2, x, y);
				priv->button_down = FALSE;
			}
			/* 
			 * the untouch should always come after the button release
			 */
			if (state == ELO_RELEASE)
					xf86PostProximityEvent (local->dev, 0, 0, 2, x, y);
    
			xf86ErrorFVerb (4, "TouchScreen: x(%d), y(%d), %s\n",
							x, y,
							(state == ELO_PRESS) ? "Press" :
						  ((state == ELO_RELEASE) ? "Release" : "Stream"));
  ++event;
  }
}

/* 
 * The ControlProc function may need to be tailored for your device
 */
static int
ControlProc (InputInfoPtr local, xDeviceCtl * control)
{
	xDeviceTSCalibrationCtl *c = (xDeviceTSCalibrationCtl *) control;
	EloPrivatePtr priv = (EloPrivatePtr) (local->private);

        priv->min_x = c->min_x;
        priv->max_x = c->max_x;
        priv->min_y = c->min_y;
        priv->max_y = c->max_y;
 
	return (Success);
}

/* 
 * the CloseProc should not need to be tailored to your device
 */
static void
CloseProc (InputInfoPtr local)
{
}

/* 
 * The SwitchMode function may need to be tailored for your device
 */
static int
SwitchMode (ClientPtr client, DeviceIntPtr dev, int mode)
{
	InputInfoPtr local = dev->public.devicePrivate;
  EloPrivatePtr priv = (EloPrivatePtr) (local->private);

	if ((mode == TS_Raw) || (mode == TS_Scaled))
        {
                priv->reporting_mode = mode;
                return (Success);
        }
        else if ((mode == SendCoreEvents) || (mode == DontSendCoreEvents))
        {
                xf86XInputSetSendCoreEvents (local, (mode == SendCoreEvents));
                return (Success);
        }
        else
                return (!Success);   
}

/* 
 * The ConvertProc function may need to be tailored for your device.
 * This function converts the device's valuator outputs to x and y coordinates
 * to simulate mouse events.
 */
static Bool
ConvertProc (InputInfoPtr local,
			 int first,
			 int num,
			 int v0,
			 int v1,
			 int v2,
			 int v3,
			 int v4,
			 int v5,
			 int *x,
			 int *y)
{
	EloPrivatePtr priv = (EloPrivatePtr) (local->private);
	
	if (priv->reporting_mode == TS_Raw)
	{
		*x = xf86ScaleAxis (v0, priv->x_offset, priv->screen_width, priv->min_x,
							priv->max_x);
		*y = xf86ScaleAxis (v1, priv->y_offset, priv->screen_height, priv->min_y,
							priv->max_y);
	}
	else
	{
		*x = v0;
		*y = v1;
	}
  
  xf86ErrorFVerb (4, "Convert: x(%d), y(%d)\n",	*x, *y);

	return (TRUE);
}

/* 
 * the QueryHardware fuction should be tailored to your device to
 * verify the device is attached and functional and perform any
 * needed initialization.
 */
static Bool
QueryHardware (LocalDevicePtr local)
{    
  int version;
	char name[256] = "Unknown";
	char nameElo[3] = "Elo";

	if (!ioctl(local->fd, EVIOCGVERSION, &version))
		xf86Msg( X_PROBED, " USB Kernel Input driver version is %d.%d.%d\n",
	       version >> 16, (version >> 8) & 0xff, version & 0xff);

  ioctl(local->fd, EVIOCGNAME(sizeof(name)), name);
  xf86Msg( X_PROBED, " USB Kernel Input device name: \"%s\"\n", name);

	if (strncasecmp(name, nameElo, sizeof (nameElo)) == 0)
		return (Success);

  ErrorF("%s: unknown type hardware.\n", local->name);
	return (!Success);
}

// Create memory
static Bool MemoryCreate( 
		xf86key_t	key,
		int		size )
{
	int md;
	if( (md = shmget( key, size, IPC_CREAT | PERM )) == -1 )
		return( FALSE );
	if( (state_memory = (char*)shmat( md, 0, 0 )) == (char*)-1 )
		return( FALSE );
	memset( state_memory, 0, size );
	return( TRUE );
}	

// Open memory
static Bool MemoryOpen( 	
		xf86key_t	key,
		int		size )
{
	int md;
	if( (md = shmget( key, size, 0 )) == -1 )
		return( FALSE );
	if( (state_memory = (char*)shmat( md, 0, 0 )) == (char*)-1 )
		return( FALSE );
	return( TRUE );
}	

