/* $XConsortium: xf86Wacom.c /main/20 1996/10/27 11:05:20 kaleb $ */
/*
 * Copyright 1995-2004 by Frederic Lepied, France. <Lepied@XFree86.org>
 *                                                                            
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is  hereby granted without fee, provided that
 * the  above copyright   notice appear  in   all  copies and  that both  that
 * copyright  notice   and   this  permission   notice  appear  in  supporting
 * documentation, and that   the  name of  Frederic   Lepied not  be  used  in
 * advertising or publicity pertaining to distribution of the software without
 * specific,  written      prior  permission.     Frederic  Lepied   makes  no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.                   
 *                                                                            
 * FREDERIC  LEPIED DISCLAIMS ALL   WARRANTIES WITH REGARD  TO  THIS SOFTWARE,
 * INCLUDING ALL IMPLIED   WARRANTIES OF MERCHANTABILITY  AND   FITNESS, IN NO
 * EVENT  SHALL FREDERIC  LEPIED BE   LIABLE   FOR ANY  SPECIAL, INDIRECT   OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA  OR PROFITS, WHETHER  IN  AN ACTION OF  CONTRACT,  NEGLIGENCE OR OTHER
 * TORTIOUS  ACTION, ARISING    OUT OF OR   IN  CONNECTION  WITH THE USE    OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 */

/* $XFree86: xc/programs/Xserver/hw/xfree86/input/wacom/xf86Wacom.c,v 1.26 2001/04/01 14:00:13 tsi Exp $ */

/*
 * This driver is currently able to handle Wacom IV, V, and ISDV4 protocols.
 *
 * Wacom V protocol work done by Raph Levien <raph@gtk.org> and
 * Frédéric Lepied <lepied@xfree86.org>.
 *
 * Many thanks to Dave Fleck from Wacom for the help provided to
 * build this driver.
 *
 * Modified for Linux USB by MATSUMURA Namihiko,
 * Daniel Egger, Germany. <egger@suse.de>,
 * Frederic Lepied <lepied@xfree86.org>,
 * Brion Vibber <brion@pobox.com>,
 * Aaron Optimizer Digulla <digulla@hepe.com>,
 * Jonathan Layes <jonathan@layes.com>.
 *
 */

/*
 * REVISION HISTORY
 *
 * 2002-12-17 26-j0.3.3 - added Intuos2
 * 2002-12-31 26-j0.3.5 - added module loading for usb wacom and evdev
 * 2003-01-01 26-j0.3.6 - fix for 2D Intuos2 mouse buttons
 * 2003-01-25 26-j0.3.7 - cleaned up usb conditions for FreeBSD
 * 2003-01-31 26-j0.5.0 - new release
 * 2003-01-31 26-j0.5.1 - added Ping Cheng's PL code
 * 2003-01-31 26-j0.5.2 - fixed serial number code for Intuos and Intuos2
 * 2003-02-12 26-j0.5.3 - added Ping Cheng's USB patch
 * 2003-02-12 26-j0.5.4 - added Ping Cheng's "device_on" patch
 * 2003-02-22 26-j0.5.5 - added Ping Cheng's "multi" patch
 * 2003-02-22 26-j0.5.6 - applied J. Yen's origin patch
 * 2003-03-06 26-j0.5.7 - added Ping Cheng's "suppress" patch
 * 2003-03-22 26-j0.5.8 - added Dean Townsley's ISDV4 patch
 * 2003-04-02 26-j0.5.9 - added J. Yen's "misc fixes" patch
 * 2003-04-06 26-j0.5.10 - refactoring
 * 2003-04-29 26-j0.5.11 - all devices using same data path
 * 2003-05-01 26-j0.5.12 - changed graphire wheel to report relative
 * 2003-05-02 26-j0.5.13 - added parameter configuration code
 * 2003-05-15 26-j0.5.14 - added relative wheel button 4 and 5
 * 2003-05-15 26-j0.5.15 - intuos filter code on by default, fixed APM init
 * 2003-06-19 26-j0.5.16 - added Intuos2 6x8 id 0x47, suppress of 0 disables
 * 2003-06-25 26-j0.5.17 - support TwinView and kernel 2.5 for USB tablet 
 * 2003-07-10 26-j0.5.18 - fix to Intuos filter, ignores first samples
 * 2003-07-16 26-j0.5.19 - added noise reducing filter, improved USB relative mode
 * 2003-07-24 26-j0.5.20 - added new xsetwacom commands (Mode, SpeedLevel, and ClickForce)
 * 2003-08-13 26-j0.5.21 - added speed acceleration xsetwacom commands (Accel)
 * 2003-09-30 26-j0.5.22 - added TwinView with different resolution support and
			 - enabled ScreenNo option for TwinView
 * 2003-11-10 26-j0.5.23 - support kernel 2.4.22 and user specified tcl/tk src dir
 * 2003-11-18 26-j0.5.24 - support general Tablet PC (ISDV4) and xsetwacom mmonitor
 * 2003-12-10 26-j0.5.25 - support kernel 2.6
 * 2003-01-10 26-j0.5.26 - added double click speed and radius
 * 2004-02-02 26-j0.6.0  - new release
 * 2004-03-02 26-j0.6.1  - new release
 * 2004-04-04 26-j0.6.2  - new release
 * 2004-05-25 26-j0.6.3  - new release
 * 2004-10-05 26-j0.6.5  - new release
 * 2004-11-22 42-j0.6.6  - new release
*/

static const char identification[] = "$Identification: 42-j0.6.6 $";

/****************************************************************************/

#include "xf86Wacom.h"
#include "wcmFilter.h"

static int xf86WcmDevOpen(DeviceIntPtr pWcm);
static void xf86WcmDevReadInput(LocalDevicePtr local);
static void xf86WcmDevControlProc(DeviceIntPtr device, PtrCtrl* ctrl);
static void xf86WcmDevClose(LocalDevicePtr local);
static int xf86WcmDevProc(DeviceIntPtr pWcm, int what);
static int xf86WcmSetParam(LocalDevicePtr local, int param, int value);
static int xf86WcmOptionCommandToFile(LocalDevicePtr local);
static int xf86WcmModelToFile(LocalDevicePtr local);
static int xf86WcmDevChangeControl(LocalDevicePtr local, xDeviceCtl* control);
static int xf86WcmDevSwitchMode(ClientPtr client, DeviceIntPtr dev, int mode);
static Bool xf86WcmDevConvert(LocalDevicePtr local, int first, int num,
		int v0, int v1, int v2, int v3, int v4, int v5, int* x, int* y);
