/* 
 * Copyright (c) 1999  Machine Vision Holdings Incorporated
 * Author: David Woodhouse <David.Woodhouse@mvhi.com>
 * CoAuthor: Mayk Langer <langer@vsys.de>
 *
 * Template driver used: Copyright (c) 1998  Metro Link Incorporated
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, cpy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Except as contained in this notice, the name of the Metro Link shall not be
 * used in advertising or otherwise to promote the sale, use or other dealings
 * in this Software without prior written authorization from Metro Link.
 *
 */
/* $XFree86: xc/programs/Xserver/hw/xfree86/input/penmount/xf86PM.c,v 1.2 2000/08/11 19:10:46 dawes Exp $ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define _PENMOUNT_C_

/* 
   #define PENMOUNT_XF86 : for XFree86 4.x
   #undef  PENMOUNT_XF86 : for Xorg
 */
#define	PENMOUNT_XF86 1



/*****************************************************************************
 *	Standard Headers
 ****************************************************************************/
#include <asm/types.h>
#include <linux/input.h>

/* keithp - a hack to avoid redefinitions of these in xf86str.h */
#ifdef BUS_PCI
#undef BUS_PCI
#endif
#ifdef BUS_ISA
#undef BUS_ISA
#endif

#include <misc.h>
#include <xf86.h>
#define NEED_XF86_TYPES
#include <xf86_ansic.h>
#include <xf86_OSproc.h>
#include <xf86Xinput.h>
#include <xisb.h>
#include <exevents.h>

/*****************************************************************************
 *	Local Headers
 ****************************************************************************/
#include "xf86PM.h"


/*****************************************************************************
 *	Macros
 ****************************************************************************/
#define SYSCALL(call) while(((call) == -1) && (errno == EINTR))
#define ABS(x) ((x) > 0 ? (x) : -(x))


/*****************************************************************************
 *	Local Variables
 ****************************************************************************/
#ifndef PENMOUNT_XF86
_X_EXPORT InputDriverRec PENMOUNT =
#else
InputDriverRec PENMOUNT =
#endif
{
        1,                  /* driverVersion    */
        "penmount",         /* driverName       */
        NULL,               /* Identify         */
        PenMountPreInit,    /* PreInit          */
        NULL,               /* UnInit           */
        NULL,               /* module           */
        0                   /* refCount         */
};


#ifdef XFree86LOADER

static XF86ModuleVersionInfo VersionRec =
{
	"penmount",             /* name of module                   */
	MODULEVENDORSTRING,     /* vendor specific string           */
	MODINFOSTRING1,         
	MODINFOSTRING2,
#ifndef PENMOUNT_XF86
	XORG_VERSION_CURRENT,   /* contains XF86_VERSION_CURRENT    */
#else
	XF86_VERSION_CURRENT,   /* contains XF86_VERSION_CURRENT    */
#endif
	1, 1, 0,                /* major version, minor version, patchlevel */
	ABI_CLASS_XINPUT,       /* ABI class                */
	ABI_XINPUT_VERSION,     /* ABI version              */
	MOD_CLASS_XINPUT,       /* module class description */
	{0, 0, 0, 0}			/* checksum, digital signature of the version info structure */

};


static const char *reqSymbols[] = {
	"AddEnabledDevice",
	"ErrorF",
	"InitButtonClassDeviceStruct",
	"InitProximityClassDeviceStruct",
	"InitValuatorAxisStruct",
	"InitValuatorClassDeviceStruct",
	"InitPtrFeedbackClassDeviceStruct",
	"RemoveEnabledDevice",
	"Xcalloc",
	"Xfree",
	"XisbBlockDuration",
	"XisbFree",
	"XisbNew",
	"XisbRead",
	"XisbTrace",
	"screenInfo",
	"xf86AddInputDriver",
	"xf86AllocateInput",
	"xf86CloseSerial",
	"xf86CollectInputOptions",
	"xf86ErrorFVerb",
	"xf86FindOptionValue",
	"xf86GetMotionEvents",
	"xf86GetVerbosity",
	"xf86MotionHistoryAllocate",
	"xf86NameCmp",
	"xf86OpenSerial",
	"xf86CloseSerial",				/* second one ? */
	"xf86OptionListCreate",
	"xf86OptionListMerge",
	"xf86OptionListReport",
	"xf86PostButtonEvent",
	"xf86PostMotionEvent",
	"xf86PostProximityEvent",
	"xf86ProcessCommonOptions",
	"xf86RemoveLocalDevice",
	"xf86ScaleAxis",
	"xf86SetIntOption",
	"xf86SetStrOption",
	"xf86XInputSetScreen",
	"xf86XInputSetSendCoreEvents",
	NULL
};


static pointer
PenMountSetupProc( pointer module, pointer options, int *errmaj, int *errmin )
{
    xf86LoaderReqSymLists(reqSymbols, NULL);
    xf86AddInputDriver(&PENMOUNT, module, 0);
    return (pointer) 1;
}

#ifndef PENMOUNT_XF86
_X_EXPORT XF86ModuleData penmountModuleData =
#else
XF86ModuleData penmountModuleData =
#endif
{
    &VersionRec,            /* vers     */
    PenMountSetupProc,      /* setup    */
    TearDownProc            /* teardown */
};

#endif /* XFree86LOADER */


/* 
 * 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",     "19200",
    "StopBits",     "1",
    "DataBits",     "8",
    "Parity",       "None",
    "Vmin",         "3",
    "Vtime",        "1",
    "FlowControl",  "None",
    NULL,
};


static int debug_level = 0;
static unsigned char g_last_packet[5];
#define TRACE(lvl, f) {if ((lvl) <= debug_level) f;}
/*****************************************************************************
 *    Function Definitions
 ****************************************************************************/
#define IOCTL_COMMAND_BUFFER_LEN    10
#define IOCTL_SET_CALIBRATION       0xFF

