>From d10cd2623b71f38605386c2c23cfa1983aa7841d Mon Sep 17 00:00:00 2001
From: Sebastian Berthold <sb@sleif.de>
Date: Mon, 24 May 2010 20:43:54 +0200
Subject: [PATCH 2/2] reenable support for legacy serial tablets
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

mainly based on the patch from
http://codelab.wordpress.com/2010/02/21/wacom-hacking/ on John
Tsiombikas’ (Nuclear) blog, this is a reintegration of the serial
code from linuxwacom 0.8.4 to xf86-wacom-input
Signed-off-by: Sebastian Berthold <sb@sleif.de>
---
 man/wacom.man           |    8 +-
 src/Makefile.am         |    1 +
 src/wcmConfig.c         |    2 +-
 src/wcmSerial.c         | 1363 +++++++++++++++++++++++++++++++++++++++++++++++
 src/wcmSerial.h         |   88 +++
 src/wcmValidateDevice.c |    1 +
 src/xf86Wacom.c         |   14 +-
 src/xf86WacomDefs.h     |    8 +
 8 files changed, 1479 insertions(+), 6 deletions(-)
 create mode 100644 src/wcmSerial.c
 create mode 100644 src/wcmSerial.h

diff --git a/man/wacom.man b/man/wacom.man
index 1bccb93..81e3ae4 100644
--- a/man/wacom.man
+++ b/man/wacom.man
@@ -51,10 +51,10 @@ the tablet is plugged.  You have to specify it for each subsection with
 the same value if you want to have multiple devices with the same tablet.
 This option is mandatory.
 .TP 4
-.B Option \fI"ForceDevice"\fP \fI"ISDV4"\fP
-tells the driver to dialog with the tablet the serial Tablet PC way.  It 
-is a special Wacom IV protocol, called ISDV4 protocol.  This option is 
-mandatory for serial Tablet PCs only.
+.B Option \fI"ForceDevice"\fP \fI"ISDV4"|"SERIAL"\fP
+tells the driver to wether dialog with the tablet the serial Tablet PC way "ISDV4" or
+communicate with a legacy serial tablet "SERIAL". ISDV4 is a special Wacom IV protocol.
+This option is mandatory for legacy serial tablets only.
 .TP 4
 .B Option \fI"Suppress"\fP \fI"number"\fP
 sets the position increment under which not to transmit coordinates.
diff --git a/src/Makefile.am b/src/Makefile.am
index 9216f69..4eb0ccc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -34,6 +34,7 @@ AM_CFLAGS = $(XORG_CFLAGS) $(CWARNFLAGS)
 
 @DRIVER_NAME@_drv_la_SOURCES = xf86Wacom.c xf86Wacom.h \
 	wcmCommon.c wcmConfig.c wcmISDV4.c wcmISDV4.h \
+	wcmSerial.c wcmSerial.h \
 	wcmFilter.c wcmFilter.h xf86WacomDefs.h \
 	wcmUSB.c wcmXCommand.c wcmValidateDevice.c \
 	wcmTouchFilter.c
diff --git a/src/wcmConfig.c b/src/wcmConfig.c
index 2c00fea..1b38644 100644
--- a/src/wcmConfig.c
+++ b/src/wcmConfig.c
@@ -108,7 +108,7 @@ static int wcmAllocate(LocalDevicePtr local)
 	common->wcmFlags = RAW_FILTERING_FLAG;   /* various flags */
 	common->wcmDevices = priv;
 	common->wcmProtocolLevel = 4;      /* protocol level */
-	common->wcmSerialSpeed = 19200;		/* serial link speed */
+	common->wcmSerialSpeed = 38400;		/* serial link speed */
 
 	common->wcmDevCls = &gWacomUSBDevice; /* device-specific functions */
 	common->wcmTPCButton = 