static Bool xf86WcmDevReverseConvert(LocalDevicePtr local, int x, int y,
		int* valuators);
static Bool xf86WcmInitDevice(LocalDevicePtr local);

/*****************************************************************************
 * Keyboard symbol data
 ****************************************************************************/

static KeySym wacom_map[] = 
{
	NoSymbol,  /* 0x00 */
	NoSymbol,  /* 0x01 */
	NoSymbol,  /* 0x02 */
	NoSymbol,  /* 0x03 */
	NoSymbol,  /* 0x04 */
	NoSymbol,  /* 0x05 */
	NoSymbol,  /* 0x06 */
	NoSymbol,  /* 0x07 */
	XK_F1,     /* 0x08 */
	XK_F2,     /* 0x09 */
	XK_F3,     /* 0x0a */
	XK_F4,     /* 0x0b */
	XK_F5,     /* 0x0c */
	XK_F6,     /* 0x0d */
	XK_F7,     /* 0x0e */
	XK_F8,     /* 0x0f */
	XK_F9,     /* 0x10 */
	XK_F10,    /* 0x11 */
	XK_F11,    /* 0x12 */
	XK_F12,    /* 0x13 */
	XK_F13,    /* 0x14 */
	XK_F14,    /* 0x15 */
	XK_F15,    /* 0x16 */
	XK_F16,    /* 0x17 */
	XK_F17,    /* 0x18 */
	XK_F18,    /* 0x19 */
	XK_F19,    /* 0x1a */
	XK_F20,    /* 0x1b */
	XK_F21,    /* 0x1c */
	XK_F22,    /* 0x1d */
	XK_F23,    /* 0x1e */
	XK_F24,    /* 0x1f */
	XK_F25,    /* 0x20 */
	XK_F26,    /* 0x21 */
	XK_F27,    /* 0x22 */
	XK_F28,    /* 0x23 */
	XK_F29,    /* 0x24 */
	XK_F30,    /* 0x25 */
	XK_F31,    /* 0x26 */
	XK_F32     /* 0x27 */
};

/* minKeyCode = 8 because this is the min legal key code */

static KeySymsRec wacom_keysyms =
{
	wacom_map, /* map */
	8,         /* min key code */
	0x27,      /* max key code */
	1          /* width */
};

WacomModule gWacomModule =
{
	0,          /* debug level */
	wacom_map,  /* key map */
	identification, /* version */
	{ NULL, },   /* input driver pointer */

	/* device procedures */
	xf86WcmDevOpen,
	xf86WcmDevReadInput,
	xf86WcmDevControlProc,
	xf86WcmDevClose,
	xf86WcmDevProc,
	xf86WcmDevChangeControl,
	xf86WcmDevSwitchMode,
	xf86WcmDevConvert,
	xf86WcmDevReverseConvert,
};

/*****************************************************************************
 * xf86WcmDevOpen --
 *    Open the physical device and init information structs.
 ****************************************************************************/