void IOWorkerThread(InputInfoPtr pInfo)
{
    PenMountPrivatePtr priv = (PenMountPrivatePtr) (pInfo->private);
    int fd1, fd2;
    unsigned char cmdbuf[IOCTL_COMMAND_BUFFER_LEN];

    TRACE(4, ErrorF("%sIOWorkerThread.\n", PM_INFO));

    fd1 = open(HALF_DUPLEX_READ, O_RDONLY);
    if(fd1 != -1) {

        if(read(fd1, cmdbuf, IOCTL_COMMAND_BUFFER_LEN) > 0) {

            TRACE(4, ErrorF("%s: receive %.2X %.2X %.2x %.2x %.2x", PM_INFO,
                    cmdbuf[0], cmdbuf[1], cmdbuf[2], cmdbuf[3], cmdbuf[4]));

            switch(cmdbuf[0]) {
                case IOCTL_SET_CALIBRATION:
                    priv->pmode = cmdbuf[1];
                    priv->min_x = cmdbuf[2] << 8 | cmdbuf[3];
                    priv->max_x = cmdbuf[4] << 8 | cmdbuf[5];    
                    priv->min_y = cmdbuf[6] << 8 | cmdbuf[7];
                    priv->max_y = cmdbuf[8] << 8 | cmdbuf[9];                

                    xf86SetIntOption(pInfo->options, "MinX", priv->min_x);
                    xf86SetIntOption(pInfo->options, "MaxX", priv->max_x);
                    xf86SetIntOption(pInfo->options, "MinY", priv->min_y);    
                    xf86SetIntOption(pInfo->options, "MaxY", priv->max_y);    
                    xf86SetIntOption(pInfo->options, "PMode", priv->pmode);
                    break;
                default:
                    break;
            }

            fd2 = open(HALF_DUPLEX_WRITE, O_WRONLY);
            if(fd2 != -1)
                write(fd2, g_last_packet, IOCTL_COMMAND_BUFFER_LEN);
        }
    }
}

static int bBrokenPipeCleanup = 1;

static InputInfoPtr PenMountPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
{              
    InputInfoPtr        pInfo;
    PenMountPrivatePtr  priv;
    char *s;
    char                model_string[64];
    
    priv = xcalloc (1, sizeof (PenMountPrivateRec));
    if (!priv)
        return NULL;

    pInfo = xf86AllocateInput(drv, 0);
    if (!pInfo) {
        xfree(priv);
        return NULL;
    }

    /* remove FIFOs to avoid system is hanged at restarting X */
    if (bBrokenPipeCleanup) {
        xf86remove(HALF_DUPLEX_READ);
        xf86remove(HALF_DUPLEX_WRITE);
        bBrokenPipeCleanup = 0;
    }

	/* Process the options for your device like this */
    priv->pmode             = PM_DEF_PMODE;
    priv->min_x             = PM_DEF_MIN_X;
    priv->max_x             = PM_DEF_MAX_X;
    priv->min_y             = PM_DEF_MIN_Y;
    priv->max_y             = PM_DEF_MAX_Y;
    priv->screen_num        = 0;
    priv->screen_width      = -1;
    priv->screen_height     = -1;
    priv->lex_mode          = PM_Byte0;
    priv->swap_xy           = 0;
    priv->button_down       = FALSE;
    priv->button_number     = 1;
    /* why button_number = 2 in PenMount USB ????? */
    /*priv->button_number     = 2;*/

    priv->proximity         = FALSE;
    /*priv->chip              = CHIP_UNKNOWN;*/
    priv->pen_down          = 0;
    priv->CalibrationMode   = 0;

    pInfo->type_name        = XI_TOUCHSCREEN;
    pInfo->device_control   = DeviceControl;
    pInfo->read_input       = ReadInput;
    pInfo->control_proc     = ControlProc;
    pInfo->close_proc       = CloseProc;
    pInfo->switch_mode      = SwitchMode;
    pInfo->conversion_proc  = ConvertProc;
    pInfo->dev              = NULL;
    pInfo->private          = priv;
    pInfo->private_flags    = 0;
    pInfo->flags            = XI86_POINTER_CAPABLE | XI86_SEND_DRAG_EVENTS;
    pInfo->conf_idev        = dev;

    xf86CollectInputOptions(pInfo, default_options, NULL);

    xf86OptionListReport( pInfo->options );

    pInfo->fd = xf86OpenSerial (pInfo->options);
    if (pInfo->fd == -1) {
        ErrorF("%sPenMount driver unable to open device.\n", PM_ERROR);
        goto SetupProc_fail;
    }
    xf86CloseSerial(pInfo->fd);
    
    /*
     * Process the options for your device like this
     */

    priv->pmode             = xf86SetIntOption( pInfo->options, "PMode",PM_DEF_PMODE );
    priv->min_x             = xf86SetIntOption( pInfo->options, "MinX", PM_DEF_MIN_X );
    priv->max_x             = xf86SetIntOption( pInfo->options, "MaxX", PM_DEF_MAX_X );
    priv->min_y             = xf86SetIntOption( pInfo->options, "MinY", PM_DEF_MIN_Y );
    priv->max_y             = xf86SetIntOption( pInfo->options, "MaxY", PM_DEF_MAX_Y );

    LoadUserCalibration(priv);

    priv->screen_num        = xf86SetIntOption( pInfo->options, "ScreenNumber", 0 );
    /* WHY button number = 2 in PenMount USB ????? */
    /*priv->button_number     = xf86SetIntOption( pInfo->options, "ButtonNumber", 1 );*/
    priv->button_number     = xf86SetIntOption( pInfo->options, "ButtonNumber", 2 );
    
    priv->swap_xy           = xf86SetIntOption( pInfo->options, "SwapXY", 0 );
    priv->buffer            = NULL;

    /*priv->chip              = PM9000;*/
    /*priv->packet_size       = 5;*/
    
    s = xf86SetStrOption(pInfo->options, "ADBit", "10-bit");
    if (strcmp( s, "12-bit" ) == 0) {
        priv->ad_bit = 12;
    } else if (strcmp( s, "10-bit" ) == 0) {
        priv->ad_bit = 10;
    } else {
        priv->ad_bit = 10;
    }
    

    s = xf86SetStrOption(pInfo->options, "Protocol", "PM9000");

    if (strcmp( s, "PM9000" ) == 0 || strcmp( s, "Auto" ) == 0) {
        priv->chip                  = PM9000;
        priv->packet_size           = 5;
        strcpy(model_string, "SALT PenMount 9000 series");
    } else if (strcmp( s, "PM9512" ) == 0) {
        priv->chip                  = PM9512;
        priv->packet_size           = 5;
        strcpy(model_string, "SALT PenMount 9512 series");
    } else if (strcmp( s, "PM8910" ) == 0) {
        priv->chip                  = PM8910;
        priv->packet_size           = 5;
        strcpy(model_string, "SALT PenMount 8910 series");
    } else if (strcmp( s, "PM9100" ) == 0) {
        priv->chip                  = PM9100;
        priv->packet_size           = 5;
        strcpy(model_string, "SALT PenMount 9100 series");
    } else if (strcmp( s, "PM6000COM" ) == 0) {
        priv->chip                  = PM6000COM;
        priv->packet_size           = 6;
        strcpy(model_string, "SALT PenMount 6000 serial series");
    } else if (strcmp( s, "PM6000USB" ) == 0) {
        priv->chip                  = PM6000USB;
        priv->packet_size           = 6;
        strcpy(model_string, "SALT PenMount 6000 USB series");
    } else if (strcmp( s, "PM5000" ) == 0) {
        priv->chip                  = PM5000;
        priv->packet_size           = 6;
        strcpy(model_string, "SALT PenMount 5000 series");
    }

    priv->beep              = xf86SetIntOption(pInfo->options, "Beep", 0);
    priv->press_vol         = xf86SetIntOption(pInfo->options, "PressVol", 100);
    priv->press_pitch       = xf86SetIntOption(pInfo->options, "PressPitch", 880);
    priv->press_dur         = xf86SetIntOption(pInfo->options, "PressDur", 15) & 0xff;
    priv->rel_vol           = xf86SetIntOption(pInfo->options, "ReleaseVol", 0);
    priv->rel_pitch         = xf86SetIntOption(pInfo->options, "ReleasePitch", 1200);
    priv->rel_dur           = xf86SetIntOption(pInfo->options, "ReleaseDur", 10) & 0xff;
    
    /* PenMount USB add */
    /*priv->rclick_pending    = 0;*/

    debug_level             = xf86SetIntOption(pInfo->options, "DebugLevel", 0);    


    s = xf86FindOptionValue (pInfo->options, "ReportingMode");
    if ((s) && (xf86NameCmp (s, "raw") == 0))
        priv->reporting_mode = TS_Raw;
    else
        priv->reporting_mode = TS_Scaled;

    priv->proximity     = FALSE;
    priv->button_down   = FALSE;
    priv->lex_mode      = PM_Byte0;

/*
    if (QueryHardware(priv) != Success) {
        ErrorF ("Unable to query/initialize PenMount hardware.\n");
        goto SetupProc_fail;
    }
*/
    /*
    pInfo->history_size = xf86SetIntOption( pInfo->options, "HistorySize", 0 );
    */


    /* this results in an xstrdup that must be freed later */
    pInfo->name = xf86SetStrOption( pInfo->options, "DeviceName", model_string);
    xf86ProcessCommonOptions(pInfo, pInfo->options);

    pInfo->flags |= XI86_CONFIGURED;

    return (pInfo);

SetupProc_fail:
    if ((pInfo) && (pInfo->fd))
        xf86CloseSerial(pInfo->fd);
    if ((pInfo) && (pInfo->name))
        xfree(pInfo->name);
    if ((priv) && (priv->buffer))
        XisbFree(priv->buffer);
    if (priv)
        xfree(priv);
    /*return (pInfo);*/
    return NULL;

}



