/* $XConsortium: xf86Wacom.c /main/20 1996/10/27 11:05:20 kaleb $ */
/*
 * Copyright 1995-2001 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.32 2003/01/15 03:48:36 dawes Exp $ */

/*
 * This driver is only able to handle the Wacom IV and Wacom V 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>.
 *
 */

static const char identification[] = "$Identification: 23 $";

#include "xf86Version.h"

#if XF86_VERSION_CURRENT >= XF86_VERSION_NUMERIC(3,9,0,0,0)
#define XFREE86_V4 1
#endif

#ifdef LINUX_INPUT
#include <asm/types.h>
#include <linux/input.h>

/* max number of input events to read in one read call */
#define MAX_EVENTS 50

/* 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
#endif

/* post 3.9 headers */

#ifndef XFree86LOADER
#include <unistd.h>
#include <errno.h>
#endif

#include "misc.h"
#include "xf86.h"
#define NEED_XF86_TYPES
#if !defined(DGUX)
#include "xf86_ansic.h"
#include "xisb.h"
#endif
#include "xf86_OSproc.h"
#include "xf86Xinput.h"
#include "exevents.h"		/* Needed for InitValuator/Proximity stuff */
#include "keysym.h"
#include "mipointer.h"

#ifdef XFree86LOADER
#include "xf86Module.h"
#endif

#define wait_for_fd(fd) xf86WaitForInput((fd), 1000000)
#define tcflush(fd, n) xf86FlushInput((fd))
#undef read
#define read(a,b,c) xf86ReadSerial((a),(b),(c))
#undef write
#define write(a,b,c) xf86WriteSerial((a),(char*)(b),(c))
#undef close
#define close(a) xf86CloseSerial((a))
#define XCONFIG_PROBED "(==)"
#define XCONFIG_GIVEN "(**)"
#define xf86Verbose 1
#undef PRIVATE
#define PRIVATE(x) XI_PRIVATE(x)

/* 
 * Be sure to set vmin appropriately for your device's protocol. You want to
 * read a full packet before returning
 */
static const char *default_options[] =
{
	"BaudRate", "9600",
	"StopBits", "1",
	"DataBits", "8",
	"Parity", "None",
	"Vmin", "1",
	"Vtime", "10",
	"FlowControl", "Xoff",
	NULL
};

static InputDriverPtr wcmDrv;


#if defined(__QNX__) || defined(__QNXNTO__)
#define POSIX_TTY
#endif

/******************************************************************************
 * debugging macro
 *****************************************************************************/
#ifdef DBG
#undef DBG
#endif
#ifdef DEBUG
#undef DEBUG
#endif

static int      debug_level = 0;
#define DEBUG 1
#if DEBUG
#define DBG(lvl, f) {if ((lvl) <= debug_level) f;}
#else
#define DBG(lvl, f)
#endif

#define ABS(x) ((x) > 0 ? (x) : -(x))

/******************************************************************************
 * WacomDeviceRec flags
 *****************************************************************************/
#define DEVICE_ID(flags) ((flags) & 0x07)

#define STYLUS_ID		1
#define CURSOR_ID		2
#define ERASER_ID		4
#define ABSOLUTE_FLAG		8
#define FIRST_TOUCH_FLAG	16
#define	KEEP_SHAPE_FLAG		32
#define BAUD_19200_FLAG		64

/******************************************************************************
 * WacomCommonRec flags
 *****************************************************************************/
#define TILT_FLAG	1
#define GRAPHIRE_FLAG	2

typedef struct
{
    int		state;
    int		coord[3];
    int		tilt[3];
} WacomFilterState;

typedef struct
{
    int			device_id;
    int			device_type;
    unsigned int	serial_num;
    int			x;
    int			y;
    int			buttons;
    int			pressure;
    int			tiltx;
    int			tilty;
    int			rotation;
    int			wheel;
    int			discard_first;
    int			proximity;
    WacomFilterState	x_filter;
    WacomFilterState	y_filter;
} WacomDeviceState;

#define PEN(ds)         (((ds->device_id) & 0x07ff) == 0x0022)
#define STROKING_PEN(ds) (((ds->device_id) & 0x07ff) == 0x0032)
#define AIRBRUSH(ds)    (((ds->device_id) & 0x07ff) == 0x0112)
#define MOUSE_4D(ds)    (((ds->device_id) & 0x07ff) == 0x0094)
#define LENS_CURSOR(ds) (((ds->device_id) & 0x07ff) == 0x0096)
#define INKING_PEN(ds)  (((ds->device_id) & 0x07ff) == 0x0012)

typedef struct
{
    /* configuration fields */
    unsigned char	flags;		/* various flags (device type, absolute, first touch...) */
    int			topX;		/* X top */
    int			topY;		/* Y top */
    int			bottomX;	/* X bottom */
    int			bottomY;	/* Y bottom */
    double		factorX;	/* X factor */
    double		factorY;	/* Y factor */
    unsigned int	serial;	        /* device serial number */
    int			initNumber;     /* magic number for the init phasis */
    int			screen_no;	/* associated screen */
    
    struct _WacomCommonRec *common;	/* common info pointer */
    
    /* state fields */
    int			oldX;		/* previous X position */
    int			oldY;		/* previous Y position */
    int			oldZ;		/* previous pressure */
    int			oldTiltX;	/* previous tilt in x direction */
    int			oldTiltY;	/* previous tilt in y direction */    
    int			oldWheel;	/* previous wheel value */    
    int			oldButtons;	/* previous buttons state */
    int			oldProximity;	/* previous proximity */
} WacomDeviceRec, *WacomDevicePtr;

typedef struct _WacomCommonRec 
{
    char		*wcmDevice;	/* device file name */
    int			wcmSuppress;	/* transmit position if increment is superior */
    unsigned char	wcmFlags;	/* various flags (handle tilt) */
    int			wcmMaxX;	/* max X value */
    int			wcmMaxY;	/* max Y value */
    int			wcmMaxZ;	/* max Z value */
    int			wcmResolX;	/* X resolution in points/inch */
    int			wcmResolY;	/* Y resolution in points/inch */
    int			wcmResolZ;	/* Z resolution in points/inch */
    LocalDevicePtr	*wcmDevices;	/* array of all devices sharing the same port */
    int			wcmNumDevices;	/* number of devices */
    int			wcmIndex;	/* number of bytes read */
    int			wcmPktLength;	/* length of a packet */
    unsigned char	wcmData[9];	/* data read on the device */
    Bool		wcmHasEraser;	/* True if an eraser has been configured */
    Bool		wcmStylusSide;	/* eraser or stylus ? */
    Bool		wcmStylusProximity; /* the stylus is in proximity ? */
    int			wcmProtocolLevel; /* 4 for Wacom IV, 5 for Wacom V */
    int			wcmThreshold;	/* Threshold for counting pressure as a button */
    WacomDeviceState	wcmDevStat[2];	/* device state for each tool */
    int			wcmInitNumber;  /* magic number for the init phasis */
    unsigned int	wcmLinkSpeed;   /* serial link speed */
    Bool		(*wcmOpen)(LocalDevicePtr /*local*/); /* function used to open the line (serial or USB) */
    unsigned int	wcmLastSerial;	/* last device (used by the USB part) */
} WacomCommonRec, *WacomCommonPtr;

/******************************************************************************
 * configuration stuff
 *****************************************************************************/
#define CURSOR_SECTION_NAME "wacomcursor"
#define STYLUS_SECTION_NAME "wacomstylus"
#define ERASER_SECTION_NAME "wacomeraser"


/******************************************************************************
 * constant and macros declarations
 *****************************************************************************/
#define DEFAULT_MAXZ 240	/* default value of MaxZ when nothing is configured */
#define BUFFER_SIZE 256		/* size of reception buffer */
#define XI_STYLUS "STYLUS"	/* X device name for the stylus */
#define XI_CURSOR "CURSOR"	/* X device name for the cursor */
#define XI_ERASER "ERASER"	/* X device name for the eraser */
#define MAX_VALUE 100           /* number of positions */
#define MAXTRY 3                /* max number of try to receive magic number */
#define MAX_COORD_RES 1270.0	/* Resolution of the returned MaxX and MaxY */
#define INVALID_THRESHOLD 30000 /* Invalid threshold value used to test if the user
				 * configured it or not */

#define SYSCALL(call) while(((call) == -1) && (errno == EINTR))

#define WC_RESET	"\r#" /* reset to wacom IV command set or wacom V reset */
#define WC_RESET_BAUD	"\r$" /* reset baud rate to default (wacom V) or switch to wacom IIs (wacom IV) */
#define WC_CONFIG	"~R\r"	/* request a configuration string */
#define WC_COORD	"~C\r"	/* request max coordinates */
#define WC_MODEL	"~#\r"	/* request model and ROM version */

#define WC_MULTI	"MU1\r"	/* multi mode input */
#define WC_UPPER_ORIGIN	"OC1\r"	/* origin in upper left */
#define WC_SUPPRESS	"SU"	/* suppress mode */
#define WC_ALL_MACRO	"~M0\r"	/* enable all macro buttons */
#define WC_NO_MACRO1	"~M1\r"	/* disable macro buttons of group 1 */
#define WC_RATE 	"IT0\r"	/* max transmit rate (unit of 5 ms) */
#define WC_TILT_MODE	"FM1\r"	/* enable extra protocol for tilt management */
#define WC_NO_INCREMENT	"IN0\r"	/* do not enable increment mode */
#define WC_STREAM_MODE	"SR\r"	/* enable continuous mode */
#define WC_PRESSURE_MODE "PH1\r" /* enable pressure mode */
#define WC_ZFILTER	"ZF1\r" /* stop sending coordinates */
#define WC_STOP		"\nSP\r" /* stop sending coordinates */
#define WC_START	"ST\r"	/* start sending coordinates */
#define WC_NEW_RESOLUTION "NR"	/* change the resolution */

static const char * setup_string = WC_MULTI WC_UPPER_ORIGIN
 WC_ALL_MACRO WC_NO_MACRO1 WC_RATE WC_NO_INCREMENT WC_STREAM_MODE WC_ZFILTER;

static const char * penpartner_setup_string = WC_PRESSURE_MODE WC_START;

#define WC_V_SINGLE	"MT0\r"
#define WC_V_MULTI	"MT1\r"
#define WC_V_ID		"ID1\r"
#define WC_V_19200	"BA19\r"
#define WC_V_38400	"BA38\r"
/*  #define WC_V_9600	"BA96\r" */
#define WC_V_9600	"$\r"

#define WC_RESET_19200	"\r$"	/* reset to 9600 baud */
#define WC_RESET_19200_IV "\r#"

static const char * intuos_setup_string = WC_V_MULTI WC_V_ID WC_RATE;

#define COMMAND_SET_MASK	0xc0
#define BAUD_RATE_MASK		0x0a
#define PARITY_MASK		0x30
#define DATA_LENGTH_MASK	0x40
#define STOP_BIT_MASK		0x80

#define HEADER_BIT	0x80
#define ZAXIS_SIGN_BIT	0x40
#define ZAXIS_BIT    	0x04
#define ZAXIS_BITS    	0x3f
#define POINTER_BIT     0x20
#define PROXIMITY_BIT   0x40
#define BUTTON_FLAG	0x08
#define BUTTONS_BITS	0x78
#define TILT_SIGN_BIT	0x40
#define TILT_BITS	0x3f

/* defines to discriminate second side button and the eraser */
#define ERASER_PROX	4
#define OTHER_PROX	1

#define HANDLE_TILT(comm) ((comm)->wcmPktLength == 9)

#define mils(res) (res * 100 / 2.54) /* resolution */

/******************************************************************************
 * Function/Macro keys variables
 *****************************************************************************/
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_F8,	/* 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 = {
  /* map	minKeyCode	maxKC	width */
  wacom_map,	8,		0x27,	1
};

/******************************************************************************
 * external declarations
 *****************************************************************************/

#ifdef LINUX_INPUT
static void xf86WcmReadUSBInput(LocalDevicePtr);
static Bool xf86WcmUSBOpen(LocalDevicePtr);
#endif

#if NeedFunctionPrototypes
static LocalDevicePtr xf86WcmAllocateStylus(void);
static LocalDevicePtr xf86WcmAllocateCursor(void);
static LocalDevicePtr xf86WcmAllocateEraser(void);
#endif

/*
 ***************************************************************************
 *
 * send_request --
 *
 ***************************************************************************
 */
/*
 * send a request and wait for the answer.
 * the answer must begin with the first two chars of the request and must end
 * with \r. The last character in the answer string (\r) is replaced by a \0.
 */