static int xf86WcmDevOpen(DeviceIntPtr pWcm)
{
	LocalDevicePtr local = (LocalDevicePtr)pWcm->public.devicePrivate;
	WacomDevicePtr priv = (WacomDevicePtr)PRIVATE(pWcm);
	WacomCommonPtr common = priv->common;
	int totalWidth = 0, maxHeight = 0, tabletSize = 0;
	double screenRatio, tabletRatio;
	char m1[32], m2[32];			
 
	/* open file, if not already open */
	if (local->fd < 0)
	{
		if (!xf86WcmInitDevice(local) || (local->fd < 0))
		{
			DBG(1,ErrorF("Failed to initialize device\n"));
			return FALSE;
		}
	}

	/* if factorX is not set, initialize bounding rect */
	if (priv->factorX == 0.0)
	{
		if (priv->twinview != TV_NONE && priv->screen_no == -1) 
		{
			priv->tvoffsetX = 60;
			priv->tvoffsetY = 60;
		}

		if (priv->bottomX == 0) priv->bottomX = common->wcmMaxX;
		if (priv->bottomY == 0) priv->bottomY = common->wcmMaxY;

		/* Verify Box validity */
		if (priv->topX > common->wcmMaxX)
		{
			ErrorF("Wacom invalid TopX (%d) reseting to 0\n",
					priv->topX);
			priv->topX = 0;
		}

		if (priv->topY > common->wcmMaxY)
		{
			ErrorF("Wacom invalid TopY (%d) reseting to 0\n",
					priv->topY);
			priv->topY = 0;
		}

		if (priv->bottomX < priv->topX)
		{
			ErrorF("Wacom invalid BottomX (%d) reseting to %d\n",
					priv->bottomX, common->wcmMaxX);
			priv->bottomX = common->wcmMaxX;
		}

		if (priv->bottomY < priv->topY)
		{
			ErrorF("Wacom invalid BottomY (%d) reseting to %d\n",
					priv->bottomY, common->wcmMaxY);
			priv->bottomY = common->wcmMaxY;
		}

		if (priv->screen_no != -1 &&
			(priv->screen_no >= priv->numScreen ||
			priv->screen_no < 0))
		{
			if (priv->twinview == TV_NONE || priv->screen_no != 1)
			{
				ErrorF("%s: invalid screen number %d, resetting to 0\n",
					local->name, priv->screen_no);
				priv->screen_no = 0;
			}
		}

		/* Calculate the ratio according to KeepShape, TopX and TopY */
		if (priv->screen_no != -1)
		{
			priv->currentScreen = priv->screen_no;
			if (priv->twinview == TV_NONE)
			{
				totalWidth = screenInfo.screens[priv->currentScreen]->width;
				maxHeight = screenInfo.screens[priv->currentScreen]->height;
			}
			else
			{
				totalWidth = priv->tvResolution[2*priv->currentScreen];
				maxHeight = priv->tvResolution[2*priv->currentScreen+1];
			}
		}
		else
		{
			int i;
			for (i = 0; i < priv->numScreen; i++)
			{
				totalWidth += screenInfo.screens[i]->width;
				if (maxHeight < screenInfo.screens[i]->height)
					maxHeight=screenInfo.screens[i]->height;
			}
		}

		/* Maintain aspect ratio */
		if (priv->flags & KEEP_SHAPE_FLAG)
		{
			screenRatio = totalWidth / (double)maxHeight;

			tabletRatio = ((double)(common->wcmMaxX - priv->topX)) /
					(common->wcmMaxY - priv->topY);

			DBG(2, ErrorF("screenRatio = %.3g, tabletRatio = %.3g\n"
						, screenRatio, tabletRatio));

			if (screenRatio > tabletRatio)
			{
				priv->bottomX = common->wcmMaxX;
				priv->bottomY = (common->wcmMaxY - priv->topY) *
					tabletRatio / screenRatio + priv->topY;
			}
			else
			{
				priv->bottomX = (common->wcmMaxX - priv->topX) *
					screenRatio / tabletRatio + priv->topX;
				priv->bottomY = common->wcmMaxY;
			}
		} /* end keep shape */

		if (xf86Verbose)
			ErrorF("%s Wacom device \"%s\" top X=%d top Y=%d "
				"bottom X=%d bottom Y=%d\n",
				XCONFIG_PROBED, local->name, priv->topX,
				priv->topY, priv->bottomX, priv->bottomY);

		if (priv->numScreen == 1)
		{
			priv->factorX = totalWidth
				/ (double)(priv->bottomX - priv->topX - 2*priv->tvoffsetX);
			priv->factorY = maxHeight
				/ (double)(priv->bottomY - priv->topY - 2*priv->tvoffsetY);
			DBG(2, ErrorF("X factor = %.3g, Y factor = %.3g\n",
				priv->factorX, priv->factorY));
		}
	} /* end bounding rect */

	/* x and y axes */
	if (priv->twinview == TV_LEFT_RIGHT)
		tabletSize = 2*(priv->bottomX - priv->topX - 2*priv->tvoffsetX);
	else
		tabletSize = priv->bottomX - priv->topX;

	InitValuatorAxisStruct(pWcm, 0, 0, tabletSize, /* max val */
		mils(common->wcmResolX), /* tablet resolution */
		0, mils(common->wcmResolX)); /* max_res */

	if (priv->twinview == TV_ABOVE_BELOW)
		tabletSize = 2*(priv->bottomY - priv->topY - 2*priv->tvoffsetY);
	else
		tabletSize = priv->bottomY - priv->topY;

	InitValuatorAxisStruct(pWcm, 1, 0, tabletSize, /* max val */
		mils(common->wcmResolY), /* tablet resolution */
		0, mils(common->wcmResolY)); /* max_res */

	/* pressure */
	InitValuatorAxisStruct(pWcm, 2, 0,
		common->wcmMaxZ, /* max val */
		1, 1, 1);

	if (IsCursor(priv))
	{
		/* z-rot and throttle */
		InitValuatorAxisStruct(pWcm, 3, -900, 899, 1, 1, 1);
		InitValuatorAxisStruct(pWcm, 4, -1023, 1023, 1, 1, 1);
	}
	else if (IsPad(priv))
	{
		/* strip-x and strip-y */
		InitValuatorAxisStruct(pWcm, 3, 0, 4097, 1, 1, 1);
		InitValuatorAxisStruct(pWcm, 4, 0, 4097, 1, 1, 1);
	}
	else
	{
		/* tilt-x and tilt-y */
		InitValuatorAxisStruct(pWcm, 3, -64, 63, 1, 1, 1);
		InitValuatorAxisStruct(pWcm, 4, -64, 63, 1, 1, 1);
	}

	sscanf((char*)(common->wcmModel)->name, "%s %s", m1, m2);
	if (strstr(m2, "Intuos3") && IsStylus(priv))
		/* Intuos3 Marker Pen rotation */
		InitValuatorAxisStruct(pWcm, 5, -900, 899, 1, 1, 1);
	else
		/* absolute wheel */
		InitValuatorAxisStruct(pWcm, 5, 0, 1023, 1, 1, 1);
	return TRUE;
}

/*****************************************************************************
 * xf86WcmDevReadInput --
 *   Read the device on IO signal
 ****************************************************************************/

static void xf86WcmDevReadInput(LocalDevicePtr local)
{
	int loop=0;
	#define MAX_READ_LOOPS 10

	WacomCommonPtr common = ((WacomDevicePtr)local->private)->common;

	/* move data until we exhaust the device */
	for (loop=0; loop < MAX_READ_LOOPS; ++loop)
	{
		/* dispatch */
		common->wcmDevCls->Read(local);

		/* verify that there is still data in pipe */
		if (!xf86WcmReady(local->fd)) break;
	}

	/* report how well we're doing */
	if (loop >= MAX_READ_LOOPS)
		DBG(1,ErrorF("xf86WcmDevReadInput: Can't keep up!!!\n"));
	else if (loop > 0)
		DBG(10,ErrorF("xf86WcmDevReadInput: Read (%d)\n",loop));
}

void xf86WcmReadPacket(LocalDevicePtr local)
{
	WacomCommonPtr common = ((WacomDevicePtr)(local->private))->common;
	int len, pos, cnt, remaining;

	if (!common->wcmModel) return;

	remaining = sizeof(common->buffer) - common->bufpos;

	DBG(12, ErrorF("xf86WcmDevReadPacket: device=%s fd=%d "
		"pos=%d remaining=%d\n",
		common->wcmDevice, local->fd,
		common->bufpos, remaining));

	/* fill buffer with as much data as we can handle */
	SYSCALL(len = xf86WcmRead(local->fd,
		common->buffer + common->bufpos, remaining));

	if (len <= 0)
	{
		ErrorF("Error reading wacom device : %s\n", strerror(errno));
		return;
	}

	/* account for new data */
	common->bufpos += len;
	DBG(12, ErrorF("xf86WcmReadPacket buffer has %d bytes\n",
		common->bufpos));

	pos = 0;

	/* while there are whole packets present, parse data */
	while ((common->bufpos - pos) >=  common->wcmPktLength)
	{
		/* parse packet */
		cnt = common->wcmModel->Parse(common, common->buffer + pos);
		if (cnt <= 0)
		{
			DBG(1,ErrorF("Misbehaving parser returned %d\n",cnt));
			break;
		}
		pos += cnt;
	}

	if (pos)
	{
		/* if half a packet remains, move it down */
		if (pos < common->bufpos)
		{
			DBG(7, ErrorF("MOVE %d bytes\n", common->bufpos - pos));
			memmove(common->buffer,common->buffer+pos,
				common->bufpos-pos);
			common->bufpos -= pos;
		}

		/* otherwise, reset the buffer for next time */
		else
		{
			common->bufpos = 0;
		}
	}
}