/**********************************************************
 *  PenMount 9000 : F2 00 00 00 00    --> F2 D9 0A 02 00
 *  PenMount 6000 : EE 00 00 00 00 11 --> EE 17 70 00 02 XX
 **********************************************************/
/*
static Bool
QueryHardware (PenMountPrivatePtr priv)
{
    return Success;
*/
#if 0
    unsigned char   cmd9[5] = { 0xF2, 0, 0, 0, 0 };
    unsigned char   cmd6[6] = { 0xFF, 0, 0, 0, 0, 0 };
    unsigned char   sum;



    /* 9000 */
    priv->packet_size = 5;
    priv->lex_mode = PM_Byte0;
    PenMountSendCommand(priv, cmd9);

	xf86Msg( X_PROBED, "raw9 = %02x %02x %02x %02x %02x\n",
	     priv->packet[0],
	     priv->packet[1],
	     priv->packet[2],
	     priv->packet[3],
	     priv->packet[4]
	     );

	xf86Msg( X_PROBED, " PenMount touchscreen is " );

    if ((priv->packet[0] == 0xf2) && (priv->packet[1] == 0xd9)) {
        priv->chip = PM9000;
    
    	xf86Msg( X_PROBED, "9000.\n" );
        
        return Success;
    }

    /* 6000 */
    priv->packet_size = 6;
    priv->lex_mode = PM_Byte0;
    PenMountSendCommand(priv, cmd6);

	xf86Msg( X_PROBED, "raw6 = %02x %02x %02x %02x %02x %02x\n",
	     priv->packet[0],
	     priv->packet[1],
	     priv->packet[2],
	     priv->packet[3],
	     priv->packet[4],
	     priv->packet[5]
	     );

    sum = 0;
    sum += priv->packet[0];
    sum += priv->packet[1];
    sum += priv->packet[2];
    sum += priv->packet[3];
    sum += priv->packet[4];
    sum = 0xFF - sum;
    if ( sum == priv->packet[5] ) {
        priv->chip = CHIP_UNKNOWN;
        if (priv->packet[4] == 0x0c) {
            priv->chip = PM6000COM;
        	xf86Msg( X_PROBED, "6000-12.\n" );
        } else if (priv->packet[4] == 0x0a) {
            priv->chip = PM6000COM;
        	xf86Msg( X_PROBED, "6000-10.\n" );
        }

        if ( priv->chip == CHIP_UNKNOWN ) {
            priv->packet_size = 5;
        	xf86Msg( X_PROBED, "??.\n" );
            return !Success;
        } else {
            return Success;
        }
    } else {
        priv->packet_size = 5;
     	xf86Msg( X_PROBED, "??.\n" );
    }

    return !Success;