static char *
send_request(int	fd,
	     char	*request,
	     char	*answer)
{
    int	len, nr;
    int	maxtry = MAXTRY;
  
    /* send request string */
    do {
	SYSCALL(len = write(fd, request, strlen(request)));
	if ((len == -1) && (errno != EAGAIN)) {
	    ErrorF("Wacom write error : %s", strerror(errno));
	    return NULL;
	}
	maxtry--;
    } while ((len == -1) && maxtry);

    if (maxtry == 0) {
	ErrorF("Wacom unable to write request string '%s' after %d tries\n", request, MAXTRY);
	return NULL;
    }
  
    do {
	maxtry = MAXTRY;
    
	/* Read the first byte of the answer which must be equal to the first
	 * byte of the request.
	 */
	do {    
	    if ((nr = wait_for_fd(fd)) > 0) {
		SYSCALL(nr = read(fd, answer, 1));
		if ((nr == -1) && (errno != EAGAIN)) {
		    ErrorF("Wacom read error : %s\n", strerror(errno));
		    return NULL;
		}
		DBG(10, ErrorF("%c err=%d [0]\n", answer[0], nr));
	    }
	    maxtry--;  
	} while ((answer[0] != request[0]) && maxtry);

	if (maxtry == 0) {
	    ErrorF("Wacom unable to read first byte of request '%c%c' answer after %d tries\n",
		   request[0], request[1], MAXTRY);
	    return NULL;
	}

	/* Read the second byte of the answer which must be equal to the second
	 * byte of the request.
	 */
	do {    
	    maxtry = MAXTRY;
	    do {    
		if ((nr = wait_for_fd(fd)) > 0) {
		    SYSCALL(nr = read(fd, answer+1, 1));
		    if ((nr == -1) && (errno != EAGAIN)) {
			ErrorF("Wacom read error : %s\n", strerror(errno));
			return NULL;
		    }
		    DBG(10, ErrorF("%c err=%d [1]\n", answer[1], nr));
		}
		maxtry--;  
	    } while ((nr <= 0) && maxtry);
      
	    if (maxtry == 0) {
		ErrorF("Wacom unable to read second byte of request '%c%c' answer after %d tries\n",
		       request[0], request[1], MAXTRY);
		return NULL;
	    }

	    if (answer[1] != request[1])
		answer[0] = answer[1];
      
	} while ((answer[0] == request[0]) &&
		 (answer[1] != request[1]));

    } while ((answer[0] != request[0]) &&
	     (answer[1] != request[1]));

    /* Read until carriage return or timeout (to handle broken protocol
     * implementations which don't end with a <cr>).
     */
    len = 2;
    maxtry = MAXTRY;
    do {    
	do {    
	    if ((nr = wait_for_fd(fd)) > 0) {
		SYSCALL(nr = read(fd, answer+len, 1));
		if ((nr == -1) && (errno != EAGAIN)) {
		    ErrorF("Wacom read error : %s\n", strerror(errno));
		    return NULL;
		}
		DBG(10, ErrorF("%c err=%d [%d]\n", answer[len], nr, len));
	    }
	    else {
		DBG(10, ErrorF("timeout remains %d tries\n", maxtry));
		maxtry--;
	    }
	} while ((nr <= 0) && maxtry);

	if (nr > 0) {
	    len += nr;
	}
	
	if (maxtry == 0) {
	    ErrorF("Wacom unable to read last byte of request '%c%c' answer after %d tries\n",
		   request[0], request[1], MAXTRY);
	    break;
	}
    } while (answer[len-1] != '\r');

    if (len <= 3)
	return NULL;
    
    answer[len-1] = '\0';
  
    return answer;
}

/*
 ***************************************************************************
 *
 * xf86WcmConvert --
 *	Convert valuators to X and Y.
 *
 ***************************************************************************
 */
static Bool
xf86WcmConvert(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;
    int			width, height;
    
    DBG(6, ErrorF("xf86WcmConvert\n"));

    if (first != 0 || num == 1)
      return FALSE;

    if (priv->screen_no != -1) {
	width = screenInfo.screens[priv->screen_no]->width;
	height = screenInfo.screens[priv->screen_no]->height;
    } else {
	width = miPointerCurrentScreen()->width;
	height = miPointerCurrentScreen()->height;
    }
    
    priv->factorX = ((double) width)
	/ (priv->bottomX - priv->topX);
    priv->factorY = ((double) height)
	/ (priv->bottomY - priv->topY);
    
    *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));
    if (priv->screen_no != -1) {
	xf86XInputSetScreen(local, priv->screen_no, *x, *y);
    }
    return TRUE;
}

/*
 ***************************************************************************
 *
 * xf86WcmReverseConvert --
 *	Convert X and Y to valuators.
 *
 ***************************************************************************
 */
static Bool
xf86WcmReverseConvert(LocalDevicePtr	local,
		      int		x,
		      int		y,
		      int		*valuators)
{
    WacomDevicePtr	priv = (WacomDevicePtr) local->private;

    priv->factorX = ((double) miPointerCurrentScreen()->width)
	/ (priv->bottomX - priv->topX);
    priv->factorY = ((double) miPointerCurrentScreen()->height)
	/ (priv->bottomY - priv->topY);
    
    valuators[0] = x / priv->factorX + 0.5;
    valuators[1] = y / priv->factorY + 0.5;

    DBG(6, ErrorF("Wacom converted x=%d y=%d to v0=%d v1=%d\n", x, y,
		  valuators[0], valuators[1]));

    return TRUE;
}
 
/*
 ***************************************************************************
 *
 * xf86WcmSendButtons --
 *	Send button events by comparing the current button mask with the
 *      previous one.
 *
 ***************************************************************************
 */
static void
xf86WcmSendButtons(LocalDevicePtr	local,
		   int                  buttons,
		   int                  rx,
		   int                  ry,
		   int                  rz,
		   int                  rtx,
		   int                  rty,
		   int			rwheel)
		   
{
    int             button;
    WacomDevicePtr  priv = (WacomDevicePtr) local->private;

    for (button=1; button<=16; button++) {
	int mask = 1 << (button-1);
	
	if ((mask & priv->oldButtons) != (mask & buttons)) {
	    DBG(4, ErrorF("xf86WcmSendButtons button=%d state=%d\n", 
			  button, (buttons & mask) != 0));
	    xf86PostButtonEvent(local->dev, 
				(priv->flags & ABSOLUTE_FLAG),
				button, (buttons & mask) != 0,
				0, 6, rx, ry, rz, rtx, rty, rwheel);
	}
    }
}

/*
 ***************************************************************************
 *
 * xf86WcmSendEvents --
 *	Send events according to the device state.
 *
 ***************************************************************************
 */
static void
xf86WcmSendEvents(LocalDevicePtr	local,
		  int			type,
		  unsigned int		serial,
		  int			is_stylus,
		  int			is_button,
		  int			is_proximity,
		  int			x,
		  int			y,
		  int			z,
		  int			buttons,
		  int			tx,
		  int			ty,
		  int			wheel)
{
    WacomDevicePtr	priv = (WacomDevicePtr) local->private;
    WacomCommonPtr	common = priv->common;
    int			rx, ry, rz, rtx, rty, rwheel;
    int			is_core_pointer, is_absolute;

    if ((DEVICE_ID(priv->flags) != type) ||
	((common->wcmProtocolLevel == 5) &&
	 priv->serial && (serial != priv->serial))) {
	DBG(7,
	    {if (common->wcmProtocolLevel == 5) {
	    ErrorF("xf86WcmSendEvents not the same device id (%u,%u)\n",
		   serial, priv->serial);
	    } else {
            ErrorF("xf86WcmSendEvents not the same device type (%u,%u)\n",
		   DEVICE_ID(priv->flags), type);}});
	return;
    }
    
    DBG(7, ErrorF("[%s] prox=%s\tx=%d\ty=%d\tz=%d\tbutton=%s\tbuttons=%d\ttx=%d ty=%d\twl=%d\n",
		  (type == STYLUS_ID) ? "stylus" : (type == CURSOR_ID) ? "cursor" : "eraser",
		  is_proximity ? "true" : "false",
		  x, y, z,
		  is_button ? "true" : "false", buttons,
		  tx, ty, wheel));

    /* Translate coordinates according to Top and Bottom points
     * if we are outside the zone do as a ProximityOut event.
     */

    if (x > priv->bottomX) {
	is_proximity = FALSE;
	buttons = 0;
	x = priv->bottomX;
    }
	    
    if (y > priv->bottomY) {
	is_proximity = FALSE;
	buttons = 0;
	y = priv->bottomY;
    }

    DBG(10, ErrorF("topX=%d topY=%d\n", priv->topX, priv->topY));

    x = x - priv->topX;
    y = y - priv->topY;

    if (x < 0) {
	is_proximity = FALSE;
	buttons = 0;
	x = 0;
    }
    
    if (y < 0) {
	is_proximity = FALSE;
	buttons = 0;
	y = 0;
    }
    
    is_absolute = (priv->flags & ABSOLUTE_FLAG);
    is_core_pointer = xf86IsCorePointer(local->dev);

    DBG(6, ErrorF("[%s] %s prox=%s\tx=%d\ty=%d\tz=%d\tbutton=%s\tbuttons=%d\n",
		  is_stylus ? "stylus" : "cursor",
		  is_absolute ? "abs" : "rel",
		  is_proximity ? "true" : "false",
		  x, y, z,
		  is_button ? "true" : "false", buttons));

    /* Hardware filtering isn't working on Graphire so we do it here.
     */
    if ((common->wcmFlags & GRAPHIRE_FLAG) &&
	((is_proximity && priv->oldProximity) ||
	 ((is_proximity == 0) && (priv->oldProximity == 0))) &&
	(buttons == priv->oldButtons) &&
	(ABS(x - priv->oldX) <= common->wcmSuppress) &&
	(ABS(y - priv->oldY) <= common->wcmSuppress) &&
	 (ABS(z - priv->oldZ) < 3) &&
	(ABS(tx - priv->oldTiltX) < 3) &&
	(ABS(ty - priv->oldTiltY) < 3)) {
	
	DBG(10, ErrorF("Graphire filtered\n"));

	return;
    }
		
    /* sets rx and ry according to the mode */
    if (is_absolute) {
	rx = x;
	ry = y;
	rz = z;
	rtx = tx;
	rty = ty;
	rwheel = wheel;
    } else {
	rx = x - priv->oldX;
	ry = y - priv->oldY;
	rz = z - priv->oldZ;  
	rtx = tx - priv->oldTiltX;
	rty = ty - priv->oldTiltY;
	rwheel = wheel - priv->oldWheel;
    }

    /* coordinates are ready we can send events */
    if (is_proximity) {

	if (!priv->oldProximity) {
	    xf86PostProximityEvent(local->dev, 1, 0, 6, rx, ry, z, tx, ty, rwheel);

	    priv->flags |= FIRST_TOUCH_FLAG;
	    DBG(4, ErrorF("xf86WcmSendEvents FIRST_TOUCH_FLAG set\n"));
		    
	    if (common->wcmProtocolLevel == 4) {
		/* handle the two sides switches in the stylus */
		if (is_stylus && (buttons == 4)) {
		    priv->oldProximity = ERASER_PROX;
		}
		else {
		    priv->oldProximity = OTHER_PROX;
		}
	    }
	    else {
		priv->oldProximity = OTHER_PROX;
	    }
	}

	if (common->wcmProtocolLevel == 4 &&
	    !(common->wcmFlags & GRAPHIRE_FLAG)) {
	    /* The stylus reports button 4 for the second side
	     * switch and button 4/5 for the eraser tip. We know
	     * how to choose when we come in proximity for the
	     * first time. If we are in proximity and button 4 then
	     * we have the eraser else we have the second side
	     * switch.
	     */
	    if (is_stylus) {
		if (buttons == 4) {
		    buttons = (priv->oldProximity == ERASER_PROX) ? 0 : 3;
		}
		else {
		    if (priv->oldProximity == ERASER_PROX && buttons == 5) {
			buttons = ((DEVICE_ID(priv->flags) == ERASER_ID) ? 1 : 4);
		    }
		}
	    }
	    else {
		/* If the button flag is pressed, but the switch state
		 * is zero, this means that cursor button 16 was pressed
		 */
		if (is_button && buttons == 0) {
		    buttons = 16;
		}
	    }
	}
	DBG(4, ErrorF("xf86WcmSendEvents %s rx=%d ry=%d rz=%d buttons=%d\n",
		      is_stylus ? "stylus" : "cursor", rx, ry, rz, buttons));
	
	/* Turn button index reported into a bit mask for WACOM IV.
	 * The WACOM V and Graphire models already report buttons
	 * as a bit mask.
	 */
	if (common->wcmProtocolLevel == 4 &&
	    !(common->wcmFlags & GRAPHIRE_FLAG)) {
	    buttons = 1 << (buttons - 1);
	}
	
	if ((priv->oldX != x) ||
	    (priv->oldY != y) ||
	    (priv->oldZ != z) ||
	    (is_stylus && HANDLE_TILT(common) &&
	     (tx != priv->oldTiltX || ty != priv->oldTiltY))) {
	    if (!is_absolute && (priv->flags & FIRST_TOUCH_FLAG)) {
		priv->flags -= FIRST_TOUCH_FLAG;
		DBG(4, ErrorF("xf86WcmSendEvents FIRST_TOUCH_FLAG unset\n"));
	    } else {
		xf86PostMotionEvent(local->dev, is_absolute, 0, 6, rx, ry, rz,
				    rtx, rty, rwheel); 
	    }
	}

	if (priv->oldButtons != buttons) {
	    xf86WcmSendButtons (local, buttons, rx, ry, rz, rtx, rty, rwheel);
	}

	/* Simulate buttons 4 and 5 for Graphire wheel */
	if ((common->wcmProtocolLevel == 4) &&
	    !is_stylus &&
	    (common->wcmFlags & GRAPHIRE_FLAG) &&
	    (wheel != 0)) {
	    int fake_button = (wheel > 0) ? 5 : 4;

	    xf86PostButtonEvent(local->dev,
				(priv->flags & ABSOLUTE_FLAG),
				fake_button, 1,
				0, 6, rx, ry, rz, rtx, rty, rwheel);
	    
	    xf86PostButtonEvent(local->dev,
				(priv->flags & ABSOLUTE_FLAG),
				fake_button, 0,
				0, 6, rx, ry, rz, rtx, rty, rwheel);
	}
	
	priv->oldButtons = buttons;
	priv->oldX = x;
	priv->oldY = y;
	priv->oldZ = z;
	priv->oldTiltX = tx;
	priv->oldTiltY = ty;
	priv->oldWheel = wheel;
    }
    else { /* !PROXIMITY */
	/* reports button up when the device has been down and becomes out of proximity */
	if (priv->oldButtons) {
	    xf86WcmSendButtons (local, 0, rx, ry, rz, rtx, rty, rwheel);
	    priv->oldButtons = 0;
	}
	if (!is_core_pointer) {
	    /* macro button management */
	    if (common->wcmProtocolLevel == 4 && buttons) {
		int	macro = z / 2;

		DBG(6, ErrorF("macro=%d buttons=%d wacom_map[%d]=%x\n",
			      macro, buttons, macro, wacom_map[macro]));

		/* First available Keycode begins at 8 => macro+7 */
		xf86PostKeyEvent(local->dev, macro+7, 1,
				 is_absolute, 0, 6,
				 0, 0, buttons, rtx, rty, rwheel);
		xf86PostKeyEvent(local->dev, macro+7, 0,
				 is_absolute, 0, 6,
				 0, 0, buttons, rtx, rty, rwheel);
	    }
	    if (priv->oldProximity) {
		xf86PostProximityEvent(local->dev, 0, 0, 6, rx, ry, rz,
				       rtx, rty, rwheel);
	    }
	}
	priv->oldProximity = 0;
    }
}