/*****************************************************************************
 * xf86WcmDevControlProc --
 ****************************************************************************/

static void xf86WcmDevControlProc(DeviceIntPtr device, PtrCtrl* ctrl)
{
	DBG(2, ErrorF("xf86WcmControlProc\n"));
}

/*****************************************************************************
 * xf86WcmDevClose --
 ****************************************************************************/

static void xf86WcmDevClose(LocalDevicePtr local)
{
	WacomDevicePtr priv = (WacomDevicePtr)local->private;
	WacomCommonPtr common = priv->common;
	int loop, num = 0;

	for (loop=0; loop<common->wcmNumDevices; loop++)
	{
		if (common->wcmDevices[loop]->fd >= 0)
			num++;
	}
	DBG(4, ErrorF("Wacom number of open devices = %d\n", num));

	if (num == 1)
	{
		DBG(1,ErrorF("Closing device; uninitializing.\n"));
		SYSCALL(xf86WcmClose(local->fd));
		common->wcmInitialized = FALSE;
	}

	local->fd = -1;
}
 
/*****************************************************************************
 * xf86WcmDevProc --
 *   Handle the initialization, etc. of a wacom
 ****************************************************************************/

static int xf86WcmDevProc(DeviceIntPtr pWcm, int what)
{
	CARD8 map[(32 << 4) + 1];
	int nbaxes;
	int nbbuttons;
	int loop;
	LocalDevicePtr local = (LocalDevicePtr)pWcm->public.devicePrivate;
	WacomDevicePtr priv = (WacomDevicePtr)PRIVATE(pWcm);

	DBG(2, ErrorF("BEGIN xf86WcmProc dev=%p priv=%p "
			"type=%s flags=%d what=%d\n",
			(void *)pWcm, (void *)priv,
			IsStylus(priv) ? "stylus" :
			IsCursor(priv) ? "cursor" :
			IsPad(priv) ? "pad" :
			"eraser", priv->flags, what));

	switch (what)
	{
		case DEVICE_INIT: 
			DBG(1, ErrorF("xf86WcmProc pWcm=%p what=INIT\n", (void *)pWcm));

			nbaxes = 6;  /* X, Y, Pressure, Tilt-X, Tilt-Y, Wheel */

			switch(DEVICE_ID(priv->flags))
			{
				case ERASER_ID:
					nbbuttons = 1;
					break;
				case STYLUS_ID:
					nbbuttons = 4;
					break;
				default:
					nbbuttons = 16;
					break;
			}

			for(loop=1; loop<=nbbuttons; loop++) map[loop] = loop;

			if (InitButtonClassDeviceStruct(pWcm,
					nbbuttons, map) == FALSE)
			{
				ErrorF("unable to allocate Button class device\n");
				return !Success;
			}

			if (InitFocusClassDeviceStruct(pWcm) == FALSE)
			{
				ErrorF("unable to init Focus class device\n");
				return !Success;
			}

			if (InitPtrFeedbackClassDeviceStruct(pWcm,
					xf86WcmDevControlProc) == FALSE)
			{
				ErrorF("unable to init ptr feedback\n");
				return !Success;
			}

			if (InitProximityClassDeviceStruct(pWcm) == FALSE)
			{
				ErrorF("unable to init proximity class device\n");
				return !Success;
			}

			if (InitKeyClassDeviceStruct(pWcm,
					&wacom_keysyms, NULL) == FALSE)
			{
				ErrorF("unable to init key class device\n"); 
				return !Success;
			}

			if (InitValuatorClassDeviceStruct(pWcm, 
					nbaxes, xf86GetMotionEvents, 
					local->history_size,
					((priv->flags & ABSOLUTE_FLAG) ?
						Absolute : Relative) |
						OutOfProximity) == FALSE)
			{
				ErrorF("unable to allocate Valuator class device\n"); 
				return !Success;
			}
			else
			{
				/* allocate motion history buffer if needed */
				xf86MotionHistoryAllocate(local);
			}

			/* open the device to gather informations */
			if (!xf86WcmDevOpen(pWcm))
			{
				/* Sometimes PL does not open the first time */
				DBG(1, ErrorF("xf86WcmProc try to open "
						"pWcm=%p again\n", (void *)pWcm));
				if (!xf86WcmDevOpen(pWcm))
				{
					DBG(1, ErrorF("xf86WcmProc "
						"pWcm=%p what=INIT FALSE\n",
						(void *)pWcm));
					return !Success;
				}
			}
			break; 

		case DEVICE_ON:
			DBG(1, ErrorF("xf86WcmProc fd=%d pWcm=%p what=ON\n",
				local->fd, (void *)pWcm));

			if ((local->fd < 0) && (!xf86WcmDevOpen(pWcm)))
			{
				pWcm->inited = FALSE;
				return !Success;
			}
			xf86AddEnabledDevice(local);
			pWcm->public.on = TRUE;
			break;

		case DEVICE_OFF:
		case DEVICE_CLOSE:
			DBG(1, ErrorF("xf86WcmProc pWcm=%p what=%s\n", (void *)pWcm,
				(what == DEVICE_CLOSE) ?  "CLOSE" : "OFF"));

			if (local->fd >= 0)
			{
				xf86RemoveEnabledDevice(local);
				xf86WcmDevClose(local);
			}
			pWcm->public.on = FALSE;
			break;

		default:
			ErrorF("wacom unsupported mode=%d\n", what);
			return !Success;
	} /* end switch */

	DBG(2, ErrorF("END xf86WcmProc Success what=%d dev=%p priv=%p\n",
			what, (void *)pWcm, (void *)priv));

	return Success;
}

/*****************************************************************************
 * xf86WcmSetParam
 ****************************************************************************/