#endif
/*
}
*/

#if 0
/***************************************************************
 * PenMountSendCommand
 *   PenMountSendPacket
 *   PenMountWaitReply
 *     PenMountGetPacket
 ***************************************************************/
static Bool
PenMountSendCommand (PenMountPrivatePtr priv, unsigned char *cmd)
{
    int     retries = 3;
    Bool    result;

    result = !Success;

    if (priv->packet_size == 5)
	xf86Msg( X_PROBED, "PenMountSendCommand(%d) : %02x %02x %02x %02x %02x \n",
	        priv->packet_size,
            cmd[0],
            cmd[1],
            cmd[2],
            cmd[3],
            cmd[4]);
    else
	xf86Msg( X_PROBED, "PenMountSendCommand(%d) : %02x %02x %02x %02x %02x %02x\n",
	        priv->packet_size,
            cmd[0],
            cmd[1],
            cmd[2],
            cmd[3],
            cmd[4],
            cmd[5]);
    

    while (retries--) {

        if (PenMountSendPacket(priv, cmd) != Success)
            continue;

        result = PenMountWaitReply(priv);
        if (result == Success)
            break;
    }

    return result;
}
#endif

#if 0
static Bool
PenMountSendPacket (PenMountPrivatePtr priv, unsigned char *buf)
{
	/* int     result; */

	int	count = 0;
	int len;


    if (priv->packet_size == 5)
	xf86Msg( X_PROBED, "PenMountSendPacket(%d) : %02x %02x %02x %02x %02x \n",
	        priv->packet_size,
            buf[0],
            buf[1],
            buf[2],
            buf[3],
            buf[4]);
    else
	xf86Msg( X_PROBED, "PenMountSendPacket(%d) : %02x %02x %02x %02x %02x %02x\n",
	        priv->packet_size,
            buf[0],
            buf[1],
            buf[2],
            buf[3],
            buf[4],
            buf[5]);


    len = priv->packet_size;

	while (len > 0)
	{
		if ( XisbWrite(priv->buffer, buf, 1) == 1 )
		{
			buf++;
			len--;
			continue;
		}
		if ( count++ > 500 )
			break;
	}
	return (len ? !Success : Success);


/*

	
	result = XisbWrite(priv->buffer, buf, priv->packet_size);
	
	xf86Msg( X_PROBED, "PenMountSendPacket result : %d\n", result);
	

    if ( result == priv->packet_size ) {
        return Success;
    } else {
        ErrorF ("System error while sending to PenMount touchscreen.\n");
        return !Success;
    }
*/
}
#endif

static Bool
PenMountGetPacket (PenMountPrivatePtr priv)
{
    int     cnt;
    int     c;
    
    cnt = 0;

    while ((c = XisbRead(priv->buffer)) >= 0) {

        /* xf86Msg( X_PROBED, "PenMountGetPacket = %02x\n", c); */

        if (cnt++ > 500)
            return !Success;

        switch (priv->lex_mode) {
            case PM_Byte0:
                if (priv->chip == PM9000) {
                    if ( (c!=0xFF) && (c!=0xBF) )
                        return (!Success);
                } else if (priv->chip == PM6000COM ) {
                    if ( (c!=0x70) && (c!=0x30) )
                        return (!Success);
                }
                priv->packet[0] = (unsigned char) c;
                priv->lex_mode  = PM_Byte1;
                break;
            case PM_Byte1:
                priv->packet[1] = (unsigned char) c;
                priv->lex_mode  = PM_Byte2;
                break;
            case PM_Byte2:
                priv->packet[2] = (unsigned char) c;
                priv->lex_mode  = PM_Byte3;
                break;
            case PM_Byte3:
                priv->packet[3] = (unsigned char) c;
                priv->lex_mode  = PM_Byte4;
                break;
            case PM_Byte4:
                priv->packet[4] = (unsigned char) c;
                if (priv->chip == PM9000) {
                    priv->lex_mode  = PM_Byte0;
                    return Success;
                } else if (priv->chip == PM6000COM ||  priv->chip == PM6000COM) {
                    priv->lex_mode  = PM_Byte5;
                }
                break;
            case PM_Byte5:
                priv->packet[5] = (unsigned char) c;
                priv->lex_mode  = PM_Byte0;
                return Success;
                break;
            default:
                break;
        }
    }

    return !Success;
}

#if 0
static Bool
PenMountWaitReply (PenMountPrivatePtr priv)
{
    Bool    ok;
    int     cnt;

    cnt = 3;
    priv->lex_mode = PM_Byte0;
    do {
        ok = !Success;

        XisbBlockDuration(priv->buffer, -1);
        ok = PenMountGetPacket(priv);
        
	        xf86Msg( X_PROBED, "PenMountWaitReply(%d) : %02x %02x %02x %02x %02x %02x\n",
	        cnt,
            priv->packet[0],
            priv->packet[1],
            priv->packet[2],
            priv->packet[3],
            priv->packet[4],
            priv->packet[5]);

    } while ( ok != Success && cnt--);

    return ok;
}
#endif