/*
 ***************************************************************************
 *
 * xf86WcmSuppress --
 *	Determine whether device state has changed enough - return 1
 *	if not.
 *
 ***************************************************************************
 */
static int
xf86WcmSuppress(int			suppress,
		WacomDeviceState 	*ds1,
		WacomDeviceState	*ds2)
{
    if (ds1->buttons != ds2->buttons) return 0;
    if (ds1->proximity != ds2->proximity) return 0;
    if (ABS(ds1->x - ds2->x) >= suppress) return 0;
    if (ABS(ds1->y - ds2->y) >= suppress) return 0;
    if (ABS(ds1->pressure - ds2->pressure) >= suppress) return 0;
    if ((1800 + ds1->rotation - ds2->rotation) % 1800 >= suppress &&
    	(1800 + ds2->rotation - ds1->rotation) % 1800 >= suppress) return 0;
    if (ABS(ds1->wheel - ds2->wheel) >= suppress) return 0;
    return 1;
}

/*
 ***************************************************************************
 *
 * xf86WcmIntuosFilter --
 *	 Correct some hardware defects we've been seeing in Intuos pads,
 *       but also cuts down quite a bit on jitter.
 *
 ***************************************************************************
 */
static int
xf86WcmIntuosFilter(WacomFilterState	*state,
		    int			coord,
		    int			tilt)
{
    int tilt_filtered;
    int ts;
    int x0_pred;
    int x0_pred1;
    int x0, x1, x2, x3;
    int x;
    
    tilt_filtered = tilt + state->tilt[1] + state->tilt[2] + state->tilt[3];
    state->tilt[2] = state->tilt[1];
    state->tilt[1] = state->tilt[0];
    state->tilt[0] = tilt;
    
    x0 = coord;
    x1 = state->coord[0];
    x2 = state->coord[1];
    x3 = state->coord[2];
    state->coord[0] = x0;
    state->coord[1] = x1;
    state->coord[2] = x2;
    
    ts = tilt_filtered >= 0 ? 1 : -1;
    
    if (state->state == 0 || state->state == 3) {
	x0_pred = 2 * x1 - x2;
	x0_pred1 = 3 * x2 - 2 * x3;
	if (ts * (x0 - x0_pred) > 12 &&
	    ts * (x0 - x0_pred1) > 12) {
	    /* detected a jump at x0 */
	    state->state = 1;
	    x = x1;
	}
	else if (state->state == 0) {
	    x = (7 * x0 + 14 * x1 + 15 * x2 - 4 * x3 + 16) >> 5;
	}
	else { /* state->state == 3 */
	    /* a jump at x3 was detected */
	    x = (x0 + 2 * x1 + x2 + 2) >> 2;
	    state->state = 0;
	}
    }
    else if (state->state == 1) {
	/* a jump at x1 was detected */
	x = (3 * x0 + 7 * x2 - 2 * x3 + 4) >> 3;
	state->state = 2;
    }
    else { /* state->state == 2 */
	/* a jump at x2 was detected */
	x = x1;
	state->state = 3;
    }

    return x;
}

/*
 ***************************************************************************
 *
 * xf86WcmReadInput --
 *	Read the new events from the device, and enqueue them.
 *
 ***************************************************************************
 */
