USB Input: Added kernel module to support all GTCO CalComp USB InterWrite School...
authorJeremy Roberson <jroberson@gtcocalcomp.com>
Thu, 18 Jan 2007 15:10:25 +0000 (08:10 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 7 Feb 2007 23:44:39 +0000 (15:44 -0800)
Added a kernel module (gtco) to the USB Input subsystem.  This kernel
module adds support for all GTCO CalComp USB InterWrite School products.

Signed-off-by: Jeremy A. Roberson <jroberson@gtcocalcomp.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/input/Kconfig
drivers/usb/input/Makefile
drivers/usb/input/gtco.c [new file with mode: 0644]

index aa6a620..2e71d3c 100644 (file)
@@ -352,3 +352,15 @@ config USB_APPLETOUCH
 
          To compile this driver as a module, choose M here: the
          module will be called appletouch.
+
+config USB_GTCO
+        tristate "GTCO CalComp/InterWrite USB Support"
+        depends on USB && INPUT
+        ---help---
+          Say Y here if you want to use the USB version of the GTCO
+          CalComp/InterWrite Tablet.  Make sure to say Y to "Mouse support"
+          (CONFIG_INPUT_MOUSEDEV) and/or "Event interface support"
+          (CONFIG_INPUT_EVDEV) as well.
+
+          To compile this driver as a module, choose M here: the
+          module will be called gtco.
index a06024e..a9d206c 100644 (file)
@@ -48,6 +48,7 @@ obj-$(CONFIG_USB_ACECAD)      += acecad.o
 obj-$(CONFIG_USB_YEALINK)      += yealink.o
 obj-$(CONFIG_USB_XPAD)         += xpad.o
 obj-$(CONFIG_USB_APPLETOUCH)   += appletouch.o
+obj-$(CONFIG_USB_GTCO)         += gtco.o
 
 ifeq ($(CONFIG_USB_DEBUG),y)
 EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/usb/input/gtco.c b/drivers/usb/input/gtco.c
new file mode 100644 (file)
index 0000000..203cdc1
--- /dev/null
@@ -0,0 +1,1104 @@
+/*    -*- linux-c -*-
+
+GTCO digitizer USB driver
+
+Use the err(), dbg() and info() macros from usb.h for system logging
+
+TO CHECK:  Is pressure done right on report 5?
+
+Copyright (C) 2006  GTCO CalComp
+
+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; version 2
+of the License.
+
+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 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation, and that the name of GTCO-CalComp not be used in advertising
+or publicity pertaining to distribution of the software without specific,
+written prior permission. GTCO-CalComp makes no representations about the
+suitability of this software for any purpose.  It is provided "as is"
+without express or implied warranty.
+
+GTCO-CALCOMP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+EVENT SHALL GTCO-CALCOMP BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTIONS, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
+GTCO CalComp, Inc.
+7125 Riverwood Drive
+Columbia, MD 21046
+
+Jeremy Roberson jroberson@gtcocalcomp.com
+Scott Hill shill@gtcocalcomp.com
+*/
+
+
+
+/*#define DEBUG*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <asm/uaccess.h>
+#include <asm/unaligned.h>
+#include <asm/byteorder.h>
+
+
+#include <linux/version.h>
+#include <linux/usb/input.h>
+
+/* Version with a Major number of 2 is for kernel inclusion only. */
+#define  GTCO_VERSION   "2.00.0006"
+
+
+/*   MACROS  */
+
+#define VENDOR_ID_GTCO       0x078C
+#define PID_400               0x400
+#define PID_401               0x401
+#define PID_1000              0x1000
+#define PID_1001              0x1001
+#define PID_1002              0x1002
+
+/* Max size of a single report */
+#define REPORT_MAX_SIZE       10
+
+
+/* Bitmask whether pen is in range */
+#define MASK_INRANGE 0x20
+#define MASK_BUTTON  0x01F
+
+#define  PATHLENGTH     64
+
+/* DATA STRUCTURES */
+
+/* Device table */
+static struct usb_device_id gtco_usbid_table [] = {
+       { USB_DEVICE(VENDOR_ID_GTCO, PID_400) },
+       { USB_DEVICE(VENDOR_ID_GTCO, PID_401) },
+       { USB_DEVICE(VENDOR_ID_GTCO, PID_1000) },
+       { USB_DEVICE(VENDOR_ID_GTCO, PID_1001) },
+       { USB_DEVICE(VENDOR_ID_GTCO, PID_1002) },
+       { }
+};
+MODULE_DEVICE_TABLE (usb, gtco_usbid_table);
+
+
+/* Structure to hold all of our device specific stuff */
+struct gtco {
+
+       struct input_dev  *inputdevice; /* input device struct pointer  */
+       struct usb_device *usbdev; /* the usb device for this device */
+       struct urb        *urbinfo;      /* urb for incoming reports      */
+       dma_addr_t        buf_dma;  /* dma addr of the data buffer*/
+       unsigned char *   buffer;   /* databuffer for reports */
+
+       char  usbpath[PATHLENGTH];
+       int   openCount;
+
+       /* Information pulled from Report Descriptor */
+       u32  usage;
+       u32  min_X;
+       u32  max_X;
+       u32  min_Y;
+       u32  max_Y;
+       s8   mintilt_X;
+       s8   maxtilt_X;
+       s8   mintilt_Y;
+       s8   maxtilt_Y;
+       u32  maxpressure;
+       u32  minpressure;
+};
+
+
+
+/*   Code for parsing the HID REPORT DESCRIPTOR          */
+
+/* From HID1.11 spec */
+struct hid_descriptor
+{
+       struct usb_descriptor_header header;
+       __le16   bcdHID;
+       u8       bCountryCode;
+       u8       bNumDescriptors;
+       u8       bDescriptorType;
+       __le16   wDescriptorLength;
+} __attribute__ ((packed));
+
+
+#define HID_DESCRIPTOR_SIZE   9
+#define HID_DEVICE_TYPE       33
+#define REPORT_DEVICE_TYPE    34
+
+
+#define PREF_TAG(x)     ((x)>>4)
+#define PREF_TYPE(x)    ((x>>2)&0x03)
+#define PREF_SIZE(x)    ((x)&0x03)
+
+#define TYPE_MAIN       0
+#define TYPE_GLOBAL     1
+#define TYPE_LOCAL      2
+#define TYPE_RESERVED   3
+
+#define TAG_MAIN_INPUT        0x8
+#define TAG_MAIN_OUTPUT       0x9
+#define TAG_MAIN_FEATURE      0xB
+#define TAG_MAIN_COL_START    0xA
+#define TAG_MAIN_COL_END      0xC
+
+#define TAG_GLOB_USAGE        0
+#define TAG_GLOB_LOG_MIN      1
+#define TAG_GLOB_LOG_MAX      2
+#define TAG_GLOB_PHYS_MIN     3
+#define TAG_GLOB_PHYS_MAX     4
+#define TAG_GLOB_UNIT_EXP     5
+#define TAG_GLOB_UNIT         6
+#define TAG_GLOB_REPORT_SZ    7
+#define TAG_GLOB_REPORT_ID    8
+#define TAG_GLOB_REPORT_CNT   9
+#define TAG_GLOB_PUSH         10
+#define TAG_GLOB_POP          11
+
+#define TAG_GLOB_MAX          12
+
+#define DIGITIZER_USAGE_TIP_PRESSURE   0x30
+#define DIGITIZER_USAGE_TILT_X         0x3D
+#define DIGITIZER_USAGE_TILT_Y         0x3E
+
+
+/*
+ *
+ *   This is an abbreviated parser for the HID Report Descriptor.  We
+ *   know what devices we are talking to, so this is by no means meant
+ *   to be generic.  We can make some safe assumptions:
+ *
+ *   - We know there are no LONG tags, all short
+ *   - We know that we have no MAIN Feature and MAIN Output items
+ *   - We know what the IRQ reports are supposed to look like.
+ *
+ *   The main purpose of this is to use the HID report desc to figure
+ *   out the mins and maxs of the fields in the IRQ reports.  The IRQ
+ *   reports for 400/401 change slightly if the max X is bigger than 64K.
+ *
+ */
+static void parse_hid_report_descriptor(struct gtco *device, char * report,
+                                       int length)
+{
+       int   x,i=0;
+
+       /* Tag primitive vars */
+       __u8   prefix;
+       __u8   size;
+       __u8   tag;
+       __u8   type;
+       __u8   data   = 0;
+       __u16  data16 = 0;
+       __u32  data32 = 0;
+
+
+       /* For parsing logic */
+       int   inputnum = 0;
+       __u32 usage = 0;
+
+       /* Global Values, indexed by TAG */
+       __u32 globalval[TAG_GLOB_MAX];
+       __u32 oldval[TAG_GLOB_MAX];
+
+       /* Debug stuff */
+       char  maintype='x';
+       char  globtype[12];
+       int   indent=0;
+       char  indentstr[10]="";
+
+
+
+       dbg("======>>>>>>PARSE<<<<<<======");
+
+       /* Walk  this report and pull out the info we need */
+       while (i<length){
+               prefix=report[i];
+
+               /* Skip over prefix */
+               i++;
+
+               /* Determine data size and save the data in the proper variable */
+               size = PREF_SIZE(prefix);
+               switch(size){
+               case 1:
+                       data = report[i];
+                       break;
+               case 2:
+                       data16 = le16_to_cpu(get_unaligned((__le16*)(&(report[i]))));
+                       break;
+               case 3:
+                       size = 4;
+                       data32 = le32_to_cpu(get_unaligned((__le32*)(&(report[i]))));
+               }
+
+               /* Skip size of data */
+               i+=size;
+
+               /* What we do depends on the tag type */
+               tag  = PREF_TAG(prefix);
+               type = PREF_TYPE(prefix);
+               switch(type){
+               case TYPE_MAIN:
+                       strcpy(globtype,"");
+                       switch(tag){
+
+                       case TAG_MAIN_INPUT:
+                               /*
+                                * The INPUT MAIN tag signifies this is
+                                * information from a report.  We need to
+                                * figure out what it is and store the
+                                * min/max values
+                                */
+
+                               maintype='I';
+                               if (data==2){
+                                       strcpy(globtype,"Variable");
+                               }
+                               if (data==3){
+                                       strcpy(globtype,"Var|Const");
+                               }
+
+                               dbg("::::: Saving Report: %d input #%d Max: 0x%X(%d) Min:0x%X(%d) of %d bits",
+                                   globalval[TAG_GLOB_REPORT_ID],inputnum,
+                                   globalval[TAG_GLOB_LOG_MAX],globalval[TAG_GLOB_LOG_MAX],
+                                   globalval[TAG_GLOB_LOG_MIN],globalval[TAG_GLOB_LOG_MIN],
+                                   (globalval[TAG_GLOB_REPORT_SZ] * globalval[TAG_GLOB_REPORT_CNT]));
+
+
+                               /*
+                                 We can assume that the first two input items
+                                 are always the X and Y coordinates.  After
+                                 that, we look for everything else by
+                                 local usage value
+                                */
+                               switch (inputnum){
+                               case 0:  /* X coord */
+                                       dbg("GER: X Usage: 0x%x",usage);
+                                       if (device->max_X == 0){
+                                               device->max_X = globalval[TAG_GLOB_LOG_MAX];
+                                               device->min_X = globalval[TAG_GLOB_LOG_MIN];
+                                       }
+
+                                       break;
+                               case 1:  /* Y coord */
+                                       dbg("GER: Y Usage: 0x%x",usage);
+                                       if (device->max_Y == 0){
+                                               device->max_Y = globalval[TAG_GLOB_LOG_MAX];
+                                               device->min_Y = globalval[TAG_GLOB_LOG_MIN];
+                                       }
+                                       break;
+                               default:
+                                       /* Tilt X */
+                                       if (usage == DIGITIZER_USAGE_TILT_X){
+                                               if (device->maxtilt_X == 0){
+                                                       device->maxtilt_X = globalval[TAG_GLOB_LOG_MAX];
+                                                       device->mintilt_X = globalval[TAG_GLOB_LOG_MIN];
+                                               }
+                                       }
+
+                                       /* Tilt Y */
+                                       if (usage == DIGITIZER_USAGE_TILT_Y){
+                                               if (device->maxtilt_Y == 0){
+                                                       device->maxtilt_Y = globalval[TAG_GLOB_LOG_MAX];
+                                                       device->mintilt_Y = globalval[TAG_GLOB_LOG_MIN];
+                                               }
+                                       }
+
+
+                                       /* Pressure */
+                                       if (usage == DIGITIZER_USAGE_TIP_PRESSURE){
+                                               if (device->maxpressure == 0){
+                                                       device->maxpressure = globalval[TAG_GLOB_LOG_MAX];
+                                                       device->minpressure = globalval[TAG_GLOB_LOG_MIN];
+                                               }
+                                       }
+
+                                       break;
+                               }
+
+                               inputnum++;
+
+
+                               break;
+                       case TAG_MAIN_OUTPUT:
+                               maintype='O';
+                               break;
+                       case TAG_MAIN_FEATURE:
+                               maintype='F';
+                               break;
+                       case TAG_MAIN_COL_START:
+                               maintype='S';
+
+                               if (data==0){
+                                       dbg("======>>>>>> Physical");
+                                       strcpy(globtype,"Physical");
+                               }else{
+                                       dbg("======>>>>>>");
+                               }
+
+                               /* Indent the debug output */
+                               indent++;
+                               for (x=0;x<indent;x++){
+                                       indentstr[x]='-';
+                               }
+                               indentstr[x]=0;
+
+                               /* Save global tags */
+                               for (x=0;x<TAG_GLOB_MAX;x++){
+                                       oldval[x] = globalval[x];
+                               }
+
+                               break;
+                       case TAG_MAIN_COL_END:
+                               dbg("<<<<<<======");
+                               maintype='E';
+                               indent--;
+                               for (x=0;x<indent;x++){
+                                       indentstr[x]='-';
+                               }
+                               indentstr[x]=0;
+
+                               /* Copy global tags back */
+                               for (x=0;x<TAG_GLOB_MAX;x++){
+                                       globalval[x] = oldval[x];
+                               }
+
+                               break;
+                       }
+
+                       switch (size){
+                       case 1:
+                               dbg("%sMAINTAG:(%d) %c SIZE: %d Data: %s 0x%x",
+                                   indentstr,tag,maintype,size,globtype,data);
+                               break;
+                       case 2:
+                               dbg("%sMAINTAG:(%d) %c SIZE: %d Data: %s 0x%x",
+                                   indentstr,tag,maintype,size,globtype, data16);
+                               break;
+                       case 4:
+                               dbg("%sMAINTAG:(%d) %c SIZE: %d Data: %s 0x%x",
+                                   indentstr,tag,maintype,size,globtype,data32);
+                               break;
+                       }
+                       break;
+               case TYPE_GLOBAL:
+                       switch(tag){
+                       case TAG_GLOB_USAGE:
+                               /*
+                                * First time we hit the global usage tag,
+                                * it should tell us the type of device
+                                */
+                               if (device->usage == 0){
+                                       device->usage = data;
+                               }
+                               strcpy(globtype,"USAGE");
+                               break;
+                       case TAG_GLOB_LOG_MIN   :
+                               strcpy(globtype,"LOG_MIN");
+                               break;
+                       case TAG_GLOB_LOG_MAX   :
+                               strcpy(globtype,"LOG_MAX");
+                               break;
+                       case TAG_GLOB_PHYS_MIN  :
+                               strcpy(globtype,"PHYS_MIN");
+                               break;
+                       case TAG_GLOB_PHYS_MAX  :
+                               strcpy(globtype,"PHYS_MAX");
+                               break;
+                       case TAG_GLOB_UNIT_EXP  :
+                               strcpy(globtype,"EXP");
+                               break;
+                       case TAG_GLOB_UNIT      :
+                               strcpy(globtype,"UNIT");
+                               break;
+                       case TAG_GLOB_REPORT_SZ :
+                               strcpy(globtype,"REPORT_SZ");
+                               break;
+                       case TAG_GLOB_REPORT_ID :
+                               strcpy(globtype,"REPORT_ID");
+                               /* New report, restart numbering */
+                               inputnum=0;
+                               break;
+                       case TAG_GLOB_REPORT_CNT:
+                               strcpy(globtype,"REPORT_CNT");
+                               break;
+                       case TAG_GLOB_PUSH :
+                               strcpy(globtype,"PUSH");
+                               break;
+                       case TAG_GLOB_POP:
+                               strcpy(globtype,"POP");
+                               break;
+                       }
+
+
+                       /* Check to make sure we have a good tag number
+                          so we don't overflow array */
+                       if (tag < TAG_GLOB_MAX){
+                               switch (size){
+                               case 1:
+                                       dbg("%sGLOBALTAG:%s(%d) SIZE: %d Data: 0x%x",indentstr,globtype,tag,size,data);
+                                       globalval[tag]=data;
+                                       break;
+                               case 2:
+                                       dbg("%sGLOBALTAG:%s(%d) SIZE: %d Data: 0x%x",indentstr,globtype,tag,size,data16);
+                                       globalval[tag]=data16;
+                                       break;
+                               case 4:
+                                       dbg("%sGLOBALTAG:%s(%d) SIZE: %d Data: 0x%x",indentstr,globtype,tag,size,data32);
+                                       globalval[tag]=data32;
+                                       break;
+                               }
+                       }else{
+                               dbg("%sGLOBALTAG: ILLEGAL TAG:%d SIZE: %d ",
+                                   indentstr,tag,size);
+                       }
+
+
+                       break;
+
+               case TYPE_LOCAL:
+                       switch(tag){
+                       case TAG_GLOB_USAGE:
+                               strcpy(globtype,"USAGE");
+                               /* Always 1 byte */
+                               usage = data;
+                               break;
+                       case TAG_GLOB_LOG_MIN   :
+                               strcpy(globtype,"MIN");
+                               break;
+                       case TAG_GLOB_LOG_MAX   :
+                               strcpy(globtype,"MAX");
+                               break;
+                       default:
+                               strcpy(globtype,"UNKNOWN");
+                       }
+
+                       switch (size){
+                       case 1:
+                               dbg("%sLOCALTAG:(%d) %s SIZE: %d Data: 0x%x",
+                                   indentstr,tag,globtype,size,data);
+                               break;
+                       case 2:
+                               dbg("%sLOCALTAG:(%d) %s SIZE: %d Data: 0x%x",
+                                   indentstr,tag,globtype,size,data16);
+                               break;
+                       case 4:
+                               dbg("%sLOCALTAG:(%d) %s SIZE: %d Data: 0x%x",
+                                   indentstr,tag,globtype,size,data32);
+                               break;
+                       }
+
+                       break;
+               }
+
+       }
+
+}
+
+
+
+/*   INPUT DRIVER Routines                               */
+
+
+/*
+ *    Called when opening the input device.  This will submit the URB to
+ *    the usb system so we start getting reports
+ */
+static int gtco_input_open(struct input_dev *inputdev)
+{
+       struct gtco *device;
+       device = inputdev->private;
+
+       device->urbinfo->dev = device->usbdev;
+       if (usb_submit_urb(device->urbinfo, GFP_KERNEL)) {
+               return -EIO;
+       }
+       return 0;
+}
+
+/**
+    Called when closing the input device.  This will unlink the URB
+*/
+static void gtco_input_close(struct input_dev *inputdev)
+{
+       struct gtco *device = inputdev->private;
+
+       usb_kill_urb(device->urbinfo);
+
+}
+
+
+/*
+ *  Setup input device capabilities.  Tell the input system what this
+ *  device is capable of generating.
+ *
+ *  This information is based on what is read from the HID report and
+ *  placed in the struct gtco structure
+ *
+ */
+static void  gtco_setup_caps(struct input_dev  *inputdev)
+{
+       struct gtco *device = inputdev->private;
+
+
+       /* Which events */
+       inputdev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_MSC);
+
+
+       /* Misc event menu block */
+       inputdev->mscbit[0] = BIT(MSC_SCAN)|BIT(MSC_SERIAL)|BIT(MSC_RAW) ;
+
+
+       /* Absolute values based on HID report info */
+       input_set_abs_params(inputdev, ABS_X, device->min_X, device->max_X,
+                            0, 0);
+       input_set_abs_params(inputdev, ABS_Y, device->min_Y, device->max_Y,
+                            0, 0);
+
+       /* Proximity */
+       input_set_abs_params(inputdev, ABS_DISTANCE, 0, 1, 0, 0);
+
+       /* Tilt & pressure */
+       input_set_abs_params(inputdev, ABS_TILT_X, device->mintilt_X,
+                            device->maxtilt_X, 0, 0);
+       input_set_abs_params(inputdev, ABS_TILT_Y, device->mintilt_Y,
+                            device->maxtilt_Y, 0, 0);
+       input_set_abs_params(inputdev, ABS_PRESSURE, device->minpressure,
+                            device->maxpressure, 0, 0);
+
+
+       /* Transducer */
+       input_set_abs_params(inputdev, ABS_MISC, 0,0xFF, 0, 0);
+
+}
+
+
+
+/*   USB Routines  */
+
+
+/*
+ * URB callback routine.  Called when we get IRQ reports from the
+ *  digitizer.
+ *
+ *  This bridges the USB and input device worlds.  It generates events
+ *  on the input device based on the USB reports.
+ */
+static void gtco_urb_callback(struct urb *urbinfo)
+{
+
+
+       struct gtco     *device = urbinfo->context;
+       struct input_dev  *inputdev;
+       int               rc;
+       u32               val = 0;
+       s8                valsigned = 0;
+       char              le_buffer[2];
+
+       inputdev = device->inputdevice;
+
+
+       /* Was callback OK? */
+       if ((urbinfo->status == -ECONNRESET ) ||
+           (urbinfo->status == -ENOENT ) ||
+           (urbinfo->status == -ESHUTDOWN )){
+
+               /* Shutdown is occurring. Return and don't queue up any more */
+               return;
+       }
+
+       if (urbinfo->status != 0 ) {
+               /* Some unknown error.  Hopefully temporary.  Just go and */
+               /* requeue an URB */
+               goto resubmit;
+       }
+
+       /*
+        * Good URB, now process
+        */
+
+       /* PID dependent when we interpret the report */
+       if ((inputdev->id.product == PID_1000 )||
+           (inputdev->id.product == PID_1001 )||
+           (inputdev->id.product == PID_1002 ))
+       {
+
+               /*
+                * Switch on the report ID
+                * Conveniently, the reports have more information, the higher
+                * the report number.  We can just fall through the case
+                * statements if we start with the highest number report
+                */
+               switch(device->buffer[0]){
+               case 5:
+                       /* Pressure is 9 bits */
+                       val =  ((u16)(device->buffer[8]) << 1);
+                       val |= (u16)(device->buffer[7] >> 7);
+                       input_report_abs(inputdev, ABS_PRESSURE,
+                                        device->buffer[8]);
+
+                       /* Mask out the Y tilt value used for pressure */
+                       device->buffer[7] = (u8)((device->buffer[7]) & 0x7F);
+
+
+                       /* Fall thru */
+               case 4:
+                       /* Tilt */
+
+                       /* Sign extend these 7 bit numbers.  */
+                       if (device->buffer[6] & 0x40)
+                               device->buffer[6] |= 0x80;
+
+                       if (device->buffer[7] & 0x40)
+                               device->buffer[7] |= 0x80;
+
+
+                       valsigned = (device->buffer[6]);
+                       input_report_abs(inputdev, ABS_TILT_X, (s32)valsigned);
+
+                       valsigned = (device->buffer[7]);
+                       input_report_abs(inputdev, ABS_TILT_Y, (s32)valsigned);
+
+                       /* Fall thru */
+
+               case 2:
+               case 3:
+                       /* Convert buttons, only 5 bits possible */
+                       val = (device->buffer[5])&MASK_BUTTON;
+
+                       /* We don't apply any meaning to the bitmask,
+                          just report */
+                       input_event(inputdev, EV_MSC, MSC_SERIAL, val);
+
+                       /*  Fall thru */
+               case 1:
+
+                       /* All reports have X and Y coords in the same place */
+                       val = le16_to_cpu(get_unaligned((__le16 *) &(device->buffer[1])));
+                       input_report_abs(inputdev, ABS_X, val);
+
+                       val = le16_to_cpu(get_unaligned((__le16 *) &(device->buffer[3])));
+                       input_report_abs(inputdev, ABS_Y, val);
+
+
+                       /* Ditto for proximity bit */
+                       if (device->buffer[5]& MASK_INRANGE){
+                               val = 1;
+                       }else{
+                               val=0;
+                       }
+                       input_report_abs(inputdev, ABS_DISTANCE, val);
+
+
+                       /* Report 1 is an exception to how we handle buttons */
+                       /* Buttons are an index, not a bitmask */
+                       if (device->buffer[0] == 1){
+
+                               /* Convert buttons, 5 bit index */
+                               /* Report value of index set as one,
+                                  the rest as 0 */
+                               val = device->buffer[5]& MASK_BUTTON;
+                               dbg("======>>>>>>REPORT 1: val 0x%X(%d)",
+                                   val,val);
+
+                               /*
+                                * We don't apply any meaning to the button
+                                * index, just report it
+                                */
+                               input_event(inputdev, EV_MSC, MSC_SERIAL, val);
+
+
+                       }
+
+                       break;
+               case 7:
+                       /* Menu blocks */
+                       input_event(inputdev, EV_MSC, MSC_SCAN,
+                                   device->buffer[1]);
+
+
+                       break;
+
+               }
+
+
+       }
+       /* Other pid class */
+       if ((inputdev->id.product == PID_400 )||
+           (inputdev->id.product == PID_401 ))
+       {
+
+               /* Report 2 */
+               if (device->buffer[0] == 2){
+                       /* Menu blocks */
+                       input_event(inputdev, EV_MSC, MSC_SCAN,
+                                   device->buffer[1]);
+               }
+
+               /*  Report 1 */
+               if (device->buffer[0] == 1){
+                       char buttonbyte;
+
+
+                       /*  IF X max > 64K, we still a bit from the y report */
+                       if (device->max_X > 0x10000){
+
+                               val = (u16)(((u16)(device->buffer[2]<<8))|((u8)(device->buffer[1])));
+                               val |= (u32)(((u8)device->buffer[3]&0x1)<< 16);
+
+                               input_report_abs(inputdev, ABS_X, val);
+
+                               le_buffer[0]  = (u8)((u8)(device->buffer[3])>>1);
+                               le_buffer[0] |= (u8)((device->buffer[3]&0x1)<<7);
+
+                               le_buffer[1]  = (u8)(device->buffer[4]>>1);
+                               le_buffer[1] |= (u8)((device->buffer[5]&0x1)<<7);
+
+                               val = le16_to_cpu(get_unaligned((__le16 *)(le_buffer)));
+
+                               input_report_abs(inputdev, ABS_Y, val);
+
+
+                               /*
+                                * Shift the button byte right by one to
+                                * make it look like the standard report
+                                */
+                               buttonbyte = (device->buffer[5])>>1;
+                       }else{
+
+                               val = le16_to_cpu(get_unaligned((__le16 *) (&(device->buffer[1]))));
+                               input_report_abs(inputdev, ABS_X, val);
+
+                               val = le16_to_cpu(get_unaligned((__le16 *) (&(device->buffer[3]))));
+                               input_report_abs(inputdev, ABS_Y, val);
+
+                               buttonbyte = device->buffer[5];
+
+                       }
+
+
+                       /* BUTTONS and PROXIMITY */
+                       if (buttonbyte& MASK_INRANGE){
+                               val = 1;
+                       }else{
+                               val=0;
+                       }
+                       input_report_abs(inputdev, ABS_DISTANCE, val);
+
+                       /* Convert buttons, only 4 bits possible */
+                       val = buttonbyte&0x0F;
+#ifdef USE_BUTTONS
+                       for ( i=0;i<5;i++){
+                               input_report_key(inputdev, BTN_DIGI+i,val&(1<<i));
+                       }
+#else
+                       /* We don't apply any meaning to the bitmask, just report */
+                       input_event(inputdev, EV_MSC, MSC_SERIAL, val);
+#endif
+                       /* TRANSDUCER */
+                       input_report_abs(inputdev, ABS_MISC, device->buffer[6]);
+
+               }
+       }
+
+       /* Everybody gets report ID's */
+       input_event(inputdev, EV_MSC, MSC_RAW,  device->buffer[0]);
+
+       /* Sync it up */
+       input_sync(inputdev);
+
+ resubmit:
+       rc = usb_submit_urb(urbinfo, GFP_ATOMIC);
+       if (rc != 0) {
+               err("usb_submit_urb failed rc=0x%x",rc);
+       }
+
+}
+
+/*
+ *  The probe routine.  This is called when the kernel find the matching USB
+ *   vendor/product.  We do the following:
+ *
+ *    - Allocate mem for a local structure to manage the device
+ *    - Request a HID Report Descriptor from the device and parse it to
+ *      find out the device parameters
+ *    - Create an input device and assign it attributes
+ *   - Allocate an URB so the device can talk to us when the input
+ *      queue is open
+ */
+static int gtco_probe(struct usb_interface *usbinterface,
+                     const struct usb_device_id *id)
+{
+
+       struct gtco             *device = NULL;
+       char                    path[PATHLENGTH];
+       struct input_dev        *inputdev;
+       struct hid_descriptor   *hid_desc;
+       char                    *report;
+       int                     result=0, retry;
+       struct usb_endpoint_descriptor *endpoint;
+
+       /* Allocate memory for device structure */
+       device = kzalloc(sizeof(struct gtco), GFP_KERNEL);
+       if (device == NULL) {
+               err("No more memory");
+               return -ENOMEM;
+       }
+
+
+       device->inputdevice = input_allocate_device();
+       if (!device->inputdevice){
+               kfree(device);
+               err("No more memory");
+               return -ENOMEM;
+       }
+
+       /* Get pointer to the input device */
+       inputdev = device->inputdevice;
+
+       /* Save interface information */
+       device->usbdev     = usb_get_dev(interface_to_usbdev(usbinterface));
+
+
+       /* Allocate some data for incoming reports */
+       device->buffer = usb_buffer_alloc(device->usbdev, REPORT_MAX_SIZE,
+                                          GFP_KERNEL, &(device->buf_dma));
+       if (!device->buffer){
+               input_free_device(device->inputdevice);
+               kfree(device);
+               err("No more memory");
+               return -ENOMEM;
+       }
+
+       /* Allocate URB for reports */
+       device->urbinfo = usb_alloc_urb(0, GFP_KERNEL);
+       if (!device->urbinfo) {
+               usb_buffer_free(device->usbdev, REPORT_MAX_SIZE,
+                               device->buffer, device->buf_dma);
+               input_free_device(device->inputdevice);
+               kfree(device);
+               err("No more memory");
+               return -ENOMEM;
+       }
+
+
+       /*
+        * The endpoint is always altsetting 0, we know this since we know
+        * this device only has one interrupt endpoint
+        */
+       endpoint = &usbinterface->altsetting[0].endpoint[0].desc;
+
+       /* Some debug */
+       dbg("gtco # interfaces: %d",usbinterface->num_altsetting);
+       dbg("num endpoints:     %d",usbinterface->cur_altsetting->desc.bNumEndpoints);
+       dbg("interface class:   %d",usbinterface->cur_altsetting->desc.bInterfaceClass);
+       dbg("endpoint: attribute:0x%x type:0x%x",endpoint->bmAttributes,endpoint->bDescriptorType);
+       if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
+               dbg("endpoint: we have interrupt endpoint\n");
+
+       dbg("endpoint extra len:%d ",usbinterface->altsetting[0].extralen);
+
+
+
+       /*
+        * Find the HID descriptor so we can find out the size of the
+        * HID report descriptor
+        */
+       if (usb_get_extra_descriptor(usbinterface->cur_altsetting,
+                                    HID_DEVICE_TYPE,&hid_desc) != 0){
+               err("Can't retrieve exta USB descriptor to get hid report descriptor length");
+               usb_buffer_free(device->usbdev, REPORT_MAX_SIZE,
+                               device->buffer, device->buf_dma);
+               input_free_device(device->inputdevice);
+               kfree(device);
+               return -EIO;
+       }
+
+       dbg("Extra descriptor success: type:%d  len:%d",
+           hid_desc->bDescriptorType,  hid_desc->wDescriptorLength);
+
+       if (!(report = kzalloc(hid_desc->wDescriptorLength, GFP_KERNEL))) {
+               usb_buffer_free(device->usbdev, REPORT_MAX_SIZE,
+                               device->buffer, device->buf_dma);
+
+               input_free_device(device->inputdevice);
+               kfree(device);
+               err("No more memory");
+               return -ENOMEM;
+       }
+
+       /* Couple of tries to get reply */
+       for (retry=0;retry<3;retry++) {
+               result = usb_control_msg(device->usbdev,
+                                        usb_rcvctrlpipe(device->usbdev, 0),
+                                        USB_REQ_GET_DESCRIPTOR,
+                                        USB_RECIP_INTERFACE | USB_DIR_IN,
+                                        (REPORT_DEVICE_TYPE << 8),
+                                        0, /* interface */
+                                        report,
+                                        hid_desc->wDescriptorLength,
+                                        5000); /* 5 secs */
+
+               if (result == hid_desc->wDescriptorLength)
+                       break;
+       }
+
+       /* If we didn't get the report, fail */
+       dbg("usb_control_msg result: :%d", result);
+       if (result != hid_desc->wDescriptorLength){
+               kfree(report);
+               usb_buffer_free(device->usbdev, REPORT_MAX_SIZE,
+                               device->buffer, device->buf_dma);
+               input_free_device(device->inputdevice);
+               kfree(device);
+               err("Failed to get HID Report Descriptor of size: %d",
+                   hid_desc->wDescriptorLength);
+               return -EIO;
+       }
+
+
+       /* Now we parse the report */
+       parse_hid_report_descriptor(device,report,result);
+
+       /* Now we delete it */
+       kfree(report);
+
+       /* Create a device file node */
+       usb_make_path(device->usbdev, path, PATHLENGTH);
+       sprintf(device->usbpath, "%s/input0", path);
+
+
+       /* Set Input device functions */
+       inputdev->open     = gtco_input_open;
+       inputdev->close    = gtco_input_close;
+
+       /* Set input device information */
+       inputdev->name     = "GTCO_CalComp";
+       inputdev->phys     = device->usbpath;
+       inputdev->private  = device;
+
+
+       /* Now set up all the input device capabilities */
+       gtco_setup_caps(inputdev);
+
+       /* Set input device required ID information */
+       usb_to_input_id(device->usbdev, &device->inputdevice->id);
+       inputdev->cdev.dev = &usbinterface->dev;
+
+       /* Setup the URB, it will be posted later on open of input device */
+       endpoint = &usbinterface->altsetting[0].endpoint[0].desc;
+
+       usb_fill_int_urb(device->urbinfo,
+                        device->usbdev,
+                        usb_rcvintpipe(device->usbdev,
+                                       endpoint->bEndpointAddress),
+                        device->buffer,
+                        REPORT_MAX_SIZE,
+                        gtco_urb_callback,
+                        device,
+                        endpoint->bInterval);
+
+       device->urbinfo->transfer_dma = device->buf_dma;
+       device->urbinfo->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+
+       /* Save device pointer in USB interface device */
+       usb_set_intfdata(usbinterface, device);
+
+       /* All done, now register the input device */
+       input_register_device(inputdev);
+
+       info( "gtco driver created usb:  %s\n",  path);
+       return 0;
+
+}
+
+/*
+ *  This function is a standard USB function called when the USB device
+ *  is disconnected.  We will get rid of the URV, de-register the input
+ *  device, and free up allocated memory
+ */
+static void gtco_disconnect(struct usb_interface *interface)
+{
+
+       /* Grab private device ptr */
+       struct gtco    *device = usb_get_intfdata (interface);
+       struct input_dev *inputdev;
+
+       inputdev = device->inputdevice;
+
+       /* Now reverse all the registration stuff */
+       if (device) {
+               input_unregister_device(inputdev);
+               usb_kill_urb(device->urbinfo);
+               usb_free_urb(device->urbinfo);
+               usb_buffer_free(device->usbdev, REPORT_MAX_SIZE,
+                               device->buffer, device->buf_dma);
+               kfree(device);
+       }
+
+       info("gtco driver disconnected");
+}
+
+
+/*   STANDARD MODULE LOAD ROUTINES  */
+
+static struct usb_driver gtco_driverinfo_table = {
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16))
+       .owner      = THIS_MODULE,
+#endif
+       .name       = "gtco",
+       .id_table   = gtco_usbid_table,
+       .probe      = gtco_probe,
+       .disconnect = gtco_disconnect,
+};
+/*
+ *  Register this module with the USB subsystem
+ */
+static int __init gtco_init(void)
+{
+       int rc;
+       rc = usb_register(&gtco_driverinfo_table);
+       if (rc) {
+               err("usb_register() failed rc=0x%x", rc);
+       }
+       printk("GTCO usb driver version: %s",GTCO_VERSION);
+       return rc;
+}
+
+/*
+ *   Deregister this module with the USB subsystem
+ */
+static void __exit gtco_exit(void)
+{
+       usb_deregister(&gtco_driverinfo_table);
+}
+
+module_init (gtco_init);
+module_exit (gtco_exit);
+
+MODULE_LICENSE("GPL");