static Bool
DeviceInit (DeviceIntPtr dev)
{
    InputInfoPtr        pInfo = (InputInfoPtr) dev->public.devicePrivate;
    PenMountPrivatePtr  priv  = (PenMountPrivatePtr) (pInfo->private);
    /* PenMount USB */
	/*unsigned char map[] = {0, 1};*/
	unsigned char map[] = {0, 1, 2, 3, 4};

    /*
     * these have to be here instead of in the SetupProc, because when the
     * SetupProc is run at server startup, screenInfo is not setup yet
     */
    priv->screen_width  = screenInfo.screens[priv->screen_num]->width;
    priv->screen_height = screenInfo.screens[priv->screen_num]->height;
        
    /*
     * Device reports button press for 1 button.
     */
    /* PenMount USB */
    /* if (InitButtonClassDeviceStruct (dev, 1, map) == FALSE) {*/
    if (InitButtonClassDeviceStruct (dev, 4, map) == FALSE) {
        ErrorF("%sUnable to allocate PenMount ButtonClassDeviceStruct.\n", PM_ERROR);
        return !Success;
    }

    /*
     * Device reports motions on 2 axes in absolute coordinates.
     * Axes min and max values are reported in raw coordinates.
     */
    if (InitValuatorClassDeviceStruct (dev, 2, xf86GetMotionEvents,
        pInfo->history_size, Absolute) == FALSE) {
        ErrorF("%sUnable to allocate PenMount ValuatorClassDeviceStruct.\n", PM_ERROR);
        return !Success;
    } else {
        if (priv->ad_bit == 12) {
    
            InitValuatorAxisStruct (dev, 0, priv->min_x, priv->max_x,
                    PM_DEF_MAX_12X,
                    PM_DEF_MIN_X /* min_res */ ,
                    PM_DEF_MAX_12X /* max_res */ );

            InitValuatorAxisStruct (dev, 1, priv->min_y, priv->max_y,
                    PM_DEF_MAX_12Y,
                    PM_DEF_MIN_Y /* min_res */ ,
                    PM_DEF_MAX_12Y /* max_res */ );
        } else {

            InitValuatorAxisStruct (dev, 0, priv->min_x, priv->max_x,
                    PM_DEF_MAX_X,
                    PM_DEF_MIN_X /* min_res */ ,
                    PM_DEF_MAX_X /* max_res */ );

            InitValuatorAxisStruct (dev, 1, priv->min_y, priv->max_y,
                    PM_DEF_MAX_Y,
                    PM_DEF_MIN_Y /* min_res */ ,
                    PM_DEF_MAX_Y /* max_res */ );
       }


    }

    if (InitProximityClassDeviceStruct(dev) == FALSE) {
        ErrorF("%sunable to allocate PenMount ProximityClassDeviceStruct.\n", PM_ERROR);
        return !Success;
    }
        
    if (InitPtrFeedbackClassDeviceStruct(dev, PenMountPtrCtrl) == FALSE) {
        ErrorF("%sunable to allocate PenMount PtrFeedbackClassDeviceStruct\n", PM_ERROR);
        return !Success;
    }
        
    /* Allocate the motion events buffer. */
    xf86MotionHistoryAllocate (pInfo);
    return (Success);

}


static Bool
DeviceOn (DeviceIntPtr dev)
{
    InputInfoPtr        pInfo = (InputInfoPtr) dev->public.devicePrivate;
    PenMountPrivatePtr  priv  = (PenMountPrivatePtr) (pInfo->private);

    unsigned char       enable_6000_cmd[6] = { 0xf1, 0, 0, 0, 0, 0x0e };
    unsigned char       enable_9000_cmd[5] = { 0xf1, 0, 0, 0, 0 };
/*
    unsigned char       cmd9[5] = { 0xf1, 0, 0, 0, 0 };
*/

    pInfo->fd = xf86OpenSerial(pInfo->options);
    if (pInfo->fd == -1) {
        xf86Msg(X_WARNING, "%s: cannot open input device\n", pInfo->name);
        return (!Success);
    } else {
        priv->buffer = XisbNew(pInfo->fd, PM_BUFFER_SIZE);
        if (!priv->buffer) {
            xf86CloseSerial(pInfo->fd);
            pInfo->fd = -1;
            return (!Success);
        }            
    }

    /*  enable PenMount */
    switch (priv->chip) {
        case PM6000COM:
            priv->packet_size = 6;
            XisbWrite(priv->buffer, enable_6000_cmd, 6);
            break;
        case PM9000:
            priv->packet_size = 5;
            XisbWrite(priv->buffer, enable_9000_cmd, 5);
            break;
        default:
            break;
    }


    XisbBlockDuration (priv->buffer, -1);
    
    priv->lex_mode = PM_Byte0;

    if (priv->chip == PM6000COM || priv->chip == PM9000)
        xf86FlushInput(pInfo->fd);
    
    AddEnabledDevice (pInfo->fd);
    
    dev->public.on = TRUE;

    return (Success);
}

static Bool
DeviceOff (DeviceIntPtr dev)
{
    InputInfoPtr        pInfo = (InputInfoPtr) dev->public.devicePrivate;
    PenMountPrivatePtr  priv  = (PenMountPrivatePtr) (pInfo->private);

    if (pInfo->fd != -1) {
        RemoveEnabledDevice (pInfo->fd);
        if (priv->buffer) {
            XisbFree(priv->buffer);
            priv->buffer = NULL;
        }
        xf86CloseSerial(pInfo->fd);
    }
    dev->public.on = FALSE;

    return (Success);
}

static Bool
DeviceControl (DeviceIntPtr dev, int mode)
{
    Bool    result;

    switch (mode) {
        case DEVICE_INIT:
            result = DeviceInit(dev);
            break;
        case DEVICE_ON:
            result = DeviceOn(dev);
            break;
        case DEVICE_OFF:
        case DEVICE_CLOSE:
            result = DeviceOff(dev);
            break;
        default:
            result = BadValue;
    }

    return result;
}




/* 
 * The ReadInput function will have to be tailored to your device
 */