static void
xf86WcmReadInput(LocalDevicePtr         local)
{
    WacomDevicePtr	priv = (WacomDevicePtr) local->private;
    WacomCommonPtr	common = priv->common;
    int			len, loop, idx;
    int			is_stylus = 1, is_button, is_proximity, wheel=0;
    int			is_absolute = (priv->flags & ABSOLUTE_FLAG);
    int			x, y, z, buttons, tx = 0, ty = 0;
    unsigned char	buffer[BUFFER_SIZE];
    WacomDeviceState	*ds;
    WacomDeviceState	old_ds;
    int			have_data;
  
    DBG(7, ErrorF("xf86WcmReadInput BEGIN device=%s fd=%d\n",
		  common->wcmDevice, local->fd));

    SYSCALL(len = read(local->fd, buffer, sizeof(buffer)));

    if (len <= 0) {
	ErrorF("Error reading wacom device : %s\n", strerror(errno));
	return;
    } else {
	DBG(10, ErrorF("xf86WcmReadInput read %d bytes\n", len));
    }

    for(loop=0; loop<len; loop++) {

	/* Format of 7 bytes data packet for Wacom Tablets
	Byte 1
	bit 7  Sync bit always 1
	bit 6  Pointing device detected
	bit 5  Cursor = 0 / Stylus = 1
	bit 4  Reserved
	bit 3  1 if a button on the pointing device has been pressed
	bit 2  Reserved
	bit 1  X15
	bit 0  X14

	Byte 2
	bit 7  Always 0
	bits 6-0 = X13 - X7

	Byte 3
	bit 7  Always 0
	bits 6-0 = X6 - X0

	Byte 4
	bit 7  Always 0
	bit 6  B3
	bit 5  B2
	bit 4  B1
	bit 3  B0
	bit 2  P0
	bit 1  Y15
	bit 0  Y14

	Byte 5
	bit 7  Always 0
	bits 6-0 = Y13 - Y7

	Byte 6
	bit 7  Always 0
	bits 6-0 = Y6 - Y0

	Byte 7
	bit 7 Always 0
	bit 6  Sign of pressure data
	bit 5  P6
	bit 4  P5
	bit 3  P4
	bit 2  P3
	bit 1  P2
	bit 0  P1

	byte 8 and 9 are optional and present only
	in tilt mode.

	Byte 8
	bit 7 Always 0
	bit 6 Sign of tilt X
	bit 5  Xt6
	bit 4  Xt5
	bit 3  Xt4
	bit 2  Xt3
	bit 1  Xt2
	bit 0  Xt1
       
	Byte 9
	bit 7 Always 0
	bit 6 Sign of tilt Y
	bit 5  Yt6
	bit 4  Yt5
	bit 3  Yt4
	bit 2  Yt3
	bit 1  Yt2
	bit 0  Yt1
       
	*/

	if ((common->wcmIndex == 0) && !(buffer[loop] & HEADER_BIT)) { /* magic bit is not OK */
	    DBG(6, ErrorF("xf86WcmReadInput bad magic number 0x%x (pktlength=%d) %d\n",
			  buffer[loop], common->wcmPktLength, loop));
	    continue;
	}
	else { /* magic bit at wrong place */
	    if ((common->wcmIndex != 0) && (buffer[loop] & HEADER_BIT)) {
		DBG(6, ErrorF("xf86WcmReadInput magic number 0x%x detetected at index %d loop=%d\n",
			      (unsigned int) buffer[loop], common->wcmIndex, loop));
		common->wcmIndex = 0;
	    }
	}
	
	common->wcmData[common->wcmIndex++] = buffer[loop];

	if (common->wcmProtocolLevel == 4 &&
	    common->wcmIndex == common->wcmPktLength) {
	    int	is_graphire = common->wcmFlags & GRAPHIRE_FLAG;
	    
	    /* the packet is OK */

	    /* reset char count for next read */
	    common->wcmIndex = 0;

	    x = (((common->wcmData[0] & 0x3) << 14) +
		 (common->wcmData[1] << 7) +
		 common->wcmData[2]);
	    y = (((common->wcmData[3] & 0x3) << 14) +
		 (common->wcmData[4] << 7) +
		 common->wcmData[5]);

	    /* check which device we have */
	    is_stylus = (common->wcmData[0] & POINTER_BIT);
	      
	    z = ((common->wcmData[6] & ZAXIS_BITS) * 2) +
		((common->wcmData[3] & ZAXIS_BIT) >> 2);

	    if (common->wcmMaxZ == 512) {
		z = z*4 + ((common->wcmData[0] & ZAXIS_BIT) >> 1);

		if (!(common->wcmData[6] & ZAXIS_SIGN_BIT)) {
		    z += 256;
		}
		DBG(10, ErrorF("graphire pressure(%c)=%d\n",
			       (common->wcmData[6] & ZAXIS_SIGN_BIT) ? '-' : '+', z));
	    }
	    else {
		if (!(common->wcmData[6] & ZAXIS_SIGN_BIT)) {
		    z += (common->wcmMaxZ / 2);
		}
	    }
	    
	    is_proximity = (common->wcmData[0] & PROXIMITY_BIT);

	    if (is_graphire) {
		if (is_stylus) {
		    buttons = ((common->wcmData[3] & 0x30) >> 3) |
			(z >= common->wcmThreshold ? 1 : 0);
		}
		else {
		    buttons = (common->wcmData[3] & 0x38) >> 3;

		    wheel = (common->wcmData[6] & 0x30) >> 4;
		
		    if (common->wcmData[6] & 0x40) {
			wheel = -wheel;
		    }
		}
		is_button = (buttons != 0);

		DBG(10, ErrorF("graphire buttons=%d prox=%d wheel=%d\n", buttons, is_proximity, wheel));
	    }
	    else {
		is_button = (common->wcmData[0] & BUTTON_FLAG);
		buttons = (common->wcmData[3] & BUTTONS_BITS) >> 3;
	    }
	    
	    /* The stylus reports button 4 for the second side
	     * switch and button 4/5 for the eraser tip. We know
	     * how to choose when we come in proximity for the
	     * first time. If we are in proximity and button 4 then
	     * we have the eraser else we have the second side
	     * switch.
	     */
	    if (is_stylus) {
		if (!common->wcmStylusProximity && is_proximity) {
		    if (is_graphire) {
			    common->wcmStylusSide = !(common->wcmData[3] & 0x40);
		    }
		    else {
			common->wcmStylusSide = (buttons != 4);
		    }
		}
		DBG(8, ErrorF("xf86WcmReadInput %s side\n",
			      common->wcmStylusSide ? "stylus" : "eraser"));
		common->wcmStylusProximity = is_proximity;

		/* handle tilt values only for stylus */
		if (HANDLE_TILT(common)) {
		    tx = (common->wcmData[7] & TILT_BITS);
		    ty = (common->wcmData[8] & TILT_BITS);
		    if (common->wcmData[7] & TILT_SIGN_BIT)
			tx -= (TILT_BITS + 1);
		    if (common->wcmData[8] & TILT_SIGN_BIT)
			ty -= (TILT_BITS + 1);
		}
	    }

	    for(idx=0; idx<common->wcmNumDevices; idx++) {
	        LocalDevicePtr  local_dev = common->wcmDevices[idx];
		WacomDevicePtr	priv = (WacomDevicePtr) local_dev->private;
		int		temp_buttons = buttons;
		int		temp_is_proximity = is_proximity;
		int             curDevice;
		
		DBG(7, ErrorF("xf86WcmReadInput trying to send to %s\n",
			      local_dev->name));
		
		/* check for device type (STYLUS, ERASER or CURSOR) */
		
		if (is_stylus) {
		    /*
		     * The eraser is reported as button 4 and 5 of the stylus.
		     * if we haven't an independent device for the eraser
		     * report the button as button 3 of the stylus.
		     */
		    if (is_proximity) {
			if (is_graphire) {
			    if (common->wcmData[3] & 0x40) {
				curDevice = ERASER_ID;
			    }
			    else {
				curDevice = STYLUS_ID;
			    }
			}
			else {
			    if ((buttons & 4) && common->wcmHasEraser &&
				((!priv->oldProximity ||
				  (priv->oldProximity == ERASER_PROX)))) {
				curDevice = ERASER_ID;
			    } else {
				curDevice = STYLUS_ID;
			    }
			}
		    } else {
			/*
			 * When we are out of proximity with the eraser the
			 * button 4 isn't reported so we must check the
			 * previous proximity device.
			 */
			if (common->wcmHasEraser && (priv->oldProximity == ERASER_PROX)) {
			    curDevice = ERASER_ID;
			} else {
			    curDevice = STYLUS_ID;
			}
		    }
		    
		    /* We check here to see if we changed between eraser and stylus
		     * without leaving proximity. The most likely cause is that
		     * we were fooled by the second side switch into thinking the
		     * stylus was the eraser. If this happens, we send
		     * a proximity-out for the old device.
		     */
		    if ((DEVICE_ID(priv->flags) == STYLUS_ID ||
			 DEVICE_ID(priv->flags) == ERASER_ID) &&
			curDevice != DEVICE_ID(priv->flags)) {
			if (priv->oldProximity) {
			    curDevice = DEVICE_ID(priv->flags);
			    temp_buttons = 0;
			    temp_is_proximity = 0;
			    DBG(10, ErrorF("eraser and stylus mix\n"));
			} else 
			    continue;
		    }
		    
		    DBG(10, ErrorF((DEVICE_ID(priv->flags) == ERASER_ID) ? 
				   "Eraser\n" : 
				   "Stylus\n"));
		}
		else {
		    if (DEVICE_ID(priv->flags) != CURSOR_ID)
			continue;	
		    DBG(10, ErrorF("Cursor\n"));
		    curDevice = CURSOR_ID;
		}
		
    		xf86WcmSendEvents(common->wcmDevices[idx],
				  curDevice, 0,
				  is_stylus,
				  is_button,
				  temp_is_proximity,
				  x, y, z, temp_buttons,
				  tx, ty, wheel);
	    }
	}
	else if (common->wcmProtocolLevel == 5 &&
		 common->wcmIndex == common->wcmPktLength) {
	    /* the packet is OK */
	    int x, y;

	    /* reset count for read of next packet */
	    common->wcmIndex = 0;

	    ds = &common->wcmDevStat[common->wcmData[0] & 0x01];
	    old_ds = *ds;
	    have_data = 0;

	    DBG(7, ErrorF("packet header = 0x%x\n",
			  (unsigned int)common->wcmData[0]));
	    
	    /* Device ID packet */
	    if ((common->wcmData[0] & 0xfc) == 0xc0) {
		memset(ds, 0, sizeof(*ds));
		ds->proximity = 1;
		ds->device_id = (((common->wcmData[1] & 0x7f) << 5) |
				 ((common->wcmData[2] & 0x7c) >> 2));
		ds->serial_num = (((common->wcmData[2] & 0x03) << 30) |
				  ((common->wcmData[3] & 0x7f) << 23) |
				  ((common->wcmData[4] & 0x7f) << 16) |
				  ((common->wcmData[5] & 0x7f) << 9) |
				  ((common->wcmData[6] & 0x7f) << 23) |
				  ((common->wcmData[7] & 0x60) >> 5));
		if ((ds->device_id & 0xf06) != 0x802)
		  ds->discard_first = 1;

		if (PEN(ds) || STROKING_PEN(ds) || INKING_PEN(ds) || AIRBRUSH(ds))
		    ds->device_type = STYLUS_ID;
		else if (MOUSE_4D(ds) || LENS_CURSOR(ds))
		    ds->device_type = CURSOR_ID;
		else
		    ds->device_type = ERASER_ID;
		
		DBG(6, ErrorF("device_id=0x%x serial_num=%u type=%s\n",
			      ds->device_id, ds->serial_num,
			      (ds->device_type == STYLUS_ID) ? "stylus"
			      : (ds->device_type == CURSOR_ID) ? "cursor"
			      : "eraser"));
	    }
	    /* Out of proximity packet */
	    else if ((common->wcmData[0] & 0xfe) == 0x80) {
		ds->proximity = 0;
		have_data = 1;
	    }
	    /* General pen packet or eraser packet or airbrush first packet */
	    else if (((common->wcmData[0] & 0xb8) == 0xa0) ||
		     /* airbrush second packet */
		     ((common->wcmData[0] & 0xbe) == 0xb4)) {
		is_stylus = 1;
		ds->x = (((common->wcmData[1] & 0x7f) << 9) |
			 ((common->wcmData[2] & 0x7f) << 2) |
			 ((common->wcmData[3] & 0x60) >> 5));
		ds->y = (((common->wcmData[3] & 0x1f) << 11) |
			 ((common->wcmData[4] & 0x7f) << 4) |
			 ((common->wcmData[5] & 0x78) >> 3));
		if ((common->wcmData[0] & 0xb8) == 0xa0) {
		    ds->pressure = (((common->wcmData[5] & 0x07) << 7) |
				    (common->wcmData[6] & 0x7f));
		    ds->buttons = (((common->wcmData[0]) & 0x06) |
				   (ds->pressure >= common->wcmThreshold));
		}
		else {
		    ds->wheel = (((common->wcmData[5] & 0x07) << 7) |
				 (common->wcmData[6] & 0x7f));
		}
		ds->tiltx = (common->wcmData[7] & TILT_BITS);
		ds->tilty = (common->wcmData[8] & TILT_BITS);
		if (common->wcmData[7] & TILT_SIGN_BIT)
		  ds->tiltx -= (TILT_BITS + 1);
		if (common->wcmData[8] & TILT_SIGN_BIT)
		  ds->tilty -= (TILT_BITS + 1);
		ds->proximity = (common->wcmData[0] & PROXIMITY_BIT);
		have_data = 1;
	    }
	    /* 4D mouse 1st packet or Lens cursor packet */
	    else if (((common->wcmData[0] & 0xbe) == 0xa8) ||
		     ((common->wcmData[0] & 0xbe) == 0xb0)) {
		is_stylus = 0;
		ds->x = (((common->wcmData[1] & 0x7f) << 9) |
		     ((common->wcmData[2] & 0x7f) << 2) |
		     ((common->wcmData[3] & 0x60) >> 5));
		ds->y = (((common->wcmData[3] & 0x1f) << 11) |
		     ((common->wcmData[4] & 0x7f) << 4) |
		     ((common->wcmData[5] & 0x78) >> 3));
		ds->tilty = 0;
		ds->wheel = (((common->wcmData[5] & 0x07) << 7) |
		     (common->wcmData[6] & 0x7f));
		if (common->wcmData[8] & 0x08) ds->wheel = -ds->wheel;
		/* 4D mouse */
		if (MOUSE_4D(ds)) {
		    ds->buttons = (((common->wcmData[8] & 0x70) >> 1) |
				   (common->wcmData[8] & 0x07));
		    have_data = !ds->discard_first;
		}
		/* Lens cursor */
		else {
		    ds->buttons = common->wcmData[8];
		    have_data = 1;
		}
		ds->proximity = (common->wcmData[0] & PROXIMITY_BIT);
	    }
	    /* 4D mouse 2nd packet */
	    else if ((common->wcmData[0] & 0xbe) == 0xaa) {
		is_stylus = 0;
		ds->x = (((common->wcmData[1] & 0x7f) << 9) |
			 ((common->wcmData[2] & 0x7f) << 2) |
			 ((common->wcmData[3] & 0x60) >> 5));
		ds->y = (((common->wcmData[3] & 0x1f) << 11) |
			 ((common->wcmData[4] & 0x7f) << 4) |
			 ((common->wcmData[5] & 0x78) >> 3));
		ds->tilty = 0;
		ds->rotation = (((common->wcmData[6] & 0x0f) << 7) |
				(common->wcmData[7] & 0x7f));
		ds->tiltx = ((900 - ((ds->rotation + 900) % 1800)) >> 1);
		ds->proximity = (common->wcmData[0] & PROXIMITY_BIT);
		have_data = 1;
		ds->discard_first = 0;
	    }
	    else {
		DBG(10, ErrorF("unknown wacom V packet 0x%x\n",
			       common->wcmData[0]));
	    }
	    
	    /* Suppress data */
	    if (have_data &&
		xf86WcmSuppress(common->wcmSuppress, &old_ds, ds)) {
	        DBG(10, ErrorF("Suppressing data according to filter\n"));
		*ds = old_ds;
		have_data = 0;
	    }

	    if (have_data) {
	        if (is_absolute) {
		    x = xf86WcmIntuosFilter (&ds->x_filter, ds->x, ds->tiltx);
		    y = xf86WcmIntuosFilter (&ds->y_filter, ds->y, ds->tilty);
		} 
		else {
		    x = ds->x;
		    y = ds->y;
		}
		for(idx=0; idx<common->wcmNumDevices; idx++) {
		    DBG(7, ErrorF("xf86WcmReadInput trying to send to %s\n",
				  common->wcmDevices[idx]->name));

		    xf86WcmSendEvents(common->wcmDevices[idx],
				      ds->device_type, ds->serial_num,
				      is_stylus,
				      ds->buttons,
				      ds->proximity,
				      x, y,
				      ds->pressure,
				      ds->buttons,
				      ds->tiltx, ds->tilty,
				      ds->wheel);
		}
	    }
	}
    }
    DBG(7, ErrorF("xf86WcmReadInput END   local=0x%x priv=0x%x index=%d\n",
		  local, priv, common->wcmIndex));
}

#ifdef LINUX_INPUT
/*
 ***************************************************************************
 *
 * xf86WcmIsUSBLine --
 *	Test if the attached device is a USB one.
 *
 ***************************************************************************
 */
static int
xf86WcmIsUSBLine(int	fd)
{
    int version;
    int err;

    SYSCALL(err = ioctl(fd, EVIOCGVERSION, &version));
    
    if (!err) {
	ErrorF("%s Wacom Kernel Input driver version is %d.%d.%d\n", XCONFIG_PROBED,
	       version >> 16, (version >> 8) & 0xff, version & 0xff);
	return 1;
    } else {
	return 0;
    }
}

/*
 ***************************************************************************
 *
 * xf86WcmReadUSBInput --
 *	Read the new events from the device, and enqueue them.
 *
 ***************************************************************************
 */