static int xf86WcmSetParam(LocalDevicePtr local, int param, int value)
{
	WacomDevicePtr priv = (WacomDevicePtr)local->private;
	char st[32];

	switch (param) 
	{
	    case XWACOM_PARAM_TOPX:
		xf86ReplaceIntOption(local->options, "TopX", value);
		priv->topX = xf86SetIntOption(local->options, "TopX", 0);
		break;
	    case XWACOM_PARAM_TOPY:
		xf86ReplaceIntOption(local->options, "TopY", value);
		priv->topY = xf86SetIntOption(local->options, "TopY", 0);
		break;
	    case XWACOM_PARAM_BOTTOMX:
		xf86ReplaceIntOption(local->options, "BottomX", value);
		priv->bottomX = xf86SetIntOption(local->options, "BottomX", 0);
		break;
	    case XWACOM_PARAM_BOTTOMY:
		xf86ReplaceIntOption(local->options, "BottomY", value);
		priv->bottomY = xf86SetIntOption(local->options, "BottomY", 0);
		break;
	    case XWACOM_PARAM_BUTTON1:
		if ((value < 0) || (value > 19)) return BadValue;
		xf86ReplaceIntOption(local->options,"Button1",value);
		priv->button[0] = xf86SetIntOption(local->options,"Button1",1);
		break;
	    case XWACOM_PARAM_BUTTON2:
		if ((value < 0) || (value > 19)) return BadValue;
		xf86ReplaceIntOption(local->options, "Button2", value);
		priv->button[1] = xf86SetIntOption(local->options,"Button2",2);
		break;
	    case XWACOM_PARAM_BUTTON3:
		if ((value < 0) || (value > 19)) return BadValue;
		xf86ReplaceIntOption(local->options, "Button3", value);
		priv->button[2] = xf86SetIntOption(local->options,"Button3",3);
		break;
	    case XWACOM_PARAM_BUTTON4:
		if ((value < 0) || (value > 19)) return BadValue;
		xf86ReplaceIntOption(local->options, "Button4", value);
		priv->button[3] = xf86SetIntOption(local->options,"Button4",4);
		break;
	    case XWACOM_PARAM_BUTTON5:
		if ((value < 0) || (value > 19)) return BadValue;
		xf86ReplaceIntOption(local->options, "Button5", value);
		priv->button[4] = xf86SetIntOption(local->options,"Button5",5);
		break;
	    case XWACOM_PARAM_DEBUGLEVEL:
		if ((value < 1) || (value > 100)) return BadValue;
		xf86ReplaceIntOption(local->options, "DebugLevel", value);
		gWacomModule.debugLevel = value;
		break;
	    case XWACOM_PARAM_RAWFILTER:
		if ((value < 0) || (value > 1)) return BadValue;
		if (value) 
		{
			priv->common->wcmFlags |= RAW_FILTERING_FLAG;
			xf86ReplaceStrOption(local->options, "RawFilter", "On");
		}
		else 
		{
			priv->common->wcmFlags &= ~(RAW_FILTERING_FLAG);
			xf86ReplaceStrOption(local->options, "RawFilter", "Off");
		}
		break;
	    case XWACOM_PARAM_PRESSCURVE:
	    {
		if ( !IsCursor(priv) ) 
		{
			char chBuf[64];
			int x0 = (value >> 24) & 0xFF;
			int y0 = (value >> 16) & 0xFF;
			int x1 = (value >> 8) & 0xFF;
			int y1 = value & 0xFF;
			if ((x0 > 100) || (y0 > 100) || (x1 > 100) || (y1 > 100))
			    return BadValue;
			snprintf(chBuf,sizeof(chBuf),"%d,%d,%d,%d",x0,y0,x1,y1);
			xf86ReplaceStrOption(local->options, "PressCurve",chBuf);
			xf86WcmSetPressureCurve(priv,x0,y0,x1,y1);
		}
		break;
	    }
	    case XWACOM_PARAM_MODE:
		if ((value < 0) || (value > 1)) return BadValue;
		if (value) 
		{
			priv->flags |= ABSOLUTE_FLAG;
			xf86ReplaceStrOption(local->options, "Mode", "Absolute");
		}
		else 
		{
			priv->flags &= ~ABSOLUTE_FLAG;
			xf86ReplaceStrOption(local->options, "Mode", "Relative");
		}
		break;
	    case XWACOM_PARAM_SPEEDLEVEL:
		if ((value < 1) || (value > 11)) return BadValue;
		if (value > 6) priv->speed = 2.00*((double)value - 6.00);
		else priv->speed = ((double)value) / 6.00;
		sprintf(st, "%.3f", priv->speed);
		xf86AddNewOption(local->options, "Speed", st);
		break;
	    case XWACOM_PARAM_ACCEL:
		if ((value < 1) || (value > MAX_ACCEL)) return BadValue;
		priv->accel = value-1;
		xf86ReplaceIntOption(local->options, "Accel", priv->accel);
		break;
	    case XWACOM_PARAM_CLICKFORCE:
		if ((value < 1) || (value > 21)) return BadValue;
		priv->common->wcmThreshold = (int)((double)
				(value*priv->common->wcmMaxZ)/100.00+0.5);
		xf86ReplaceIntOption(local->options, "Threshold", 
				priv->common->wcmThreshold);
		break;
	    case XWACOM_PARAM_XYDEFAULT:
		xf86ReplaceIntOption(local->options, "TopX", 0);
		priv->topX = xf86SetIntOption(local->options, "TopX", 0);
		xf86ReplaceIntOption(local->options, "TopY", 0);
		priv->topY = xf86SetIntOption(local->options, "TopY", 0);
		xf86ReplaceIntOption(local->options,
				"BottomX", priv->common->wcmMaxX);
		priv->bottomX = xf86SetIntOption(local->options,
				"BottomX", priv->common->wcmMaxX);
		xf86ReplaceIntOption(local->options,
				"BottomY", priv->common->wcmMaxY);
		priv->bottomY = xf86SetIntOption(local->options,
				"BottomY", priv->common->wcmMaxY);
		break;
	    case XWACOM_PARAM_GIMP:
		if ((value != 0) && (value != 1)) return BadValue;
		priv->common->wcmGimp = 0;
		if (value) priv->common->wcmGimp = value;
		break;
	    case XWACOM_PARAM_MMT:
		if ((value != 0) && (value != 1)) return BadValue;
		priv->common->wcmMMonitor = value;
		break;
	    case XWACOM_PARAM_TPCBUTTON:
		if ((value != 0) && (value != 1)) return BadValue;
		priv->common->wcmTPCButton = value;
		if (value)
		{
			xf86ReplaceStrOption(local->options, "TPCButton", "on");
		}
		else
		{
			xf86ReplaceStrOption(local->options, "TPCButton", "off");
		}
		break;
	    default:
    		DBG(10, ErrorF("xf86WcmSetParam invalid param %d\n",param));
		return BadMatch;
	}
	return Success;
}

/*****************************************************************************
 * xf86WcmOptionCommandToFile
 ****************************************************************************/