static void
ReadInput (InputInfoPtr pInfo)
{
    PenMountPrivatePtr priv = (PenMountPrivatePtr) (pInfo->private);
    int x, y, button;
    int temp = 0;
    int pmode = 1;
    unsigned char opck[ PM_PACKET_SIZE ];
/*    int fd, retries = 0; */
/*    unsigned char   sum;*/
    unsigned int    mask = 0xFFFFFFFF;

    struct input_event  *event;
    char                eventbuf[256];  /* buffer size : 256 */
    ssize_t             len;
    int                 pass = 0;


    switch (priv->chip) {
        case PM6000COM:
            priv->packet_size = 6;
            x = 0;
            y = 0;
            break;
        case PM9000:
            priv->packet_size = 5;
            x = 0;
            y = 0;
            break;
        case PM6000USB:
        case PM5000:
            priv->packet_size = 6;  /*?????*/
            x = priv->old_x;
            y = priv->old_y;
            button = priv->old_button;
        default:
            priv->packet_size = 5;
            break;
    }


/*-----------------------------------------------------------------------------------
     USB Port
  -----------------------------------------------------------------------------------*/
    if (priv->chip == PM6000USB || priv->chip == PM5000) {

        SYSCALL(len = read(pInfo->fd, eventbuf, 256));  /* 256 = buffer size*/

        if (len <= 0) {
            ErrorF("Error reading PenMount USB device : %s\n", stderror(errno));
            return;
        }

        for (event = (struct input_device *)eventbuf;
             event < (struct input_device *)(eventbuf+len); event++) {
            switch (event->type) {
            case EV_ABS:
                TRACE(5, ErrorF("%sevent->type=%d   ", PM_INFO, event->type));
                TRACE(3, ErrorF("%sevent->code=%d   ", PM_INFO, event->code));
                switch(event->code) {
                case ABS_X:
                    x = event->value;
                    pass = 0;
                    TRACE(3, ErrorF("%sx=%d\n", PM_INFO, x));
                    break;
                case ABS_Y:
                    y = event->value;
                    pass = 1;
                    TRACE(3, ErrorF("%sy=%d\n", PM_INFO, y));
                    break;
                }
                break;  /* EV_ABS */

            case EV_KEY:
                TRACE(5, ErrorF("%sevent->type=%d   ", PM_INFO, event->type));
                TRACE(3, ErrorF("%sevent->code=%d   ", PM_INFO, event->code));
                switch(event->code) {
                case BTN_LEFT:
                    button = event->value;
                    pass = 1;
                    TRACE(3, ErrorF("%spendown=%d\n", PM_INFO, event->value));
                    break;
                }
                break; /* EV_KEY */
            default:
                continue;
            }/* switch */


            if ( (ABS(x - priv->old_x) <= 0) && (ABS(y - priv->old_y) <= 0) &&
                 (button == priv->old_button) ) {
                TRACE(5, ErrorF("%sfiltered", PM_INFO));
                continue;
            }

            priv->old_x = x;
            priv->old_y = y;
            priv->old_button = button;
            
            if (priv->chip == PM5000) {
                g_last_packet[0] = button? 0xFF : 0xBF;
                g_last_packet[1] = (x >> 7) & 0xFF;
                g_last_packet[2] = (x & 0x7F);
                g_last_packet[3] = (y >> 7) & 0xFF;
                g_last_packet[4] = (y & 0x7F);
            } else if (priv->chip == PM6000USB) {
                g_last_packet[0] = button? 0x70 : 0x30;
                if (priv->ad_bit == 10) {
	                g_last_packet[1] = (x >> 8) & 0x03;
                	g_last_packet[3] = (y >> 8) & 0x03;
				} else if (priv->ad_bit == 12) {
	                g_last_packet[1] = (x >> 8) & 0x0F;
                	g_last_packet[3] = (y >> 8) & 0x0F;
				}
                g_last_packet[2] = (x & 0xFF);
                g_last_packet[4] = (y & 0xFF);
            }

            TRACE(4, ErrorF("%sLast Packet: %02x %02x %02x %02x %02x\n", PM_INFO, g_last_packet[0], g_last_packet[1], g_last_packet[2], g_last_packet[3], g_last_packet[4]));

            /* just cord X, process next time with y */
            if (pass == 0)
                continue;

            /* Scaling */
            if (priv->reporting_mode == TS_Scaled) {
                pmode = priv->pmode;

                /* swap x, y */
                if (pmode == 5 || pmode == 6 || pmode == 7 || pmode == 8) {
                    temp = x;
                    x    = y;
                    y    = temp;
                }

                if (pmode == 1 || pmode == 2 || pmode == 5 || pmode == 6) {
                    if (x < priv->min_x)
                        x = priv->min_x;
                    if (x > priv->max_x)
                        x = priv->max_x;
                    /* convert to screen x */
                    x = ((x - priv->min_x) * priv->screen_width) / (priv->max_x - priv->min_x);
                } else {
                    if (x < priv->min_x)
                        x = priv->min_x;
                    if (x > priv->max_x)
                        x = priv->max_x;
                    /* convert to screen x */
                    x = ((priv->max_x - x) * priv->screen_width) / (priv->max_x - priv->min_x);
                }

                if (pmode == 1 || pmode == 3 || pmode == 5 || pmode == 7) {
                    if (y < priv->min_y)
                        y = priv->min_y;
                    if (y > priv->max_y)
                        y = priv->max_y;
                    y = ((y - priv->min_y) * priv->screen_height) / (priv->max_y - priv->min_y);
                } else {
                    if (y < priv->min_y)
                        y = priv->min_y;
                    if (y > priv->max_y)
                        y = priv->max_y;
                    y = ((priv->max_y - y) * priv->screen_height) / (priv->max_y - priv->min_y);
                }
                
                TRACE(4, ErrorF("%sAfter scale 2, x=%d, y=%d.\n", PM_INFO, x, y));
            }

            xf86XInputSetScreen(pInfo, priv->screen_num, x, y);

            /*
             * Send event
             * We *must* generate a motion before a button change if pointer
             * location has changed as DIX assumes this. This is why we always
             * emit a motion, regardless of the kind of packet processed.
             */
            xf86PostMotionEvent(pInfo->dev, TRUE, 0, 2, x, y);
            
            /*
             * Emit a button press or release.
             */
            if ((priv->button_down == FALSE) && (button == 1)) {
                xf86PostButtonEvent(pInfo->dev, TRUE, 1, 1, 0, 2, x, y);
                priv->button_down = TRUE;
                PenMountBeep(priv, 1);
            }
            if ((priv->button_down == TRUE) && (button == 0)) {
                xf86PostButtonEvent(pInfo->dev, TRUE, 1, 0, 0, 2, x, y);
                priv->button_down = FALSE;
                PenMountBeep(priv, 0);
            }
            
            /*
             * FIFO operation
             */
            IOWorkerThread(pInfo);
        }/*for*/
/*-----------------------------------------------------------------------------------
     COM Port
  -----------------------------------------------------------------------------------*/
    } else if ((priv->chip == PM9000) || (priv->chip == PM6000COM)){
        /* 
         * set blocking to -1 on the first call because we know there is data to
         * read. Xisb automatically clears it after one successful read so that
         * succeeding reads are preceeded buy a select with a 0 timeout to prevent
         * read from blocking indefinately.
         */
        XisbBlockDuration (priv->buffer, -1);
        
        while (1) {
            if (priv->chip == PM9000)
                memcpy(opck, priv->packet, 5);
            else if (priv->chip == PM6000COM)
                memcpy(opck, priv->packet, 6);

            if (PenMountGetPacket(priv) != Success)
                break;

            switch(priv->chip) {
                case PM9000:
                    if (priv->packet[0] == 0xFF)
                        priv->pen_down = 1;
                    else if (priv->packet[0] == 0xBF)
                        priv->pen_down = 0;
                    if (priv->swap_xy) {
                        y = ( priv->packet[1] & 0x00000FFF );
                        y <<= 7;
                        y += ( priv->packet[2] & 0x0FFFFFFF );
                        x = ( priv->packet[3] & 0x00000FFF );
                        x <<= 7;
                        x += ( priv->packet[4] & 0x0FFFFFFF );
                    } else {
                        x = ( priv->packet[1] & 0x00000FFF );
                        x <<= 7;
                        x += ( priv->packet[2] & 0x0FFFFFFF );
                        y = ( priv->packet[3] & 0x00000FFF );
                        y <<= 7;
                        y += ( priv->packet[4] & 0x0FFFFFFF );
                    }
                    break;
                case PM6000COM:
                    /* none checksum */
                    if ( priv->packet[0] == 0x70 )
                        priv->pen_down = 1;
                    if ( priv->packet[0] == 0x30 )
                        priv->pen_down = 0;
                    
                    if (priv->ad_bit == 10)
                        mask = 0x03;
                    else if (priv->ad_bit == 12)
                        mask = 0x0f;

                    if ( priv->swap_xy ) {
                        y = ( priv->packet[2] & mask );
                        y <<= 8;
                        y += priv->packet[1];
                        x = ( priv->packet[4] & mask );
                        x <<= 8;
                        x += priv->packet[3];
                    } else {
                        x = ( priv->packet[2] & mask );
                        x <<= 8;
                        x += priv->packet[1];
                        y = ( priv->packet[4] & mask );
                        y <<= 8;
                        y += priv->packet[3];
                    }
                    break;
                case PM8910:
			        if (( priv->packet[1] == 0xfd ) && ( priv->packet[2] == 0xfd )) {
				        priv->pen_down = 1;
				        continue;
			        }
			        if (( priv->packet[1] == 0xfe ) && ( priv->packet[2] == 0xfe )) {
				        /* memcpy(priv->packet,opck,5); */
				        if ( !priv->pen_down )
					        continue;
				        priv->pen_down = 0;
			        }
			        if ( priv->swap_xy) {
				        y = priv->packet[1]*256+priv->packet[2];
				        x = priv->packet[3]*256+priv->packet[4];
			        } else {
				        x = priv->packet[1]*256+priv->packet[2];
				        y = priv->packet[3]*256+priv->packet[4];
			        }
			        priv->packet[0] = priv->pen_down ? 0x01 : 0x00;
			        break;
			    default:
			        break;
			}/*switch*/

            TRACE(4, ErrorF("%spen data, x=%d, y=%d.\n", PM_INFO, x, y));

            priv->packet[0] = priv->pen_down ? 0x01 : 0x00;

            if (priv->reporting_mode == TS_Scaled) {
                pmode = priv->pmode;
                /* swap x, y */
                if ( pmode==5 || pmode==6 || pmode==7 || pmode==8 ) {
                    temp = x;
                    x    = y;
                    y    = temp;
                }
                if ( pmode==1 || pmode==2 || pmode==5 || pmode==6 ) {
                    if ( x < priv->min_x )
                        x = priv->min_x;
                    if ( x > priv->max_x )
                        x = priv->max_x;
                    /*  convert to screen x */
                    x = ((x- priv->min_x)*priv->screen_width) / (priv->max_x - priv->min_x);
                } else {
                    if ( x < priv->min_x )
                        x = priv->min_x;
                    if ( x > priv->max_x )
                        x = priv->max_x;
                    /*  convert to screen x */
                    x = ((priv->max_x-x)*priv->screen_width) / (priv->max_x - priv->min_x); 
                }
    
                if ( pmode==1 || pmode==3 || pmode==5 || pmode==7 ) {
                    if ( y < priv->min_y )
                        y = priv->min_y;
                    if ( y > priv->max_y )
                        y = priv->max_y;
                    /* convert to screen y */
                    y = ((y-priv->min_y)*priv->screen_height) / (priv->max_y - priv->min_y);
                } else {
                    if ( y < priv->min_y )
                        y = priv->min_y;
                    if ( y > priv->max_y )
                        y = priv->max_y;
                    /*  convert to screen y */
                    y = ((priv->max_y-y)*priv->screen_height) / (priv->max_y - priv->min_y);
                }
                TRACE(4, ErrorF("%sAfter scale 2, x=%d, y=%d.\n", PM_INFO, x, y));
            }               
            /* KEN: Store the packet for retrieval from IOWorkerThread */
            if ( priv->chip == PM9000 ) {
                g_last_packet[0] = priv->packet[0]? 0xFF : 0xBF;
                g_last_packet[1] = priv->packet[1];
                g_last_packet[2] = priv->packet[2];
                g_last_packet[3] = priv->packet[3];
                g_last_packet[4] = priv->packet[4];
            } else if ( priv->chip == PM6000COM ) {
                g_last_packet[0] = priv->packet[0]? 0x70 : 0x30;
                g_last_packet[1] = priv->packet[2];
                g_last_packet[2] = priv->packet[1];
                g_last_packet[3] = priv->packet[4];
                g_last_packet[4] = priv->packet[3];
            }
            TRACE(4, ErrorF("%sLast Packet: %x %x %x %x %x\n", PM_INFO, g_last_packet[0], g_last_packet[1], 
                        g_last_packet[2], g_last_packet[3], g_last_packet[4]));

            IOWorkerThread(pInfo);

            xf86XInputSetScreen (pInfo, priv->screen_num, x, y);

            if ((priv->proximity == FALSE) && (priv->packet[0] & 0x01)) {
                priv->proximity = TRUE;
                xf86PostProximityEvent (pInfo->dev, 1, 0, 2, x, y);
            }

            /*****************************************************************
            * Send events.
            *
            * We *must* generate a motion before a button change if pointer
            * location has changed as DIX assumes this. This is why we always
            * emit a motion, regardless of the kind of packet processed.
            *****************************************************************/
            xf86PostMotionEvent (pInfo->dev, TRUE, 0, 2, x, y);


            /*********************************
            * Emit a button press or release.
            *********************************/
            /* press */
            if ((priv->button_down == FALSE) && (priv->packet[0] & 0x01)) {
                xf86PostButtonEvent(pInfo->dev, TRUE, priv->button_number, 1, 0, 2, x, y);
                priv->button_down = TRUE;
                PenMountBeep(priv, 1);
            }

            /* release */
            if ((priv->button_down == TRUE) && !(priv->packet[0] & 0x01)) {
                xf86PostButtonEvent(pInfo->dev, TRUE, priv->button_number, 0, 0, 2, x, y);
                priv->button_down = FALSE;
                PenMountBeep(priv, 0);
            }

            /*********************************************************
            * the untouch should always come after the button release
            *********************************************************/
            if ((priv->proximity == TRUE) && !(priv->packet[0] & 0x01)) {
                priv->proximity = FALSE;
                xf86PostProximityEvent(pInfo->dev, 0, 0, 2, x, y);
            }

                        
                        
        }/*while*/
                
    }/*end-if*/

}