static void
xf86WcmReadUSBInput(LocalDevicePtr         local)
{
    WacomDevicePtr	priv = (WacomDevicePtr) local->private;
    WacomCommonPtr	common = priv->common;
    int serial = common->wcmLastSerial;
    int is_proximity = priv->oldProximity;
    int x = priv->oldX;
    int y = priv->oldY;
    int pressure = priv->oldZ;
    int buttons = priv->oldButtons;
    int tilt_x = priv->oldTiltX;
    int tilt_y = priv->oldTiltY;
    int wheel = priv->oldWheel;

    ssize_t              len;
    int                  idx;
    struct input_event * event;
    char                 eventbuf[sizeof(struct input_event) * MAX_EVENTS];
#define MOD_BUTTONS(bit, value) \
    { int _b=bit, _v=value; buttons = (((_v) != 0) ? (buttons | _b) : (buttons & ~ _b)); }

    SYSCALL(len = read(local->fd, eventbuf, sizeof(eventbuf)));

    if (len <= 0) {
	ErrorF("Error reading wacom device : %s\n", strerror(errno));
	return;
    }
    
    for (event=(struct input_event *)eventbuf;
	 event<(struct input_event *)(eventbuf+len); event++) {
	DBG(10, ErrorF("xf86WcmReadUSBInput event->type=%d\n", event->type));
	
	switch (event->type) {
	case EV_ABS:
	    DBG(10, ErrorF("xf86WcmReadUSBInput event->code=%d\n", event->code));
	    switch (event->code) {
	    case ABS_X:
		x = event->value;
		break;

	    case ABS_Y:
		y = event->value;
		break;

	    case ABS_TILT_X:
	    case ABS_RZ:
		tilt_x = event->value;
		break;

	    case ABS_TILT_Y:
		tilt_y = event->value;
		break;

	    case ABS_PRESSURE:
		pressure = event->value;
 		MOD_BUTTONS (1, event->value > common->wcmThreshold ? 1 : 0);
		break;

	    case ABS_DISTANCE:
		/* This is not sent by the driver */
		break;

	    case ABS_MISC:
		serial = event->value;
		DBG(10, ErrorF("wacom tool serial id=%d\n", serial));
		break;

	    case ABS_WHEEL:
	    case ABS_THROTTLE:
		wheel = event->value;
		break;

	    }
	    break; /* EV_ABS */

	case EV_REL:
	    switch (event->code) {
	    case REL_WHEEL:
		/* FIXME */
		break;
	    default:
		ErrorF("wacom: relative event received (%d)!!!\n", event->code);
		break;
	    }
	    break; /* EV_REL */

	case EV_KEY:
	    switch (event->code) {
	    case BTN_TOOL_PEN:
	    case BTN_TOOL_PENCIL:
	    case BTN_TOOL_BRUSH:
	    case BTN_TOOL_AIRBRUSH:
		DBG(10, ErrorF("USB Stylus detected %x\n", event->code));
		common->wcmIndex = STYLUS_ID;
		is_proximity = (event->value != 0);
		break;

	    case BTN_TOOL_RUBBER:
		DBG(10, ErrorF("USB eraser detected %x\n", event->code));
		common->wcmIndex = ERASER_ID;
		is_proximity = (event->value != 0);
		break;

	    case BTN_TOOL_MOUSE:
	    case BTN_TOOL_LENS:
		DBG(10, ErrorF("USB mouse detected %x\n", event->code));
		common->wcmIndex = CURSOR_ID;
		is_proximity = (event->value != 0);
		break;

	    case BTN_TOUCH:
		/* we use the pressure to determine the button 1 */
		break;

	    case BTN_STYLUS:
		MOD_BUTTONS (2, event->value);
		break;

	    case BTN_STYLUS2:
		MOD_BUTTONS (4, event->value);
		break;

	    case BTN_LEFT:
		MOD_BUTTONS (1, event->value);
		break;

	    case BTN_MIDDLE:
		MOD_BUTTONS (2, event->value);
		break;

	    case BTN_RIGHT:
		MOD_BUTTONS (4, event->value);
		break;

	    case BTN_SIDE:
		MOD_BUTTONS (8, event->value);
		break;

	    case BTN_EXTRA:
		MOD_BUTTONS (16, event->value);
		break;
	    }
	    break; /* EV_KEY */
	} /* switch event->type */

	/* ABS_MISC is the event terminator */
	if (event->type != EV_ABS || event->code != ABS_MISC) {
	    continue;
	}
	
	if ((is_proximity == priv->oldProximity) &&
	    (buttons == priv->oldButtons) &&
	    (ABS(x - priv->oldX) <= common->wcmSuppress) &&
	    (ABS(y - priv->oldY) <= common->wcmSuppress) &&
	    (ABS(pressure - priv->oldZ) < 3) &&
	    (ABS(tilt_x - priv->oldTiltX) < 3) &&
	    (ABS(tilt_y - priv->oldTiltY) < 3)) {
	    DBG(10, ErrorF("filtered\n"));
	    continue;
	}
    
    for (idx=0; idx<common->wcmNumDevices; idx++) {
	WacomDevicePtr	dev = common->wcmDevices[idx]->private;
	int		id;

	id  = DEVICE_ID (dev->flags);

	/* Find the device the current events are meant for */
	if (id == common->wcmIndex) {
	    xf86WcmSendEvents(common->wcmDevices[idx],
			common->wcmIndex,
			serial,
			(common->wcmIndex == STYLUS_ID || common->wcmIndex == ERASER_ID),
			!!(buttons),
			is_proximity,
			x, y, pressure, buttons,
			tilt_x, tilt_y, wheel
			);
	}
    }
    priv->oldX = x;
    priv->oldY = y;
    priv->oldZ = pressure;
    priv->oldTiltX = tilt_x;
    priv->oldTiltY = tilt_y;
    priv->oldProximity = is_proximity;
    priv->oldButtons = buttons;
    priv->oldWheel = wheel;
    common->wcmLastSerial = serial;
    }
}

/*
 ***************************************************************************
 *
 * xf86WcmUSBOpen --
 *
 ***************************************************************************
 */

#define BITS_PER_LONG (sizeof(long) * 8)
#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
#define test_bit(bit, array)	((array[LONG(bit)] >> OFF(bit)) & 1)
#define OFF(x)  ((x)%BITS_PER_LONG)
#define LONG(x) ((x)/BITS_PER_LONG)

static Bool
xf86WcmUSBOpen(LocalDevicePtr	local)
{
    int			err = 0;
    WacomDevicePtr	priv = (WacomDevicePtr)local->private;
    WacomCommonPtr	common = priv->common;
    char		name[256] = "Unknown";
    int			abs[5];
    unsigned long	bit[EV_MAX][NBITS(KEY_MAX)];
    int			i, j;
    
    local->fd = xf86OpenSerial(local->options);
    if (local->fd == -1) {
	ErrorF("Error opening %s : %s\n", common->wcmDevice, strerror(errno));
	return !Success;
    }

    ioctl(local->fd, EVIOCGNAME(sizeof(name)), name);
    ErrorF("%s Wacom Kernel Input device name: \"%s\"\n", XCONFIG_PROBED, name);

    memset(bit, 0, sizeof(bit));
    ioctl(local->fd, EVIOCGBIT(0, EV_MAX), bit[0]);

    for (i = 0; i < EV_MAX; i++)
	if (test_bit(i, bit[0])) {
	    ioctl(local->fd, EVIOCGBIT(i, KEY_MAX), bit[i]);
	    for (j = 0; j < KEY_MAX; j++) 
		if (test_bit(j, bit[i])) {
		    if (i == EV_ABS) {
			ioctl(local->fd, EVIOCGABS(j), abs);
			switch (j) {
			case ABS_X:
			    if (common->wcmMaxX == 0) {
				common->wcmMaxX = abs[2];
			    }
			    break;
			    
			case ABS_Y:
			    if (common->wcmMaxY == 0) {
				common->wcmMaxY = abs[2];
			    }
			    break;
			    
			case ABS_Z:
			    if (common->wcmMaxZ == DEFAULT_MAXZ) {
				common->wcmMaxZ = abs[2];
			    }
			    break;
			}
		    }
		}
	}
    
    DBG(2, ErrorF("setup is max X=%d max Y=%d resol X=%d resol Y=%d\n",
		  common->wcmMaxX, common->wcmMaxY, common->wcmResolX,
		  common->wcmResolY));
  
    /* send the tilt mode command after setup because it must be enabled */
    /* after multi-mode to take precedence */
    if (HANDLE_TILT(common)) {
      /* Unfortunately, the USB driver doesn't allow to send this
       * command to the tablet. Any other solutions ? */
	DBG(2, ErrorF("Sending tilt mode order\n"));
    }
  
    {
	if (common->wcmSuppress < 0) {
	    common->wcmSuppress = 0;
	} else {
	    if (common->wcmSuppress > 100) {
		common->wcmSuppress = 99;
	    }
	}
	/* Cannot send WC_SUPPRESS to the table. Will have to do
	 * this manually. */
    }
    
    priv->topX = 0;
    priv->bottomX = common->wcmMaxX;
    priv->topY = 0;
    priv->bottomY = common->wcmMaxY;

    if (xf86Verbose)
	ErrorF("%s Wacom tablet maximum X=%d maximum Y=%d "
	       "X resolution=%d Y resolution=%d suppress=%d%s\n",
	       XCONFIG_PROBED, common->wcmMaxX, common->wcmMaxY,
	       common->wcmResolX, common->wcmResolY, common->wcmSuppress,
	       HANDLE_TILT(common) ? " Tilt" : "");
  
    if (err < 0) {
        ErrorF("ERROR: %d\n", err);
	SYSCALL(close(local->fd));
	return !Success;
    }

    /* to have the button field handled as a bit field */
    common->wcmProtocolLevel = 5;
    
    return Success;
}

#endif /* LINUX_INPUT */

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

/*
 ***************************************************************************
 *
 * xf86WcmOpen --
 *
 ***************************************************************************
 */
#define WAIT(t)							\
    err = xf86WaitForInput(-1, ((t) * 1000));			\
    if (err == -1) {						\
	ErrorF("Wacom select error : %s\n", strerror(errno));	\
	return !Success;					\
    }