static int xf86WcmOptionCommandToFile(LocalDevicePtr local)
{
	WacomDevicePtr priv = (WacomDevicePtr)local->private;
	char 		fileName[80] = "/etc/X11/wcm.";
	char		command[256];
	FILE		*fp = 0;
	int		value, p1,p2,p3,p4;
	double		speed;
	char		*s;

    	DBG(10, ErrorF("xf86WcmOptionCommandToFile for %s\n", local->name));
	strcat(fileName, local->name);
	fp = fopen(fileName, "w+");
	if ( fp )
	{
		/* write user defined options as xsetwacom commands into fp */
        	s = xf86FindOptionValue(local->options, "TopX");
		if ( s && priv->topX )
			fprintf(fp, "xsetwacom set %s TopX %s\n", local->name, s);

        	s = xf86FindOptionValue(local->options, "TopY");
		if ( s && priv->topY )
			fprintf(fp, "xsetwacom set %s TopY %s\n", local->name, s);

        	s = xf86FindOptionValue(local->options, "BottomX");
		if ( s && priv->bottomX != priv->common->wcmMaxX )
			fprintf(fp, "xsetwacom set %s BottomX %s\n", local->name, s);

        	s = xf86FindOptionValue(local->options, "BottomY");
		if ( s && priv->bottomY != priv->common->wcmMaxY )
			fprintf(fp, "xsetwacom set %s BottomY %s\n", local->name, s);

        	s = xf86FindOptionValue(local->options, "Button1");
		if ( s && priv->button[0] != 1 )
			fprintf(fp, "xsetwacom set %s Button1 %s\n", local->name, s);

        	s = xf86FindOptionValue(local->options, "Button2");
		if ( s && priv->button[1] != 2 )
			fprintf(fp, "xsetwacom set %s Button2 %s\n", local->name, s);

        	s = xf86FindOptionValue(local->options, "Button3");
		if ( s && priv->button[2] != 3 )
			fprintf(fp, "xsetwacom set %s Button3 %s\n", local->name, s);

        	s = xf86FindOptionValue(local->options, "Button4");
		if ( s && priv->button[3] != 4 )
			fprintf(fp, "xsetwacom set %s Button4 %s\n", local->name, s);

        	s = xf86FindOptionValue(local->options, "Button5");
		if ( s && priv->button[4] != 5 )
			fprintf(fp, "xsetwacom set %s Button5 %s\n", local->name, s);

        	s = xf86FindOptionValue(local->options, "PressCurve");
		if ( s && !IsCursor(priv) )
		{
			sscanf(s, "%d,%d,%d,%d", &p1, &p2, &p3, &p4);
			if ( p1 || p2 || p3 != 100 || p4 != 100 )
				fprintf(fp, "xsetwacom set %s PressCurve %d %d %d %d\n", 
					local->name, p1, p2, p3, p4);
		}

        	s = xf86FindOptionValue(local->options, "Mode");
		if ( s && (((priv->flags & ABSOLUTE_FLAG) && IsCursor(priv))
			 || (!(priv->flags & ABSOLUTE_FLAG) && !IsCursor(priv))))
			fprintf(fp, "xsetwacom set %s Mode %s\n", local->name, s);

        	s = xf86FindOptionValue(local->options, "RawFilter");
		if ( s )
			fprintf(fp, "xsetwacom set %s RawFilter %s\n", local->name, s);

        	s = xf86FindOptionValue(local->options, "Accel");
		if ( s && priv->accel )
			fprintf(fp, "xsetwacom set %s Accel %d\n", local->name, priv->accel+1);

        	s = xf86FindOptionValue(local->options, "Speed");
		if ( s && priv->speed != DEFAULT_SPEED )
		{
			speed = strtod(s, NULL);
			if(speed >= 10.00) value = 11;
			else if(speed >= 1.00) value = (int)(speed/2.00 + 6.00);
			else if(speed < (double)(1.00/6.00)) value = 1;
			else value = (int)(speed*6.00 + 0.50);
			if ( value != 6 )
				fprintf(fp, "xsetwacom set %s SpeedLevel %d\n", local->name, value);
		}

		s = xf86FindOptionValue(local->options, "Threshold");
		if ( s )
		{
			value = atoi(s);
			value = (int)((double)value*100.00/(double)priv->common->wcmMaxZ+0.5);
			if ( value != 6 )
				fprintf(fp, "xsetwacom set %s ClickForce %d\n", local->name, value);
		}

		s = xf86FindOptionValue(local->options, "ForceDevice");
		if ( s )
		{
			if ( !priv->common->wcmTPCButton )
				fprintf(fp, "xsetwacom set %s TPCButton off\n", local->name);
			fprintf(fp, "default TPCButton on\n");
		}
		else
		{
			if ( priv->common->wcmTPCButton )
				fprintf(fp, "xsetwacom set %s TPCButton on\n", local->name);
			fprintf(fp, "default TPCButton off\n");
		}
		fprintf(fp, "default TopX 0\n");
		fprintf(fp, "default TopY 0\n");
		fprintf(fp, "default BottomX %d\n", priv->common->wcmMaxX);
		fprintf(fp, "default BottomY %d\n", priv->common->wcmMaxY);
		if (IsCursor(priv) || IsPad(priv))
			sprintf(command, "default Mode Relative\n");
		else
			sprintf(command, "default Mode Absolute\n");
		fprintf(fp, "%s", command);		
		fprintf(fp, "default SpeedLevel 6\n");
		fprintf(fp, "default ClickForce 6\n");
		fprintf(fp, "default Accel 0\n");
		fclose(fp);
	}
	return(Success);
}


/*****************************************************************************
 * xf86WcmModelToFile
 ****************************************************************************/