/* The ControlProc function may need to be tailored for your device */
static int
ControlProc (InputInfoPtr pInfo, xDeviceCtl * control)
{
    xDeviceTSCalibrationCtl *c = (xDeviceTSCalibrationCtl *) control;
    PenMountPrivatePtr priv = (PenMountPrivatePtr) (pInfo->private);

    priv->min_x = c->min_x;
    priv->max_x = c->max_x;
    priv->min_y = c->min_y;
    priv->max_y = c->max_y;

    return (Success);
}


/* the CloseProc should not need to be tailored to your device */
static void
CloseProc (InputInfoPtr pInfo)
{
}


/* The SwitchMode function may need to be tailored for your device */
static int
SwitchMode (ClientPtr client, DeviceIntPtr dev, int mode)
{
    InputInfoPtr pInfo = dev->public.devicePrivate;
    PenMountPrivatePtr priv = (PenMountPrivatePtr) (pInfo->private);

    if ((mode == TS_Raw) || (mode == TS_Scaled)) {
        priv->reporting_mode = mode;
        return (Success);
    } else if ((mode == SendCoreEvents) || (mode == DontSendCoreEvents)) {
        xf86XInputSetSendCoreEvents (pInfo, (mode == SendCoreEvents));
        return (Success);
    } else {
        return (!Success);
    }
}