static Bool
xf86WcmOpen(LocalDevicePtr	local)
{
    char		buffer[256];
    char		header[64]; /* This is a small buffer for discarding the unwanted header */
    int			err;
    WacomDevicePtr	priv = (WacomDevicePtr)local->private;
    WacomCommonPtr	common = priv->common;
    int			a, b;
    int			loop, idx;
    float		version = 0.0;
    int			is_a_penpartner = 0;
    
    DBG(1, ErrorF("opening %s\n", common->wcmDevice));

    local->fd = xf86OpenSerial(local->options);
    if (local->fd < 0) {
	ErrorF("Error opening %s : %s\n", common->wcmDevice, strerror(errno));
	return !Success;
    }

#ifdef LINUX_INPUT
    DBG(1, ErrorF("testing USB\n"));
    
    if (xf86WcmIsUSBLine(local->fd)) {
	int	loop;
	
	SYSCALL(close(local->fd));

	for(loop=0; loop<common->wcmNumDevices; loop++) {
	    common->wcmDevices[loop]->read_input=xf86WcmReadUSBInput;
	}
	common->wcmOpen=xf86WcmUSBOpen;
	
	return xf86WcmUSBOpen(local);
    }
#endif
    
    DBG(1, ErrorF("initializing tablet\n"));    

    /* Set the speed of the serial link to 38400 */
   if (xf86SetSerialSpeed(local->fd, 38400) < 0) {
       return !Success;
   }
    
    /* Send reset to the tablet */
    SYSCALL(err = write(local->fd, WC_RESET_BAUD, strlen(WC_RESET_BAUD)));
    if (err == -1) {
	ErrorF("Wacom write error : %s\n", strerror(errno));
	return !Success;
    }
    
    /* Wait 250 mSecs */
    WAIT(250);

    /* Send reset to the tablet */
    SYSCALL(err = write(local->fd, WC_RESET, strlen(WC_RESET)));
    if (err == -1) {
	ErrorF("Wacom write error : %s\n", strerror(errno));
	return !Success;
    }
    
    /* Wait 75 mSecs */
    WAIT(75);

    /* Set the speed of the serial link to 19200 */
   if (xf86SetSerialSpeed(local->fd, 19200) < 0) {
       return !Success;
   }
    
    /* Send reset to the tablet */
    SYSCALL(err = write(local->fd, WC_RESET_BAUD, strlen(WC_RESET_BAUD)));
    if (err == -1) {
	ErrorF("Wacom write error : %s\n", strerror(errno));
	return !Success;
    }
    
    /* Wait 250 mSecs */
    WAIT(250);

    /* Send reset to the tablet */
    SYSCALL(err = write(local->fd, WC_RESET, strlen(WC_RESET)));
    if (err == -1) {
	ErrorF("Wacom write error : %s\n", strerror(errno));
	return !Success;
    }
    
    /* Wait 75 mSecs */
    WAIT(75);

    /* Set the speed of the serial link to 9600 */
   if (xf86SetSerialSpeed(local->fd, 9600) < 0) {
       return !Success;
   }
    
    /* Send reset to the tablet */
    SYSCALL(err = write(local->fd, WC_RESET_BAUD, strlen(WC_RESET_BAUD)));
    if (err == -1) {
	ErrorF("Wacom write error : %s\n", strerror(errno));
	return !Success;
    }
    
    /* Wait 250 mSecs */
    WAIT(250);

    SYSCALL(err = write(local->fd, WC_STOP, strlen(WC_STOP)));
    if (err == -1) {
	ErrorF("Wacom write error : %s\n", strerror(errno));
	return !Success;
    }

    /* Wait 30 mSecs */
    WAIT(30);

    xf86FlushInput(local->fd);

    DBG(2, ErrorF("reading model\n"));
    if (!send_request(local->fd, WC_MODEL, buffer)) {
	return !Success;
    }
    DBG(2, ErrorF("%s\n", buffer));
  
    if (xf86Verbose) {
	ErrorF("%s Wacom tablet model : %s\n", XCONFIG_PROBED, buffer+2);
    }
    
    /* Answer is in the form ~#Tablet-Model VRom_Version */
    /* look for the first V from the end of the string */
    /* this seems to be the better way to find the version of the ROM */
    for(loop=strlen(buffer); loop>=0 && *(buffer+loop) != 'V'; loop--);
    for(idx=loop; idx<strlen(buffer) && *(buffer+idx) != '-'; idx++);
    *(buffer+idx) = '\0';

    /* Extract version numbers */
    sscanf(buffer+loop+1, "%f", &version);

    if ((buffer[2] == 'G' || buffer[2] == 'X') && buffer[3] == 'D') {
	DBG(2, ErrorF("detected an Intuos model\n"));
	common->wcmProtocolLevel = 5;
	common->wcmMaxZ = 1023;		/* max Z value */
	common->wcmResolX = 2540;	/* X resolution in points/inch */
	common->wcmResolY = 2540;	/* Y resolution in points/inch */
	common->wcmResolZ = 2540;	/* Z resolution in points/inch */
	common->wcmPktLength = 9;	/* length of a packet */
	if (common->wcmThreshold == INVALID_THRESHOLD) {
	    common->wcmThreshold = -480; /* Threshold for counting pressure as a button */
	    if (xf86Verbose) {
		ErrorF("%s Wacom using pressure threshold of %d for button 1\n",
		       XCONFIG_PROBED, common->wcmThreshold);
	    }
	}
    }
	
    /* Tilt works on ROM 1.4 and above */
    DBG(2, ErrorF("wacom flags=%d ROM version=%f buffer=%s\n",
		  common->wcmFlags, version, buffer+loop+1));
    if (common->wcmProtocolLevel == 4 &&
	(common->wcmFlags & TILT_FLAG) && (version >= (float)1.4)) {
	common->wcmPktLength = 9;
    }

    /* Check for a PenPartner or Graphire model which doesn't answer WC_CONFIG
     * request. The Graphire model is handled like a PenPartner except that
     * it doesn't answer WC_COORD requests.
     */
    if ((buffer[2] == 'C' || buffer[2] == 'E') && buffer[3] == 'T') {
	if (buffer[2] == 'E') {
	    DBG(2, ErrorF("detected a Graphire model\n"));
	    common->wcmFlags |= GRAPHIRE_FLAG;
	    /* Graphire models don't answer WC_COORD requests */
	    common->wcmMaxX = 5103;
	    common->wcmMaxY = 3711;
	    common->wcmMaxZ = 512;
	}
	else {
	    DBG(2, ErrorF("detected a PenPartner model\n"));
	    common->wcmMaxZ = 256;
	}
	common->wcmResolX = 1000;
	common->wcmResolY = 1000;
	is_a_penpartner = 1;
    }
    else if (common->wcmProtocolLevel == 4 && !(common->wcmResolX && common->wcmResolY)) {
	DBG(2, ErrorF("reading config\n"));
	if (send_request(local->fd, WC_CONFIG, buffer)) {
	    DBG(2, ErrorF("%s\n", buffer));
	    /* The header string is simply a place to put the unwanted
	     * config header don't use buffer+xx because the header size
	     * varies on different tablets
	     */
	    if (sscanf(buffer, "%[^,],%d,%d,%d,%d", header, &a, &b, &common->wcmResolX, &common->wcmResolY) == 5) {
		DBG(6, ErrorF("WC_CONFIG Header = %s\n", header));
	    }
	    else {
		ErrorF("WACOM: unable to parse resolution. Using default.\n");
		common->wcmResolX = common->wcmResolY = 1270;
	    }
	}
	else {
	    ErrorF("WACOM: unable to read resolution. Using default.\n");
	    common->wcmResolX = common->wcmResolY = 1270;
	}
    }

    if (!(common->wcmFlags & GRAPHIRE_FLAG) && !(common->wcmMaxX && common->wcmMaxY)) {
	DBG(2, ErrorF("reading max coordinates\n"));
	if (!send_request(local->fd, WC_COORD, buffer)) {
	    ErrorF("WACOM: unable to read max coordinates. Use the MaxX and MaxY options.\n");
	    return !Success;
	}
	DBG(2, ErrorF("%s\n", buffer));
	if (sscanf(buffer+2, "%d,%d", &common->wcmMaxX, &common->wcmMaxY) != 2) {
	    ErrorF("WACOM: unable to parse max coordinates. Use the MaxX and MaxY options.\n");
	    return !Success;
	}
    }
    
    DBG(2, ErrorF("setup is max X=%d max Y=%d resol X=%d resol Y=%d\n",
		  common->wcmMaxX, common->wcmMaxY, common->wcmResolX,
		  common->wcmResolY));

    /* We can't change the resolution on PenPartner and Graphire models */
    if (!is_a_penpartner && common->wcmProtocolLevel == 4) {
	int resolX = common->wcmResolX, resolY = common->wcmResolY;
	
	/* Force the resolution.
	 */
        if (((float)version) >= 1.2) {
	    resolX = resolY = 2540;
	}
	sprintf(buffer, "%s%d\r", WC_NEW_RESOLUTION, resolX);
	SYSCALL(err = write(local->fd, buffer, strlen(buffer)));
	
	/* Verify the resolution change.
	 */
	DBG(2, ErrorF("rereading config\n"));
	if (send_request(local->fd, WC_CONFIG, buffer)) {
	    DBG(2, ErrorF("%s\n", buffer));
	    /* The header string is simply a place to put the unwanted
	     * config header don't use buffer+xx because the header size
	     * varies on different tablets
	     */
	    if (sscanf(buffer, "%[^,],%d,%d,%d,%d", header, &a, &b, &common->wcmResolX, &common->wcmResolY) == 5) {
		DBG(6, ErrorF("WC_CONFIG Header = %s\n", header));
	    }
	    else {
		ErrorF("WACOM: unable to reparse resolution. Using previous values.\n");
	    }
	}
	else {
	    ErrorF("WACOM: unable to reread resolution. Using previous values.\n");
	}
	
	/* The following couple of lines convert the MaxX and MaxY returned by
	 * the Wacom from 1270lpi to the Wacom's active resolution.
	 */
	common->wcmMaxX = (common->wcmMaxX / MAX_COORD_RES) * common->wcmResolX;
	common->wcmMaxY = (common->wcmMaxY / MAX_COORD_RES) * common->wcmResolY;
    }
    
    DBG(2, ErrorF("setup is max X=%d max Y=%d resol X=%d resol Y=%d\n",
		  common->wcmMaxX, common->wcmMaxY, common->wcmResolX,
		  common->wcmResolY));
  
    /* Send a setup string to the tablet */
    if (is_a_penpartner) {
	SYSCALL(err = write(local->fd, penpartner_setup_string,
			    strlen(penpartner_setup_string)));
    }
    else if (common->wcmProtocolLevel == 4) {
        SYSCALL(err = write(local->fd, WC_RESET, strlen(WC_RESET)));
	WAIT(75);
	SYSCALL(err = write(local->fd, setup_string, strlen(setup_string)));
    }
    else {
	SYSCALL(err = write(local->fd, intuos_setup_string,
			    strlen(intuos_setup_string)));
    }
    
    if (err == -1) {
	ErrorF("Wacom write error : %s\n", strerror(errno));
	return !Success;
    }

    /* Send the tilt mode command after setup because it must be enabled */
    /* after multi-mode to take precedence */
    if (common->wcmProtocolLevel == 4 && HANDLE_TILT(common)) {
	DBG(2, ErrorF("Sending tilt mode order\n"));
	
	SYSCALL(err = write(local->fd, WC_TILT_MODE, strlen(WC_TILT_MODE)));
	if (err == -1) {
	    ErrorF("Wacom write error : %s\n", strerror(errno));
	    return !Success;
	}
    }
  
    if (common->wcmSuppress < 0) {
	int	xratio = common->wcmMaxX/screenInfo.screens[0]->width;
	int	yratio = common->wcmMaxY/screenInfo.screens[0]->height;
	
	common->wcmSuppress = (xratio > yratio) ? yratio : xratio;
    }
    
    if (common->wcmSuppress > 100) {
	common->wcmSuppress = 99;
    }

    if (common->wcmProtocolLevel == 4) {
	char	buf[20];
      
	sprintf(buf, "%s%d\r", WC_SUPPRESS, common->wcmSuppress);
	SYSCALL(err = write(local->fd, buf, strlen(buf)));

	if (err == -1) {
	    ErrorF("Wacom write error : %s\n", strerror(errno));
	    return !Success;
	}
    }
    
    if (xf86Verbose)
	ErrorF("%s Wacom %s tablet maximum X=%d maximum Y=%d "
	       "X resolution=%d Y resolution=%d suppress=%d%s\n",
	       XCONFIG_PROBED, common->wcmProtocolLevel == 4 ? "IV" : "V",
	       common->wcmMaxX, common->wcmMaxY,
	       common->wcmResolX, common->wcmResolY, common->wcmSuppress,
	       HANDLE_TILT(common) ? " Tilt" : "");
  
    if (err <= 0) {
	SYSCALL(close(local->fd));
	return !Success;
    }

    /* change the serial speed if requested */
    if (common->wcmLinkSpeed > 9600) {
	if (common->wcmProtocolLevel == 5) {
	    char	*speed_init_string = WC_V_19200;
	    DBG(1, ErrorF("Switching serial link to %d\n", common->wcmLinkSpeed));

	    if (common->wcmLinkSpeed == 38400 && version < 2.0) {
		ErrorF("Wacom: 38400 speed not supported with this Intuos firmware (%f)\n", version);
		ErrorF("Switching to 19200\n");
		common->wcmLinkSpeed = 19200;
	    }

	    switch (common->wcmLinkSpeed) {
	    case 38400:
		speed_init_string = WC_V_38400;
		break;

	    case 19200:
		speed_init_string = WC_V_19200;
		break;
	    }
	    /* Switch the tablet to the requested speed */
	    SYSCALL(err = write(local->fd, speed_init_string, strlen(speed_init_string)));
	    if (err == -1) {
		ErrorF("Wacom write error : %s\n", strerror(errno));
		return !Success;
	    }
	    
	    /* Wait 75 mSecs */
	    WAIT(75);
    
	    /* Set the speed of the serial link to requested speed */
	    if (xf86SetSerialSpeed(local->fd, common->wcmLinkSpeed) < 0) {
		return !Success;
	    }
	}
	else {
	    ErrorF("Changing the speed of a wacom IV device is not yet implemented\n");
	}
    }

    /* Tell the tablet to start sending coordinates */
    SYSCALL(err = write(local->fd, WC_START, strlen(WC_START)));

    if (err == -1) {
	ErrorF("Wacom write error : %s\n", strerror(errno));
	return !Success;
    }

    return Success;
}

/*
 ***************************************************************************
 *
 * xf86WcmOpenDevice --
 *	Open the physical device and init information structs.
 *
 ***************************************************************************
 */