static int xf86WcmModelToFile(LocalDevicePtr local)
{
	FILE		*fp = 0;
	LocalDevicePtr	localDevices = xf86FirstLocalDevice();
	WacomDevicePtr	priv = NULL, lprv;
	char		m1[32], m2[32], *m3;			
	int 		i = 0, x = 0, y = 0;

    	DBG(10, ErrorF("xf86WcmModelToFile \n"));
	fp = fopen("/etc/wacom.dat", "w+");
	if ( fp )
	{
		while(localDevices) 
		{
			m3 = xf86FindOptionValue(localDevices->options, "Type");
			if (m3 && (strstr(m3, "eraser") || strstr(m3, "stylus") 
					|| strstr(m3, "cursor")))
				lprv = (WacomDevicePtr)localDevices->private;
			else
				lprv = NULL;
			if ( lprv && lprv->common && lprv->common->wcmModel ) 
			{
				sscanf((char*)(lprv->common->wcmModel)->name, "%s %s", m1, m2);
				if ( lprv->common->wcmEraserID )
					fprintf(fp, "%s %s %s %s\n", localDevices->name, m2, m3, lprv->common->wcmEraserID);
				else
					fprintf(fp, "%s %s %s %s\n", localDevices->name, m2, m3, localDevices->name);
				if (lprv->twinview != TV_NONE)
				{
					priv = lprv;
				}
				if( !priv ) priv = lprv;
			}
			localDevices = localDevices->next;
		}
		/* write TwinView ScreenInfo */
		if (priv->twinview == TV_ABOVE_BELOW)
		{
			fprintf(fp, "Screen0 %d %d %d %d\n", priv->tvResolution[0], 
				priv->tvResolution[1], 0, 0);
			fprintf(fp, "Screen1 %d %d %d %d\n", priv->tvResolution[2], 
				priv->tvResolution[3], 0, priv->tvResolution[1]);
		}
		else if (priv->twinview == TV_LEFT_RIGHT)
		{
			fprintf(fp, "Screen0 %d %d %d %d\n", priv->tvResolution[0], 
				priv->tvResolution[1], 0, 0);
			fprintf(fp, "Screen1 %d %d %d %d\n", priv->tvResolution[2], 
				priv->tvResolution[3], priv->tvResolution[0], 0);
		}
		/* write other screen setup info */
		else
		{
			for (i = 0; i<screenInfo.numScreens; i++)
			{
				fprintf(fp, "Screen%d %d %d %d %d\n", 
				i, screenInfo.screens[i]->width,
				screenInfo.screens[i]->height, x, y);
				x += screenInfo.screens[i]->width;
			}
		}
		fclose(fp);
	} 
	return(Success);
}

/*****************************************************************************
* xf86WcmDevSwitchMode --
*****************************************************************************/

static int xf86WcmDevSwitchMode(ClientPtr client, DeviceIntPtr dev, int mode)
{
	LocalDevicePtr local = (LocalDevicePtr)dev->public.devicePrivate;
	WacomDevicePtr priv = (WacomDevicePtr)local->private;

	DBG(3, ErrorF("xf86WcmSwitchMode dev=%p mode=%d\n", (void *)dev, mode));

	if (mode == Absolute)
		priv->flags |= ABSOLUTE_FLAG;
	else if (mode == Relative)
		priv->flags &= ~ABSOLUTE_FLAG; 
	else
	{
		DBG(10, ErrorF("xf86WcmSwitchMode dev=%p invalid mode=%d\n",
				(void *)dev, mode));
		return BadMatch;
	}

	return Success;
}

/*****************************************************************************
 * xf86WcmDevChangeControl --
 ****************************************************************************/

static int xf86WcmDevChangeControl(LocalDevicePtr local, xDeviceCtl* control)
{
	xDeviceResolutionCtl* res = (xDeviceResolutionCtl *)control;
	int* r = (int*)(res+1);
	int param = r[0], value = r[1];

	DBG(10, ErrorF("xf86WcmChangeControl firstValuator=%d\n",
		res->first_valuator));
	
	if (control->control != DEVICE_RESOLUTION || !res->num_valuators)
		return BadMatch;

	r[0] = 1, r[1] = 1;
	switch (res->first_valuator)
	{
		case 0:  /* a new write to wcm.$name */
		{
			return xf86WcmOptionCommandToFile(local);
		}
		case 1: /* a new write to wacom.dat */
		{
			return xf86WcmModelToFile(local);
		}
		case 4: /* JEJ - test */
		{
			DBG(10,ErrorF("xf86WcmChangeControl: %x,%x\n",
				param,value));
			return xf86WcmSetParam(local,param,value);
		}
		default:
			DBG(10,ErrorF("xf86WcmChangeControl invalid "
				"firstValuator=%d\n",res->first_valuator));
			return BadMatch;
	}
}

/*****************************************************************************
 * xf86WcmDevConvert --
 *   Convert valuators to X and Y. Only used by core events.
 ****************************************************************************/

static Bool xf86WcmDevConvert(LocalDevicePtr local, int first, int num,
		int v0, int v1, int v2, int v3, int v4, int v5, int* x, int* y)
{
	WacomDevicePtr priv = (WacomDevicePtr) local->private;
    
	DBG(6, ErrorF("xf86WcmDevConvert v0=%d v1=%d \n", v0, v1));

	if (first != 0 || num == 1)
		return FALSE;
#ifdef PANORAMIX
	if (!noPanoramiXExtension && (priv->flags & ABSOLUTE_FLAG) && 
		priv->common->wcmGimp && priv->common->wcmMMonitor)
	{
		int i, totalWidth, leftPadding = 0;
		for (i = 0; i < priv->currentScreen; i++)
			leftPadding += screenInfo.screens[i]->width;
		for (totalWidth = leftPadding; i < priv->numScreen; i++)
			totalWidth += screenInfo.screens[i]->width;
		v0 -= (priv->bottomX - priv->topX) * leftPadding
			/ (double)totalWidth + 0.5;
	}
#endif
	if (priv->twinview != TV_NONE && (priv->flags & ABSOLUTE_FLAG))
	{
		v0 -= priv->topX - priv->tvoffsetX;
		v1 -= priv->topY - priv->tvoffsetY;
                if (priv->twinview == TV_LEFT_RIGHT)
		{
			if (v0 > priv->bottomX - priv->tvoffsetX)
			{
				if (priv->currentScreen == 0)
					v0 = priv->bottomX - priv->tvoffsetX;
				else
				{
					v0 -= priv->bottomX - priv->topX - 2*priv->tvoffsetX;
					if (v0 > priv->bottomX - priv->tvoffsetX)
						v0 = 2*(priv->bottomX - priv->tvoffsetX) - v0;
				}
			}
			if (priv->currentScreen == 1)
			{
                       		*x = priv->tvResolution[0] + priv->tvResolution[2]
					* v0 / (priv->bottomX - priv->topX - 2*priv->tvoffsetX);
				*y = v1 * priv->tvResolution[3] /
					(priv->bottomY - priv->topY - 2*priv->tvoffsetY) + 0.5;
			}
			else
			{
				*x = priv->tvResolution[0] * v0 
					 / (priv->bottomX - priv->topX - 2*priv->tvoffsetX);
				*y = v1 * priv->tvResolution[1] /
					(priv->bottomY - priv->topY - 2*priv->tvoffsetY) + 0.5;
			}
		}
                if (priv->twinview == TV_ABOVE_BELOW)
		{
			if (v1 > priv->bottomY - priv->tvoffsetY)
			{
				if (priv->currentScreen == 0)
					v1 = priv->bottomY - priv->tvoffsetY;
				else
				{
					v1 -= priv->bottomY - priv->topY - 2*priv->tvoffsetY;
					if (v1 > priv->bottomY - priv->tvoffsetY)
						v1 = 2*(priv->bottomY - priv->tvoffsetY) - v1;
				}
			}
			if (priv->currentScreen == 1)
			{
 				*x = v0 * priv->tvResolution[2] /
					(priv->bottomX - priv->topX - 2*priv->tvoffsetX) + 0.5;
				*y = priv->tvResolution[1] + 
					priv->tvResolution[3] * v1 / 
					(priv->bottomY - priv->topY - 2*priv->tvoffsetY);
			}
			else
			{
				*x = v0 * priv->tvResolution[0] /
					(priv->bottomX - priv->topX - 2*priv->tvoffsetX) + 0.5;
				*y = priv->tvResolution[1] * v1 /
					 (priv->bottomY - priv->topY - 2*priv->tvoffsetY);
			}
		}
	}
	else
	{
		*x = v0 * priv->factorX + 0.5;
		*y = v1 * priv->factorY + 0.5;
	}

	DBG(6, ErrorF("Wacom converted v0=%d v1=%d to x=%d y=%d\n",
		v0, v1, *x, *y));
	return TRUE;
}