diff --git a/src/wcmSerial.c b/src/wcmSerial.c
new file mode 100644
index 0000000..72cde22
--- /dev/null
+++ b/src/wcmSerial.c
@@ -0,0 +1,1363 @@
+/*
+ * Copyright 1995-2002 by Frederic Lepied, France. <Lepied@XFree86.org>
+ * Copyright 2002-2008 by Ping Cheng, Wacom Technology. <pingc@wacom.com>		
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "xf86Wacom.h"
+#include "xf86WacomDefs.h"
+#include <xf86_OSproc.h>
+#include "wcmSerial.h"
+#include "wcmFilter.h"
+
+/* Serial Support */
+static Bool serialDetect(LocalDevicePtr pDev);
+static Bool serialInit(LocalDevicePtr pDev, char* id, float *version);
+
+static int serialInitTablet(LocalDevicePtr local, char* id, float *version);
+static void serialInitIntuos(WacomCommonPtr common, const char* id, float version);
+static void serialInitCintiq(WacomCommonPtr common, const char* id, float version);
+static void serialInitPenPartner(WacomCommonPtr common, const char* id, float version);
+static void serialInitGraphire(WacomCommonPtr common, const char* id, float version);
+static void serialInitProtocol4(WacomCommonPtr common, const char* id, float version);
+static void serialGetResolution(LocalDevicePtr local);
+static int serialGetRanges(LocalDevicePtr local);
+static int serialResetIntuos(LocalDevicePtr local);
+static int serialResetCintiq(LocalDevicePtr local);
+static int serialResetPenPartner(LocalDevicePtr local);
+static int serialResetProtocol4(LocalDevicePtr local);
+static int serialEnableTiltProtocol4(LocalDevicePtr local);
+static int serialEnableSuppressProtocol4(LocalDevicePtr local);
+static int serialSetLinkSpeedIntuos(LocalDevicePtr local);
+static int serialSetLinkSpeedProtocol5(LocalDevicePtr local);
+static int serialStartTablet(LocalDevicePtr local);
+static int serialParseCintiq(LocalDevicePtr local, const unsigned char* data, int len);
+static int serialParseGraphire(LocalDevicePtr local, const unsigned char* data, int len);
+static int serialParseProtocol4(LocalDevicePtr local, const unsigned char* data, int len);
+static int serialParseProtocol5(LocalDevicePtr local, const unsigned char* data, int len);
+static void serialParseP4Common(LocalDevicePtr local, const unsigned char* data, 
+	WacomDeviceState* last, WacomDeviceState* ds);
+
+static int wcmWait(int t);
+static int wcmWriteWait(int fd, const char* request);
+static int wcmWaitForTablet(int fd, char* answer, int size);
+
+
+/*****************************************************************************
+ * Global Structures
+ ****************************************************************************/
+
+	WacomDeviceClass gWacomSerialDevice =
+	{
+		serialDetect,
+		serialInit,
+		wcmReadPacket,
+	};
+
+/*****************************************************************************
+ * Static Structures
+ ****************************************************************************/
+
+	static WacomModel serialIntuos =
+	{
+		"Serial Intuos",
+		serialInitIntuos,
+		NULL,           /* resolution not queried */
+		serialGetRanges,
+		serialStartTablet,
+		serialParseProtocol5,
+		wcmFilterIntuos,
+		serialResetIntuos,
+		NULL,           /* tilt automatically enabled */
+		NULL,           /* suppress implemented in software */
+		serialSetLinkSpeedIntuos,
+	};
+
+	static WacomModel serialIntuos2 =
+	{
+		"Serial Intuos2",
+		serialInitIntuos,
+		NULL,                 /* resolution not queried */
+		serialGetRanges,
+		serialStartTablet,
+		serialParseProtocol5,
+		wcmFilterIntuos,
+		serialResetIntuos,    /* same as Intuos */
+		NULL,                 /* tilt automatically enabled */
+		NULL,                 /* suppress implemented in software */
+		serialSetLinkSpeedProtocol5,
+	};
+
+	static WacomModel serialCintiq =
+	{
+		"Serial Cintiq",
+		serialInitCintiq,
+		serialGetResolution,
+		serialGetRanges,
+		serialStartTablet,
+		serialParseCintiq,
+		NULL,
+		serialResetCintiq,
+		serialEnableTiltProtocol4,
+		serialEnableSuppressProtocol4,
+		NULL,               /* link speed cannot be changed */
+	};
+
+	static WacomModel serialPenPartner =
+	{
+		"Serial PenPartner",
+		serialInitPenPartner,
+		NULL,               /* resolution not queried */
+		serialGetRanges,
+		serialStartTablet,
+		serialParseProtocol4,
+		wcmFilterCoord,
+		serialResetPenPartner,
+		serialEnableTiltProtocol4,
+		serialEnableSuppressProtocol4,
+		NULL,              /* link speed cannot be changed */
+	};	
+
+	static WacomModel serialGraphire =
+	{
+		"Serial Graphire",
+		serialInitGraphire,
+		NULL,                     /* resolution not queried */
+		NULL,                     /* ranges not supported */
+		serialStartTablet,
+		serialParseGraphire,
+		wcmFilterCoord,
+		serialResetPenPartner,    /* functionally very similar */
+		serialEnableTiltProtocol4,
+		serialEnableSuppressProtocol4,
+		NULL,                    /* link speed cannot be changed */
+	};
+
+	static WacomModel serialProtocol4 =
+	{
+		"Serial UD",
+		serialInitProtocol4,
+		serialGetResolution,
+		serialGetRanges,
+		serialStartTablet,
+		serialParseProtocol4,
+		NULL,
+		serialResetProtocol4,
+		serialEnableTiltProtocol4,
+		serialEnableSuppressProtocol4,
+		NULL,               /* link speed cannot be changed */
+	};
+
+/*****************************************************************************
+ * Setup strings
+ ****************************************************************************/
+
+	static const char * setup_string = WCSERIAL_MULTI WCSERIAL_UPPER_ORIGIN
+		WCSERIAL_ALL_MACRO WCSERIAL_NO_MACRO1 WCSERIAL_RATE WCSERIAL_NO_INCREMENT
+		WCSERIAL_STREAM_MODE WCSERIAL_ZFILTER;
+	static const char * pl_setup_string = WCSERIAL_UPPER_ORIGIN WCSERIAL_RATE
+		WCSERIAL_STREAM_MODE;
+	static const char * penpartner_setup_string = WCSERIAL_PRESSURE_MODE
+		WCSERIAL_START;
+	static const char * intuos_setup_string = WCSERIAL_V_MULTI WCSERIAL_V_ID WCSERIAL_RATE
+		WCSERIAL_START;
+
+	/* PROTOCOL 4 */
+
+	/* 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
+	*/
+
+/* TODO: To differ between driver and protocol parsing bugs, the same approach 
+         like in src/wcmISDV4.c and the isdv4Parse* functions should be 
+	 implemented here. */
+
+
+
+/*****************************************************************************
+ * wcmWrite --
+ *   send a request 
+ ****************************************************************************/
+
+static int wcmWriteWait(int fd, const char* request)
+{
+	int len, maxtry = MAXTRY;
+
+	/* send request string */
+	do
+	{
+		len = xf86WriteSerial(fd, request, strlen(request));
+		if ((len == -1) && (errno != EAGAIN))
+		{
+			xf86Msg(X_ERROR, "Wacom wcmWriteWait error : %s", strerror(errno));
+			return 0;
+		}
+
+		maxtry--;
+
+	} while ((len <= 0) && maxtry);
+
+	return maxtry;
+}
+
+/*****************************************************************************
+ * wcmWaitForTablet --
+ *   wait for tablet data 
+ ****************************************************************************/
+
+static int wcmWaitForTablet(int fd, char* answer, int size)
+{
+	int len, maxtry = MAXTRY;
+
+	/* Read size bytes of the answer */
+	do
+	{
+		if ((len = xf86WaitForInput(fd, 1000000)) > 0)
+		{
+			len = xf86ReadSerial(fd, answer, size);
+			if ((len == -1) && (errno != EAGAIN))
+			{
+				xf86Msg(X_ERROR, "Wacom wcmRead error : %s\n",
+						strerror(errno));
+				return 0;
+			}
+		}
+		maxtry--;
+	} while ((len <= 0) && maxtry);
+
+	return maxtry;
+}
+
+/*****************************************************************************
+ * xf86WcmSendRequest --
+ *   send a request and wait for the answer.
+ *   the answer must begin with the first two chars of the request.
+ *   The last character in the answer string is replaced by a \0.
+ ****************************************************************************/
+
+char* xf86WcmSendRequest(int fd, const char* request, char* answer, int maxlen)
+{
+	int len, nr = 0;
+	int maxtry = MAXTRY;
+
+	if (maxlen < 3)
+		return NULL;
+  
+	/* wait for request return */
+	if (!wcmWriteWait(fd, request))
+	{
+		xf86Msg(X_ERROR, "Wacom unable to wcmWrite request string '%s' "
+				"after %d tries\n", request, MAXTRY);
+		return NULL;
+	}
+
+	do
+	{
+		/* Read the first byte of the answer which must
+		 * be equal to the first byte of the request.
+		 */
+		maxtry = wcmWaitForTablet(fd, answer, 1);
+		if (answer[0] != request[0])
+		{
+			xf86Msg(X_ERROR, "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. */
+		if (!wcmWaitForTablet(fd, answer+1, 1))
+		{
+			xf86Msg(X_ERROR, "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]));
+
+
+	/* Read until we don't get anything or timeout. */
+
+	len = 2;
+	do
+	{
+		if (len == 2)
+		{
+			if (!wcmWaitForTablet(fd, answer+len, 1))
+			{
+				xf86Msg(X_ERROR, "Wacom unable to read last byte of request '%c%c' "
+					"answer after %d tries\n",
+					request[0], request[1], MAXTRY);
+				return NULL;
+			}
+			len++;
+		}
+
+		if ((nr = xf86WaitForInput(fd, 1000000)) > 0)
+		{
+			nr = xf86ReadSerial(fd, answer+len, 1);
+			if ((nr == -1) && (errno != EAGAIN))
+			{
+				xf86Msg(X_ERROR, "Wacom wcmRead in xf86WcmSendRequest error : %s\n",
+						strerror(errno));
+				return NULL;
+			}
+		}
+
+		if (nr > 0)
+		{
+			len += nr;
+			if (len >= (maxlen - 1))
+				return NULL;
+		}
+
+	} while (nr > 0);
+
+	if (len <= 3)
+		return NULL;
+
+	answer[len-1] = '\0';
+
+	return answer;
+}
+
+/*****************************************************************************
+ * serialDetect -- Test if the attached device is a serial.
+ ****************************************************************************/
+
+static Bool serialDetect(LocalDevicePtr local)
+{
+	char		*s;
+	s = xf86FindOptionValue(local->options, "ForceDevice");
+	if (s && (xf86NameCmp(s, "SERIAL") == 0))
+	{
+		return 1;
+	}
+	else
+	{
+		return 0;
+	}
+}
+
+static Bool serialInit(LocalDevicePtr local, char* id, float *version)
+{
+	int err;
+#ifdef DEBUG
+	WacomDevicePtr priv = (WacomDevicePtr)local->private;
+
+	DBG(1, priv,"initializing serial tablet\n");
+#endif
+
+	/* Set the speed of the serial link to 38400 */
+	if (xf86SetSerialSpeed(local->fd, 38400) < 0)
+		return !Success;
+
+	/* Send reset to the tablet */
+	err = xf86WriteSerial(local->fd, WCSERIAL_RESET_BAUD,
+		strlen(WCSERIAL_RESET_BAUD));
+
+	if (err == -1)
+	{
+		xf86Msg(X_ERROR, "Wacom wcmWrite error : %s\n", strerror(errno));
+		return !Success;
+	}
+
+	/* Wait 250 mSecs */
+	if (wcmWait(250))
+		return !Success;
+
+	/* Send reset to the tablet */
+	err = xf86WriteSerial(local->fd, WCSERIAL_RESET, strlen(WCSERIAL_RESET));
+	if (err == -1)
+	{
+		xf86Msg(X_ERROR, "Wacom wcmWrite error : %s\n", strerror(errno));
+		return !Success;
+	}
+
+	/* Wait 75 mSecs */
+	if (wcmWait(75))
+		return !Success;
+
+	/* Set the speed of the serial link to 19200 */
+	if (xf86SetSerialSpeed(local->fd, 19200) < 0)
+		return !Success;
+
+	/* Send reset to the tablet */
+	err = xf86WriteSerial(local->fd, WCSERIAL_RESET_BAUD,
+		strlen(WCSERIAL_RESET_BAUD));
+
+	if (err == -1)
+	{
+		xf86Msg(X_ERROR, "Wacom wcmWrite error : %s\n", strerror(errno));
+		return !Success;
+	}
+
+	/* Wait 250 mSecs */
+	if (wcmWait(250))
+		return !Success;
+
+	/* Send reset to the tablet */
+	err = xf86WriteSerial(local->fd, WCSERIAL_RESET, strlen(WCSERIAL_RESET));
+	if (err == -1)
+	{
+		xf86Msg(X_ERROR, "Wacom wcmWrite error : %s\n", strerror(errno));
+		return !Success;
+	}
+
+	/* Wait 75 mSecs */
+	if (wcmWait(75))
+		return !Success;
+
+	/* Set the speed of the serial link to 9600 */
+	if (xf86SetSerialSpeed(local->fd, 9600) < 0)
+		return !Success;
+
+	/* Send reset to the tablet */
+	err = xf86WriteSerial(local->fd, WCSERIAL_RESET_BAUD, strlen(WCSERIAL_RESET_BAUD));
+	if (err == -1)
+	{
+		xf86Msg(X_ERROR, "Wacom wcmWrite error : %s\n", strerror(errno));
+		return !Success;
+	}
+
+	/* Wait 250 mSecs */
+	if (wcmWait(250))
+		return !Success;
+
+	err = xf86WriteSerial(local->fd, WCSERIAL_STOP, strlen(WCSERIAL_STOP));
+	if (err == -1)
+	{
+		xf86Msg(X_ERROR, "Wacom wcmWrite error : %s\n", strerror(errno));
+		return !Success;
+	}
+
+	/* Wait 30 mSecs */
+	if (wcmWait(30))
+		return !Success;
+
+	xf86FlushInput(local->fd);
+
+	return serialInitTablet(local, id, version);
+}
+
+/*****************************************************************************
+ * serialInitTablet --
+ *   Initialize the tablet
+ ****************************************************************************/
+
+/* TODO: externalize all these checks into lookup tables instead of having
+         if/else for every combination.. */
+
+
+static int serialInitTablet(LocalDevicePtr local, char* id, float *version)
+{
+	int loop, idx;
+	char getID[BUFFER_SIZE];
+	WacomDevicePtr priv = (WacomDevicePtr)local->private;
+	WacomCommonPtr common = priv->common;
+
+	DBG(2, priv, "reading model\n");
+
+	if (!xf86WcmSendRequest(local->fd, WCSERIAL_MODEL, getID, sizeof(getID)))
+		return !Success;
+
+	DBG(2, priv, "%s\n", getID);
+
+	xf86Msg(X_DEFAULT, "Wacom tablet model : %s\n", getID+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(getID); loop>=0 && *(getID+loop) != 'V'; loop--);
+	for(idx=loop; idx<strlen(getID) && *(getID+idx) != '-'; idx++);
+	*(getID+idx) = '\0';
+
+	/* Extract version numbers */
+	sscanf(getID+loop+1, "%f", version);
+
+	/* Detect tablet model based on identifier */
+	if (getID[2] == 'G' && getID[3] == 'D')
+	{
+		common->wcmModel = &serialIntuos;
+		common->tablet_id = 0x20;
+	}
+	else if (getID[2] == 'X' && getID[3] == 'D')
+	{
+		common->wcmModel = &serialIntuos2;
+		common->tablet_id = 0x40;
+	}
+	else if ( (getID[2] == 'P' && getID[3] == 'L') ||
+		(getID[2] == 'D' && getID[3] == 'T') )
+	{
+		common->wcmModel = &serialCintiq;
+		common->tablet_id = 0x30;
+	}
+	else if (getID[2] == 'C' && getID[3] == 'T')
+	{
+		common->wcmModel = &serialPenPartner;
+		common->tablet_id = 0x00;
+	}
+	else if (getID[2] == 'E' && getID[3] == 'T')
+	{
+		common->wcmModel = &serialGraphire;
+		common->tablet_id = 0x10;
+	}
+	else
+	{
+		common->wcmModel = &serialProtocol4;
+		common->tablet_id = 0x03;
+	}
+	sprintf(id, "%s", getID);
+
+	return Success;
+}
+
+static int serialParseGraphire(LocalDevicePtr local, const unsigned char* data, int len)
+{
+	int n;
+	WacomDevicePtr priv = (WacomDevicePtr)local->private;
+	WacomCommonPtr common = priv->common;
+	WacomDeviceState* last = &common->wcmChannel[0].valid.state;
+	WacomDeviceState* ds;
+
+	if (len < sizeof(struct input_event))
+		return 0;
+
+	/* positive value is skip */
+	if ((n = xf86WcmSerialValidate(common,data)) > 0) return n;
+
+	/* pick up where we left off, minus relative values */
+	ds = &common->wcmChannel[0].work;
+	RESET_RELATIVE(*ds);
+
+	/* get pressure */
+	ds->pressure = ((data[6]&ZAXIS_BITS) << 2 ) +
+		((data[3]&ZAXIS_BIT) >> 1) +
+		((data[0]&ZAXIS_BIT) >> 6) +
+		((data[6]&ZAXIS_SIGN_BIT) ? 0 : 0x100);
+
+	/* get buttons */
+	ds->buttons = (data[3] & BUTTONS_BITS) >> 3;
+	
+	/* requires button info, so it goes down here. */
+	serialParseP4Common(local, data, last, ds);
+
+	/* handle relative wheel for non-stylus device */
+	if (ds->device_type == CURSOR_ID)
+	{
+		ds->relwheel = (data[6] & 0x30) >> 4;
+		if (data[6] & 0x40)
+			ds->relwheel = -ds->relwheel;
+	}
+
+	wcmEvent(common,0,ds);
+	return common->wcmPktLength;
+}
+
+static int serialParseCintiq(LocalDevicePtr local, const unsigned char* data, int len)
+{
+	int n;
+	WacomDevicePtr priv = (WacomDevicePtr)local->private;
+	WacomCommonPtr common = priv->common;
+	WacomDeviceState* last = &common->wcmChannel[0].valid.state;
+	WacomDeviceState* ds;
+
+	if (len < sizeof(struct input_event))
+		return 0;
+
+	/* positive value is skip */
+	if ((n = xf86WcmSerialValidate(common,data)) > 0) return n;
+
+	/* pick up where we left off, minus relative values */
+	ds = &common->wcmChannel[0].work;
+	RESET_RELATIVE(*ds);
+
+	/* get pressure */
+	if (common->wcmMaxZ == 255)
+	{
+		ds->pressure = ((data[6] & ZAXIS_BITS) << 1 ) |
+			((data[3] & ZAXIS_BIT) >> 2) |
+			((data[6] & ZAXIS_SIGN_BIT) ? 0 : 0x80);
+	}
+	else
+	{
+		/* which tablets use this? */
+		/* PL550 and PL800 apparently */
+		ds->pressure = ((data[6]&ZAXIS_BITS) << 2 ) +
+			((data[3]&ZAXIS_BIT) >> 1) +
+			((data[0]&ZAXIS_BIT) >> 6) +
+			((data[6]&ZAXIS_SIGN_BIT) ? 0 : 0x100);
+	}
+
+	/* get buttons */
+	ds->buttons = (data[3] & BUTTONS_BITS) >> 3;
+
+	/* requires button info, so it goes down here. */
+	serialParseP4Common(local, data, last, ds);
+
+	wcmEvent(common,0,ds);
+	return common->wcmPktLength;
+}
+
+static int serialParseProtocol4(LocalDevicePtr local, const unsigned char* data, int len)
+{
+	int n;
+	WacomDevicePtr priv = (WacomDevicePtr)local->private;
+	WacomCommonPtr common = priv->common;
+	WacomDeviceState* last = &common->wcmChannel[0].valid.state;
+	WacomDeviceState* ds;
+
+	if (len < sizeof(struct input_event))
+		return 0;
+
+	DBG(10, common, "serialParseProtocol4 \n");
+	/* positive value is skip */
+	if ((n = xf86WcmSerialValidate(common,data)) > 0) return n;
+
+	/* pick up where we left off, minus relative values */
+	ds = &common->wcmChannel[0].work;
+	RESET_RELATIVE(*ds);
+
+	/* get pressure */
+	if (common->wcmMaxZ == 255)
+		ds->pressure = ((data[6] & ZAXIS_BITS) << 1 ) |
+			((data[3] & ZAXIS_BIT) >> 2) |
+			((data[6] & ZAXIS_SIGN_BIT) ? 0 : 0x80);
+
+	else
+		ds->pressure = (data[6] & ZAXIS_BITS) |
+			((data[6] & ZAXIS_SIGN_BIT) ? 0 : 0x40);
+
+	/* get button state */
+	ds->buttons = (data[3] & BUTTONS_BITS) >> 3;
+
+	/* requires button info, so it goes down here. */
+	serialParseP4Common(local, data, last, ds);
+
+	wcmEvent(common,0,ds);
+	return common->wcmPktLength;
+}
+
+static int serialParseProtocol5(LocalDevicePtr local, const unsigned char* data, int len)
+{
+	int n;
+	int have_data=0;
+	int channel;
+	WacomDeviceState* ds;
+	WacomDevicePtr priv = (WacomDevicePtr)local->private;
+	WacomCommonPtr common = priv->common;
+
+	if (len < sizeof(struct input_event))
+		return 0;
+
+	DBG(10, common, "serialParseProtocol5 \n");
+	/* positive value is skip */
+	if ((n = xf86WcmSerialValidate(common,data)) > 0) return n;
+
+	/* Protocol 5 devices support 2 data channels */
+	channel = data[0] & 0x01;
+
+	/* pick up where we left off, minus relative values */
+	ds = &common->wcmChannel[channel].work;
+	RESET_RELATIVE(*ds);
+
+	DBG(7, common, "packet header = %x\n", data[0]);
+
+	/* Device ID packet */
+	if ((data[0] & 0xfc) == 0xc0)
+	{
+		/* start from scratch */
+		memset(ds, 0, sizeof(*ds));
+
+		ds->proximity = 1;
+		ds->device_id = (((data[1] & 0x7f) << 5) |
+				((data[2] & 0x7c) >> 2));
+		ds->serial_num = (((data[2] & 0x03) << 30) |
+				((data[3] & 0x7f) << 23) |
+				((data[4] & 0x7f) << 16) |
+				((data[5] & 0x7f) << 9) |
+				((data[6] & 0x7f) << 2) |
+				((data[7] & 0x60) >> 5));
+
+		if ((ds->device_id & 0xf06) != 0x802)
+			ds->discard_first = 1;
+
+		if (STYLUS_TOOL(ds))
+			ds->device_type = STYLUS_ID;
+		else if (CURSOR_TOOL(ds))
+			ds->device_type = CURSOR_ID;
+		else
+			ds->device_type = ERASER_ID;
+
+		DBG(6, common, 
+			"device_id=%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 ((data[0] & 0xfe) == 0x80)
+	{
+		ds->proximity = 0;
+		have_data = 1;
+	}
+
+	/* General pen packet or eraser packet or airbrush first packet
+	 * airbrush second packet */
+	else if (((data[0] & 0xb8) == 0xa0) ||
+			((data[0] & 0xbe) == 0xb4))
+	{
+		ds->x = (((data[1] & 0x7f) << 9) |
+				((data[2] & 0x7f) << 2) |
+				((data[3] & 0x60) >> 5));
+		ds->y = (((data[3] & 0x1f) << 11) |
+				((data[4] & 0x7f) << 4) |
+				((data[5] & 0x78) >> 3));
+		if ((data[0] & 0xb8) == 0xa0)
+		{
+			ds->pressure = (((data[5] & 0x07) << 7) |
+				(data[6] & 0x7f));
+			ds->buttons = (data[0] & 0x06);
+		}
+		else
+		{
+			/* 10 bits for absolute wheel position */
+			ds->abswheel = (((data[5] & 0x07) << 7) |
+				(data[6] & 0x7f));
+		}
+		ds->tiltx = (data[7] & TILT_BITS);
+		ds->tilty = (data[8] & TILT_BITS);
+		if (data[7] & TILT_SIGN_BIT)
+			ds->tiltx -= (TILT_BITS + 1);
+		if (data[8] & TILT_SIGN_BIT)
+			ds->tilty -= (TILT_BITS + 1);
+		ds->proximity = (data[0] & PROXIMITY_BIT);
+		have_data = 1;
+	} /* end pen packet */
+
+	/* 4D mouse 1st packet or Lens cursor packet or 2D mouse packet*/
+	else if (((data[0] & 0xbe) == 0xa8) ||
+			((data[0] & 0xbe) == 0xb0))
+	{
+		ds->x = (((data[1] & 0x7f) << 9) |
+				((data[2] & 0x7f) << 2) |
+				((data[3] & 0x60) >> 5));
+		ds->y = (((data[3] & 0x1f) << 11) |
+				((data[4] & 0x7f) << 4) |
+				((data[5] & 0x78) >> 3));
+		ds->tilty = 0;
+
+		/* 4D mouse */
+		if (MOUSE_4D(ds))
+		{
+			ds->throttle = (((data[5] & 0x07) << 7) |
+				(data[6] & 0x7f));
+			if (data[8] & 0x08)
+				ds->throttle = -ds->throttle;
+			ds->buttons = (((data[8] & 0x70) >> 1) |
+				(data[8] & 0x07));
+			have_data = !ds->discard_first;
+		}
+
+		/* Lens cursor */
+		else if (LENS_CURSOR(ds))
+		{
+			ds->buttons = data[8];
+			have_data = 1;
+		}
+
+		/* 2D mouse */
+		else if (MOUSE_2D(ds))
+		{
+			ds->buttons = (data[8] & 0x1C) >> 2;
+			ds->relwheel = - (data[8] & 1) +
+					((data[8] & 2) >> 1);
+			have_data = 1; /* must send since relwheel is reset */
+		}
+
+		ds->proximity = (data[0] & PROXIMITY_BIT);
+	} /* end 4D mouse 1st packet */
+
+	/* 4D mouse 2nd packet */
+	else if ((data[0] & 0xbe) == 0xaa)
+	{
+		ds->x = (((data[1] & 0x7f) << 9) |
+			((data[2] & 0x7f) << 2) |
+			((data[3] & 0x60) >> 5));
+		ds->y = (((data[3] & 0x1f) << 11) |
+			((data[4] & 0x7f) << 4) |
+			((data[5] & 0x78) >> 3));
+		ds->rotation = (((data[6] & 0x0f) << 7) |
+			(data[7] & 0x7f));
+		if (ds->rotation < 900) ds->rotation = -ds->rotation;
+		else ds->rotation = 1799 - ds->rotation;
+		ds->proximity = (data[0] & PROXIMITY_BIT);
+		have_data = 1;
+		ds->discard_first = 0;
+	}
+	else
+	{
+		DBG(10, common, "unknown wacom V packet %x\n",data[0]);
+	}
+
+	/* if new data is available, send it */
+	if (have_data)
+	{
+	       	wcmEvent(common,channel,ds);
+	}
+	return common->wcmPktLength;
+}
+
+/*****************************************************************************
+ * Model-specific functions
+ ****************************************************************************/
+
+static void serialInitIntuos(WacomCommonPtr common, const char* id, float version)
+{
+	common->wcmProtocolLevel = 5;
+	common->wcmVersion = version;
+
+	common->wcmMaxZ = 1023;   /* max Z value */
+	common->wcmResolX = 2540; /* tablet X resolution in points/inch */
+	common->wcmResolY = 2540; /* tablet Y resolution in points/inch */
+	common->wcmPktLength = 9; /* length of a packet */
+	common->wcmFlags |= TILT_ENABLED_FLAG;
+}
+
+static void serialInitCintiq(WacomCommonPtr common, const char* id, float version)
+{
+	common->wcmProtocolLevel = 4;
+	common->wcmPktLength = 7;
+	common->wcmVersion = version;
+	common->wcmResolX = 508; /* tablet X resolution in points/inch */
+	common->wcmResolY = 508; /* tablet Y resolution in points/inch */
+
+	if (id[5] == '2')
+	{
+		/* PL-250  */
+		if ( id[6] == '5' )
+		{
+			common->wcmMaxZ = 255;
+		}
+		/* PL-270  */
+		else
+		{
+			common->wcmMaxZ = 255;
+		}
+	}
+	else if (id[5] == '3')
+	{
+		/* PL-300  */
+		common->wcmMaxZ = 255;
+	}
+	else if (id[5] == '4')
+	{
+		/* PL-400  */
+		common->wcmMaxZ = 255;
+	}
+	else if (id[5] == '5')
+	{
+		/* PL-550  */
+		if ( id[6] == '5' )
+		{
+			common->wcmMaxZ = 511;
+		}
+		/* PL-500  */
+		else
+		{
+			common->wcmMaxZ = 255;
+		}
+	}
+	else if (id[5] == '6')
+	{
+		/* PL-600SX  */
+		if ( id[8] == 'S' )
+		{
+			common->wcmMaxZ = 255;
+		}
+		/* PL-600  */
+		else
+		{
+			common->wcmMaxZ = 255;
+		}
+	}
+	else if (id[5] == '7')
+	{
+		/* PL-710  */
+		common->wcmMaxZ = 511;
+		common->wcmResolX = 2540;
+		common->wcmResolY = 2540;
+	}
+	else if (id[5] == '8')
+	{
+		/* PL-800  */
+		common->wcmMaxZ = 511;
+	}
+
+	/* tilt disabled */
+	common->wcmFlags &= ~TILT_ENABLED_FLAG;
+}
+
+static void serialInitPenPartner(WacomCommonPtr common, const char* id, float version)
+{
+	common->wcmProtocolLevel = 4;
+	common->wcmPktLength = 7;
+	common->wcmVersion = version;
+
+	common->wcmMaxZ = 255;
+	common->wcmResolX = 1000; /* tablet X resolution in points/inch */
+	common->wcmResolY = 1000; /* tablet Y resolution in points/inch */
+
+	/* tilt disabled */
+	common->wcmFlags &= ~TILT_ENABLED_FLAG;
+}
+
+static void serialInitGraphire(WacomCommonPtr common, const char* id, float version)
+{
+	common->wcmProtocolLevel = 4;
+	common->wcmPktLength = 7;
+	common->wcmVersion = version;
+
+	/* Graphire models don't answer WCSERIAL_COORD requests */
+	common->wcmMaxX = 5103;
+	common->wcmMaxY = 3711;
+	common->wcmMaxZ = 511;
+	common->wcmResolX = 1016; /* tablet X resolution in points/inch */
+	common->wcmResolY = 1016; /* tablet Y resolution in points/inch */
+
+	/* tilt disabled */
+	common->wcmFlags &= ~TILT_ENABLED_FLAG;
+}
+
+static void serialInitProtocol4(WacomCommonPtr common, const char* id, float version)
+{
+	common->wcmProtocolLevel = 4;
+	common->wcmPktLength = 7;
+	common->wcmVersion = version;
+
+	/* If no maxZ is set, determine from version */
+	if (!common->wcmMaxZ)
+	{
+		/* the rom version determines the max z */
+		if (version >= (float)1.2)
+			common->wcmMaxZ = 255;
+		else
+			common->wcmMaxZ = 120;
+	}
+
+	/* tilt disabled */
+	common->wcmFlags &= ~TILT_ENABLED_FLAG;
+}
+
+static void serialGetResolution(LocalDevicePtr local)
+{
+	int a, b;
+	char buffer[BUFFER_SIZE], header[BUFFER_SIZE];
+	WacomDevicePtr priv = (WacomDevicePtr)local->private;
+	WacomCommonPtr common = priv->common;
+
+	if (!(common->wcmResolX && common->wcmResolY))
+	{
+		DBG(2, priv,
+			"Requesting resolution from device\n");
+		if (xf86WcmSendRequest(local->fd, WCSERIAL_CONFIG, buffer,
+			sizeof(buffer)))
+		{
+			DBG(2, priv, "%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, priv, 
+					"WCSERIAL_CONFIG Header = %s\n", header);
+			}
+			else
+			{
+				xf86Msg(X_ERROR, "WACOM: unable to parse resolution. "
+					"Using default.\n");
+				common->wcmResolX = common->wcmResolY = 1270;
+			}
+		}
+		else
+		{
+			xf86Msg(X_ERROR, "WACOM: unable to read resolution. "
+				"Using default.\n");
+			common->wcmResolX = common->wcmResolY = 1270;
+		}
+	}
+
+	DBG(2, priv, "serialGetResolution: ResolX=%d ResolY=%d\n",
+		common->wcmResolX, common->wcmResolY);
+}
+
+static int serialGetRanges(LocalDevicePtr local)
+{
+	char buffer[BUFFER_SIZE];
+	WacomDevicePtr priv = (WacomDevicePtr)local->private;
+	WacomCommonPtr common = priv->common;
+
+	if (!(common->wcmMaxX && common->wcmMaxY))
+	{
+		DBG(2, priv, "Requesting max coordinates\n");
+		if (!xf86WcmSendRequest(local->fd, WCSERIAL_COORD, buffer,
+			sizeof(buffer)))
+		{
+			xf86Msg(X_ERROR, "WACOM: unable to read max coordinates. "
+				"Use the MaxX and MaxY options.\n");
+			return !Success;
+		}
+		DBG(2, priv, "%s\n", buffer);
+		if (sscanf(buffer+2, "%d,%d", &common->wcmMaxX,
+			&common->wcmMaxY) != 2)
+		{
+			xf86Msg(X_ERROR, "WACOM: unable to parse max coordinates. "
+				"Use the MaxX and MaxY options.\n");
+			return !Success;
+		}
+	}
+
+	DBG(2, priv, "serialGetRanges: maxX=%d maxY=%d (%g,%g in)\n",
+		common->wcmMaxX, common->wcmMaxY,
+		(double)common->wcmMaxX / common->wcmResolX,
+		(double)common->wcmMaxY / common->wcmResolY);
+
+	return Success;
+}
+
+static int serialResetIntuos(LocalDevicePtr local)
+{
+	int err;
+	err = xf86WriteSerial(local->fd, intuos_setup_string,
+		strlen(intuos_setup_string));
+	return (err == -1) ? !Success : Success;
+}
+
+static int serialResetCintiq(LocalDevicePtr local)
+{
+	int err;
+
+	err = xf86WriteSerial(local->fd, WCSERIAL_RESET, strlen(WCSERIAL_RESET));
+
+	if (wcmWait(75)) return !Success;
+
+	err = xf86WriteSerial(local->fd, pl_setup_string,
+		strlen(pl_setup_string));
+	if (err == -1) return !Success;
+
+	err = xf86WriteSerial(local->fd, penpartner_setup_string,
+		strlen(penpartner_setup_string));
+
+	return (err == -1) ? !Success : Success;
+}
+
+static int serialResetPenPartner(LocalDevicePtr local)
+{
+	int err;
+	err = xf86WriteSerial(local->fd, penpartner_setup_string,
+		strlen(penpartner_setup_string));
+	return (err == -1) ? !Success : Success;
+}
+
+static int serialResetProtocol4(LocalDevicePtr local)
+{
+	int err;
+
+	err = xf86WriteSerial(local->fd, WCSERIAL_RESET, strlen(WCSERIAL_RESET));
+
+	if (wcmWait(75)) return !Success;
+
+	err = xf86WriteSerial(local->fd, setup_string,
+		strlen(setup_string));
+	if (err == -1) return !Success;
+
+	err = xf86WriteSerial(local->fd, penpartner_setup_string,
+		strlen(penpartner_setup_string));
+	return (err == -1) ? !Success : Success;
+}
+
+static int serialEnableTiltProtocol4(LocalDevicePtr local)
+{
+	return Success;
+}
+
+static int serialEnableSuppressProtocol4(LocalDevicePtr local)
+{
+	char buf[20];
+	int err;
+	WacomCommonPtr common =	((WacomDevicePtr)(local->private))->common;
+
+	sprintf(buf, "%s%d\r", WCSERIAL_SUPPRESS, common->wcmSuppress);
+	err = xf86WriteSerial(local->fd, buf, strlen(buf));
+
+	if (err == -1)
+	{
+		xf86Msg(X_ERROR, "Wacom wcmWrite error : %s\n",
+			strerror(errno));
+		return !Success;
+	}
+	return Success;
+}
+
+static int serialSetLinkSpeedIntuos(LocalDevicePtr local)
+{
+	WacomCommonPtr common =	((WacomDevicePtr)(local->private))->common;
+
+	if ((common->wcmSerialSpeed == 38400) &&
+		(common->wcmVersion < 2.0F))
+	{
+		xf86Msg(X_ERROR, "Wacom: 38400 speed not supported with this Intuos "
+			"firmware (%f)\n", common->wcmVersion);
+		xf86Msg(X_ERROR, "Switching to 19200\n");
+		common->wcmSerialSpeed = 19200;
+	}
+	return serialSetLinkSpeedProtocol5(local);
+}
+
+static int serialSetLinkSpeedProtocol5(LocalDevicePtr local)
+{
+	int err;
+	char* speed_init_string;
+	WacomDevicePtr priv = (WacomDevicePtr)local->private;
+	WacomCommonPtr common =	priv->common;
+
+	DBG(1, priv, "Switching serial link to %d\n",
+		common->wcmSerialSpeed);
+
+	/* set init string according to speed */
+	speed_init_string = (common->wcmSerialSpeed == 38400) ?
+		WCSERIAL_V_38400 : WCSERIAL_V_19200;
+
+	/* Switch the tablet to the requested speed */
+	err = xf86WriteSerial(local->fd, speed_init_string,
+		strlen(speed_init_string));
+
+	if (err == -1)
+	{
+		xf86Msg(X_ERROR, "Wacom wcmWrite error : %s\n", strerror(errno));
+		return !Success;
+	}
+
+	/* Wait 75 mSecs */
+	if (wcmWait(75))
+		return !Success;
+
+	/* Set speed of serial link to requested speed */
+	if (xf86SetSerialSpeed(local->fd, common->wcmSerialSpeed) < 0)
+		return !Success;
+
+	return Success;
+}
+
+static int serialStartTablet(LocalDevicePtr local)
+{
+	int err;
+
+	/* Tell the tablet to start sending coordinates */
+	err = xf86WriteSerial(local->fd, WCSERIAL_START, strlen(WCSERIAL_START));
+
+	if (err == -1)
+	{
+		xf86Msg(X_ERROR, "Wacom wcmWrite error : %s\n", strerror(errno));
+		return !Success;
+	}
+
+	return Success;
+}
+
+static void serialParseP4Common(LocalDevicePtr local,
+	const unsigned char* data, WacomDeviceState* last,
+	WacomDeviceState* ds)
+{
+	WacomDevicePtr priv = (WacomDevicePtr)local->private;
+	WacomCommonPtr common = priv->common;
+	int is_stylus = (data[0] & POINTER_BIT);
+	int cur_type = is_stylus ?
+		((ds->buttons & 4) ? ERASER_ID : STYLUS_ID) :
+		CURSOR_ID;
+
+	/* for Graphire eraser */
+	if(ds->buttons & 8) cur_type = ERASER_ID;
+
+	/* proximity bit */
+	ds->proximity = (data[0] & PROXIMITY_BIT);
+
+	/* x and y coordinates */
+	ds->x = (((data[0] & 0x3) << 14) + (data[1] << 7) + data[2]);
+	ds->y = (((data[3] & 0x3) << 14) + (data[4] << 7) + data[5]);
+
+	/* handle tilt values only for stylus */
+	if (HANDLE_TILT(common) && is_stylus)
+	{
+		ds->tiltx = (data[7] & TILT_BITS);
+		ds->tilty = (data[8] & TILT_BITS);
+		if (data[7] & TILT_SIGN_BIT)
+			ds->tiltx -= common->wcmMaxtiltX/2;
+		if (data[8] & TILT_SIGN_BIT)
+			ds->tilty -= common->wcmMaxtiltY/2;
+	}
+
+	/* first time into prox */
+	if (!last->proximity && ds->proximity)
+		ds->device_type = cur_type;
+	/* check on previous proximity */
+	else if (ds->buttons && ds->proximity)
+	{
+		/* we might have been fooled by tip and second
+		 * sideswitch when it came into prox */
+		if ((ds->device_type != cur_type) &&
+			(ds->device_type == ERASER_ID))
+		{
+			/* send a prox-out for old device */
+			WacomDeviceState out = { 0 };
+			out.device_type = ERASER_ID;
+			wcmEvent(common, 0, &out);
+			ds->device_type = cur_type;
+		}
+	}
+
+	ds->device_id = (ds->device_type == CURSOR_ID) ? CURSOR_DEVICE_ID : STYLUS_DEVICE_ID;
+
+	/* don't send button event for eraser 
+	 * button 1 event will be sent by testing presure level
+	 */
+	if (ds->device_type == ERASER_ID) {
+		ds->buttons = 0;
+		ds->device_id = ERASER_DEVICE_ID;
+	}
+
+	DBG(8, common, "serialParseP4Common %s\n",
+		ds->device_type == CURSOR_ID ? "CURSOR" :
+		ds->device_type == ERASER_ID ? "ERASER " :
+		ds->device_type == STYLUS_ID ? "STYLUS" : "NONE");
+}
+
+static int wcmWait(int t)
+{
+	int err = xf86WaitForInput(-1, ((t) * 1000));
+	if (err != -1)
+		return Success;
+
+	xf86Msg(X_ERROR, "Wacom select error : %s\n", strerror(errno));
+	return err;
+}
+
+
+/*****************************************************************************
+ * xf86WcmSerialValidate -- validates serial packet; returns 0 on success,
+ *   positive number of bytes to skip on error.
+ ****************************************************************************/
+
+int xf86WcmSerialValidate(WacomCommonPtr common, const unsigned char* data)
+{
+	int i, bad = 0;
+
+	/* check magic */
+	for (i=0; i<common->wcmPktLength; ++i)
+	{
+		if ( ((i==0) && !(data[i] & HEADER_BIT)) || 
+				((i!=0) && (data[i] & HEADER_BIT)) )
+		{
+			bad = 1;
+			if (i!=0 && (data[i] & HEADER_BIT)) {
+				xf86Msg(X_ERROR, "xf86WcmSerialValidate: bad magic at %d "
+					"v=%x l=%d\n", i, data[i], common->wcmPktLength);
+				return i;
+			}
+		}
+	}
+	if (bad) return common->wcmPktLength;
+	else return 0;
+}
+
diff --git a/src/wcmSerial.h b/src/wcmSerial.h
new file mode 100644
index 0000000..98d75a4
--- /dev/null
+++ b/src/wcmSerial.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright 1995-2003 by Frederic Lepied, France. <Lepied@XFree86.org>
+ * Copyright 2002-2008 by Ping Cheng, Wacom Technology. <pingc@wacom.com>		
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __XF86_WCMSERIAL_H
+#define __XF86_WCMSERIAL_H
+
+#include "xf86Wacom.h"
+
+#define WCSERIAL_RESET 		"\r#"	/* reset wacom IV or wacom V command set */
+#define WCSERIAL_RESET_BAUD 	"\r$"	/* reset baud rate to default (wacom V) ors
+                                  	* switch to wacom IIs (wacom IV) */
+#define WCSERIAL_CONFIG 	"~R\r"	/* request a configuration string */
+#define WCSERIAL_COORD 		"~C\r"	/* request max coordinates */
+#define WCSERIAL_MODEL 		"~#\r"	/* request model and ROM version */
+
+#define WCSERIAL_MULTI 		"MU1\r"	/* multi mode input */
+#define WCSERIAL_UPPER_ORIGIN 	"OC1\r"	/* origin in upper left */
+#define WCSERIAL_SUPPRESS 	"SU"	/* suppress mode */
+#define WCSERIAL_ALL_MACRO 	"~M0\r"	/* enable all macro buttons */
+#define WCSERIAL_NO_MACRO1 	"~M1\r"	/* disable macro buttons of group 1 */
+#define WCSERIAL_RATE  		"IT0\r"	/* max transmit rate (unit of 5 ms) */
+#define WCSERIAL_TILT_MODE 	"FM1\r"	/* enable extra protocol for tilt management */
+#define WCSERIAL_NO_INCREMENT 	"IN0\r"	/* do not enable increment mode */
+#define WCSERIAL_STREAM_MODE 	"SR\r"	/* enable continuous mode */
+#define WCSERIAL_PRESSURE_MODE 	"PH1\r"	/* enable pressure mode */
+#define WCSERIAL_ZFILTER 	"ZF1\r"	/* stop sending coordinates */
+#define WCSERIAL_STOP  		"\nSP\r"	/* stop sending coordinates */
+#define WCSERIAL_START 		"ST\r"	/* start sending coordinates */
+#define WCSERIAL_NEW_RESOLUTION	"NR"	/* change the resolution */
+
+#define WCSERIAL_V_SINGLE	"MT0\r"
+#define WCSERIAL_V_MULTI 	"MT1\r"
+#define WCSERIAL_V_HEIGHT 	"HT1\r"
+#define WCSERIAL_V_ID  		"ID1\r"
+#define WCSERIAL_V_19200 	"BA19\r"
+#define WCSERIAL_V_38400 	"BA38\r"
+/*  #define WCSERIAL_V_9600 "BA96\r" */
+#define WCSERIAL_V_9600 	"$\r"
+
+#define WCSERIAL_RESET_19200 	"\r$"	/* reset to 9600 baud */
+
+#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
+#define RESET_RELATIVE(ds) do { (ds).relwheel = 0; } while (0)
+
+#define PEN(ds)         ((((ds)->device_id) & 0x07ff) == 0x0022 || \
+			(((ds)->device_id) & 0x07ff) == 0x0042 || \
+			(((ds)->device_id) & 0x07ff) == 0x0052)
+#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 MOUSE_2D(ds)	((((ds)->device_id) & 0x07ff) == 0x0007)
+#define LENS_CURSOR(ds)	((((ds)->device_id) & 0x07ff) == 0x0096)
+#define INKING_PEN(ds)	((((ds)->device_id) & 0x07ff) == 0x0012)
+#define STYLUS_TOOL(ds)	(PEN(ds) || STROKING_PEN(ds) || INKING_PEN(ds) || \
+			AIRBRUSH(ds))
+#define CURSOR_TOOL(ds)	(MOUSE_4D(ds) || LENS_CURSOR(ds) || MOUSE_2D(ds))
+
+
+char* xf86WcmSendRequest(int fd, const char* request, char* answer, int maxlen);
+int xf86WcmSerialValidate(WacomCommonPtr common, const unsigned char* data);
+
+#endif /* __XF86_WCMSERIAL_H */
+
diff --git a/src/wcmValidateDevice.c b/src/wcmValidateDevice.c
index 4938ff5..957fcaf 100644
--- a/src/wcmValidateDevice.c
+++ b/src/wcmValidateDevice.c
@@ -614,6 +614,7 @@ int wcmParseOptions(LocalDevicePtr local)
 		}
 	}
 
+	/* FIXME: common->wcmForceDevice isn't initialized at this point? */
 	if (common->wcmForceDevice == DEVICE_ISDV4)
         {
 		int val;
diff --git a/src/xf86Wacom.c b/src/xf86Wacom.c
index 0b39ce0..c685493 100644
--- a/src/xf86Wacom.c
+++ b/src/xf86Wacom.c
@@ -950,12 +950,24 @@ static Bool wcmOpen(LocalDevicePtr local)
 	 * Bluetooth is also considered as USB */
 	if (rc == 0) /* serial device */
 	{
-		/* only ISDV4 are supported on X server 1.7 and later */
+		/* Detect legacy serial device class */
+		if ((&gWacomSerialDevice)->Detect(local))
+		{
+			common->wcmForceDevice=DEVICE_SERIAL;
+			common->wcmDevCls = &gWacomSerialDevice;
+			common->wcmTPCButtonDefault = 0;
+			xf86Msg(X_CONFIG, "%s: forcing legacy tablet protocol\n",
+				local->name);
+		}
+		else
+		{
+		/* if ForceDevice option is not "SERIAL" we fallback to ISDV4 support */
 		common->wcmForceDevice=DEVICE_ISDV4;
 		common->wcmDevCls = &gWacomISDV4Device;
 
 		/* Tablet PC buttons on by default */
 		common->wcmTPCButtonDefault = 1;
+		}
 	}
 	else
 	{
diff --git a/src/xf86WacomDefs.h b/src/xf86WacomDefs.h
index 6a3bee1..2453716 100644
--- a/src/xf86WacomDefs.h
+++ b/src/xf86WacomDefs.h
@@ -91,6 +91,10 @@ struct _WacomModel
 	int (*FilterRaw)(WacomCommonPtr common, WacomChannelPtr pChannel,
 		WacomDeviceStatePtr ds);
 	int (*DetectConfig)(LocalDevicePtr local);
+	int (*Reset)(LocalDevicePtr local);
+	int (*EnableTilt)(LocalDevicePtr local);
+	int (*EnableSuppress)(LocalDevicePtr local);
+	int (*SetLinkSpeed)(LocalDevicePtr local);
 };
 
 /******************************************************************************
@@ -335,9 +339,12 @@ struct _WacomDeviceClass
 {
 	Bool (*Detect)(LocalDevicePtr local); /* detect device */
 	Bool (*Init)(LocalDevicePtr local, char* id, float *version);   /* initialize device */
+	void (*Read)(LocalDevicePtr local);   /* reads device */
+
 };
 
 extern WacomDeviceClass gWacomUSBDevice;
+extern WacomDeviceClass gWacomSerialDevice;
 extern WacomDeviceClass gWacomISDV4Device;
 
 /******************************************************************************
@@ -354,6 +361,7 @@ extern WacomDeviceClass gWacomISDV4Device;
 #define AUTODEV_FLAG		16
 
 #define DEVICE_ISDV4 		0x000C
+#define DEVICE_SERIAL 		0x000D
 
 #define MAX_CHANNELS 2
 #define MAX_FINGERS  2
-- 
1.7.0.4