static int
xf86WcmOpenDevice(DeviceIntPtr       pWcm)
{
    LocalDevicePtr	local = (LocalDevicePtr)pWcm->public.devicePrivate;
    WacomDevicePtr	priv = (WacomDevicePtr)PRIVATE(pWcm);
    WacomCommonPtr	common = priv->common;
    double		screenRatio, tabletRatio;
    int			gap;
    int			loop;
    int			screen_idx = 0;
    
    if (local->fd < 0) {
        if (common->wcmInitNumber > 2 ||
	    priv->initNumber == common->wcmInitNumber) {
	    if (common->wcmOpen(local) != Success) {
	        if (local->fd >= 0) {
		    SYSCALL(close(local->fd));
	        }
	        local->fd = -1;
	    }
	    else {
	        /* report the file descriptor to all devices */
	        for(loop=0; loop<common->wcmNumDevices; loop++) {
		    common->wcmDevices[loop]->fd = local->fd;
	        }
	    }
	    common->wcmInitNumber++;
	    priv->initNumber = common->wcmInitNumber;
	}
	else {
	  priv->initNumber = common->wcmInitNumber;
	}
    }

    if (local->fd != -1 &&
	priv->factorX == 0.0) {
	
	if (priv->bottomX == 0) priv->bottomX = common->wcmMaxX;

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

	/* Verify Box validity */

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

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

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

	if (priv->bottomY > common->wcmMaxY ||
	    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 >= screenInfo.numScreens ||
	     priv->screen_no < 0)) {
	    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) {
	    screen_idx = priv->screen_no;
	}
	
	if (priv->flags & KEEP_SHAPE_FLAG) {
	    screenRatio = ((double) screenInfo.screens[screen_idx]->width)
		/ screenInfo.screens[screen_idx]->height;

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

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

	    if (screenRatio > tabletRatio) {
		gap = common->wcmMaxY * (1 - tabletRatio/screenRatio);
		priv->bottomX = common->wcmMaxX;
		priv->bottomY = common->wcmMaxY - gap;
	    } else {
		gap = common->wcmMaxX * (1 - screenRatio/tabletRatio);
		priv->bottomX = common->wcmMaxX - gap;
		priv->bottomY = common->wcmMaxY;
	    }
	}
	priv->factorX = ((double) screenInfo.screens[0]->width)
	    / (priv->bottomX - priv->topX);
	priv->factorY = ((double) screenInfo.screens[0]->height)
	    / (priv->bottomY - priv->topY);
    
	if (xf86Verbose)
	    ErrorF("%s Wacom tablet top X=%d top Y=%d "
		   "bottom X=%d bottom Y=%d\n",
		   XCONFIG_PROBED, priv->topX, priv->topY,
		   priv->bottomX, priv->bottomY);
	
	DBG(2, ErrorF("X factor = %.3g, Y factor = %.3g\n",
		      priv->factorX, priv->factorY));
    }

    /* Check threshold correctness */
    DBG(2, ErrorF("Threshold=%d\n", common->wcmThreshold));
    
    if (common->wcmThreshold > common->wcmMaxZ ||
	common->wcmThreshold < 0) {
	if (((common->wcmProtocolLevel == 5) ||
	     (common->wcmFlags & GRAPHIRE_FLAG)) &&
	    xf86Verbose &&
	    common->wcmThreshold != INVALID_THRESHOLD)
	    ErrorF("%s Wacom invalid threshold %d. Reset to %d\n",
		   XCONFIG_PROBED, common->wcmThreshold, common->wcmMaxZ / 3);
	common->wcmThreshold = common->wcmMaxZ / 3;
    }
    DBG(2, ErrorF("New threshold=%d\n", common->wcmThreshold));    

    /* Set the real values */
    InitValuatorAxisStruct(pWcm,
			   0,
			   0,		/* min val */
			   priv->bottomX - priv->topX, /* max val */
			   mils(common->wcmResolX), /* resolution */
			   0,		/* min_res */
			   mils(common->wcmResolX)); /* max_res */
    InitValuatorAxisStruct(pWcm,
			   1,
			   0,		/* min val */
			   priv->bottomY - priv->topY, /* max val */
			   mils(common->wcmResolY), /* resolution */
			   0,		/* min_res */
			   mils(common->wcmResolY)); /* max_res */
    InitValuatorAxisStruct(pWcm,
			   2,
			   0,		/* min val */
			   common->wcmMaxZ, /* max val */
			   mils(common->wcmResolZ), /* resolution */
			   0,		/* min_res */
			   mils(common->wcmResolZ)); /* max_res */
    InitValuatorAxisStruct(pWcm,
			   3,
			   -64,		/* min val */
			   63,		/* max val */
			   128,		/* resolution ??? */
			   0,
			   128);
    InitValuatorAxisStruct(pWcm,
			   4,
			   -64,		/* min val */
			   63,		/* max val */
			   128,		/* resolution ??? */
			   0,
			   128);
    InitValuatorAxisStruct(pWcm,
			   5,
			   0,		/* min val */
			   1023,        /* max val */
			   128,		/* resolution ??? */
			   0,
			   128);
    
    return (local->fd != -1);
}

/*
 ***************************************************************************
 *
 * xf86WcmClose --
 *
 ***************************************************************************
 */
static void
xf86WcmClose(LocalDevicePtr	local)
{
    WacomDevicePtr	priv = (WacomDevicePtr)local->private;
    WacomCommonPtr	common = priv->common;
    int			loop;
    int			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) {		    
	SYSCALL(close(local->fd));
    }
    
    local->fd = -1;
}

/*
 ***************************************************************************
 *
 * xf86WcmProc --
 *      Handle the initialization, etc. of a wacom
 *
 ***************************************************************************
 */
static int
xf86WcmProc(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=0x%x priv=0x%x type=%s flags=%d what=%d\n",
		  pWcm, priv, (DEVICE_ID(priv->flags) == STYLUS_ID) ? "stylus" :
		  (DEVICE_ID(priv->flags) == CURSOR_ID) ? "cursor" : "eraser",
		  priv->flags, what));
  
    switch (what)
	{
	case DEVICE_INIT: 
	    DBG(1, ErrorF("xf86WcmProc pWcm=0x%x what=INIT\n", 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,
						 xf86WcmControlProc) == 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 the motion history buffer if needed */
		xf86MotionHistoryAllocate(local);
	    }

	    /* open the device to gather informations */
	    xf86WcmOpenDevice(pWcm);

	    break; 
      
	case DEVICE_ON:
	    DBG(1, ErrorF("xf86WcmProc pWcm=0x%x what=ON\n", pWcm));

	    if ((local->fd < 0) && (!xf86WcmOpenDevice(pWcm))) {
		return !Success;
	    }
	    xf86AddEnabledDevice(local);
	    pWcm->public.on = TRUE;
	    break;
      
	case DEVICE_OFF:
	    DBG(1, ErrorF("xf86WcmProc  pWcm=0x%x what=%s\n", pWcm,
			  (what == DEVICE_CLOSE) ? "CLOSE" : "OFF"));
	    if (local->fd >= 0) {
		xf86RemoveEnabledDevice(local);
		xf86WcmClose(local);
	    }
	    pWcm->public.on = FALSE;
	    break;
      
	case DEVICE_CLOSE:
	    DBG(1, ErrorF("xf86WcmProc  pWcm=0x%x what=%s\n", pWcm,
			  (what == DEVICE_CLOSE) ? "CLOSE" : "OFF"));
	    xf86WcmClose(local);
	    break;

	default:
	    ErrorF("unsupported mode=%d\n", what);
	    return !Success;
	    break;
	}
    DBG(2, ErrorF("END   xf86WcmProc Success what=%d dev=0x%x priv=0x%x\n",
		  what, pWcm, priv));
    return Success;
}

/*
 ***************************************************************************
 *
 * xf86WcmChangeControl --
 *
 ***************************************************************************
 */
static int
xf86WcmChangeControl(LocalDevicePtr	local,
		     xDeviceCtl		*control)
{
    xDeviceResolutionCtl	*res;
    int				*resolutions;
    char			str[10];
  
    res = (xDeviceResolutionCtl *)control;
	
    if ((control->control != DEVICE_RESOLUTION) ||
	(res->num_valuators < 1))
	return (BadMatch);
  
    resolutions = (int *)(res +1);
    
    DBG(3, ErrorF("xf86WcmChangeControl changing to %d (suppressing under)\n",
		  resolutions[0]));

    sprintf(str, "SU%d\r", resolutions[0]);
    SYSCALL(write(local->fd, str, strlen(str)));
  
    return(Success);
}

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

    DBG(3, ErrorF("xf86WcmSwitchMode dev=0x%x mode=%d\n", dev, mode));
  
    if (mode == Absolute) {
	priv->flags = priv->flags | ABSOLUTE_FLAG;
    }
    else {
	if (mode == Relative) {
	    priv->flags = priv->flags & ~ABSOLUTE_FLAG; 
	}
	else {
	    DBG(1, ErrorF("xf86WcmSwitchMode dev=0x%x invalid mode=%d\n", dev,
			  mode));
	    return BadMatch;
	}
    }
    return Success;
}

/*
 ***************************************************************************
 *
 * xf86WcmAllocate --
 *
 ***************************************************************************
 */
static LocalDevicePtr
xf86WcmAllocate(char *  name,
                int     flag)
{
    LocalDevicePtr        local;
    WacomDevicePtr        priv;
    WacomCommonPtr        common;

    priv = (WacomDevicePtr) xalloc(sizeof(WacomDeviceRec));
    if (!priv)
	return NULL;

    common = (WacomCommonPtr) xalloc(sizeof(WacomCommonRec));
    if (!common) {
	xfree(priv);
	return NULL;
    }

    local = xf86AllocateInput(wcmDrv, 0);
    if (!local) {
	xfree(priv);
	xfree(common);
	return NULL;
    }

    local->name = name;
    local->flags = 0;
    local->device_control = xf86WcmProc;
    local->read_input = xf86WcmReadInput;
    local->control_proc = xf86WcmChangeControl;
    local->close_proc = xf86WcmClose;
    local->switch_mode = xf86WcmSwitchMode;
    local->conversion_proc = xf86WcmConvert;
    local->reverse_conversion_proc = xf86WcmReverseConvert;
    local->fd = -1;
    local->atom = 0;
    local->dev = NULL;
    local->private = priv;
    local->private_flags = 0;
    local->history_size  = 0;
    local->old_x = -1;
    local->old_y = -1;
    
    priv->flags = flag;			/* various flags (device type, absolute, first touch...) */
    priv->oldX = -1;			/* previous X position */
    priv->oldY = -1;			/* previous Y position */
    priv->oldZ = -1;			/* previous pressure */
    priv->oldTiltX = -1;		/* previous tilt in x direction */
    priv->oldTiltY = -1;		/* previous tilt in y direction */
    priv->oldButtons = 0;		/* previous buttons state */
    priv->oldProximity = 0;		/* previous proximity */
    priv->topX = 0;			/* X top */
    priv->topY = 0;			/* Y top */
    priv->bottomX = 0;			/* X bottom */
    priv->bottomY = 0;			/* Y bottom */
    priv->factorX = 0.0;		/* X factor */
    priv->factorY = 0.0;		/* Y factor */
    priv->common = common;		/* common info pointer */
    priv->oldProximity = 0;		/* previous proximity */
    priv->serial = 0;		        /* serial number */
    priv->initNumber = 0;	        /* magic number for the init phasis */
    priv->screen_no = -1;		/* associated screen */
    
    common->wcmDevice = "";		/* device file name */
    common->wcmSuppress = -1;		/* transmit position if increment is superior */
    common->wcmFlags = 0;		/* various flags */
    common->wcmDevices = (LocalDevicePtr*) xalloc(sizeof(LocalDevicePtr));
    common->wcmDevices[0] = local;
    common->wcmNumDevices = 1;		/* number of devices */
    common->wcmIndex = 0;		/* number of bytes read */
    common->wcmPktLength = 7;		/* length of a packet */
    common->wcmMaxX = 0;		/* max X value */
    common->wcmMaxY = 0;		/* max Y value */
    common->wcmMaxZ = DEFAULT_MAXZ;	/* max Z value */
    common->wcmResolX = 0;		/* X resolution in points/inch */
    common->wcmResolY = 0;		/* Y resolution in points/inch */
    common->wcmResolZ = 1270;		/* Z resolution in points/inch */
    common->wcmHasEraser = (flag & ERASER_ID) ? TRUE : FALSE;	/* True if an eraser has been configured */
    common->wcmStylusSide = TRUE;	/* eraser or stylus ? */
    common->wcmStylusProximity = FALSE;	/* a stylus is in proximity ? */
    common->wcmProtocolLevel = 4;	/* protocol level */
    common->wcmThreshold = INVALID_THRESHOLD; /* button 1 threshold for some tablet models */
    common->wcmInitNumber = 0;	        /* magic number for the init phasis */
    common->wcmLinkSpeed = 9600;        /* serial link speed */
    common->wcmOpen = xf86WcmOpen;	/* function used to open the line (serial or USB) */
    common->wcmLastSerial = 0;		/* last device (used by the USB part) */
    return local;
}

/*
 ***************************************************************************
 *
 * xf86WcmAllocateStylus --
 *
 ***************************************************************************
 */
static LocalDevicePtr
xf86WcmAllocateStylus()
{
    LocalDevicePtr        local = xf86WcmAllocate(XI_STYLUS, STYLUS_ID);

    if (local)
	local->type_name = "Wacom Stylus";
    return local;
}

/*
 ***************************************************************************
 *
 * xf86WcmAllocateCursor --
 *
 ***************************************************************************
 */
static LocalDevicePtr
xf86WcmAllocateCursor()
{
    LocalDevicePtr        local = xf86WcmAllocate(XI_CURSOR, CURSOR_ID);

    if (local)
	local->type_name = "Wacom Cursor";
    return local;
}

/*
 ***************************************************************************
 *
 * xf86WcmAllocateEraser --
 *
 ***************************************************************************
 */
static LocalDevicePtr
xf86WcmAllocateEraser()
{
    LocalDevicePtr        local = xf86WcmAllocate(XI_ERASER, ABSOLUTE_FLAG|ERASER_ID);

    if (local)
	local->type_name = "Wacom Eraser";
    return local;
}

/*
 ***************************************************************************
 *
 * Wacom Stylus device association --
 *
 ***************************************************************************
 */