/*****************************************************************************
 * xf86WcmDevReverseConvert --
 *  Convert X and Y to valuators. Only used by core events.
 ****************************************************************************/

static Bool xf86WcmDevReverseConvert(LocalDevicePtr local, int x, int y,
		int* valuators)
{
	WacomDevicePtr priv = (WacomDevicePtr) local->private;
    
	DBG(6, ErrorF("xf86WcmDevReverseConvert x=%d y=%d \n", x, y));
	valuators[0] = x / priv->factorX + 0.5;
	valuators[1] = y / priv->factorY + 0.5;
#ifdef PANORAMIX
	if (!noPanoramiXExtension && (priv->flags & ABSOLUTE_FLAG) && 
		priv->common->wcmGimp && priv->common->wcmMMonitor)
	{
		int i, totalWidth, leftPadding = 0;
		for (i = 0; i < priv->currentScreen; i++)
			leftPadding += screenInfo.screens[i]->width;
		for (totalWidth = leftPadding; i < priv->numScreen; i++)
			totalWidth += screenInfo.screens[i]->width;
		valuators[0] += (priv->bottomX - priv->topX)
			* leftPadding / (double)totalWidth + 0.5;
	}
#endif
	if (priv->twinview != TV_NONE && (priv->flags & ABSOLUTE_FLAG))
	{
                if (priv->twinview == TV_LEFT_RIGHT)
		{
			if (x > priv->tvResolution[0])
				x -= priv->tvResolution[0];
			if (priv->currentScreen == 1)
			{
				valuators[0] = x * (priv->bottomX - priv->topX - 2*priv->tvoffsetX)
				 	/ priv->tvResolution[2] + (priv->bottomX - priv->topX - 
					2*priv->tvoffsetX) +0.5;
				valuators[1] = y * (priv->bottomY - priv->topY - 2*priv->tvoffsetY) /
					priv->tvResolution[3] + 0.5;
			}
			else
			{
				valuators[0] = x * (priv->bottomX - priv->topX - 2*priv->tvoffsetX)
					 / priv->tvResolution[0] + 0.5;
				valuators[1] = y * (priv->bottomY - priv->topY - 2*priv->tvoffsetY) /
					priv->tvResolution[1] + 0.5;
			}
		}
                if (priv->twinview == TV_ABOVE_BELOW)
		{
			if (y > priv->tvResolution[1])
				y -= priv->tvResolution[1];
			if (priv->currentScreen == 1)
			{
				valuators[0] = x * (priv->bottomX - priv->topX - 2*priv->tvoffsetX) /
					priv->tvResolution[2] + 0.5;
				valuators[1] = y *(priv->bottomY - priv->topY - 2*priv->tvoffsetY)
					/ priv->tvResolution[3] +
					priv->bottomY - priv->topY - 2*priv->tvoffsetY + 0.5;
			}
			else
			{
				valuators[0] = x * (priv->bottomX - priv->topX - 2*priv->tvoffsetX) /
					priv->tvResolution[0] + 0.5;
				valuators[1] = y *(priv->bottomY - priv->topY - 2*priv->tvoffsetY)
					/ priv->tvResolution[1] + 0.5;
			}
		}
		valuators[0] += priv->topX + priv->tvoffsetX;
		valuators[1] += priv->topY + priv->tvoffsetY;
	}
	DBG(6, ErrorF("Wacom converted x=%d y=%d to v0=%d v1=%d\n", x, y,
		valuators[0], valuators[1]));

	return TRUE;
}

/*****************************************************************************
 * xf86WcmInitDevice --
 *   Open and initialize the tablet
 ****************************************************************************/

static Bool xf86WcmInitDevice(LocalDevicePtr local)
{
	WacomCommonPtr common = ((WacomDevicePtr)local->private)->common;
	int loop;

	DBG(1,ErrorF("xf86WcmInitDevice: "));
	if (common->wcmInitialized)
	{
		DBG(1,ErrorF("already initialized\n"));
		return TRUE;
	}

	/* attempt to open the device */
	if ((xf86WcmOpen(local) != Success) || (local->fd < 0))
	{
		DBG(1,ErrorF("Failed to open device (fd=%d)\n",local->fd));
		if (local->fd >= 0)
		{
			DBG(1,ErrorF("Closing device\n"));
			SYSCALL(xf86WcmClose(local->fd));
		}
		local->fd = -1;
		return FALSE;
	}

	/* on success, mark all other local devices as open and initialized */
	common->wcmInitialized = TRUE;

	DBG(1,ErrorF("Marking all devices open\n"));
	/* report the file descriptor to all devices */
	for (loop=0; loop<common->wcmNumDevices; loop++)
		common->wcmDevices[loop]->fd = local->fd;

	return TRUE;
}