/*
 * The ConvertProc function may need to be tailored for your device.
 * This function converts the device's valuator outputs to x and y coordinates
 * to simulate mouse events.
 */
static Bool
ConvertProc (InputInfoPtr pInfo,
             int first,
             int num,
             int v0,
             int v1,
             int v2,
             int v3,
             int v4,
             int v5,
             int *x,
             int *y)
{
    PenMountPrivatePtr priv = (PenMountPrivatePtr) (pInfo->private);

    if (priv->reporting_mode == TS_Raw) {

        *x = xf86ScaleAxis (v0, 0, priv->screen_width, priv->min_x, priv->max_x);
        *y = xf86ScaleAxis (v1, 0, priv->screen_height, priv->min_y, priv->max_y);

        TRACE(4, ErrorF("%sin ConvertProc TS_RAW. \n", PM_INFO));

    } else {

        *x = v0;
        *y = v1;
    }

    return (TRUE);
}


static void TearDownProc(pointer p)
{
}


static void LoadUserCalibration(PenMountPrivatePtr priv)
{
	FILE *f = xf86fopen(CALIBRATION_DATA, "r");

	if(!f)
	    return;

	xf86fscanf(f, "%d", &priv->pmode);
	xf86fscanf(f, "%d", &priv->min_x);
	xf86fscanf(f, "%d", &priv->max_x);
	xf86fscanf(f, "%d", &priv->min_y);
	xf86fscanf(f, "%d", &priv->max_y);

	xf86fclose(f);	
}

static void
PenMountPtrCtrl (DeviceIntPtr device, PtrCtrl *ctrl)
{
  /* I have no clue what this does, except that registering it stops the 
     X server segfaulting in ProcGetPointerMapping()
     Ho Hum.
  */
}


static void
PenMountBeep (PenMountPrivatePtr priv, int press)
{
	if (priv->beep == 0)
		return;

	/* ring release bell */
	if (press == 0)
		xf86SoundKbdBell(priv->rel_vol, priv->rel_pitch, priv->rel_dur);
	else
	/* ring press bell */
		xf86SoundKbdBell(priv->press_vol, priv->press_pitch, priv->press_dur);
}