DeviceAssocRec wacom_stylus_assoc =
{
    STYLUS_SECTION_NAME,		/* config_section_name */
    xf86WcmAllocateStylus		/* device_allocate */
};

/*
 ***************************************************************************
 *
 * Wacom Cursor device association --
 *
 ***************************************************************************
 */
DeviceAssocRec wacom_cursor_assoc =
{
    CURSOR_SECTION_NAME,		/* config_section_name */
    xf86WcmAllocateCursor		/* device_allocate */
};

/*
 ***************************************************************************
 *
 * Wacom Eraser device association --
 *
 ***************************************************************************
 */
DeviceAssocRec wacom_eraser_assoc =
{
    ERASER_SECTION_NAME,		/* config_section_name */
    xf86WcmAllocateEraser		/* device_allocate */
};


/*
 * xf86WcmUninit --
 *
 * called when the driver is unloaded.
 */
static void
xf86WcmUninit(InputDriverPtr	drv,
	      LocalDevicePtr	local,
	      int flags)
{
    WacomDevicePtr	priv = (WacomDevicePtr) local->private;
    
    DBG(1, ErrorF("xf86WcmUninit\n"));
    
    xf86WcmProc(local->dev, DEVICE_OFF);
    
    xfree (priv);
    xf86DeleteInput(local, 0);    
}

/*
 * xf86WcmInit --
 *
 * called when the module subsection is found in XF86Config
 */
static InputInfoPtr
xf86WcmInit(InputDriverPtr	drv,
	    IDevPtr		dev,
	    int			flags)
{
    LocalDevicePtr	local = NULL;
    LocalDevicePtr	fakeLocal = NULL;
    WacomDevicePtr	priv = NULL;
    WacomCommonPtr	common = NULL;
    char		*s;
    LocalDevicePtr	localDevices;

    wcmDrv = drv;

    fakeLocal = (LocalDevicePtr) xcalloc(1, sizeof(LocalDeviceRec));
    if (!fakeLocal)
	return NULL;

    fakeLocal->conf_idev = dev;

    /* Force default serial port options to exist because the serial init
     * phasis is based on those values.
     */
    xf86CollectInputOptions(fakeLocal, default_options, NULL);

    /* Type is mandatory */
    s = xf86FindOptionValue(fakeLocal->options, "Type");

    if (s && (xf86NameCmp(s, "stylus") == 0)) {
	local = xf86WcmAllocateStylus();
    }
    else if (s && (xf86NameCmp(s, "cursor") == 0)) {
	local = xf86WcmAllocateCursor();
    }
    else if (s && (xf86NameCmp(s, "eraser") == 0)) {
	local = xf86WcmAllocateEraser();
    }
    else {
	xf86Msg(X_ERROR, "%s: No type or invalid type specified.\n"
		"Must be one of stylus, cursor or eraser\n",
		dev->identifier);
	goto SetupProc_fail;
    }
    
    if (!local) {
	xfree(fakeLocal);
	return NULL;
    }

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

    local->options = fakeLocal->options;
    local->conf_idev = fakeLocal->conf_idev;
    local->name = dev->identifier;
    xfree(fakeLocal);
    
    /* Serial Device is mandatory */
    common->wcmDevice = xf86FindOptionValue(local->options, "Device");

    if (!common->wcmDevice) {
	xf86Msg (X_ERROR, "%s: No Device specified.\n", dev->identifier);
	goto SetupProc_fail;
    }

    /* Lookup to see if there is another wacom device sharing
     * the same serial line.
     */
    localDevices = xf86FirstLocalDevice();
    
    while(localDevices) {
	if ((local != localDevices) &&
	    (localDevices->device_control == xf86WcmProc) &&
	    (strcmp(((WacomDevicePtr)localDevices->private)->common->wcmDevice,
		    common->wcmDevice) == 0)) {
		DBG(2, ErrorF("xf86WcmConfig wacom port share between"
			      " %s and %s\n",
			      local->name, localDevices->name));
		((WacomDevicePtr) localDevices->private)->common->wcmHasEraser |= common->wcmHasEraser;
		xfree(common->wcmDevices);
		xfree(common);
		common = priv->common = ((WacomDevicePtr) localDevices->private)->common;
		common->wcmNumDevices++;
		common->wcmDevices = (LocalDevicePtr *) xrealloc(common->wcmDevices,
								 sizeof(LocalDevicePtr) * common->wcmNumDevices);
		common->wcmDevices[common->wcmNumDevices - 1] = local;
		break;
	}
	localDevices = localDevices->next;
    }

    /* Process the common options. */
    xf86ProcessCommonOptions(local, local->options);

    /* Optional configuration */

    xf86Msg(X_CONFIG, "%s serial device is %s\n", dev->identifier,
	    common->wcmDevice);

    debug_level = xf86SetIntOption(local->options, "DebugLevel", debug_level);
    if (debug_level > 0) {
	xf86Msg(X_CONFIG, "WACOM: debug level set to %d\n", debug_level);
    }

    s = xf86FindOptionValue(local->options, "Mode");

    if (s && (xf86NameCmp(s, "absolute") == 0)) {
	priv->flags = priv->flags | ABSOLUTE_FLAG;
    }
    else if (s && (xf86NameCmp(s, "relative") == 0)) {
	priv->flags = priv->flags & ~ABSOLUTE_FLAG;
    }
    else if (s) {
	xf86Msg(X_ERROR, "%s: invalid Mode (should be absolute or relative). Using default.\n",
		dev->identifier);
    }
    xf86Msg(X_CONFIG, "%s is in %s mode\n", local->name,
	    (priv->flags & ABSOLUTE_FLAG) ? "absolute" : "relative");	    

    common->wcmSuppress = xf86SetIntOption(local->options, "Suppress", common->wcmSuppress);
    if (common->wcmSuppress != -1) {
	xf86Msg(X_CONFIG, "WACOM: suppress value is %d\n", XCONFIG_GIVEN,
		common->wcmSuppress);      
    }
    
    if (xf86SetBoolOption(local->options, "Tilt", (common->wcmFlags & TILT_FLAG))) {
	common->wcmFlags |= TILT_FLAG;
    }

#ifdef LINUX_INPUT
    if (xf86SetBoolOption(local->options, "USB", (common->wcmOpen == xf86WcmUSBOpen))) {
	local->read_input=xf86WcmReadUSBInput;
	common->wcmOpen=xf86WcmUSBOpen;
	xf86Msg(X_CONFIG, "%s: reading USB link\n", dev->identifier);
#else
    if (xf86SetBoolOption(local->options, "USB", 0)) {
	ErrorF("The USB version of the driver isn't available for your platform\n");
#endif
    }

    priv->screen_no = xf86SetIntOption(local->options, "ScreenNo", -1);
    if (priv->screen_no != -1) {
	xf86Msg(X_CONFIG, "%s: attached screen number %d\n", dev->identifier,
		priv->screen_no);
    }
	
    if (xf86SetBoolOption(local->options, "KeepShape", 0)) {
	priv->flags |= KEEP_SHAPE_FLAG;
	xf86Msg(X_CONFIG, "%s: keeps shape\n", dev->identifier);
    }

    priv->topX = xf86SetIntOption(local->options, "TopX", 0);
    if (priv->topX != 0) {
	xf86Msg(X_CONFIG, "%s: top x = %d\n", dev->identifier, priv->topX);
    }
    priv->topY = xf86SetIntOption(local->options, "TopY", 0);
    if (priv->topY != 0) {
	xf86Msg(X_CONFIG, "%s: top x = %d\n", dev->identifier, priv->topY);
    }
    priv->bottomX = xf86SetIntOption(local->options, "BottomX", 0);
    if (priv->bottomX != 0) {
	xf86Msg(X_CONFIG, "%s: bottom x = %d\n", dev->identifier,
		priv->bottomX);
    }
    priv->bottomY = xf86SetIntOption(local->options, "BottomY", 0);
    if (priv->bottomY != 0) {
	xf86Msg(X_CONFIG, "%s: bottom x = %d\n", dev->identifier,
		priv->bottomY);
    }
    priv->serial = xf86SetIntOption(local->options, "Serial", 0);
    if (priv->bottomY != 0) {
	xf86Msg(X_CONFIG, "%s: serial number = %u\n", dev->identifier,
		priv->serial);
    }
    common->wcmThreshold = xf86SetIntOption(local->options, "Threshold", common->wcmThreshold);
    if (common->wcmThreshold != INVALID_THRESHOLD) {
	xf86Msg(X_CONFIG, "%s: threshold = %d\n", dev->identifier,
		common->wcmThreshold);
    }
    common->wcmMaxX = xf86SetIntOption(local->options, "MaxX", common->wcmMaxX);
    if (common->wcmMaxX != 0) {
	xf86Msg(X_CONFIG, "%s: max x = %d\n", dev->identifier,
		common->wcmMaxX);
    }
    common->wcmMaxY = xf86SetIntOption(local->options, "MaxY", common->wcmMaxY);
    if (common->wcmMaxY != 0) {
	xf86Msg(X_CONFIG, "%s: max x = %d\n", dev->identifier,
		common->wcmMaxY);
    }
    common->wcmMaxZ = xf86SetIntOption(local->options, "MaxZ", common->wcmMaxZ);
    if (common->wcmMaxZ != DEFAULT_MAXZ) {
	xf86Msg(X_CONFIG, "%s: max x = %d\n", dev->identifier,
		common->wcmMaxZ);
    }
    common->wcmResolX = xf86SetIntOption(local->options, "ResolutionX", common->wcmResolX);
    if (common->wcmResolX != 0) {
	xf86Msg(X_CONFIG, "%s: resol x = %d\n", dev->identifier,
		common->wcmResolX);
    }
    common->wcmResolY = xf86SetIntOption(local->options, "ResolutionY", common->wcmResolY);
    if (common->wcmResolY != 0) {
	xf86Msg(X_CONFIG, "%s: resol y = %d\n", dev->identifier,
		common->wcmResolY);
    }
    common->wcmResolZ = xf86SetIntOption(local->options, "ResolutionZ", common->wcmResolZ);
    if (common->wcmResolZ != 0) {
	xf86Msg(X_CONFIG, "%s: resol z = %d\n", dev->identifier,
		common->wcmResolZ);
    }

    {
	int	val;
	val = xf86SetIntOption(local->options, "BaudRate", 0);

	switch(val) {
	case 38400:
	    common->wcmLinkSpeed = 38400;
	    break;
	case 19200:
	    common->wcmLinkSpeed = 19200;
	    break;
	case 9600:
	    common->wcmLinkSpeed = 9600;
	    break;
	default:
	    xf86Msg(X_ERROR, "%s: Illegal speed value (must be 9600 or 19200 or 38400).", dev->identifier);
	    break;
	}
	if (xf86Verbose)
	    xf86Msg(X_CONFIG, "%s: serial speed %u\n", dev->identifier,
		    val);
    }
    /* mark the device configured */
    local->flags |= XI86_POINTER_CAPABLE | XI86_CONFIGURED;

    /* return the LocalDevice */
    return (local);

  SetupProc_fail:
    if (common)
	xfree(common);
    if (priv)
	xfree(priv);
    if (local)
	xfree(local);
    return NULL;
}

#ifdef XFree86LOADER
static
#endif
InputDriverRec WACOM = {
    1,				/* driver version */
    "wacom",			/* driver name */
    NULL,			/* identify */
    xf86WcmInit,		/* pre-init */
    xf86WcmUninit,		/* un-init */
    NULL,			/* module */
    0				/* ref count */
};

/*
 ***************************************************************************
 *
 * Dynamic loading functions
 *
 ***************************************************************************
 */
#ifdef XFree86LOADER
/*
 * xf86WcmUnplug --
 *
 * called when the module subsection is found in XF86Config
 */
static void
xf86WcmUnplug(pointer	p)
{
    DBG(1, ErrorF("xf86WcmUnplug\n"));
}

/*
 * xf86WcmPlug --
 *
 * called when the module subsection is found in XF86Config
 */
static pointer
xf86WcmPlug(pointer	module,
	    pointer	options,
	    int		*errmaj,
	    int		*errmin)
{

    /* The following message causes xf86cfg to puke. Commented out for now */
#if 0
    xf86Msg(X_INFO, "Wacom driver level: %s\n", identification+strlen("$Identification: ")); 
#endif    

    xf86AddInputDriver(&WACOM, module, 0);

    return module;
}

static XF86ModuleVersionInfo xf86WcmVersionRec =
{
    "wacom",
    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 */
};

XF86ModuleData wacomModuleData = {&xf86WcmVersionRec,
				  xf86WcmPlug,
				  xf86WcmUnplug};

#endif /* XFree86LOADER */

/*
 * Local variables:
 * change-log-default-name: "~/xinput.log"
 * c-file-style: "bsd"
 * End:
 */
/* end of xf86Wacom.c */
