diff options
author | 2007-05-07 16:16:29 -0400 | |
---|---|---|
committer | 2007-05-08 01:41:29 -0400 | |
commit | 4104d13fe0194736393d97c88ee045fb689c783b (patch) | |
tree | 1915a03fbad7541df368f0940387f0f15b7fc380 /drivers/input | |
parent | Input: i8042 - fix AUX port detection with some chips (diff) | |
download | linux-4104d13fe0194736393d97c88ee045fb689c783b.tar.gz linux-4104d13fe0194736393d97c88ee045fb689c783b.tar.bz2 linux-4104d13fe0194736393d97c88ee045fb689c783b.zip |
Input: move USB tablets under drivers/input/tablet
This will allow concentrating all input devices in one place
in {menu|x|q}config.
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/input')
-rw-r--r-- | drivers/input/Kconfig | 2 | ||||
-rw-r--r-- | drivers/input/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/tablet/Kconfig | 74 | ||||
-rw-r--r-- | drivers/input/tablet/Makefile | 12 | ||||
-rw-r--r-- | drivers/input/tablet/acecad.c | 289 | ||||
-rw-r--r-- | drivers/input/tablet/aiptek.c | 2236 | ||||
-rw-r--r-- | drivers/input/tablet/gtco.c | 1055 | ||||
-rw-r--r-- | drivers/input/tablet/kbtab.c | 226 | ||||
-rw-r--r-- | drivers/input/tablet/wacom.h | 131 | ||||
-rw-r--r-- | drivers/input/tablet/wacom_sys.c | 318 | ||||
-rw-r--r-- | drivers/input/tablet/wacom_wac.c | 675 | ||||
-rw-r--r-- | drivers/input/tablet/wacom_wac.h | 49 |
12 files changed, 5068 insertions, 0 deletions
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 96232313b1b9..0e9b69535ad6 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -153,6 +153,8 @@ source "drivers/input/mouse/Kconfig" source "drivers/input/joystick/Kconfig" +source "drivers/input/tablet/Kconfig" + source "drivers/input/touchscreen/Kconfig" source "drivers/input/misc/Kconfig" diff --git a/drivers/input/Makefile b/drivers/input/Makefile index b4cd10653c4f..8a2dd987546c 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_INPUT_EVBUG) += evbug.o obj-$(CONFIG_INPUT_KEYBOARD) += keyboard/ obj-$(CONFIG_INPUT_MOUSE) += mouse/ obj-$(CONFIG_INPUT_JOYSTICK) += joystick/ +obj-$(CONFIG_INPUT_TABLET) += tablet/ obj-$(CONFIG_INPUT_TOUCHSCREEN) += touchscreen/ obj-$(CONFIG_INPUT_MISC) += misc/ diff --git a/drivers/input/tablet/Kconfig b/drivers/input/tablet/Kconfig new file mode 100644 index 000000000000..12dfb0eb3262 --- /dev/null +++ b/drivers/input/tablet/Kconfig @@ -0,0 +1,74 @@ +# +# Tablet driver configuration +# +menuconfig INPUT_TABLET + bool "Tablets" + help + Say Y here, and a list of supported tablets will be displayed. + This option doesn't affect the kernel. + + If unsure, say Y. + +if INPUT_TABLET + +config TABLET_USB_ACECAD + tristate "Acecad Flair tablet support (USB)" + select USB + help + Say Y here if you want to use the USB version of the Acecad Flair + 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 acecad. + +config TABLET_USB_AIPTEK + tristate "Aiptek 6000U/8000U tablet support (USB)" + select USB + help + Say Y here if you want to use the USB version of the Aiptek 6000U + or Aiptek 8000U 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 aiptek. + +config TABLET_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. + +config TABLET_USB_KBTAB + tristate "KB Gear JamStudio tablet support (USB)" + select USB + help + Say Y here if you want to use the USB version of the KB Gear + JamStudio 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 kbtab. + +config TABLET_USB_WACOM + tristate "Wacom Intuos/Graphire tablet support (USB)" + select USB + help + Say Y here if you want to use the USB version of the Wacom Intuos + or Graphire 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 wacom. + +endif diff --git a/drivers/input/tablet/Makefile b/drivers/input/tablet/Makefile new file mode 100644 index 000000000000..ce8b9a9cfa40 --- /dev/null +++ b/drivers/input/tablet/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the tablet drivers +# + +# Multipart objects. +wacom-objs := wacom_wac.o wacom_sys.o + +obj-$(CONFIG_TABLET_USB_ACECAD) += acecad.o +obj-$(CONFIG_TABLET_USB_AIPTEK) += aiptek.o +obj-$(CONFIG_TABLET_USB_GTCO) += gtco.o +obj-$(CONFIG_TABLET_USB_KBTAB) += kbtab.o +obj-$(CONFIG_TABLET_USB_WACOM) += wacom.o diff --git a/drivers/input/tablet/acecad.c b/drivers/input/tablet/acecad.c new file mode 100644 index 000000000000..dd2310458c46 --- /dev/null +++ b/drivers/input/tablet/acecad.c @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2001-2005 Edouard TISSERANT <edouard.tisserant@wanadoo.fr> + * Copyright (c) 2004-2005 Stephane VOLTZ <svoltz@numericable.fr> + * + * USB Acecad "Acecad Flair" tablet support + * + * Changelog: + * v3.2 - Added sysfs support + */ + +/* + * 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 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 <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb/input.h> + +/* + * Version Information + */ +#define DRIVER_VERSION "v3.2" +#define DRIVER_DESC "USB Acecad Flair tablet driver" +#define DRIVER_LICENSE "GPL" +#define DRIVER_AUTHOR "Edouard TISSERANT <edouard.tisserant@wanadoo.fr>" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); + +#define USB_VENDOR_ID_ACECAD 0x0460 +#define USB_DEVICE_ID_FLAIR 0x0004 +#define USB_DEVICE_ID_302 0x0008 + +struct usb_acecad { + char name[128]; + char phys[64]; + struct usb_device *usbdev; + struct input_dev *input; + struct urb *irq; + + unsigned char *data; + dma_addr_t data_dma; +}; + +static void usb_acecad_irq(struct urb *urb) +{ + struct usb_acecad *acecad = urb->context; + unsigned char *data = acecad->data; + struct input_dev *dev = acecad->input; + int prox, status; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); + goto resubmit; + } + + prox = (data[0] & 0x04) >> 2; + input_report_key(dev, BTN_TOOL_PEN, prox); + + if (prox) { + int x = data[1] | (data[2] << 8); + int y = data[3] | (data[4] << 8); + /* Pressure should compute the same way for flair and 302 */ + int pressure = data[5] | (data[6] << 8); + int touch = data[0] & 0x01; + int stylus = (data[0] & 0x10) >> 4; + int stylus2 = (data[0] & 0x20) >> 5; + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + input_report_abs(dev, ABS_PRESSURE, pressure); + input_report_key(dev, BTN_TOUCH, touch); + input_report_key(dev, BTN_STYLUS, stylus); + input_report_key(dev, BTN_STYLUS2, stylus2); + } + + /* event termination */ + input_sync(dev); + +resubmit: + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) + err("can't resubmit intr, %s-%s/input0, status %d", + acecad->usbdev->bus->bus_name, acecad->usbdev->devpath, status); +} + +static int usb_acecad_open(struct input_dev *dev) +{ + struct usb_acecad *acecad = input_get_drvdata(dev); + + acecad->irq->dev = acecad->usbdev; + if (usb_submit_urb(acecad->irq, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void usb_acecad_close(struct input_dev *dev) +{ + struct usb_acecad *acecad = input_get_drvdata(dev); + + usb_kill_urb(acecad->irq); +} + +static int usb_acecad_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_host_interface *interface = intf->cur_altsetting; + struct usb_endpoint_descriptor *endpoint; + struct usb_acecad *acecad; + struct input_dev *input_dev; + int pipe, maxp; + int err = -ENOMEM; + + if (interface->desc.bNumEndpoints != 1) + return -ENODEV; + + endpoint = &interface->endpoint[0].desc; + + if (!usb_endpoint_is_int_in(endpoint)) + return -ENODEV; + + pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); + maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + + acecad = kzalloc(sizeof(struct usb_acecad), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!acecad || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + acecad->data = usb_buffer_alloc(dev, 8, GFP_KERNEL, &acecad->data_dma); + if (!acecad->data) { + err= -ENOMEM; + goto fail1; + } + + acecad->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!acecad->irq) { + err = -ENOMEM; + goto fail2; + } + + acecad->usbdev = dev; + acecad->input = input_dev; + + if (dev->manufacturer) + strlcpy(acecad->name, dev->manufacturer, sizeof(acecad->name)); + + if (dev->product) { + if (dev->manufacturer) + strlcat(acecad->name, " ", sizeof(acecad->name)); + strlcat(acecad->name, dev->product, sizeof(acecad->name)); + } + + usb_make_path(dev, acecad->phys, sizeof(acecad->phys)); + strlcat(acecad->phys, "/input0", sizeof(acecad->phys)); + + input_dev->name = acecad->name; + input_dev->phys = acecad->phys; + usb_to_input_id(dev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, acecad); + + input_dev->open = usb_acecad_open; + input_dev->close = usb_acecad_close; + + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + input_dev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE); + input_dev->keybit[LONG(BTN_LEFT)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE); + input_dev->keybit[LONG(BTN_DIGI)] = BIT(BTN_TOOL_PEN) |BIT(BTN_TOUCH) | BIT(BTN_STYLUS) | BIT(BTN_STYLUS2); + + switch (id->driver_info) { + case 0: + input_dev->absmax[ABS_X] = 5000; + input_dev->absmax[ABS_Y] = 3750; + input_dev->absmax[ABS_PRESSURE] = 512; + if (!strlen(acecad->name)) + snprintf(acecad->name, sizeof(acecad->name), + "USB Acecad Flair Tablet %04x:%04x", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + break; + case 1: + input_dev->absmax[ABS_X] = 3000; + input_dev->absmax[ABS_Y] = 2250; + input_dev->absmax[ABS_PRESSURE] = 1024; + if (!strlen(acecad->name)) + snprintf(acecad->name, sizeof(acecad->name), + "USB Acecad 302 Tablet %04x:%04x", + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + break; + } + + input_dev->absfuzz[ABS_X] = 4; + input_dev->absfuzz[ABS_Y] = 4; + + usb_fill_int_urb(acecad->irq, dev, pipe, + acecad->data, maxp > 8 ? 8 : maxp, + usb_acecad_irq, acecad, endpoint->bInterval); + acecad->irq->transfer_dma = acecad->data_dma; + acecad->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + err = input_register_device(acecad->input); + if (err) + goto fail2; + + usb_set_intfdata(intf, acecad); + + return 0; + + fail2: usb_buffer_free(dev, 8, acecad->data, acecad->data_dma); + fail1: input_free_device(input_dev); + kfree(acecad); + return err; +} + +static void usb_acecad_disconnect(struct usb_interface *intf) +{ + struct usb_acecad *acecad = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + if (acecad) { + usb_kill_urb(acecad->irq); + input_unregister_device(acecad->input); + usb_free_urb(acecad->irq); + usb_buffer_free(interface_to_usbdev(intf), 10, acecad->data, acecad->data_dma); + kfree(acecad); + } +} + +static struct usb_device_id usb_acecad_id_table [] = { + { USB_DEVICE(USB_VENDOR_ID_ACECAD, USB_DEVICE_ID_FLAIR), .driver_info = 0 }, + { USB_DEVICE(USB_VENDOR_ID_ACECAD, USB_DEVICE_ID_302), .driver_info = 1 }, + { } +}; + +MODULE_DEVICE_TABLE(usb, usb_acecad_id_table); + +static struct usb_driver usb_acecad_driver = { + .name = "usb_acecad", + .probe = usb_acecad_probe, + .disconnect = usb_acecad_disconnect, + .id_table = usb_acecad_id_table, +}; + +static int __init usb_acecad_init(void) +{ + int result = usb_register(&usb_acecad_driver); + if (result == 0) + info(DRIVER_VERSION ":" DRIVER_DESC); + return result; +} + +static void __exit usb_acecad_exit(void) +{ + usb_deregister(&usb_acecad_driver); +} + +module_init(usb_acecad_init); +module_exit(usb_acecad_exit); diff --git a/drivers/input/tablet/aiptek.c b/drivers/input/tablet/aiptek.c new file mode 100644 index 000000000000..cc0a498763d8 --- /dev/null +++ b/drivers/input/tablet/aiptek.c @@ -0,0 +1,2236 @@ +/* + * Native support for the Aiptek HyperPen USB Tablets + * (4000U/5000U/6000U/8000U/12000U) + * + * Copyright (c) 2001 Chris Atenasio <chris@crud.net> + * Copyright (c) 2002-2004 Bryan W. Headley <bwheadley@earthlink.net> + * + * based on wacom.c by + * Vojtech Pavlik <vojtech@suse.cz> + * Andreas Bach Aaen <abach@stofanet.dk> + * Clifford Wolf <clifford@clifford.at> + * Sam Mosel <sam.mosel@computer.org> + * James E. Blair <corvus@gnu.org> + * Daniel Egger <egger@suse.de> + * + * Many thanks to Oliver Kuechemann for his support. + * + * ChangeLog: + * v0.1 - Initial release + * v0.2 - Hack to get around fake event 28's. (Bryan W. Headley) + * v0.3 - Make URB dynamic (Bryan W. Headley, Jun-8-2002) + * Released to Linux 2.4.19 and 2.5.x + * v0.4 - Rewrote substantial portions of the code to deal with + * corrected control sequences, timing, dynamic configuration, + * support of 6000U - 12000U, procfs, and macro key support + * (Jan-1-2003 - Feb-5-2003, Bryan W. Headley) + * v1.0 - Added support for diagnostic messages, count of messages + * received from URB - Mar-8-2003, Bryan W. Headley + * v1.1 - added support for tablet resolution, changed DV and proximity + * some corrections - Jun-22-2003, martin schneebacher + * - Added support for the sysfs interface, deprecating the + * procfs interface for 2.5.x kernel. Also added support for + * Wheel command. Bryan W. Headley July-15-2003. + * v1.2 - Reworked jitter timer as a kernel thread. + * Bryan W. Headley November-28-2003/Jan-10-2004. + * v1.3 - Repaired issue of kernel thread going nuts on single-processor + * machines, introduced programmableDelay as a command line + * parameter. Feb 7 2004, Bryan W. Headley. + * v1.4 - Re-wire jitter so it does not require a thread. Courtesy of + * Rene van Paassen. Added reporting of physical pointer device + * (e.g., stylus, mouse in reports 2, 3, 4, 5. We don't know + * for reports 1, 6.) + * what physical device reports for reports 1, 6.) Also enabled + * MOUSE and LENS tool button modes. Renamed "rubber" to "eraser". + * Feb 20, 2004, Bryan W. Headley. + * v1.5 - Added previousJitterable, so we don't do jitter delay when the + * user is holding a button down for periods of time. + * + * NOTE: + * This kernel driver is augmented by the "Aiptek" XFree86 input + * driver for your X server, as well as the Gaiptek GUI Front-end + * "Tablet Manager". + * These three products are highly interactive with one another, + * so therefore it's easier to document them all as one subsystem. + * Please visit the project's "home page", located at, + * http://aiptektablet.sourceforge.net. + * + * 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 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 <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb/input.h> +#include <asm/uaccess.h> +#include <asm/unaligned.h> + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.5 (May-15-2004)" +#define DRIVER_AUTHOR "Bryan W. Headley/Chris Atenasio" +#define DRIVER_DESC "Aiptek HyperPen USB Tablet Driver (Linux 2.6.x)" + +/* + * Aiptek status packet: + * + * (returned as Report 1 - relative coordinates from mouse and stylus) + * + * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 + * byte0 0 0 0 0 0 0 0 1 + * byte1 0 0 0 0 0 BS2 BS Tip + * byte2 X7 X6 X5 X4 X3 X2 X1 X0 + * byte3 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 + * + * (returned as Report 2 - absolute coordinates from the stylus) + * + * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 + * byte0 0 0 0 0 0 0 1 0 + * byte1 X7 X6 X5 X4 X3 X2 X1 X0 + * byte2 X15 X14 X13 X12 X11 X10 X9 X8 + * byte3 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 + * byte4 Y15 Y14 Y13 Y12 Y11 Y10 Y9 Y8 + * byte5 * * * BS2 BS1 Tip IR DV + * byte6 P7 P6 P5 P4 P3 P2 P1 P0 + * byte7 P15 P14 P13 P12 P11 P10 P9 P8 + * + * (returned as Report 3 - absolute coordinates from the mouse) + * + * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 + * byte0 0 0 0 0 0 0 1 0 + * byte1 X7 X6 X5 X4 X3 X2 X1 X0 + * byte2 X15 X14 X13 X12 X11 X10 X9 X8 + * byte3 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 + * byte4 Y15 Y14 Y13 Y12 Y11 Y10 Y9 Y8 + * byte5 * * * BS2 BS1 Tip IR DV + * byte6 P7 P6 P5 P4 P3 P2 P1 P0 + * byte7 P15 P14 P13 P12 P11 P10 P9 P8 + * + * (returned as Report 4 - macrokeys from the stylus) + * + * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 + * byte0 0 0 0 0 0 1 0 0 + * byte1 0 0 0 BS2 BS Tip IR DV + * byte2 0 0 0 0 0 0 1 0 + * byte3 0 0 0 K4 K3 K2 K1 K0 + * byte4 P7 P6 P5 P4 P3 P2 P1 P0 + * byte5 P15 P14 P13 P12 P11 P10 P9 P8 + * + * (returned as Report 5 - macrokeys from the mouse) + * + * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 + * byte0 0 0 0 0 0 1 0 0 + * byte1 0 0 0 BS2 BS Tip IR DV + * byte2 0 0 0 0 0 0 1 0 + * byte3 0 0 0 K4 K3 K2 K1 K0 + * byte4 P7 P6 P5 P4 P3 P2 P1 P0 + * byte5 P15 P14 P13 P12 P11 P10 P9 P8 + * + * IR: In Range = Proximity on + * DV = Data Valid + * BS = Barrel Switch (as in, macro keys) + * BS2 also referred to as Tablet Pick + * + * Command Summary: + * + * Use report_type CONTROL (3) + * Use report_id 2 + * + * Command/Data Description Return Bytes Return Value + * 0x10/0x00 SwitchToMouse 0 + * 0x10/0x01 SwitchToTablet 0 + * 0x18/0x04 SetResolution 0 + * 0x12/0xFF AutoGainOn 0 + * 0x17/0x00 FilterOn 0 + * 0x01/0x00 GetXExtension 2 MaxX + * 0x01/0x01 GetYExtension 2 MaxY + * 0x02/0x00 GetModelCode 2 ModelCode = LOBYTE + * 0x03/0x00 GetODMCode 2 ODMCode + * 0x08/0x00 GetPressureLevels 2 =512 + * 0x04/0x00 GetFirmwareVersion 2 Firmware Version + * 0x11/0x02 EnableMacroKeys 0 + * + * To initialize the tablet: + * + * (1) Send Resolution500LPI (Command) + * (2) Query for Model code (Option Report) + * (3) Query for ODM code (Option Report) + * (4) Query for firmware (Option Report) + * (5) Query for GetXExtension (Option Report) + * (6) Query for GetYExtension (Option Report) + * (7) Query for GetPressureLevels (Option Report) + * (8) SwitchToTablet for Absolute coordinates, or + * SwitchToMouse for Relative coordinates (Command) + * (9) EnableMacroKeys (Command) + * (10) FilterOn (Command) + * (11) AutoGainOn (Command) + * + * (Step 9 can be omitted, but you'll then have no function keys.) + */ + +#define USB_VENDOR_ID_AIPTEK 0x08ca +#define USB_REQ_GET_REPORT 0x01 +#define USB_REQ_SET_REPORT 0x09 + + /* PointerMode codes + */ +#define AIPTEK_POINTER_ONLY_MOUSE_MODE 0 +#define AIPTEK_POINTER_ONLY_STYLUS_MODE 1 +#define AIPTEK_POINTER_EITHER_MODE 2 + +#define AIPTEK_POINTER_ALLOW_MOUSE_MODE(a) \ + (a == AIPTEK_POINTER_ONLY_MOUSE_MODE || \ + a == AIPTEK_POINTER_EITHER_MODE) +#define AIPTEK_POINTER_ALLOW_STYLUS_MODE(a) \ + (a == AIPTEK_POINTER_ONLY_STYLUS_MODE || \ + a == AIPTEK_POINTER_EITHER_MODE) + + /* CoordinateMode code + */ +#define AIPTEK_COORDINATE_RELATIVE_MODE 0 +#define AIPTEK_COORDINATE_ABSOLUTE_MODE 1 + + /* XTilt and YTilt values + */ +#define AIPTEK_TILT_MIN (-128) +#define AIPTEK_TILT_MAX 127 +#define AIPTEK_TILT_DISABLE (-10101) + + /* Wheel values + */ +#define AIPTEK_WHEEL_MIN 0 +#define AIPTEK_WHEEL_MAX 1024 +#define AIPTEK_WHEEL_DISABLE (-10101) + + /* ToolCode values, which BTW are 0x140 .. 0x14f + * We have things set up such that if TOOL_BUTTON_FIRED_BIT is + * not set, we'll send one instance of AIPTEK_TOOL_BUTTON_xxx. + * + * Whenever the user resets the value, TOOL_BUTTON_FIRED_BIT will + * get reset. + */ +#define TOOL_BUTTON(x) ((x) & 0x14f) +#define TOOL_BUTTON_FIRED(x) ((x) & 0x200) +#define TOOL_BUTTON_FIRED_BIT 0x200 + /* toolMode codes + */ +#define AIPTEK_TOOL_BUTTON_PEN_MODE BTN_TOOL_PEN +#define AIPTEK_TOOL_BUTTON_PEN_MODE BTN_TOOL_PEN +#define AIPTEK_TOOL_BUTTON_PENCIL_MODE BTN_TOOL_PENCIL +#define AIPTEK_TOOL_BUTTON_BRUSH_MODE BTN_TOOL_BRUSH +#define AIPTEK_TOOL_BUTTON_AIRBRUSH_MODE BTN_TOOL_AIRBRUSH +#define AIPTEK_TOOL_BUTTON_ERASER_MODE BTN_TOOL_RUBBER +#define AIPTEK_TOOL_BUTTON_MOUSE_MODE BTN_TOOL_MOUSE +#define AIPTEK_TOOL_BUTTON_LENS_MODE BTN_TOOL_LENS + + /* Diagnostic message codes + */ +#define AIPTEK_DIAGNOSTIC_NA 0 +#define AIPTEK_DIAGNOSTIC_SENDING_RELATIVE_IN_ABSOLUTE 1 +#define AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE 2 +#define AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED 3 + + /* Time to wait (in ms) to help mask hand jittering + * when pressing the stylus buttons. + */ +#define AIPTEK_JITTER_DELAY_DEFAULT 50 + + /* Time to wait (in ms) in-between sending the tablet + * a command and beginning the process of reading the return + * sequence from the tablet. + */ +#define AIPTEK_PROGRAMMABLE_DELAY_25 25 +#define AIPTEK_PROGRAMMABLE_DELAY_50 50 +#define AIPTEK_PROGRAMMABLE_DELAY_100 100 +#define AIPTEK_PROGRAMMABLE_DELAY_200 200 +#define AIPTEK_PROGRAMMABLE_DELAY_300 300 +#define AIPTEK_PROGRAMMABLE_DELAY_400 400 +#define AIPTEK_PROGRAMMABLE_DELAY_DEFAULT AIPTEK_PROGRAMMABLE_DELAY_400 + + /* Mouse button programming + */ +#define AIPTEK_MOUSE_LEFT_BUTTON 0x01 +#define AIPTEK_MOUSE_RIGHT_BUTTON 0x02 +#define AIPTEK_MOUSE_MIDDLE_BUTTON 0x04 + + /* Stylus button programming + */ +#define AIPTEK_STYLUS_LOWER_BUTTON 0x08 +#define AIPTEK_STYLUS_UPPER_BUTTON 0x10 + + /* Length of incoming packet from the tablet + */ +#define AIPTEK_PACKET_LENGTH 8 + + /* We report in EV_MISC both the proximity and + * whether the report came from the stylus, tablet mouse + * or "unknown" -- Unknown when the tablet is in relative + * mode, because we only get report 1's. + */ +#define AIPTEK_REPORT_TOOL_UNKNOWN 0x10 +#define AIPTEK_REPORT_TOOL_STYLUS 0x20 +#define AIPTEK_REPORT_TOOL_MOUSE 0x40 + +static int programmableDelay = AIPTEK_PROGRAMMABLE_DELAY_DEFAULT; +static int jitterDelay = AIPTEK_JITTER_DELAY_DEFAULT; + +struct aiptek_features { + int odmCode; /* Tablet manufacturer code */ + int modelCode; /* Tablet model code (not unique) */ + int firmwareCode; /* prom/eeprom version */ + char usbPath[64 + 1]; /* device's physical usb path */ + char inputPath[64 + 1]; /* input device path */ +}; + +struct aiptek_settings { + int pointerMode; /* stylus-, mouse-only or either */ + int coordinateMode; /* absolute/relative coords */ + int toolMode; /* pen, pencil, brush, etc. tool */ + int xTilt; /* synthetic xTilt amount */ + int yTilt; /* synthetic yTilt amount */ + int wheel; /* synthetic wheel amount */ + int stylusButtonUpper; /* stylus upper btn delivers... */ + int stylusButtonLower; /* stylus lower btn delivers... */ + int mouseButtonLeft; /* mouse left btn delivers... */ + int mouseButtonMiddle; /* mouse middle btn delivers... */ + int mouseButtonRight; /* mouse right btn delivers... */ + int programmableDelay; /* delay for tablet programming */ + int jitterDelay; /* delay for hand jittering */ +}; + +struct aiptek { + struct input_dev *inputdev; /* input device struct */ + struct usb_device *usbdev; /* usb device struct */ + struct urb *urb; /* urb for incoming reports */ + dma_addr_t data_dma; /* our dma stuffage */ + struct aiptek_features features; /* tablet's array of features */ + struct aiptek_settings curSetting; /* tablet's current programmable */ + struct aiptek_settings newSetting; /* ... and new param settings */ + unsigned int ifnum; /* interface number for IO */ + int diagnostic; /* tablet diagnostic codes */ + unsigned long eventCount; /* event count */ + int inDelay; /* jitter: in jitter delay? */ + unsigned long endDelay; /* jitter: time when delay ends */ + int previousJitterable; /* jitterable prev value */ + unsigned char *data; /* incoming packet data */ +}; + +/* + * Permit easy lookup of keyboard events to send, versus + * the bitmap which comes from the tablet. This hides the + * issue that the F_keys are not sequentially numbered. + */ +static const int macroKeyEvents[] = { + KEY_ESC, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, + KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, + KEY_F12, KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, + KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22, KEY_F23, + KEY_F24, KEY_STOP, KEY_AGAIN, KEY_PROPS, KEY_UNDO, + KEY_FRONT, KEY_COPY, KEY_OPEN, KEY_PASTE, 0 +}; + +/*********************************************************************** + * Relative reports deliver values in 2's complement format to + * deal with negative offsets. + */ +static int aiptek_convert_from_2s_complement(unsigned char c) +{ + int ret; + unsigned char b = c; + int negate = 0; + + if ((b & 0x80) != 0) { + b = ~b; + b--; + negate = 1; + } + ret = b; + ret = (negate == 1) ? -ret : ret; + return ret; +} + +/*********************************************************************** + * aiptek_irq can receive one of six potential reports. + * The documentation for each is in the body of the function. + * + * The tablet reports on several attributes per invocation of + * aiptek_irq. Because the Linux Input Event system allows the + * transmission of ONE attribute per input_report_xxx() call, + * collation has to be done on the other end to reconstitute + * a complete tablet report. Further, the number of Input Event reports + * submitted varies, depending on what USB report type, and circumstance. + * To deal with this, EV_MSC is used to indicate an 'end-of-report' + * message. This has been an undocumented convention understood by the kernel + * tablet driver and clients such as gpm and XFree86's tablet drivers. + * + * Of the information received from the tablet, the one piece I + * cannot transmit is the proximity bit (without resorting to an EV_MSC + * convention above.) I therefore have taken over REL_MISC and ABS_MISC + * (for relative and absolute reports, respectively) for communicating + * Proximity. Why two events? I thought it interesting to know if the + * Proximity event occurred while the tablet was in absolute or relative + * mode. + * + * Other tablets use the notion of a certain minimum stylus pressure + * to infer proximity. While that could have been done, that is yet + * another 'by convention' behavior, the documentation for which + * would be spread between two (or more) pieces of software. + * + * EV_MSC usage was terminated for this purpose in Linux 2.5.x, and + * replaced with the input_sync() method (which emits EV_SYN.) + */ + +static void aiptek_irq(struct urb *urb) +{ + struct aiptek *aiptek = urb->context; + unsigned char *data = aiptek->data; + struct input_dev *inputdev = aiptek->inputdev; + int jitterable = 0; + int retval, macro, x, y, z, left, right, middle, p, dv, tip, bs, pck; + + switch (urb->status) { + case 0: + /* Success */ + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* This urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", + __FUNCTION__, urb->status); + return; + + default: + dbg("%s - nonzero urb status received: %d", + __FUNCTION__, urb->status); + goto exit; + } + + /* See if we are in a delay loop -- throw out report if true. + */ + if (aiptek->inDelay == 1 && time_after(aiptek->endDelay, jiffies)) { + goto exit; + } + + aiptek->inDelay = 0; + aiptek->eventCount++; + + /* Report 1 delivers relative coordinates with either a stylus + * or the mouse. You do not know, however, which input + * tool generated the event. + */ + if (data[0] == 1) { + if (aiptek->curSetting.coordinateMode == + AIPTEK_COORDINATE_ABSOLUTE_MODE) { + aiptek->diagnostic = + AIPTEK_DIAGNOSTIC_SENDING_RELATIVE_IN_ABSOLUTE; + } else { + x = aiptek_convert_from_2s_complement(data[2]); + y = aiptek_convert_from_2s_complement(data[3]); + + /* jitterable keeps track of whether any button has been pressed. + * We're also using it to remap the physical mouse button mask + * to pseudo-settings. (We don't specifically care about it's + * value after moving/transposing mouse button bitmasks, except + * that a non-zero value indicates that one or more + * mouse button was pressed.) + */ + jitterable = data[5] & 0x07; + + left = (data[5] & aiptek->curSetting.mouseButtonLeft) != 0 ? 1 : 0; + right = (data[5] & aiptek->curSetting.mouseButtonRight) != 0 ? 1 : 0; + middle = (data[5] & aiptek->curSetting.mouseButtonMiddle) != 0 ? 1 : 0; + + input_report_key(inputdev, BTN_LEFT, left); + input_report_key(inputdev, BTN_MIDDLE, middle); + input_report_key(inputdev, BTN_RIGHT, right); + input_report_rel(inputdev, REL_X, x); + input_report_rel(inputdev, REL_Y, y); + input_report_rel(inputdev, REL_MISC, 1 | AIPTEK_REPORT_TOOL_UNKNOWN); + + /* Wheel support is in the form of a single-event + * firing. + */ + if (aiptek->curSetting.wheel != AIPTEK_WHEEL_DISABLE) { + input_report_rel(inputdev, REL_WHEEL, + aiptek->curSetting.wheel); + aiptek->curSetting.wheel = AIPTEK_WHEEL_DISABLE; + } + input_sync(inputdev); + } + } + /* Report 2 is delivered only by the stylus, and delivers + * absolute coordinates. + */ + else if (data[0] == 2) { + if (aiptek->curSetting.coordinateMode == AIPTEK_COORDINATE_RELATIVE_MODE) { + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE; + } else if (!AIPTEK_POINTER_ALLOW_STYLUS_MODE + (aiptek->curSetting.pointerMode)) { + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED; + } else { + x = le16_to_cpu(get_unaligned((__le16 *) (data + 1))); + y = le16_to_cpu(get_unaligned((__le16 *) (data + 3))); + z = le16_to_cpu(get_unaligned((__le16 *) (data + 6))); + + p = (data[5] & 0x01) != 0 ? 1 : 0; + dv = (data[5] & 0x02) != 0 ? 1 : 0; + tip = (data[5] & 0x04) != 0 ? 1 : 0; + + /* Use jitterable to re-arrange button masks + */ + jitterable = data[5] & 0x18; + + bs = (data[5] & aiptek->curSetting.stylusButtonLower) != 0 ? 1 : 0; + pck = (data[5] & aiptek->curSetting.stylusButtonUpper) != 0 ? 1 : 0; + + /* dv indicates 'data valid' (e.g., the tablet is in sync + * and has delivered a "correct" report) We will ignore + * all 'bad' reports... + */ + if (dv != 0) { + /* If we've not already sent a tool_button_?? code, do + * so now. Then set FIRED_BIT so it won't be resent unless + * the user forces FIRED_BIT off. + */ + if (TOOL_BUTTON_FIRED + (aiptek->curSetting.toolMode) == 0) { + input_report_key(inputdev, + TOOL_BUTTON(aiptek->curSetting.toolMode), + 1); + aiptek->curSetting.toolMode |= TOOL_BUTTON_FIRED_BIT; + } + + if (p != 0) { + input_report_abs(inputdev, ABS_X, x); + input_report_abs(inputdev, ABS_Y, y); + input_report_abs(inputdev, ABS_PRESSURE, z); + + input_report_key(inputdev, BTN_TOUCH, tip); + input_report_key(inputdev, BTN_STYLUS, bs); + input_report_key(inputdev, BTN_STYLUS2, pck); + + if (aiptek->curSetting.xTilt != + AIPTEK_TILT_DISABLE) { + input_report_abs(inputdev, + ABS_TILT_X, + aiptek->curSetting.xTilt); + } + if (aiptek->curSetting.yTilt != AIPTEK_TILT_DISABLE) { + input_report_abs(inputdev, + ABS_TILT_Y, + aiptek->curSetting.yTilt); + } + + /* Wheel support is in the form of a single-event + * firing. + */ + if (aiptek->curSetting.wheel != + AIPTEK_WHEEL_DISABLE) { + input_report_abs(inputdev, + ABS_WHEEL, + aiptek->curSetting.wheel); + aiptek->curSetting.wheel = AIPTEK_WHEEL_DISABLE; + } + } + input_report_abs(inputdev, ABS_MISC, p | AIPTEK_REPORT_TOOL_STYLUS); + input_sync(inputdev); + } + } + } + /* Report 3's come from the mouse in absolute mode. + */ + else if (data[0] == 3) { + if (aiptek->curSetting.coordinateMode == AIPTEK_COORDINATE_RELATIVE_MODE) { + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE; + } else if (!AIPTEK_POINTER_ALLOW_MOUSE_MODE + (aiptek->curSetting.pointerMode)) { + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED; + } else { + x = le16_to_cpu(get_unaligned((__le16 *) (data + 1))); + y = le16_to_cpu(get_unaligned((__le16 *) (data + 3))); + + jitterable = data[5] & 0x1c; + + p = (data[5] & 0x01) != 0 ? 1 : 0; + dv = (data[5] & 0x02) != 0 ? 1 : 0; + left = (data[5] & aiptek->curSetting.mouseButtonLeft) != 0 ? 1 : 0; + right = (data[5] & aiptek->curSetting.mouseButtonRight) != 0 ? 1 : 0; + middle = (data[5] & aiptek->curSetting.mouseButtonMiddle) != 0 ? 1 : 0; + + if (dv != 0) { + /* If we've not already sent a tool_button_?? code, do + * so now. Then set FIRED_BIT so it won't be resent unless + * the user forces FIRED_BIT off. + */ + if (TOOL_BUTTON_FIRED + (aiptek->curSetting.toolMode) == 0) { + input_report_key(inputdev, + TOOL_BUTTON(aiptek->curSetting.toolMode), + 1); + aiptek->curSetting.toolMode |= TOOL_BUTTON_FIRED_BIT; + } + + if (p != 0) { + input_report_abs(inputdev, ABS_X, x); + input_report_abs(inputdev, ABS_Y, y); + + input_report_key(inputdev, BTN_LEFT, left); + input_report_key(inputdev, BTN_MIDDLE, middle); + input_report_key(inputdev, BTN_RIGHT, right); + + /* Wheel support is in the form of a single-event + * firing. + */ + if (aiptek->curSetting.wheel != AIPTEK_WHEEL_DISABLE) { + input_report_abs(inputdev, + ABS_WHEEL, + aiptek->curSetting.wheel); + aiptek->curSetting.wheel = AIPTEK_WHEEL_DISABLE; + } + } + input_report_rel(inputdev, REL_MISC, p | AIPTEK_REPORT_TOOL_MOUSE); + input_sync(inputdev); + } + } + } + /* Report 4s come from the macro keys when pressed by stylus + */ + else if (data[0] == 4) { + jitterable = data[1] & 0x18; + + p = (data[1] & 0x01) != 0 ? 1 : 0; + dv = (data[1] & 0x02) != 0 ? 1 : 0; + tip = (data[1] & 0x04) != 0 ? 1 : 0; + bs = (data[1] & aiptek->curSetting.stylusButtonLower) != 0 ? 1 : 0; + pck = (data[1] & aiptek->curSetting.stylusButtonUpper) != 0 ? 1 : 0; + + macro = data[3]; + z = le16_to_cpu(get_unaligned((__le16 *) (data + 4))); + + if (dv != 0) { + /* If we've not already sent a tool_button_?? code, do + * so now. Then set FIRED_BIT so it won't be resent unless + * the user forces FIRED_BIT off. + */ + if (TOOL_BUTTON_FIRED(aiptek->curSetting.toolMode) == 0) { + input_report_key(inputdev, + TOOL_BUTTON(aiptek->curSetting.toolMode), + 1); + aiptek->curSetting.toolMode |= TOOL_BUTTON_FIRED_BIT; + } + + if (p != 0) { + input_report_key(inputdev, BTN_TOUCH, tip); + input_report_key(inputdev, BTN_STYLUS, bs); + input_report_key(inputdev, BTN_STYLUS2, pck); + input_report_abs(inputdev, ABS_PRESSURE, z); + } + + /* For safety, we're sending key 'break' codes for the + * neighboring macro keys. + */ + if (macro > 0) { + input_report_key(inputdev, + macroKeyEvents[macro - 1], 0); + } + if (macro < 25) { + input_report_key(inputdev, + macroKeyEvents[macro + 1], 0); + } + input_report_key(inputdev, macroKeyEvents[macro], p); + input_report_abs(inputdev, ABS_MISC, + p | AIPTEK_REPORT_TOOL_STYLUS); + input_sync(inputdev); + } + } + /* Report 5s come from the macro keys when pressed by mouse + */ + else if (data[0] == 5) { + jitterable = data[1] & 0x1c; + + p = (data[1] & 0x01) != 0 ? 1 : 0; + dv = (data[1] & 0x02) != 0 ? 1 : 0; + left = (data[1]& aiptek->curSetting.mouseButtonLeft) != 0 ? 1 : 0; + right = (data[1] & aiptek->curSetting.mouseButtonRight) != 0 ? 1 : 0; + middle = (data[1] & aiptek->curSetting.mouseButtonMiddle) != 0 ? 1 : 0; + macro = data[3]; + + if (dv != 0) { + /* If we've not already sent a tool_button_?? code, do + * so now. Then set FIRED_BIT so it won't be resent unless + * the user forces FIRED_BIT off. + */ + if (TOOL_BUTTON_FIRED(aiptek->curSetting.toolMode) == 0) { + input_report_key(inputdev, + TOOL_BUTTON(aiptek->curSetting.toolMode), + 1); + aiptek->curSetting.toolMode |= TOOL_BUTTON_FIRED_BIT; + } + + if (p != 0) { + input_report_key(inputdev, BTN_LEFT, left); + input_report_key(inputdev, BTN_MIDDLE, middle); + input_report_key(inputdev, BTN_RIGHT, right); + } + + /* For safety, we're sending key 'break' codes for the + * neighboring macro keys. + */ + if (macro > 0) { + input_report_key(inputdev, + macroKeyEvents[macro - 1], 0); + } + if (macro < 25) { + input_report_key(inputdev, + macroKeyEvents[macro + 1], 0); + } + + input_report_key(inputdev, macroKeyEvents[macro], 1); + input_report_rel(inputdev, ABS_MISC, + p | AIPTEK_REPORT_TOOL_MOUSE); + input_sync(inputdev); + } + } + /* We have no idea which tool can generate a report 6. Theoretically, + * neither need to, having been given reports 4 & 5 for such use. + * However, report 6 is the 'official-looking' report for macroKeys; + * reports 4 & 5 supposively are used to support unnamed, unknown + * hat switches (which just so happen to be the macroKeys.) + */ + else if (data[0] == 6) { + macro = le16_to_cpu(get_unaligned((__le16 *) (data + 1))); + if (macro > 0) { + input_report_key(inputdev, macroKeyEvents[macro - 1], + 0); + } + if (macro < 25) { + input_report_key(inputdev, macroKeyEvents[macro + 1], + 0); + } + + /* If we've not already sent a tool_button_?? code, do + * so now. Then set FIRED_BIT so it won't be resent unless + * the user forces FIRED_BIT off. + */ + if (TOOL_BUTTON_FIRED(aiptek->curSetting.toolMode) == 0) { + input_report_key(inputdev, + TOOL_BUTTON(aiptek->curSetting. + toolMode), 1); + aiptek->curSetting.toolMode |= TOOL_BUTTON_FIRED_BIT; + } + + input_report_key(inputdev, macroKeyEvents[macro], 1); + input_report_abs(inputdev, ABS_MISC, + 1 | AIPTEK_REPORT_TOOL_UNKNOWN); + input_sync(inputdev); + } else { + dbg("Unknown report %d", data[0]); + } + + /* Jitter may occur when the user presses a button on the stlyus + * or the mouse. What we do to prevent that is wait 'x' milliseconds + * following a 'jitterable' event, which should give the hand some time + * stabilize itself. + * + * We just introduced aiptek->previousJitterable to carry forth the + * notion that jitter occurs when the button state changes from on to off: + * a person drawing, holding a button down is not subject to jittering. + * With that in mind, changing from upper button depressed to lower button + * WILL transition through a jitter delay. + */ + + if (aiptek->previousJitterable != jitterable && + aiptek->curSetting.jitterDelay != 0 && aiptek->inDelay != 1) { + aiptek->endDelay = jiffies + + ((aiptek->curSetting.jitterDelay * HZ) / 1000); + aiptek->inDelay = 1; + } + aiptek->previousJitterable = jitterable; + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval != 0) { + err("%s - usb_submit_urb failed with result %d", + __FUNCTION__, retval); + } +} + +/*********************************************************************** + * These are the USB id's known so far. We do not identify them to + * specific Aiptek model numbers, because there has been overlaps, + * use, and reuse of id's in existing models. Certain models have + * been known to use more than one ID, indicative perhaps of + * manufacturing revisions. In any event, we consider these + * IDs to not be model-specific nor unique. + */ +static const struct usb_device_id aiptek_ids[] = { + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x01)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x10)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x20)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x21)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x22)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x23)}, + {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x24)}, + {} +}; + +MODULE_DEVICE_TABLE(usb, aiptek_ids); + +/*********************************************************************** + * Open an instance of the tablet driver. + */ +static int aiptek_open(struct input_dev *inputdev) +{ + struct aiptek *aiptek = input_get_drvdata(inputdev); + + aiptek->urb->dev = aiptek->usbdev; + if (usb_submit_urb(aiptek->urb, GFP_KERNEL) != 0) + return -EIO; + + return 0; +} + +/*********************************************************************** + * Close an instance of the tablet driver. + */ +static void aiptek_close(struct input_dev *inputdev) +{ + struct aiptek *aiptek = input_get_drvdata(inputdev); + + usb_kill_urb(aiptek->urb); +} + +/*********************************************************************** + * aiptek_set_report and aiptek_get_report() are borrowed from Linux 2.4.x, + * where they were known as usb_set_report and usb_get_report. + */ +static int +aiptek_set_report(struct aiptek *aiptek, + unsigned char report_type, + unsigned char report_id, void *buffer, int size) +{ + return usb_control_msg(aiptek->usbdev, + usb_sndctrlpipe(aiptek->usbdev, 0), + USB_REQ_SET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_OUT, (report_type << 8) + report_id, + aiptek->ifnum, buffer, size, 5000); +} + +static int +aiptek_get_report(struct aiptek *aiptek, + unsigned char report_type, + unsigned char report_id, void *buffer, int size) +{ + return usb_control_msg(aiptek->usbdev, + usb_rcvctrlpipe(aiptek->usbdev, 0), + USB_REQ_GET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_IN, (report_type << 8) + report_id, + aiptek->ifnum, buffer, size, 5000); +} + +/*********************************************************************** + * Send a command to the tablet. + */ +static int +aiptek_command(struct aiptek *aiptek, unsigned char command, unsigned char data) +{ + const int sizeof_buf = 3 * sizeof(u8); + int ret; + u8 *buf; + + buf = kmalloc(sizeof_buf, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = 2; + buf[1] = command; + buf[2] = data; + + if ((ret = + aiptek_set_report(aiptek, 3, 2, buf, sizeof_buf)) != sizeof_buf) { + dbg("aiptek_program: failed, tried to send: 0x%02x 0x%02x", + command, data); + } + kfree(buf); + return ret < 0 ? ret : 0; +} + +/*********************************************************************** + * Retrieve information from the tablet. Querying info is defined as first + * sending the {command,data} sequence as a command, followed by a wait + * (aka, "programmaticDelay") and then a "read" request. + */ +static int +aiptek_query(struct aiptek *aiptek, unsigned char command, unsigned char data) +{ + const int sizeof_buf = 3 * sizeof(u8); + int ret; + u8 *buf; + + buf = kmalloc(sizeof_buf, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = 2; + buf[1] = command; + buf[2] = data; + + if (aiptek_command(aiptek, command, data) != 0) { + kfree(buf); + return -EIO; + } + msleep(aiptek->curSetting.programmableDelay); + + if ((ret = + aiptek_get_report(aiptek, 3, 2, buf, sizeof_buf)) != sizeof_buf) { + dbg("aiptek_query failed: returned 0x%02x 0x%02x 0x%02x", + buf[0], buf[1], buf[2]); + ret = -EIO; + } else { + ret = le16_to_cpu(get_unaligned((__le16 *) (buf + 1))); + } + kfree(buf); + return ret; +} + +/*********************************************************************** + * Program the tablet into either absolute or relative mode. + * We also get information about the tablet's size. + */ +static int aiptek_program_tablet(struct aiptek *aiptek) +{ + int ret; + /* Execute Resolution500LPI */ + if ((ret = aiptek_command(aiptek, 0x18, 0x04)) < 0) + return ret; + + /* Query getModelCode */ + if ((ret = aiptek_query(aiptek, 0x02, 0x00)) < 0) + return ret; + aiptek->features.modelCode = ret & 0xff; + + /* Query getODMCode */ + if ((ret = aiptek_query(aiptek, 0x03, 0x00)) < 0) + return ret; + aiptek->features.odmCode = ret; + + /* Query getFirmwareCode */ + if ((ret = aiptek_query(aiptek, 0x04, 0x00)) < 0) + return ret; + aiptek->features.firmwareCode = ret; + + /* Query getXextension */ + if ((ret = aiptek_query(aiptek, 0x01, 0x00)) < 0) + return ret; + aiptek->inputdev->absmin[ABS_X] = 0; + aiptek->inputdev->absmax[ABS_X] = ret - 1; + + /* Query getYextension */ + if ((ret = aiptek_query(aiptek, 0x01, 0x01)) < 0) + return ret; + aiptek->inputdev->absmin[ABS_Y] = 0; + aiptek->inputdev->absmax[ABS_Y] = ret - 1; + + /* Query getPressureLevels */ + if ((ret = aiptek_query(aiptek, 0x08, 0x00)) < 0) + return ret; + aiptek->inputdev->absmin[ABS_PRESSURE] = 0; + aiptek->inputdev->absmax[ABS_PRESSURE] = ret - 1; + + /* Depending on whether we are in absolute or relative mode, we will + * do a switchToTablet(absolute) or switchToMouse(relative) command. + */ + if (aiptek->curSetting.coordinateMode == + AIPTEK_COORDINATE_ABSOLUTE_MODE) { + /* Execute switchToTablet */ + if ((ret = aiptek_command(aiptek, 0x10, 0x01)) < 0) { + return ret; + } + } else { + /* Execute switchToMouse */ + if ((ret = aiptek_command(aiptek, 0x10, 0x00)) < 0) { + return ret; + } + } + + /* Enable the macro keys */ + if ((ret = aiptek_command(aiptek, 0x11, 0x02)) < 0) + return ret; +#if 0 + /* Execute FilterOn */ + if ((ret = aiptek_command(aiptek, 0x17, 0x00)) < 0) + return ret; +#endif + + /* Execute AutoGainOn */ + if ((ret = aiptek_command(aiptek, 0x12, 0xff)) < 0) + return ret; + + /* Reset the eventCount, so we track events from last (re)programming + */ + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_NA; + aiptek->eventCount = 0; + + return 0; +} + +/*********************************************************************** + * Sysfs functions. Sysfs prefers that individually-tunable parameters + * exist in their separate pseudo-files. Summary data that is immutable + * may exist in a singular file so long as you don't define a writeable + * interface. + */ + +/*********************************************************************** + * support the 'size' file -- display support + */ +static ssize_t show_tabletSize(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + return snprintf(buf, PAGE_SIZE, "%dx%d\n", + aiptek->inputdev->absmax[ABS_X] + 1, + aiptek->inputdev->absmax[ABS_Y] + 1); +} + +/* These structs define the sysfs files, param #1 is the name of the + * file, param 2 is the file permissions, param 3 & 4 are to the + * output generator and input parser routines. Absence of a routine is + * permitted -- it only means can't either 'cat' the file, or send data + * to it. + */ +static DEVICE_ATTR(size, S_IRUGO, show_tabletSize, NULL); + +/*********************************************************************** + * support routines for the 'product_id' file + */ +static ssize_t show_tabletProductId(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + return snprintf(buf, PAGE_SIZE, "0x%04x\n", + aiptek->inputdev->id.product); +} + +static DEVICE_ATTR(product_id, S_IRUGO, show_tabletProductId, NULL); + +/*********************************************************************** + * support routines for the 'vendor_id' file + */ +static ssize_t show_tabletVendorId(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + return snprintf(buf, PAGE_SIZE, "0x%04x\n", aiptek->inputdev->id.vendor); +} + +static DEVICE_ATTR(vendor_id, S_IRUGO, show_tabletVendorId, NULL); + +/*********************************************************************** + * support routines for the 'vendor' file + */ +static ssize_t show_tabletManufacturer(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int retval; + + if (aiptek == NULL) + return 0; + + retval = snprintf(buf, PAGE_SIZE, "%s\n", aiptek->usbdev->manufacturer); + return retval; +} + +static DEVICE_ATTR(vendor, S_IRUGO, show_tabletManufacturer, NULL); + +/*********************************************************************** + * support routines for the 'product' file + */ +static ssize_t show_tabletProduct(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int retval; + + if (aiptek == NULL) + return 0; + + retval = snprintf(buf, PAGE_SIZE, "%s\n", aiptek->usbdev->product); + return retval; +} + +static DEVICE_ATTR(product, S_IRUGO, show_tabletProduct, NULL); + +/*********************************************************************** + * support routines for the 'pointer_mode' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static ssize_t show_tabletPointerMode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + char *s; + + if (aiptek == NULL) + return 0; + + switch (aiptek->curSetting.pointerMode) { + case AIPTEK_POINTER_ONLY_STYLUS_MODE: + s = "stylus"; + break; + + case AIPTEK_POINTER_ONLY_MOUSE_MODE: + s = "mouse"; + break; + + case AIPTEK_POINTER_EITHER_MODE: + s = "either"; + break; + + default: + s = "unknown"; + break; + } + return snprintf(buf, PAGE_SIZE, "%s\n", s); +} + +static ssize_t +store_tabletPointerMode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + if (aiptek == NULL) + return 0; + + if (strcmp(buf, "stylus") == 0) { + aiptek->newSetting.pointerMode = + AIPTEK_POINTER_ONLY_STYLUS_MODE; + } else if (strcmp(buf, "mouse") == 0) { + aiptek->newSetting.pointerMode = AIPTEK_POINTER_ONLY_MOUSE_MODE; + } else if (strcmp(buf, "either") == 0) { + aiptek->newSetting.pointerMode = AIPTEK_POINTER_EITHER_MODE; + } + return count; +} + +static DEVICE_ATTR(pointer_mode, + S_IRUGO | S_IWUGO, + show_tabletPointerMode, store_tabletPointerMode); + +/*********************************************************************** + * support routines for the 'coordinate_mode' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static ssize_t show_tabletCoordinateMode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + char *s; + + if (aiptek == NULL) + return 0; + + switch (aiptek->curSetting.coordinateMode) { + case AIPTEK_COORDINATE_ABSOLUTE_MODE: + s = "absolute"; + break; + + case AIPTEK_COORDINATE_RELATIVE_MODE: + s = "relative"; + break; + + default: + s = "unknown"; + break; + } + return snprintf(buf, PAGE_SIZE, "%s\n", s); +} + +static ssize_t +store_tabletCoordinateMode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + if (aiptek == NULL) + return 0; + + if (strcmp(buf, "absolute") == 0) { + aiptek->newSetting.pointerMode = + AIPTEK_COORDINATE_ABSOLUTE_MODE; + } else if (strcmp(buf, "relative") == 0) { + aiptek->newSetting.pointerMode = + AIPTEK_COORDINATE_RELATIVE_MODE; + } + return count; +} + +static DEVICE_ATTR(coordinate_mode, + S_IRUGO | S_IWUGO, + show_tabletCoordinateMode, store_tabletCoordinateMode); + +/*********************************************************************** + * support routines for the 'tool_mode' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static ssize_t show_tabletToolMode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + char *s; + + if (aiptek == NULL) + return 0; + + switch (TOOL_BUTTON(aiptek->curSetting.toolMode)) { + case AIPTEK_TOOL_BUTTON_MOUSE_MODE: + s = "mouse"; + break; + + case AIPTEK_TOOL_BUTTON_ERASER_MODE: + s = "eraser"; + break; + + case AIPTEK_TOOL_BUTTON_PENCIL_MODE: + s = "pencil"; + break; + + case AIPTEK_TOOL_BUTTON_PEN_MODE: + s = "pen"; + break; + + case AIPTEK_TOOL_BUTTON_BRUSH_MODE: + s = "brush"; + break; + + case AIPTEK_TOOL_BUTTON_AIRBRUSH_MODE: + s = "airbrush"; + break; + + case AIPTEK_TOOL_BUTTON_LENS_MODE: + s = "lens"; + break; + + default: + s = "unknown"; + break; + } + return snprintf(buf, PAGE_SIZE, "%s\n", s); +} + +static ssize_t +store_tabletToolMode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + if (aiptek == NULL) + return 0; + + if (strcmp(buf, "mouse") == 0) { + aiptek->newSetting.toolMode = AIPTEK_TOOL_BUTTON_MOUSE_MODE; + } else if (strcmp(buf, "eraser") == 0) { + aiptek->newSetting.toolMode = AIPTEK_TOOL_BUTTON_ERASER_MODE; + } else if (strcmp(buf, "pencil") == 0) { + aiptek->newSetting.toolMode = AIPTEK_TOOL_BUTTON_PENCIL_MODE; + } else if (strcmp(buf, "pen") == 0) { + aiptek->newSetting.toolMode = AIPTEK_TOOL_BUTTON_PEN_MODE; + } else if (strcmp(buf, "brush") == 0) { + aiptek->newSetting.toolMode = AIPTEK_TOOL_BUTTON_BRUSH_MODE; + } else if (strcmp(buf, "airbrush") == 0) { + aiptek->newSetting.toolMode = AIPTEK_TOOL_BUTTON_AIRBRUSH_MODE; + } else if (strcmp(buf, "lens") == 0) { + aiptek->newSetting.toolMode = AIPTEK_TOOL_BUTTON_LENS_MODE; + } + + return count; +} + +static DEVICE_ATTR(tool_mode, + S_IRUGO | S_IWUGO, + show_tabletToolMode, store_tabletToolMode); + +/*********************************************************************** + * support routines for the 'xtilt' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static ssize_t show_tabletXtilt(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + if (aiptek->curSetting.xTilt == AIPTEK_TILT_DISABLE) { + return snprintf(buf, PAGE_SIZE, "disable\n"); + } else { + return snprintf(buf, PAGE_SIZE, "%d\n", + aiptek->curSetting.xTilt); + } +} + +static ssize_t +store_tabletXtilt(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int x; + + if (aiptek == NULL) + return 0; + + if (strcmp(buf, "disable") == 0) { + aiptek->newSetting.xTilt = AIPTEK_TILT_DISABLE; + } else { + x = (int)simple_strtol(buf, NULL, 10); + if (x >= AIPTEK_TILT_MIN && x <= AIPTEK_TILT_MAX) { + aiptek->newSetting.xTilt = x; + } + } + return count; +} + +static DEVICE_ATTR(xtilt, + S_IRUGO | S_IWUGO, show_tabletXtilt, store_tabletXtilt); + +/*********************************************************************** + * support routines for the 'ytilt' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static ssize_t show_tabletYtilt(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + if (aiptek->curSetting.yTilt == AIPTEK_TILT_DISABLE) { + return snprintf(buf, PAGE_SIZE, "disable\n"); + } else { + return snprintf(buf, PAGE_SIZE, "%d\n", + aiptek->curSetting.yTilt); + } +} + +static ssize_t +store_tabletYtilt(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int y; + + if (aiptek == NULL) + return 0; + + if (strcmp(buf, "disable") == 0) { + aiptek->newSetting.yTilt = AIPTEK_TILT_DISABLE; + } else { + y = (int)simple_strtol(buf, NULL, 10); + if (y >= AIPTEK_TILT_MIN && y <= AIPTEK_TILT_MAX) { + aiptek->newSetting.yTilt = y; + } + } + return count; +} + +static DEVICE_ATTR(ytilt, + S_IRUGO | S_IWUGO, show_tabletYtilt, store_tabletYtilt); + +/*********************************************************************** + * support routines for the 'jitter' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static ssize_t show_tabletJitterDelay(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + return snprintf(buf, PAGE_SIZE, "%d\n", aiptek->curSetting.jitterDelay); +} + +static ssize_t +store_tabletJitterDelay(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + aiptek->newSetting.jitterDelay = (int)simple_strtol(buf, NULL, 10); + return count; +} + +static DEVICE_ATTR(jitter, + S_IRUGO | S_IWUGO, + show_tabletJitterDelay, store_tabletJitterDelay); + +/*********************************************************************** + * support routines for the 'delay' file. Note that this file + * both displays current setting and allows reprogramming. + */ +static ssize_t show_tabletProgrammableDelay(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + return snprintf(buf, PAGE_SIZE, "%d\n", + aiptek->curSetting.programmableDelay); +} + +static ssize_t +store_tabletProgrammableDelay(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + aiptek->newSetting.programmableDelay = (int)simple_strtol(buf, NULL, 10); + return count; +} + +static DEVICE_ATTR(delay, + S_IRUGO | S_IWUGO, + show_tabletProgrammableDelay, store_tabletProgrammableDelay); + +/*********************************************************************** + * support routines for the 'input_path' file. Note that this file + * only displays current setting. + */ +static ssize_t show_tabletInputDevice(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + return snprintf(buf, PAGE_SIZE, "/dev/input/%s\n", + aiptek->features.inputPath); +} + +static DEVICE_ATTR(input_path, S_IRUGO, show_tabletInputDevice, NULL); + +/*********************************************************************** + * support routines for the 'event_count' file. Note that this file + * only displays current setting. + */ +static ssize_t show_tabletEventsReceived(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + return snprintf(buf, PAGE_SIZE, "%ld\n", aiptek->eventCount); +} + +static DEVICE_ATTR(event_count, S_IRUGO, show_tabletEventsReceived, NULL); + +/*********************************************************************** + * support routines for the 'diagnostic' file. Note that this file + * only displays current setting. + */ +static ssize_t show_tabletDiagnosticMessage(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + char *retMsg; + + if (aiptek == NULL) + return 0; + + switch (aiptek->diagnostic) { + case AIPTEK_DIAGNOSTIC_NA: + retMsg = "no errors\n"; + break; + + case AIPTEK_DIAGNOSTIC_SENDING_RELATIVE_IN_ABSOLUTE: + retMsg = "Error: receiving relative reports\n"; + break; + + case AIPTEK_DIAGNOSTIC_SENDING_ABSOLUTE_IN_RELATIVE: + retMsg = "Error: receiving absolute reports\n"; + break; + + case AIPTEK_DIAGNOSTIC_TOOL_DISALLOWED: + if (aiptek->curSetting.pointerMode == + AIPTEK_POINTER_ONLY_MOUSE_MODE) { + retMsg = "Error: receiving stylus reports\n"; + } else { + retMsg = "Error: receiving mouse reports\n"; + } + break; + + default: + return 0; + } + return snprintf(buf, PAGE_SIZE, retMsg); +} + +static DEVICE_ATTR(diagnostic, S_IRUGO, show_tabletDiagnosticMessage, NULL); + +/*********************************************************************** + * support routines for the 'stylus_upper' file. Note that this file + * both displays current setting and allows for setting changing. + */ +static ssize_t show_tabletStylusUpper(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + char *s; + + if (aiptek == NULL) + return 0; + + switch (aiptek->curSetting.stylusButtonUpper) { + case AIPTEK_STYLUS_UPPER_BUTTON: + s = "upper"; + break; + + case AIPTEK_STYLUS_LOWER_BUTTON: + s = "lower"; + break; + + default: + s = "unknown"; + break; + } + return snprintf(buf, PAGE_SIZE, "%s\n", s); +} + +static ssize_t +store_tabletStylusUpper(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + if (strcmp(buf, "upper") == 0) { + aiptek->newSetting.stylusButtonUpper = + AIPTEK_STYLUS_UPPER_BUTTON; + } else if (strcmp(buf, "lower") == 0) { + aiptek->newSetting.stylusButtonUpper = + AIPTEK_STYLUS_LOWER_BUTTON; + } + return count; +} + +static DEVICE_ATTR(stylus_upper, + S_IRUGO | S_IWUGO, + show_tabletStylusUpper, store_tabletStylusUpper); + +/*********************************************************************** + * support routines for the 'stylus_lower' file. Note that this file + * both displays current setting and allows for setting changing. + */ +static ssize_t show_tabletStylusLower(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + char *s; + + if (aiptek == NULL) + return 0; + + switch (aiptek->curSetting.stylusButtonLower) { + case AIPTEK_STYLUS_UPPER_BUTTON: + s = "upper"; + break; + + case AIPTEK_STYLUS_LOWER_BUTTON: + s = "lower"; + break; + + default: + s = "unknown"; + break; + } + return snprintf(buf, PAGE_SIZE, "%s\n", s); +} + +static ssize_t +store_tabletStylusLower(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + if (strcmp(buf, "upper") == 0) { + aiptek->newSetting.stylusButtonLower = + AIPTEK_STYLUS_UPPER_BUTTON; + } else if (strcmp(buf, "lower") == 0) { + aiptek->newSetting.stylusButtonLower = + AIPTEK_STYLUS_LOWER_BUTTON; + } + return count; +} + +static DEVICE_ATTR(stylus_lower, + S_IRUGO | S_IWUGO, + show_tabletStylusLower, store_tabletStylusLower); + +/*********************************************************************** + * support routines for the 'mouse_left' file. Note that this file + * both displays current setting and allows for setting changing. + */ +static ssize_t show_tabletMouseLeft(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + char *s; + + if (aiptek == NULL) + return 0; + + switch (aiptek->curSetting.mouseButtonLeft) { + case AIPTEK_MOUSE_LEFT_BUTTON: + s = "left"; + break; + + case AIPTEK_MOUSE_MIDDLE_BUTTON: + s = "middle"; + break; + + case AIPTEK_MOUSE_RIGHT_BUTTON: + s = "right"; + break; + + default: + s = "unknown"; + break; + } + return snprintf(buf, PAGE_SIZE, "%s\n", s); +} + +static ssize_t +store_tabletMouseLeft(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + if (strcmp(buf, "left") == 0) { + aiptek->newSetting.mouseButtonLeft = AIPTEK_MOUSE_LEFT_BUTTON; + } else if (strcmp(buf, "middle") == 0) { + aiptek->newSetting.mouseButtonLeft = AIPTEK_MOUSE_MIDDLE_BUTTON; + } else if (strcmp(buf, "right") == 0) { + aiptek->newSetting.mouseButtonLeft = AIPTEK_MOUSE_RIGHT_BUTTON; + } + return count; +} + +static DEVICE_ATTR(mouse_left, + S_IRUGO | S_IWUGO, + show_tabletMouseLeft, store_tabletMouseLeft); + +/*********************************************************************** + * support routines for the 'mouse_middle' file. Note that this file + * both displays current setting and allows for setting changing. + */ +static ssize_t show_tabletMouseMiddle(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + char *s; + + if (aiptek == NULL) + return 0; + + switch (aiptek->curSetting.mouseButtonMiddle) { + case AIPTEK_MOUSE_LEFT_BUTTON: + s = "left"; + break; + + case AIPTEK_MOUSE_MIDDLE_BUTTON: + s = "middle"; + break; + + case AIPTEK_MOUSE_RIGHT_BUTTON: + s = "right"; + break; + + default: + s = "unknown"; + break; + } + return snprintf(buf, PAGE_SIZE, "%s\n", s); +} + +static ssize_t +store_tabletMouseMiddle(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + if (strcmp(buf, "left") == 0) { + aiptek->newSetting.mouseButtonMiddle = AIPTEK_MOUSE_LEFT_BUTTON; + } else if (strcmp(buf, "middle") == 0) { + aiptek->newSetting.mouseButtonMiddle = + AIPTEK_MOUSE_MIDDLE_BUTTON; + } else if (strcmp(buf, "right") == 0) { + aiptek->newSetting.mouseButtonMiddle = + AIPTEK_MOUSE_RIGHT_BUTTON; + } + return count; +} + +static DEVICE_ATTR(mouse_middle, + S_IRUGO | S_IWUGO, + show_tabletMouseMiddle, store_tabletMouseMiddle); + +/*********************************************************************** + * support routines for the 'mouse_right' file. Note that this file + * both displays current setting and allows for setting changing. + */ +static ssize_t show_tabletMouseRight(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + char *s; + + if (aiptek == NULL) + return 0; + + switch (aiptek->curSetting.mouseButtonRight) { + case AIPTEK_MOUSE_LEFT_BUTTON: + s = "left"; + break; + + case AIPTEK_MOUSE_MIDDLE_BUTTON: + s = "middle"; + break; + + case AIPTEK_MOUSE_RIGHT_BUTTON: + s = "right"; + break; + + default: + s = "unknown"; + break; + } + return snprintf(buf, PAGE_SIZE, "%s\n", s); +} + +static ssize_t +store_tabletMouseRight(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + if (strcmp(buf, "left") == 0) { + aiptek->newSetting.mouseButtonRight = AIPTEK_MOUSE_LEFT_BUTTON; + } else if (strcmp(buf, "middle") == 0) { + aiptek->newSetting.mouseButtonRight = + AIPTEK_MOUSE_MIDDLE_BUTTON; + } else if (strcmp(buf, "right") == 0) { + aiptek->newSetting.mouseButtonRight = AIPTEK_MOUSE_RIGHT_BUTTON; + } + return count; +} + +static DEVICE_ATTR(mouse_right, + S_IRUGO | S_IWUGO, + show_tabletMouseRight, store_tabletMouseRight); + +/*********************************************************************** + * support routines for the 'wheel' file. Note that this file + * both displays current setting and allows for setting changing. + */ +static ssize_t show_tabletWheel(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + if (aiptek->curSetting.wheel == AIPTEK_WHEEL_DISABLE) { + return snprintf(buf, PAGE_SIZE, "disable\n"); + } else { + return snprintf(buf, PAGE_SIZE, "%d\n", + aiptek->curSetting.wheel); + } +} + +static ssize_t +store_tabletWheel(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + aiptek->newSetting.wheel = (int)simple_strtol(buf, NULL, 10); + return count; +} + +static DEVICE_ATTR(wheel, + S_IRUGO | S_IWUGO, show_tabletWheel, store_tabletWheel); + +/*********************************************************************** + * support routines for the 'execute' file. Note that this file + * both displays current setting and allows for setting changing. + */ +static ssize_t show_tabletExecute(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + /* There is nothing useful to display, so a one-line manual + * is in order... + */ + return snprintf(buf, PAGE_SIZE, + "Write anything to this file to program your tablet.\n"); +} + +static ssize_t +store_tabletExecute(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + /* We do not care what you write to this file. Merely the action + * of writing to this file triggers a tablet reprogramming. + */ + memcpy(&aiptek->curSetting, &aiptek->newSetting, + sizeof(struct aiptek_settings)); + + if (aiptek_program_tablet(aiptek) < 0) + return -EIO; + + return count; +} + +static DEVICE_ATTR(execute, + S_IRUGO | S_IWUGO, show_tabletExecute, store_tabletExecute); + +/*********************************************************************** + * support routines for the 'odm_code' file. Note that this file + * only displays current setting. + */ +static ssize_t show_tabletODMCode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + return snprintf(buf, PAGE_SIZE, "0x%04x\n", aiptek->features.odmCode); +} + +static DEVICE_ATTR(odm_code, S_IRUGO, show_tabletODMCode, NULL); + +/*********************************************************************** + * support routines for the 'model_code' file. Note that this file + * only displays current setting. + */ +static ssize_t show_tabletModelCode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + return snprintf(buf, PAGE_SIZE, "0x%04x\n", aiptek->features.modelCode); +} + +static DEVICE_ATTR(model_code, S_IRUGO, show_tabletModelCode, NULL); + +/*********************************************************************** + * support routines for the 'firmware_code' file. Note that this file + * only displays current setting. + */ +static ssize_t show_firmwareCode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + + if (aiptek == NULL) + return 0; + + return snprintf(buf, PAGE_SIZE, "%04x\n", + aiptek->features.firmwareCode); +} + +static DEVICE_ATTR(firmware_code, S_IRUGO, show_firmwareCode, NULL); + +/*********************************************************************** + * This routine removes all existing sysfs files managed by this device + * driver. + */ +static void aiptek_delete_files(struct device *dev) +{ + device_remove_file(dev, &dev_attr_size); + device_remove_file(dev, &dev_attr_product_id); + device_remove_file(dev, &dev_attr_vendor_id); + device_remove_file(dev, &dev_attr_vendor); + device_remove_file(dev, &dev_attr_product); + device_remove_file(dev, &dev_attr_pointer_mode); + device_remove_file(dev, &dev_attr_coordinate_mode); + device_remove_file(dev, &dev_attr_tool_mode); + device_remove_file(dev, &dev_attr_xtilt); + device_remove_file(dev, &dev_attr_ytilt); + device_remove_file(dev, &dev_attr_jitter); + device_remove_file(dev, &dev_attr_delay); + device_remove_file(dev, &dev_attr_input_path); + device_remove_file(dev, &dev_attr_event_count); + device_remove_file(dev, &dev_attr_diagnostic); + device_remove_file(dev, &dev_attr_odm_code); + device_remove_file(dev, &dev_attr_model_code); + device_remove_file(dev, &dev_attr_firmware_code); + device_remove_file(dev, &dev_attr_stylus_lower); + device_remove_file(dev, &dev_attr_stylus_upper); + device_remove_file(dev, &dev_attr_mouse_left); + device_remove_file(dev, &dev_attr_mouse_middle); + device_remove_file(dev, &dev_attr_mouse_right); + device_remove_file(dev, &dev_attr_wheel); + device_remove_file(dev, &dev_attr_execute); +} + +/*********************************************************************** + * This routine creates the sysfs files managed by this device + * driver. + */ +static int aiptek_add_files(struct device *dev) +{ + int ret; + + if ((ret = device_create_file(dev, &dev_attr_size)) || + (ret = device_create_file(dev, &dev_attr_product_id)) || + (ret = device_create_file(dev, &dev_attr_vendor_id)) || + (ret = device_create_file(dev, &dev_attr_vendor)) || + (ret = device_create_file(dev, &dev_attr_product)) || + (ret = device_create_file(dev, &dev_attr_pointer_mode)) || + (ret = device_create_file(dev, &dev_attr_coordinate_mode)) || + (ret = device_create_file(dev, &dev_attr_tool_mode)) || + (ret = device_create_file(dev, &dev_attr_xtilt)) || + (ret = device_create_file(dev, &dev_attr_ytilt)) || + (ret = device_create_file(dev, &dev_attr_jitter)) || + (ret = device_create_file(dev, &dev_attr_delay)) || + (ret = device_create_file(dev, &dev_attr_input_path)) || + (ret = device_create_file(dev, &dev_attr_event_count)) || + (ret = device_create_file(dev, &dev_attr_diagnostic)) || + (ret = device_create_file(dev, &dev_attr_odm_code)) || + (ret = device_create_file(dev, &dev_attr_model_code)) || + (ret = device_create_file(dev, &dev_attr_firmware_code)) || + (ret = device_create_file(dev, &dev_attr_stylus_lower)) || + (ret = device_create_file(dev, &dev_attr_stylus_upper)) || + (ret = device_create_file(dev, &dev_attr_mouse_left)) || + (ret = device_create_file(dev, &dev_attr_mouse_middle)) || + (ret = device_create_file(dev, &dev_attr_mouse_right)) || + (ret = device_create_file(dev, &dev_attr_wheel)) || + (ret = device_create_file(dev, &dev_attr_execute))) { + err("aiptek: killing own sysfs device files\n"); + aiptek_delete_files(dev); + } + return ret; +} + +/*********************************************************************** + * This routine is called when a tablet has been identified. It basically + * sets up the tablet and the driver's internal structures. + */ +static int +aiptek_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *usbdev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *endpoint; + struct aiptek *aiptek; + struct input_dev *inputdev; + struct input_handle *inputhandle; + struct list_head *node, *next; + int i; + int speeds[] = { 0, + AIPTEK_PROGRAMMABLE_DELAY_50, + AIPTEK_PROGRAMMABLE_DELAY_400, + AIPTEK_PROGRAMMABLE_DELAY_25, + AIPTEK_PROGRAMMABLE_DELAY_100, + AIPTEK_PROGRAMMABLE_DELAY_200, + AIPTEK_PROGRAMMABLE_DELAY_300 + }; + int err = -ENOMEM; + + /* programmableDelay is where the command-line specified + * delay is kept. We make it the first element of speeds[], + * so therefore, your override speed is tried first, then the + * remainder. Note that the default value of 400ms will be tried + * if you do not specify any command line parameter. + */ + speeds[0] = programmableDelay; + + aiptek = kzalloc(sizeof(struct aiptek), GFP_KERNEL); + inputdev = input_allocate_device(); + if (!aiptek || !inputdev) + goto fail1; + + aiptek->data = usb_buffer_alloc(usbdev, AIPTEK_PACKET_LENGTH, + GFP_ATOMIC, &aiptek->data_dma); + if (!aiptek->data) + goto fail1; + + aiptek->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!aiptek->urb) + goto fail2; + + aiptek->inputdev = inputdev; + aiptek->usbdev = usbdev; + aiptek->ifnum = intf->altsetting[0].desc.bInterfaceNumber; + aiptek->inDelay = 0; + aiptek->endDelay = 0; + aiptek->previousJitterable = 0; + + /* Set up the curSettings struct. Said struct contains the current + * programmable parameters. The newSetting struct contains changes + * the user makes to the settings via the sysfs interface. Those + * changes are not "committed" to curSettings until the user + * writes to the sysfs/.../execute file. + */ + aiptek->curSetting.pointerMode = AIPTEK_POINTER_EITHER_MODE; + aiptek->curSetting.coordinateMode = AIPTEK_COORDINATE_ABSOLUTE_MODE; + aiptek->curSetting.toolMode = AIPTEK_TOOL_BUTTON_PEN_MODE; + aiptek->curSetting.xTilt = AIPTEK_TILT_DISABLE; + aiptek->curSetting.yTilt = AIPTEK_TILT_DISABLE; + aiptek->curSetting.mouseButtonLeft = AIPTEK_MOUSE_LEFT_BUTTON; + aiptek->curSetting.mouseButtonMiddle = AIPTEK_MOUSE_MIDDLE_BUTTON; + aiptek->curSetting.mouseButtonRight = AIPTEK_MOUSE_RIGHT_BUTTON; + aiptek->curSetting.stylusButtonUpper = AIPTEK_STYLUS_UPPER_BUTTON; + aiptek->curSetting.stylusButtonLower = AIPTEK_STYLUS_LOWER_BUTTON; + aiptek->curSetting.jitterDelay = jitterDelay; + aiptek->curSetting.programmableDelay = programmableDelay; + + /* Both structs should have equivalent settings + */ + aiptek->newSetting = aiptek->curSetting; + + /* Determine the usb devices' physical path. + * Asketh not why we always pretend we're using "../input0", + * but I suspect this will have to be refactored one + * day if a single USB device can be a keyboard & a mouse + * & a tablet, and the inputX number actually will tell + * us something... + */ + usb_make_path(usbdev, aiptek->features.usbPath, + sizeof(aiptek->features.usbPath)); + strlcat(aiptek->features.usbPath, "/input0", + sizeof(aiptek->features.usbPath)); + + /* Set up client data, pointers to open and close routines + * for the input device. + */ + inputdev->name = "Aiptek"; + inputdev->phys = aiptek->features.usbPath; + usb_to_input_id(usbdev, &inputdev->id); + inputdev->dev.parent = &intf->dev; + + input_set_drvdata(inputdev, aiptek); + + inputdev->open = aiptek_open; + inputdev->close = aiptek_close; + + /* Now program the capacities of the tablet, in terms of being + * an input device. + */ + inputdev->evbit[0] |= BIT(EV_KEY) + | BIT(EV_ABS) + | BIT(EV_REL) + | BIT(EV_MSC); + + inputdev->absbit[0] |= BIT(ABS_MISC); + + inputdev->relbit[0] |= + (BIT(REL_X) | BIT(REL_Y) | BIT(REL_WHEEL) | BIT(REL_MISC)); + + inputdev->keybit[LONG(BTN_LEFT)] |= + (BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE)); + + inputdev->keybit[LONG(BTN_DIGI)] |= + (BIT(BTN_TOOL_PEN) | + BIT(BTN_TOOL_RUBBER) | + BIT(BTN_TOOL_PENCIL) | + BIT(BTN_TOOL_AIRBRUSH) | + BIT(BTN_TOOL_BRUSH) | + BIT(BTN_TOOL_MOUSE) | + BIT(BTN_TOOL_LENS) | + BIT(BTN_TOUCH) | BIT(BTN_STYLUS) | BIT(BTN_STYLUS2)); + + inputdev->mscbit[0] = BIT(MSC_SERIAL); + + /* Programming the tablet macro keys needs to be done with a for loop + * as the keycodes are discontiguous. + */ + for (i = 0; i < ARRAY_SIZE(macroKeyEvents); ++i) + set_bit(macroKeyEvents[i], inputdev->keybit); + + /* + * Program the input device coordinate capacities. We do not yet + * know what maximum X, Y, and Z values are, so we're putting fake + * values in. Later, we'll ask the tablet to put in the correct + * values. + */ + input_set_abs_params(inputdev, ABS_X, 0, 2999, 0, 0); + input_set_abs_params(inputdev, ABS_Y, 0, 2249, 0, 0); + input_set_abs_params(inputdev, ABS_PRESSURE, 0, 511, 0, 0); + input_set_abs_params(inputdev, ABS_TILT_X, AIPTEK_TILT_MIN, AIPTEK_TILT_MAX, 0, 0); + input_set_abs_params(inputdev, ABS_TILT_Y, AIPTEK_TILT_MIN, AIPTEK_TILT_MAX, 0, 0); + input_set_abs_params(inputdev, ABS_WHEEL, AIPTEK_WHEEL_MIN, AIPTEK_WHEEL_MAX - 1, 0, 0); + + endpoint = &intf->altsetting[0].endpoint[0].desc; + + /* Go set up our URB, which is called when the tablet receives + * input. + */ + usb_fill_int_urb(aiptek->urb, + aiptek->usbdev, + usb_rcvintpipe(aiptek->usbdev, + endpoint->bEndpointAddress), + aiptek->data, 8, aiptek_irq, aiptek, + endpoint->bInterval); + + aiptek->urb->transfer_dma = aiptek->data_dma; + aiptek->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* Program the tablet. This sets the tablet up in the mode + * specified in newSetting, and also queries the tablet's + * physical capacities. + * + * Sanity check: if a tablet doesn't like the slow programmatic + * delay, we often get sizes of 0x0. Let's use that as an indicator + * to try faster delays, up to 25 ms. If that logic fails, well, you'll + * have to explain to us how your tablet thinks it's 0x0, and yet that's + * not an error :-) + */ + + for (i = 0; i < ARRAY_SIZE(speeds); ++i) { + aiptek->curSetting.programmableDelay = speeds[i]; + (void)aiptek_program_tablet(aiptek); + if (aiptek->inputdev->absmax[ABS_X] > 0) { + info("input: Aiptek using %d ms programming speed\n", + aiptek->curSetting.programmableDelay); + break; + } + } + + /* Register the tablet as an Input Device + */ + err = input_register_device(aiptek->inputdev); + if (err) + goto fail2; + + /* We now will look for the evdev device which is mapped to + * the tablet. The partial name is kept in the link list of + * input_handles associated with this input device. + * What identifies an evdev input_handler is that it begins + * with 'event', continues with a digit, and that in turn + * is mapped to input/eventN. + */ + list_for_each_safe(node, next, &inputdev->h_list) { + inputhandle = to_handle(node); + if (strncmp(inputhandle->name, "event", 5) == 0) { + strcpy(aiptek->features.inputPath, inputhandle->name); + break; + } + } + + /* Associate this driver's struct with the usb interface. + */ + usb_set_intfdata(intf, aiptek); + + /* Set up the sysfs files + */ + aiptek_add_files(&intf->dev); + + /* Make sure the evdev module is loaded. Assuming evdev IS a module :-) + */ + if (request_module("evdev") != 0) + info("aiptek: error loading 'evdev' module"); + + return 0; + + fail2: usb_buffer_free(usbdev, AIPTEK_PACKET_LENGTH, aiptek->data, + aiptek->data_dma); + fail1: input_free_device(inputdev); + kfree(aiptek); + return err; +} + +/*********************************************************************** + * Deal with tablet disconnecting from the system. + */ +static void aiptek_disconnect(struct usb_interface *intf) +{ + struct aiptek *aiptek = usb_get_intfdata(intf); + + /* Disassociate driver's struct with usb interface + */ + usb_set_intfdata(intf, NULL); + if (aiptek != NULL) { + /* Free & unhook everything from the system. + */ + usb_kill_urb(aiptek->urb); + input_unregister_device(aiptek->inputdev); + aiptek_delete_files(&intf->dev); + usb_free_urb(aiptek->urb); + usb_buffer_free(interface_to_usbdev(intf), + AIPTEK_PACKET_LENGTH, + aiptek->data, aiptek->data_dma); + kfree(aiptek); + } +} + +static struct usb_driver aiptek_driver = { + .name = "aiptek", + .probe = aiptek_probe, + .disconnect = aiptek_disconnect, + .id_table = aiptek_ids, +}; + +static int __init aiptek_init(void) +{ + int result = usb_register(&aiptek_driver); + if (result == 0) { + info(DRIVER_VERSION ": " DRIVER_AUTHOR); + info(DRIVER_DESC); + } + return result; +} + +static void __exit aiptek_exit(void) +{ + usb_deregister(&aiptek_driver); +} + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_param(programmableDelay, int, 0); +MODULE_PARM_DESC(programmableDelay, "delay used during tablet programming"); +module_param(jitterDelay, int, 0); +MODULE_PARM_DESC(jitterDelay, "stylus/mouse settlement delay"); + +module_init(aiptek_init); +module_exit(aiptek_exit); diff --git a/drivers/input/tablet/gtco.c b/drivers/input/tablet/gtco.c new file mode 100644 index 000000000000..b2ca10f2fe0e --- /dev/null +++ b/drivers/input/tablet/gtco.c @@ -0,0 +1,1055 @@ +/* -*- 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])); + break; + } + + /* 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"); + else 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"); + break; + } + + 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 = input_get_drvdata(inputdev); + + 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 = input_get_drvdata(inputdev); + + 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 = input_get_drvdata(inputdev); + + /* 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 */ + val = device->buffer[5] & MASK_INRANGE ? 1 : 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 */ + val = buttonbyte & MASK_INRANGE ? 1 : 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 *gtco; + struct input_dev *input_dev; + struct hid_descriptor *hid_desc; + char *report = NULL; + int result = 0, retry; + int error; + struct usb_endpoint_descriptor *endpoint; + + /* Allocate memory for device structure */ + gtco = kzalloc(sizeof(struct gtco), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!gtco || !input_dev) { + err("No more memory"); + error = -ENOMEM; + goto err_free_devs; + } + + /* Set pointer to the input device */ + gtco->inputdevice = input_dev; + + /* Save interface information */ + gtco->usbdev = usb_get_dev(interface_to_usbdev(usbinterface)); + + /* Allocate some data for incoming reports */ + gtco->buffer = usb_buffer_alloc(gtco->usbdev, REPORT_MAX_SIZE, + GFP_KERNEL, >co->buf_dma); + if (!gtco->buffer) { + err("No more memory for us buffers"); + error = -ENOMEM; + goto err_free_devs; + } + + /* Allocate URB for reports */ + gtco->urbinfo = usb_alloc_urb(0, GFP_KERNEL); + if (!gtco->urbinfo) { + err("Failed to allocate URB"); + return -ENOMEM; + goto err_free_buf; + } + + /* + * 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"); + error = -EIO; + goto err_free_urb; + } + + dbg("Extra descriptor success: type:%d len:%d", + hid_desc->bDescriptorType, hid_desc->wDescriptorLength); + + report = kzalloc(hid_desc->wDescriptorLength, GFP_KERNEL); + if (!report) { + err("No more memory for report"); + error = -ENOMEM; + goto err_free_urb; + } + + /* Couple of tries to get reply */ + for (retry = 0; retry < 3; retry++) { + result = usb_control_msg(gtco->usbdev, + usb_rcvctrlpipe(gtco->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) { + err("Failed to get HID Report Descriptor of size: %d", + hid_desc->wDescriptorLength); + error = -EIO; + goto err_free_urb; + } + + /* Now we parse the report */ + parse_hid_report_descriptor(gtco, report, result); + + /* Now we delete it */ + kfree(report); + + /* Create a device file node */ + usb_make_path(gtco->usbdev, gtco->usbpath, sizeof(gtco->usbpath)); + strlcat(gtco->usbpath, "/input0", sizeof(gtco->usbpath)); + + /* Set Input device functions */ + input_dev->open = gtco_input_open; + input_dev->close = gtco_input_close; + + /* Set input device information */ + input_dev->name = "GTCO_CalComp"; + input_dev->phys = gtco->usbpath; + + input_set_drvdata(input_dev, gtco); + + /* Now set up all the input device capabilities */ + gtco_setup_caps(input_dev); + + /* Set input device required ID information */ + usb_to_input_id(gtco->usbdev, &input_dev->id); + input_dev->dev.parent = &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(gtco->urbinfo, + gtco->usbdev, + usb_rcvintpipe(gtco->usbdev, + endpoint->bEndpointAddress), + gtco->buffer, + REPORT_MAX_SIZE, + gtco_urb_callback, + gtco, + endpoint->bInterval); + + gtco->urbinfo->transfer_dma = gtco->buf_dma; + gtco->urbinfo->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* Save gtco pointer in USB interface gtco */ + usb_set_intfdata(usbinterface, gtco); + + /* All done, now register the input device */ + error = input_register_device(input_dev); + if (error) + goto err_free_urb; + + return 0; + + err_free_urb: + usb_free_urb(gtco->urbinfo); + err_free_buf: + usb_buffer_free(gtco->usbdev, REPORT_MAX_SIZE, + gtco->buffer, gtco->buf_dma); + err_free_devs: + kfree(report); + input_free_device(input_dev); + kfree(gtco); + return error; +} + +/* + * 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 *gtco = usb_get_intfdata(interface); + + /* Now reverse all the registration stuff */ + if (gtco) { + input_unregister_device(gtco->inputdevice); + usb_kill_urb(gtco->urbinfo); + usb_free_urb(gtco->urbinfo); + usb_buffer_free(gtco->usbdev, REPORT_MAX_SIZE, + gtco->buffer, gtco->buf_dma); + kfree(gtco); + } + + info("gtco driver disconnected"); +} + +/* STANDARD MODULE LOAD ROUTINES */ + +static struct usb_driver gtco_driverinfo_table = { + .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 error; + + error = usb_register(>co_driverinfo_table); + if (error) { + err("usb_register() failed rc=0x%x", error); + return error; + } + + printk("GTCO usb driver version: %s", GTCO_VERSION); + return 0; +} + +/* + * Deregister this module with the USB subsystem + */ +static void __exit gtco_exit(void) +{ + usb_deregister(>co_driverinfo_table); +} + +module_init(gtco_init); +module_exit(gtco_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/tablet/kbtab.c b/drivers/input/tablet/kbtab.c new file mode 100644 index 000000000000..91e6d00d4a43 --- /dev/null +++ b/drivers/input/tablet/kbtab.c @@ -0,0 +1,226 @@ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb/input.h> +#include <asm/unaligned.h> + +/* + * Version Information + * v0.0.1 - Original, extremely basic version, 2.4.xx only + * v0.0.2 - Updated, works with 2.5.62 and 2.4.20; + * - added pressure-threshold modules param code from + * Alex Perry <alex.perry@ieee.org> + */ + +#define DRIVER_VERSION "v0.0.2" +#define DRIVER_AUTHOR "Josh Myer <josh@joshisanerd.com>" +#define DRIVER_DESC "USB KB Gear JamStudio Tablet driver" +#define DRIVER_LICENSE "GPL" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); + +#define USB_VENDOR_ID_KBGEAR 0x084e + +static int kb_pressure_click = 0x10; +module_param(kb_pressure_click, int, 0); +MODULE_PARM_DESC(kb_pressure_click, "pressure threshold for clicks"); + +struct kbtab { + unsigned char *data; + dma_addr_t data_dma; + struct input_dev *dev; + struct usb_device *usbdev; + struct urb *irq; + int x, y; + int button; + int pressure; + __u32 serial[2]; + char phys[32]; +}; + +static void kbtab_irq(struct urb *urb) +{ + struct kbtab *kbtab = urb->context; + unsigned char *data = kbtab->data; + struct input_dev *dev = kbtab->dev; + int retval; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); + goto exit; + } + + kbtab->x = le16_to_cpu(get_unaligned((__le16 *) &data[1])); + kbtab->y = le16_to_cpu(get_unaligned((__le16 *) &data[3])); + + kbtab->pressure = (data[5]); + + input_report_key(dev, BTN_TOOL_PEN, 1); + + input_report_abs(dev, ABS_X, kbtab->x); + input_report_abs(dev, ABS_Y, kbtab->y); + + /*input_report_key(dev, BTN_TOUCH , data[0] & 0x01);*/ + input_report_key(dev, BTN_RIGHT, data[0] & 0x02); + + if (-1 == kb_pressure_click) { + input_report_abs(dev, ABS_PRESSURE, kbtab->pressure); + } else { + input_report_key(dev, BTN_LEFT, (kbtab->pressure > kb_pressure_click) ? 1 : 0); + }; + + input_sync(dev); + + exit: + retval = usb_submit_urb (urb, GFP_ATOMIC); + if (retval) + err ("%s - usb_submit_urb failed with result %d", + __FUNCTION__, retval); +} + +static struct usb_device_id kbtab_ids[] = { + { USB_DEVICE(USB_VENDOR_ID_KBGEAR, 0x1001), .driver_info = 0 }, + { } +}; + +MODULE_DEVICE_TABLE(usb, kbtab_ids); + +static int kbtab_open(struct input_dev *dev) +{ + struct kbtab *kbtab = input_get_drvdata(dev); + + kbtab->irq->dev = kbtab->usbdev; + if (usb_submit_urb(kbtab->irq, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void kbtab_close(struct input_dev *dev) +{ + struct kbtab *kbtab = input_get_drvdata(dev); + + usb_kill_urb(kbtab->irq); +} + +static int kbtab_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *endpoint; + struct kbtab *kbtab; + struct input_dev *input_dev; + int error = -ENOMEM; + + kbtab = kzalloc(sizeof(struct kbtab), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!kbtab || !input_dev) + goto fail1; + + kbtab->data = usb_buffer_alloc(dev, 8, GFP_KERNEL, &kbtab->data_dma); + if (!kbtab->data) + goto fail1; + + kbtab->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!kbtab->irq) + goto fail2; + + kbtab->usbdev = dev; + kbtab->dev = input_dev; + + usb_make_path(dev, kbtab->phys, sizeof(kbtab->phys)); + strlcat(kbtab->phys, "/input0", sizeof(kbtab->phys)); + + input_dev->name = "KB Gear Tablet"; + input_dev->phys = kbtab->phys; + usb_to_input_id(dev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, kbtab); + + input_dev->open = kbtab_open; + input_dev->close = kbtab_close; + + input_dev->evbit[0] |= BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_MSC); + input_dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE); + input_dev->keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_PEN) | BIT(BTN_TOUCH); + input_dev->mscbit[0] |= BIT(MSC_SERIAL); + input_set_abs_params(input_dev, ABS_X, 0, 0x2000, 4, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 0x1750, 4, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 0xff, 0, 0); + + endpoint = &intf->cur_altsetting->endpoint[0].desc; + + usb_fill_int_urb(kbtab->irq, dev, + usb_rcvintpipe(dev, endpoint->bEndpointAddress), + kbtab->data, 8, + kbtab_irq, kbtab, endpoint->bInterval); + kbtab->irq->transfer_dma = kbtab->data_dma; + kbtab->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + error = input_register_device(kbtab->dev); + if (error) + goto fail3; + + usb_set_intfdata(intf, kbtab); + + return 0; + + fail3: usb_free_urb(kbtab->irq); + fail2: usb_buffer_free(dev, 10, kbtab->data, kbtab->data_dma); + fail1: input_free_device(input_dev); + kfree(kbtab); + return error; +} + +static void kbtab_disconnect(struct usb_interface *intf) +{ + struct kbtab *kbtab = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + if (kbtab) { + usb_kill_urb(kbtab->irq); + input_unregister_device(kbtab->dev); + usb_free_urb(kbtab->irq); + usb_buffer_free(interface_to_usbdev(intf), 10, kbtab->data, kbtab->data_dma); + kfree(kbtab); + } +} + +static struct usb_driver kbtab_driver = { + .name = "kbtab", + .probe = kbtab_probe, + .disconnect = kbtab_disconnect, + .id_table = kbtab_ids, +}; + +static int __init kbtab_init(void) +{ + int retval; + retval = usb_register(&kbtab_driver); + if (retval) + goto out; + info(DRIVER_VERSION ":" DRIVER_DESC); +out: + return retval; +} + +static void __exit kbtab_exit(void) +{ + usb_deregister(&kbtab_driver); +} + +module_init(kbtab_init); +module_exit(kbtab_exit); diff --git a/drivers/input/tablet/wacom.h b/drivers/input/tablet/wacom.h new file mode 100644 index 000000000000..ef01a807ec0f --- /dev/null +++ b/drivers/input/tablet/wacom.h @@ -0,0 +1,131 @@ +/* + * drivers/input/tablet/wacom.h + * + * USB Wacom Graphire and Wacom Intuos tablet support + * + * Copyright (c) 2000-2004 Vojtech Pavlik <vojtech@ucw.cz> + * Copyright (c) 2000 Andreas Bach Aaen <abach@stofanet.dk> + * Copyright (c) 2000 Clifford Wolf <clifford@clifford.at> + * Copyright (c) 2000 Sam Mosel <sam.mosel@computer.org> + * Copyright (c) 2000 James E. Blair <corvus@gnu.org> + * Copyright (c) 2000 Daniel Egger <egger@suse.de> + * Copyright (c) 2001 Frederic Lepied <flepied@mandrakesoft.com> + * Copyright (c) 2004 Panagiotis Issaris <panagiotis.issaris@mech.kuleuven.ac.be> + * Copyright (c) 2002-2006 Ping Cheng <pingc@wacom.com> + * + * ChangeLog: + * v0.1 (vp) - Initial release + * v0.2 (aba) - Support for all buttons / combinations + * v0.3 (vp) - Support for Intuos added + * v0.4 (sm) - Support for more Intuos models, menustrip + * relative mode, proximity. + * v0.5 (vp) - Big cleanup, nifty features removed, + * they belong in userspace + * v1.8 (vp) - Submit URB only when operating, moved to CVS, + * use input_report_key instead of report_btn and + * other cleanups + * v1.11 (vp) - Add URB ->dev setting for new kernels + * v1.11 (jb) - Add support for the 4D Mouse & Lens + * v1.12 (de) - Add support for two more inking pen IDs + * v1.14 (vp) - Use new USB device id probing scheme. + * Fix Wacom Graphire mouse wheel + * v1.18 (vp) - Fix mouse wheel direction + * Make mouse relative + * v1.20 (fl) - Report tool id for Intuos devices + * - Multi tools support + * - Corrected Intuos protocol decoding (airbrush, 4D mouse, lens cursor...) + * - Add PL models support + * - Fix Wacom Graphire mouse wheel again + * v1.21 (vp) - Removed protocol descriptions + * - Added MISC_SERIAL for tool serial numbers + * (gb) - Identify version on module load. + * v1.21.1 (fl) - added Graphire2 support + * v1.21.2 (fl) - added Intuos2 support + * - added all the PL ids + * v1.21.3 (fl) - added another eraser id from Neil Okamoto + * - added smooth filter for Graphire from Peri Hankey + * - added PenPartner support from Olaf van Es + * - new tool ids from Ole Martin Bjoerndalen + * v1.29 (pc) - Add support for more tablets + * - Fix pressure reporting + * v1.30 (vp) - Merge 2.4 and 2.5 drivers + * - Since 2.5 now has input_sync(), remove MSC_SERIAL abuse + * - Cleanups here and there + * v1.30.1 (pi) - Added Graphire3 support + * v1.40 (pc) - Add support for several new devices, fix eraser reporting, ... + * v1.43 (pc) - Added support for Cintiq 21UX + * - Fixed a Graphire bug + * - Merged wacom_intuos3_irq into wacom_intuos_irq + * v1.44 (pc) - Added support for Graphire4, Cintiq 710, Intuos3 6x11, etc. + * - Report Device IDs + * v1.45 (pc) - Added support for DTF 521, Intuos3 12x12 and 12x19 + * - Minor data report fix + * v1.46 (pc) - Split wacom.c into wacom_sys.c and wacom_wac.c, + * - where wacom_sys.c deals with system specific code, + * - and wacom_wac.c deals with Wacom specific code + * - Support Intuos3 4x6 + */ + +/* + * 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. + */ +#ifndef WACOM_H +#define WACOM_H +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb/input.h> +#include <asm/unaligned.h> + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.46" +#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>" +#define DRIVER_DESC "USB Wacom Graphire and Wacom Intuos tablet driver" +#define DRIVER_LICENSE "GPL" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); + +#define USB_VENDOR_ID_WACOM 0x056a + +struct wacom { + dma_addr_t data_dma; + struct input_dev *dev; + struct usb_device *usbdev; + struct urb *irq; + struct wacom_wac * wacom_wac; + char phys[32]; +}; + +struct wacom_combo { + struct wacom * wacom; + struct urb * urb; +}; + +extern int wacom_wac_irq(struct wacom_wac * wacom_wac, void * wcombo); +extern void wacom_report_abs(void *wcombo, unsigned int abs_type, int abs_data); +extern void wacom_report_rel(void *wcombo, unsigned int rel_type, int rel_data); +extern void wacom_report_key(void *wcombo, unsigned int key_type, int key_data); +extern void wacom_input_event(void *wcombo, unsigned int type, unsigned int code, int value); +extern void wacom_input_sync(void *wcombo); +extern void wacom_init_input_dev(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_g4(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_g(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_i3s(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_i3(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_i(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_pl(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern void input_dev_pt(struct input_dev *input_dev, struct wacom_wac *wacom_wac); +extern __u16 wacom_le16_to_cpu(unsigned char *data); +extern __u16 wacom_be16_to_cpu(unsigned char *data); +extern struct wacom_features * get_wacom_feature(const struct usb_device_id *id); +extern const struct usb_device_id * get_device_table(void); + +#endif diff --git a/drivers/input/tablet/wacom_sys.c b/drivers/input/tablet/wacom_sys.c new file mode 100644 index 000000000000..83bddef66067 --- /dev/null +++ b/drivers/input/tablet/wacom_sys.c @@ -0,0 +1,318 @@ +/* + * drivers/input/tablet/wacom_sys.c + * + * USB Wacom Graphire and Wacom Intuos tablet support - system specific code + */ + +/* + * 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. + */ + +#include "wacom.h" +#include "wacom_wac.h" + +#define USB_REQ_GET_REPORT 0x01 +#define USB_REQ_SET_REPORT 0x09 + +static int usb_get_report(struct usb_interface *intf, unsigned char type, + unsigned char id, void *buf, int size) +{ + return usb_control_msg(interface_to_usbdev(intf), + usb_rcvctrlpipe(interface_to_usbdev(intf), 0), + USB_REQ_GET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE, + (type << 8) + id, intf->altsetting[0].desc.bInterfaceNumber, + buf, size, 100); +} + +static int usb_set_report(struct usb_interface *intf, unsigned char type, + unsigned char id, void *buf, int size) +{ + return usb_control_msg(interface_to_usbdev(intf), + usb_sndctrlpipe(interface_to_usbdev(intf), 0), + USB_REQ_SET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE, + (type << 8) + id, intf->altsetting[0].desc.bInterfaceNumber, + buf, size, 1000); +} + +static struct input_dev * get_input_dev(struct wacom_combo *wcombo) +{ + return wcombo->wacom->dev; +} + +static void wacom_sys_irq(struct urb *urb) +{ + struct wacom *wacom = urb->context; + struct wacom_combo wcombo; + int retval; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); + goto exit; + } + + wcombo.wacom = wacom; + wcombo.urb = urb; + + if (wacom_wac_irq(wacom->wacom_wac, (void *)&wcombo)) + input_sync(get_input_dev(&wcombo)); + + exit: + retval = usb_submit_urb (urb, GFP_ATOMIC); + if (retval) + err ("%s - usb_submit_urb failed with result %d", + __FUNCTION__, retval); +} + +void wacom_report_key(void *wcombo, unsigned int key_type, int key_data) +{ + input_report_key(get_input_dev((struct wacom_combo *)wcombo), key_type, key_data); + return; +} + +void wacom_report_abs(void *wcombo, unsigned int abs_type, int abs_data) +{ + input_report_abs(get_input_dev((struct wacom_combo *)wcombo), abs_type, abs_data); + return; +} + +void wacom_report_rel(void *wcombo, unsigned int rel_type, int rel_data) +{ + input_report_rel(get_input_dev((struct wacom_combo *)wcombo), rel_type, rel_data); + return; +} + +void wacom_input_event(void *wcombo, unsigned int type, unsigned int code, int value) +{ + input_event(get_input_dev((struct wacom_combo *)wcombo), type, code, value); + return; +} + +__u16 wacom_be16_to_cpu(unsigned char *data) +{ + __u16 value; + value = be16_to_cpu(*(__be16 *) data); + return value; +} + +__u16 wacom_le16_to_cpu(unsigned char *data) +{ + __u16 value; + value = le16_to_cpu(*(__le16 *) data); + return value; +} + +void wacom_input_sync(void *wcombo) +{ + input_sync(get_input_dev((struct wacom_combo *)wcombo)); + return; +} + +static int wacom_open(struct input_dev *dev) +{ + struct wacom *wacom = input_get_drvdata(dev); + + wacom->irq->dev = wacom->usbdev; + if (usb_submit_urb(wacom->irq, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void wacom_close(struct input_dev *dev) +{ + struct wacom *wacom = input_get_drvdata(dev); + + usb_kill_urb(wacom->irq); +} + +void input_dev_g4(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->evbit[0] |= BIT(EV_MSC); + input_dev->mscbit[0] |= BIT(MSC_SERIAL); + input_dev->keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_FINGER); + input_dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_0) | BIT(BTN_4); +} + +void input_dev_g(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->evbit[0] |= BIT(EV_REL); + input_dev->relbit[0] |= BIT(REL_WHEEL); + input_dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE); + input_dev->keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_RUBBER) | BIT(BTN_TOOL_MOUSE) | BIT(BTN_STYLUS2); + input_set_abs_params(input_dev, ABS_DISTANCE, 0, wacom_wac->features->distance_max, 0, 0); +} + +void input_dev_i3s(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_FINGER); + input_dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_0) | BIT(BTN_1) | BIT(BTN_2) | BIT(BTN_3); + input_set_abs_params(input_dev, ABS_RX, 0, 4096, 0, 0); +} + +void input_dev_i3(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_4) | BIT(BTN_5) | BIT(BTN_6) | BIT(BTN_7); + input_set_abs_params(input_dev, ABS_RY, 0, 4096, 0, 0); +} + +void input_dev_i(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->evbit[0] |= BIT(EV_MSC) | BIT(EV_REL); + input_dev->mscbit[0] |= BIT(MSC_SERIAL); + input_dev->relbit[0] |= BIT(REL_WHEEL); + input_dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE) | BIT(BTN_SIDE) | BIT(BTN_EXTRA); + input_dev->keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_RUBBER) | BIT(BTN_TOOL_MOUSE) | BIT(BTN_TOOL_BRUSH) + | BIT(BTN_TOOL_PENCIL) | BIT(BTN_TOOL_AIRBRUSH) | BIT(BTN_TOOL_LENS) | BIT(BTN_STYLUS2); + input_set_abs_params(input_dev, ABS_DISTANCE, 0, wacom_wac->features->distance_max, 0, 0); + input_set_abs_params(input_dev, ABS_WHEEL, 0, 1023, 0, 0); + input_set_abs_params(input_dev, ABS_TILT_X, 0, 127, 0, 0); + input_set_abs_params(input_dev, ABS_TILT_Y, 0, 127, 0, 0); + input_set_abs_params(input_dev, ABS_RZ, -900, 899, 0, 0); + input_set_abs_params(input_dev, ABS_THROTTLE, -1023, 1023, 0, 0); +} + +void input_dev_pl(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->keybit[LONG(BTN_DIGI)] |= BIT(BTN_STYLUS2) | BIT(BTN_TOOL_RUBBER); +} + +void input_dev_pt(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + input_dev->keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_RUBBER); +} + +static int wacom_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *endpoint; + struct wacom *wacom; + struct wacom_wac *wacom_wac; + struct input_dev *input_dev; + int error = -ENOMEM; + char rep_data[2], limit = 0; + + wacom = kzalloc(sizeof(struct wacom), GFP_KERNEL); + wacom_wac = kzalloc(sizeof(struct wacom_wac), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!wacom || !input_dev || !wacom_wac) + goto fail1; + + wacom_wac->data = usb_buffer_alloc(dev, 10, GFP_KERNEL, &wacom->data_dma); + if (!wacom_wac->data) + goto fail1; + + wacom->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!wacom->irq) + goto fail2; + + wacom->usbdev = dev; + wacom->dev = input_dev; + usb_make_path(dev, wacom->phys, sizeof(wacom->phys)); + strlcat(wacom->phys, "/input0", sizeof(wacom->phys)); + + wacom_wac->features = get_wacom_feature(id); + BUG_ON(wacom_wac->features->pktlen > 10); + + input_dev->name = wacom_wac->features->name; + wacom->wacom_wac = wacom_wac; + usb_to_input_id(dev, &input_dev->id); + + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, wacom); + + input_dev->open = wacom_open; + input_dev->close = wacom_close; + + input_dev->evbit[0] |= BIT(EV_KEY) | BIT(EV_ABS); + input_dev->keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_PEN) | BIT(BTN_TOUCH) | BIT(BTN_STYLUS); + input_set_abs_params(input_dev, ABS_X, 0, wacom_wac->features->x_max, 4, 0); + input_set_abs_params(input_dev, ABS_Y, 0, wacom_wac->features->y_max, 4, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, wacom_wac->features->pressure_max, 0, 0); + input_dev->absbit[LONG(ABS_MISC)] |= BIT(ABS_MISC); + + wacom_init_input_dev(input_dev, wacom_wac); + + endpoint = &intf->cur_altsetting->endpoint[0].desc; + + usb_fill_int_urb(wacom->irq, dev, + usb_rcvintpipe(dev, endpoint->bEndpointAddress), + wacom_wac->data, wacom_wac->features->pktlen, + wacom_sys_irq, wacom, endpoint->bInterval); + wacom->irq->transfer_dma = wacom->data_dma; + wacom->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + error = input_register_device(wacom->dev); + if (error) + goto fail3; + + /* Ask the tablet to report tablet data. Repeat until it succeeds */ + do { + rep_data[0] = 2; + rep_data[1] = 2; + usb_set_report(intf, 3, 2, rep_data, 2); + usb_get_report(intf, 3, 2, rep_data, 2); + } while (rep_data[1] != 2 && limit++ < 5); + + usb_set_intfdata(intf, wacom); + return 0; + + fail3: usb_free_urb(wacom->irq); + fail2: usb_buffer_free(dev, 10, wacom_wac->data, wacom->data_dma); + fail1: input_free_device(input_dev); + kfree(wacom); + kfree(wacom_wac); + return error; +} + +static void wacom_disconnect(struct usb_interface *intf) +{ + struct wacom *wacom = usb_get_intfdata (intf); + + usb_set_intfdata(intf, NULL); + if (wacom) { + usb_kill_urb(wacom->irq); + input_unregister_device(wacom->dev); + usb_free_urb(wacom->irq); + usb_buffer_free(interface_to_usbdev(intf), 10, wacom->wacom_wac->data, wacom->data_dma); + kfree(wacom->wacom_wac); + kfree(wacom); + } +} + +static struct usb_driver wacom_driver = { + .name = "wacom", + .probe = wacom_probe, + .disconnect = wacom_disconnect, +}; + +static int __init wacom_init(void) +{ + int result; + wacom_driver.id_table = get_device_table(); + result = usb_register(&wacom_driver); + if (result == 0) + info(DRIVER_VERSION ":" DRIVER_DESC); + return result; +} + +static void __exit wacom_exit(void) +{ + usb_deregister(&wacom_driver); +} + +module_init(wacom_init); +module_exit(wacom_exit); diff --git a/drivers/input/tablet/wacom_wac.c b/drivers/input/tablet/wacom_wac.c new file mode 100644 index 000000000000..7661f03a2db2 --- /dev/null +++ b/drivers/input/tablet/wacom_wac.c @@ -0,0 +1,675 @@ +/* + * drivers/input/tablet/wacom_wac.c + * + * USB Wacom Graphire and Wacom Intuos tablet support - Wacom specific code + * + */ + +/* + * 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. + */ +#include "wacom.h" +#include "wacom_wac.h" + +static int wacom_penpartner_irq(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + + switch (data[0]) { + case 1: + if (data[5] & 0x80) { + wacom->tool[0] = (data[5] & 0x20) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN; + wacom->id[0] = (data[5] & 0x20) ? ERASER_DEVICE_ID : STYLUS_DEVICE_ID; + wacom_report_key(wcombo, wacom->tool[0], 1); + wacom_report_abs(wcombo, ABS_MISC, wacom->id[0]); /* report tool id */ + wacom_report_abs(wcombo, ABS_X, wacom_le16_to_cpu(&data[1])); + wacom_report_abs(wcombo, ABS_Y, wacom_le16_to_cpu(&data[3])); + wacom_report_abs(wcombo, ABS_PRESSURE, (signed char)data[6] + 127); + wacom_report_key(wcombo, BTN_TOUCH, ((signed char)data[6] > -127)); + wacom_report_key(wcombo, BTN_STYLUS, (data[5] & 0x40)); + } else { + wacom_report_key(wcombo, wacom->tool[0], 0); + wacom_report_abs(wcombo, ABS_MISC, 0); /* report tool id */ + wacom_report_abs(wcombo, ABS_PRESSURE, -1); + wacom_report_key(wcombo, BTN_TOUCH, 0); + } + break; + case 2: + wacom_report_key(wcombo, BTN_TOOL_PEN, 1); + wacom_report_abs(wcombo, ABS_MISC, STYLUS_DEVICE_ID); /* report tool id */ + wacom_report_abs(wcombo, ABS_X, wacom_le16_to_cpu(&data[1])); + wacom_report_abs(wcombo, ABS_Y, wacom_le16_to_cpu(&data[3])); + wacom_report_abs(wcombo, ABS_PRESSURE, (signed char)data[6] + 127); + wacom_report_key(wcombo, BTN_TOUCH, ((signed char)data[6] > -80) && !(data[5] & 0x20)); + wacom_report_key(wcombo, BTN_STYLUS, (data[5] & 0x40)); + break; + default: + printk(KERN_INFO "wacom_penpartner_irq: received unknown report #%d\n", data[0]); + return 0; + } + return 1; +} + +static int wacom_pl_irq(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + int prox, id, pressure; + + if (data[0] != 2) { + dbg("wacom_pl_irq: received unknown report #%d", data[0]); + return 0; + } + + prox = data[1] & 0x40; + + id = ERASER_DEVICE_ID; + if (prox) { + + pressure = (signed char)((data[7] << 1) | ((data[4] >> 2) & 1)); + if (wacom->features->pressure_max > 255) + pressure = (pressure << 1) | ((data[4] >> 6) & 1); + pressure += (wacom->features->pressure_max + 1) / 2; + + /* + * if going from out of proximity into proximity select between the eraser + * and the pen based on the state of the stylus2 button, choose eraser if + * pressed else choose pen. if not a proximity change from out to in, send + * an out of proximity for previous tool then a in for new tool. + */ + if (!wacom->tool[0]) { + /* Eraser bit set for DTF */ + if (data[1] & 0x10) + wacom->tool[1] = BTN_TOOL_RUBBER; + else + /* Going into proximity select tool */ + wacom->tool[1] = (data[4] & 0x20) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN; + } else { + /* was entered with stylus2 pressed */ + if (wacom->tool[1] == BTN_TOOL_RUBBER && !(data[4] & 0x20)) { + /* report out proximity for previous tool */ + wacom_report_key(wcombo, wacom->tool[1], 0); + wacom_input_sync(wcombo); + wacom->tool[1] = BTN_TOOL_PEN; + return 0; + } + } + if (wacom->tool[1] != BTN_TOOL_RUBBER) { + /* Unknown tool selected default to pen tool */ + wacom->tool[1] = BTN_TOOL_PEN; + id = STYLUS_DEVICE_ID; + } + wacom_report_key(wcombo, wacom->tool[1], prox); /* report in proximity for tool */ + wacom_report_abs(wcombo, ABS_MISC, id); /* report tool id */ + wacom_report_abs(wcombo, ABS_X, data[3] | (data[2] << 7) | ((data[1] & 0x03) << 14)); + wacom_report_abs(wcombo, ABS_Y, data[6] | (data[5] << 7) | ((data[4] & 0x03) << 14)); + wacom_report_abs(wcombo, ABS_PRESSURE, pressure); + + wacom_report_key(wcombo, BTN_TOUCH, data[4] & 0x08); + wacom_report_key(wcombo, BTN_STYLUS, data[4] & 0x10); + /* Only allow the stylus2 button to be reported for the pen tool. */ + wacom_report_key(wcombo, BTN_STYLUS2, (wacom->tool[1] == BTN_TOOL_PEN) && (data[4] & 0x20)); + } else { + /* report proximity-out of a (valid) tool */ + if (wacom->tool[1] != BTN_TOOL_RUBBER) { + /* Unknown tool selected default to pen tool */ + wacom->tool[1] = BTN_TOOL_PEN; + } + wacom_report_key(wcombo, wacom->tool[1], prox); + } + + wacom->tool[0] = prox; /* Save proximity state */ + return 1; +} + +static int wacom_ptu_irq(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + int id; + + if (data[0] != 2) { + printk(KERN_INFO "wacom_ptu_irq: received unknown report #%d\n", data[0]); + return 0; + } + + if (data[1] & 0x04) { + wacom_report_key(wcombo, BTN_TOOL_RUBBER, data[1] & 0x20); + wacom_report_key(wcombo, BTN_TOUCH, data[1] & 0x08); + id = ERASER_DEVICE_ID; + } else { + wacom_report_key(wcombo, BTN_TOOL_PEN, data[1] & 0x20); + wacom_report_key(wcombo, BTN_TOUCH, data[1] & 0x01); + id = STYLUS_DEVICE_ID; + } + wacom_report_abs(wcombo, ABS_MISC, id); /* report tool id */ + wacom_report_abs(wcombo, ABS_X, wacom_le16_to_cpu(&data[2])); + wacom_report_abs(wcombo, ABS_Y, wacom_le16_to_cpu(&data[4])); + wacom_report_abs(wcombo, ABS_PRESSURE, wacom_le16_to_cpu(&data[6])); + wacom_report_key(wcombo, BTN_STYLUS, data[1] & 0x02); + wacom_report_key(wcombo, BTN_STYLUS2, data[1] & 0x10); + return 1; +} + +static int wacom_graphire_irq(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + int x, y, id, rw; + + if (data[0] != 2) { + dbg("wacom_graphire_irq: received unknown report #%d", data[0]); + return 0; + } + + id = STYLUS_DEVICE_ID; + if (data[1] & 0x80) { /* in prox */ + + switch ((data[1] >> 5) & 3) { + + case 0: /* Pen */ + wacom->tool[0] = BTN_TOOL_PEN; + break; + + case 1: /* Rubber */ + wacom->tool[0] = BTN_TOOL_RUBBER; + id = ERASER_DEVICE_ID; + break; + + case 2: /* Mouse with wheel */ + wacom_report_key(wcombo, BTN_MIDDLE, data[1] & 0x04); + if (wacom->features->type == WACOM_G4) { + rw = data[7] & 0x04 ? (data[7] & 0x03)-4 : (data[7] & 0x03); + wacom_report_rel(wcombo, REL_WHEEL, -rw); + } else + wacom_report_rel(wcombo, REL_WHEEL, -(signed char) data[6]); + /* fall through */ + + case 3: /* Mouse without wheel */ + wacom->tool[0] = BTN_TOOL_MOUSE; + id = CURSOR_DEVICE_ID; + wacom_report_key(wcombo, BTN_LEFT, data[1] & 0x01); + wacom_report_key(wcombo, BTN_RIGHT, data[1] & 0x02); + if (wacom->features->type == WACOM_G4) + wacom_report_abs(wcombo, ABS_DISTANCE, data[6] & 0x3f); + else + wacom_report_abs(wcombo, ABS_DISTANCE, data[7] & 0x3f); + break; + } + x = wacom_le16_to_cpu(&data[2]); + y = wacom_le16_to_cpu(&data[4]); + wacom_report_abs(wcombo, ABS_X, x); + wacom_report_abs(wcombo, ABS_Y, y); + if (wacom->tool[0] != BTN_TOOL_MOUSE) { + wacom_report_abs(wcombo, ABS_PRESSURE, data[6] | ((data[7] & 0x01) << 8)); + wacom_report_key(wcombo, BTN_TOUCH, data[1] & 0x01); + wacom_report_key(wcombo, BTN_STYLUS, data[1] & 0x02); + wacom_report_key(wcombo, BTN_STYLUS2, data[1] & 0x04); + } + wacom_report_abs(wcombo, ABS_MISC, id); /* report tool id */ + wacom_report_key(wcombo, wacom->tool[0], 1); + } else if (!(data[1] & 0x90)) { + wacom_report_abs(wcombo, ABS_X, 0); + wacom_report_abs(wcombo, ABS_Y, 0); + if (wacom->tool[0] == BTN_TOOL_MOUSE) { + wacom_report_key(wcombo, BTN_LEFT, 0); + wacom_report_key(wcombo, BTN_RIGHT, 0); + wacom_report_abs(wcombo, ABS_DISTANCE, 0); + } else { + wacom_report_abs(wcombo, ABS_PRESSURE, 0); + wacom_report_key(wcombo, BTN_TOUCH, 0); + wacom_report_key(wcombo, BTN_STYLUS, 0); + wacom_report_key(wcombo, BTN_STYLUS2, 0); + } + wacom_report_abs(wcombo, ABS_MISC, 0); /* reset tool id */ + wacom_report_key(wcombo, wacom->tool[0], 0); + } + + /* send pad data */ + if (wacom->features->type == WACOM_G4) { + if (data[7] & 0xf8) { + wacom_input_sync(wcombo); /* sync last event */ + wacom->id[1] = 1; + wacom->serial[1] = (data[7] & 0xf8); + wacom_report_key(wcombo, BTN_0, (data[7] & 0x40)); + wacom_report_key(wcombo, BTN_4, (data[7] & 0x80)); + rw = ((data[7] & 0x18) >> 3) - ((data[7] & 0x20) >> 3); + wacom_report_rel(wcombo, REL_WHEEL, rw); + wacom_report_key(wcombo, BTN_TOOL_FINGER, 0xf0); + wacom_report_abs(wcombo, ABS_MISC, PAD_DEVICE_ID); + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, 0xf0); + } else if (wacom->id[1]) { + wacom_input_sync(wcombo); /* sync last event */ + wacom->id[1] = 0; + wacom_report_key(wcombo, BTN_0, (data[7] & 0x40)); + wacom_report_key(wcombo, BTN_4, (data[7] & 0x80)); + wacom_report_key(wcombo, BTN_TOOL_FINGER, 0); + wacom_report_abs(wcombo, ABS_MISC, 0); + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, 0xf0); + } + } + return 1; +} + +static int wacom_intuos_inout(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + int idx; + + /* tool number */ + idx = data[1] & 0x01; + + /* Enter report */ + if ((data[1] & 0xfc) == 0xc0) { + /* serial number of the tool */ + wacom->serial[idx] = ((data[3] & 0x0f) << 28) + + (data[4] << 20) + (data[5] << 12) + + (data[6] << 4) + (data[7] >> 4); + + wacom->id[idx] = (data[2] << 4) | (data[3] >> 4); + switch (wacom->id[idx]) { + case 0x812: /* Inking pen */ + case 0x801: /* Intuos3 Inking pen */ + case 0x012: + wacom->tool[idx] = BTN_TOOL_PENCIL; + break; + case 0x822: /* Pen */ + case 0x842: + case 0x852: + case 0x823: /* Intuos3 Grip Pen */ + case 0x813: /* Intuos3 Classic Pen */ + case 0x885: /* Intuos3 Marker Pen */ + case 0x022: + wacom->tool[idx] = BTN_TOOL_PEN; + break; + case 0x832: /* Stroke pen */ + case 0x032: + wacom->tool[idx] = BTN_TOOL_BRUSH; + break; + case 0x007: /* Mouse 4D and 2D */ + case 0x09c: + case 0x094: + case 0x017: /* Intuos3 2D Mouse */ + wacom->tool[idx] = BTN_TOOL_MOUSE; + break; + case 0x096: /* Lens cursor */ + case 0x097: /* Intuos3 Lens cursor */ + wacom->tool[idx] = BTN_TOOL_LENS; + break; + case 0x82a: /* Eraser */ + case 0x85a: + case 0x91a: + case 0xd1a: + case 0x0fa: + case 0x82b: /* Intuos3 Grip Pen Eraser */ + case 0x81b: /* Intuos3 Classic Pen Eraser */ + case 0x91b: /* Intuos3 Airbrush Eraser */ + wacom->tool[idx] = BTN_TOOL_RUBBER; + break; + case 0xd12: + case 0x912: + case 0x112: + case 0x913: /* Intuos3 Airbrush */ + wacom->tool[idx] = BTN_TOOL_AIRBRUSH; + break; + default: /* Unknown tool */ + wacom->tool[idx] = BTN_TOOL_PEN; + } + return 1; + } + + /* Exit report */ + if ((data[1] & 0xfe) == 0x80) { + wacom_report_abs(wcombo, ABS_X, 0); + wacom_report_abs(wcombo, ABS_Y, 0); + wacom_report_abs(wcombo, ABS_DISTANCE, 0); + if (wacom->tool[idx] >= BTN_TOOL_MOUSE) { + wacom_report_key(wcombo, BTN_LEFT, 0); + wacom_report_key(wcombo, BTN_MIDDLE, 0); + wacom_report_key(wcombo, BTN_RIGHT, 0); + wacom_report_key(wcombo, BTN_SIDE, 0); + wacom_report_key(wcombo, BTN_EXTRA, 0); + wacom_report_abs(wcombo, ABS_THROTTLE, 0); + wacom_report_abs(wcombo, ABS_RZ, 0); + } else { + wacom_report_abs(wcombo, ABS_PRESSURE, 0); + wacom_report_abs(wcombo, ABS_TILT_X, 0); + wacom_report_abs(wcombo, ABS_TILT_Y, 0); + wacom_report_key(wcombo, BTN_STYLUS, 0); + wacom_report_key(wcombo, BTN_STYLUS2, 0); + wacom_report_key(wcombo, BTN_TOUCH, 0); + wacom_report_abs(wcombo, ABS_WHEEL, 0); + } + wacom_report_key(wcombo, wacom->tool[idx], 0); + wacom_report_abs(wcombo, ABS_MISC, 0); /* reset tool id */ + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, wacom->serial[idx]); + return 2; + } + return 0; +} + +static void wacom_intuos_general(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + unsigned int t; + + /* general pen packet */ + if ((data[1] & 0xb8) == 0xa0) { + t = (data[6] << 2) | ((data[7] >> 6) & 3); + wacom_report_abs(wcombo, ABS_PRESSURE, t); + wacom_report_abs(wcombo, ABS_TILT_X, + ((data[7] << 1) & 0x7e) | (data[8] >> 7)); + wacom_report_abs(wcombo, ABS_TILT_Y, data[8] & 0x7f); + wacom_report_key(wcombo, BTN_STYLUS, data[1] & 2); + wacom_report_key(wcombo, BTN_STYLUS2, data[1] & 4); + wacom_report_key(wcombo, BTN_TOUCH, t > 10); + } + + /* airbrush second packet */ + if ((data[1] & 0xbc) == 0xb4) { + wacom_report_abs(wcombo, ABS_WHEEL, + (data[6] << 2) | ((data[7] >> 6) & 3)); + wacom_report_abs(wcombo, ABS_TILT_X, + ((data[7] << 1) & 0x7e) | (data[8] >> 7)); + wacom_report_abs(wcombo, ABS_TILT_Y, data[8] & 0x7f); + } + return; +} + +static int wacom_intuos_irq(struct wacom_wac *wacom, void *wcombo) +{ + unsigned char *data = wacom->data; + unsigned int t; + int idx, result; + + if (data[0] != 2 && data[0] != 5 && data[0] != 6 && data[0] != 12) { + dbg("wacom_intuos_irq: received unknown report #%d", data[0]); + return 0; + } + + /* tool number */ + idx = data[1] & 0x01; + + /* pad packets. Works as a second tool and is always in prox */ + if (data[0] == 12) { + /* initiate the pad as a device */ + if (wacom->tool[1] != BTN_TOOL_FINGER) + wacom->tool[1] = BTN_TOOL_FINGER; + + wacom_report_key(wcombo, BTN_0, (data[5] & 0x01)); + wacom_report_key(wcombo, BTN_1, (data[5] & 0x02)); + wacom_report_key(wcombo, BTN_2, (data[5] & 0x04)); + wacom_report_key(wcombo, BTN_3, (data[5] & 0x08)); + wacom_report_key(wcombo, BTN_4, (data[6] & 0x01)); + wacom_report_key(wcombo, BTN_5, (data[6] & 0x02)); + wacom_report_key(wcombo, BTN_6, (data[6] & 0x04)); + wacom_report_key(wcombo, BTN_7, (data[6] & 0x08)); + wacom_report_abs(wcombo, ABS_RX, ((data[1] & 0x1f) << 8) | data[2]); + wacom_report_abs(wcombo, ABS_RY, ((data[3] & 0x1f) << 8) | data[4]); + + if((data[5] & 0x0f) | (data[6] & 0x0f) | (data[1] & 0x1f) | + data[2] | (data[3] & 0x1f) | data[4]) + wacom_report_key(wcombo, wacom->tool[1], 1); + else + wacom_report_key(wcombo, wacom->tool[1], 0); + wacom_report_abs(wcombo, ABS_MISC, PAD_DEVICE_ID); + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, 0xffffffff); + return 1; + } + + /* process in/out prox events */ + result = wacom_intuos_inout(wacom, wcombo); + if (result) + return result-1; + + /* Only large I3 and I1 & I2 support Lense Cursor */ + if((wacom->tool[idx] == BTN_TOOL_LENS) + && ((wacom->features->type == INTUOS3) + || (wacom->features->type == INTUOS3S))) + return 0; + + /* Cintiq doesn't send data when RDY bit isn't set */ + if ((wacom->features->type == CINTIQ) && !(data[1] & 0x40)) + return 0; + + if (wacom->features->type >= INTUOS3S) { + wacom_report_abs(wcombo, ABS_X, (data[2] << 9) | (data[3] << 1) | ((data[9] >> 1) & 1)); + wacom_report_abs(wcombo, ABS_Y, (data[4] << 9) | (data[5] << 1) | (data[9] & 1)); + wacom_report_abs(wcombo, ABS_DISTANCE, ((data[9] >> 2) & 0x3f)); + } else { + wacom_report_abs(wcombo, ABS_X, wacom_be16_to_cpu(&data[2])); + wacom_report_abs(wcombo, ABS_Y, wacom_be16_to_cpu(&data[4])); + wacom_report_abs(wcombo, ABS_DISTANCE, ((data[9] >> 3) & 0x1f)); + } + + /* process general packets */ + wacom_intuos_general(wacom, wcombo); + + /* 4D mouse, 2D mouse, marker pen rotation, or Lens cursor packets */ + if ((data[1] & 0xbc) == 0xa8 || (data[1] & 0xbe) == 0xb0) { + + if (data[1] & 0x02) { + /* Rotation packet */ + if (wacom->features->type >= INTUOS3S) { + /* I3 marker pen rotation reported as wheel + * due to valuator limitation + */ + t = (data[6] << 3) | ((data[7] >> 5) & 7); + t = (data[7] & 0x20) ? ((t > 900) ? ((t-1) / 2 - 1350) : + ((t-1) / 2 + 450)) : (450 - t / 2) ; + wacom_report_abs(wcombo, ABS_WHEEL, t); + } else { + /* 4D mouse rotation packet */ + t = (data[6] << 3) | ((data[7] >> 5) & 7); + wacom_report_abs(wcombo, ABS_RZ, (data[7] & 0x20) ? + ((t - 1) / 2) : -t / 2); + } + + } else if (!(data[1] & 0x10) && wacom->features->type < INTUOS3S) { + /* 4D mouse packet */ + wacom_report_key(wcombo, BTN_LEFT, data[8] & 0x01); + wacom_report_key(wcombo, BTN_MIDDLE, data[8] & 0x02); + wacom_report_key(wcombo, BTN_RIGHT, data[8] & 0x04); + + wacom_report_key(wcombo, BTN_SIDE, data[8] & 0x20); + wacom_report_key(wcombo, BTN_EXTRA, data[8] & 0x10); + t = (data[6] << 2) | ((data[7] >> 6) & 3); + wacom_report_abs(wcombo, ABS_THROTTLE, (data[8] & 0x08) ? -t : t); + + } else if (wacom->tool[idx] == BTN_TOOL_MOUSE) { + /* 2D mouse packet */ + wacom_report_key(wcombo, BTN_LEFT, data[8] & 0x04); + wacom_report_key(wcombo, BTN_MIDDLE, data[8] & 0x08); + wacom_report_key(wcombo, BTN_RIGHT, data[8] & 0x10); + wacom_report_rel(wcombo, REL_WHEEL, (data[8] & 0x01) + - ((data[8] & 0x02) >> 1)); + + /* I3 2D mouse side buttons */ + if (wacom->features->type >= INTUOS3S && wacom->features->type <= INTUOS3L) { + wacom_report_key(wcombo, BTN_SIDE, data[8] & 0x40); + wacom_report_key(wcombo, BTN_EXTRA, data[8] & 0x20); + } + + } else if (wacom->features->type < INTUOS3S || wacom->features->type == INTUOS3L) { + /* Lens cursor packets */ + wacom_report_key(wcombo, BTN_LEFT, data[8] & 0x01); + wacom_report_key(wcombo, BTN_MIDDLE, data[8] & 0x02); + wacom_report_key(wcombo, BTN_RIGHT, data[8] & 0x04); + wacom_report_key(wcombo, BTN_SIDE, data[8] & 0x10); + wacom_report_key(wcombo, BTN_EXTRA, data[8] & 0x08); + } + } + + wacom_report_abs(wcombo, ABS_MISC, wacom->id[idx]); /* report tool id */ + wacom_report_key(wcombo, wacom->tool[idx], 1); + wacom_input_event(wcombo, EV_MSC, MSC_SERIAL, wacom->serial[idx]); + return 1; +} + +int wacom_wac_irq(struct wacom_wac *wacom_wac, void *wcombo) +{ + switch (wacom_wac->features->type) { + case PENPARTNER: + return (wacom_penpartner_irq(wacom_wac, wcombo)); + break; + case PL: + return (wacom_pl_irq(wacom_wac, wcombo)); + break; + case WACOM_G4: + case GRAPHIRE: + return (wacom_graphire_irq(wacom_wac, wcombo)); + break; + case PTU: + return (wacom_ptu_irq(wacom_wac, wcombo)); + break; + case INTUOS: + case INTUOS3S: + case INTUOS3: + case INTUOS3L: + case CINTIQ: + return (wacom_intuos_irq(wacom_wac, wcombo)); + break; + default: + return 0; + } + return 0; +} + +void wacom_init_input_dev(struct input_dev *input_dev, struct wacom_wac *wacom_wac) +{ + switch (wacom_wac->features->type) { + case WACOM_G4: + input_dev_g4(input_dev, wacom_wac); + /* fall through */ + case GRAPHIRE: + input_dev_g(input_dev, wacom_wac); + break; + case INTUOS3: + case INTUOS3L: + case CINTIQ: + input_dev_i3(input_dev, wacom_wac); + /* fall through */ + case INTUOS3S: + input_dev_i3s(input_dev, wacom_wac); + case INTUOS: + input_dev_i(input_dev, wacom_wac); + break; + case PL: + case PTU: + input_dev_pl(input_dev, wacom_wac); + break; + case PENPARTNER: + input_dev_pt(input_dev, wacom_wac); + break; + } + return; +} + +static struct wacom_features wacom_features[] = { + { "Wacom Penpartner", 7, 5040, 3780, 255, 0, PENPARTNER }, + { "Wacom Graphire", 8, 10206, 7422, 511, 63, GRAPHIRE }, + { "Wacom Graphire2 4x5", 8, 10206, 7422, 511, 63, GRAPHIRE }, + { "Wacom Graphire2 5x7", 8, 13918, 10206, 511, 63, GRAPHIRE }, + { "Wacom Graphire3", 8, 10208, 7424, 511, 63, GRAPHIRE }, + { "Wacom Graphire3 6x8", 8, 16704, 12064, 511, 63, GRAPHIRE }, + { "Wacom Graphire4 4x5", 8, 10208, 7424, 511, 63, WACOM_G4 }, + { "Wacom Graphire4 6x8", 8, 16704, 12064, 511, 63, WACOM_G4 }, + { "Wacom Volito", 8, 5104, 3712, 511, 63, GRAPHIRE }, + { "Wacom PenStation2", 8, 3250, 2320, 255, 63, GRAPHIRE }, + { "Wacom Volito2 4x5", 8, 5104, 3712, 511, 63, GRAPHIRE }, + { "Wacom Volito2 2x3", 8, 3248, 2320, 511, 63, GRAPHIRE }, + { "Wacom PenPartner2", 8, 3250, 2320, 255, 63, GRAPHIRE }, + { "Wacom Intuos 4x5", 10, 12700, 10600, 1023, 31, INTUOS }, + { "Wacom Intuos 6x8", 10, 20320, 16240, 1023, 31, INTUOS }, + { "Wacom Intuos 9x12", 10, 30480, 24060, 1023, 31, INTUOS }, + { "Wacom Intuos 12x12", 10, 30480, 31680, 1023, 31, INTUOS }, + { "Wacom Intuos 12x18", 10, 45720, 31680, 1023, 31, INTUOS }, + { "Wacom PL400", 8, 5408, 4056, 255, 0, PL }, + { "Wacom PL500", 8, 6144, 4608, 255, 0, PL }, + { "Wacom PL600", 8, 6126, 4604, 255, 0, PL }, + { "Wacom PL600SX", 8, 6260, 5016, 255, 0, PL }, + { "Wacom PL550", 8, 6144, 4608, 511, 0, PL }, + { "Wacom PL800", 8, 7220, 5780, 511, 0, PL }, + { "Wacom PL700", 8, 6758, 5406, 511, 0, PL }, + { "Wacom PL510", 8, 6282, 4762, 511, 0, PL }, + { "Wacom DTU710", 8, 34080, 27660, 511, 0, PL }, + { "Wacom DTF521", 8, 6282, 4762, 511, 0, PL }, + { "Wacom DTF720", 8, 6858, 5506, 511, 0, PL }, + { "Wacom Cintiq Partner",8, 20480, 15360, 511, 0, PTU }, + { "Wacom Intuos2 4x5", 10, 12700, 10600, 1023, 31, INTUOS }, + { "Wacom Intuos2 6x8", 10, 20320, 16240, 1023, 31, INTUOS }, + { "Wacom Intuos2 9x12", 10, 30480, 24060, 1023, 31, INTUOS }, + { "Wacom Intuos2 12x12", 10, 30480, 31680, 1023, 31, INTUOS }, + { "Wacom Intuos2 12x18", 10, 45720, 31680, 1023, 31, INTUOS }, + { "Wacom Intuos3 4x5", 10, 25400, 20320, 1023, 63, INTUOS3S }, + { "Wacom Intuos3 6x8", 10, 40640, 30480, 1023, 63, INTUOS3 }, + { "Wacom Intuos3 9x12", 10, 60960, 45720, 1023, 63, INTUOS3 }, + { "Wacom Intuos3 12x12", 10, 60960, 60960, 1023, 63, INTUOS3L }, + { "Wacom Intuos3 12x19", 10, 97536, 60960, 1023, 63, INTUOS3L }, + { "Wacom Intuos3 6x11", 10, 54204, 31750, 1023, 63, INTUOS3 }, + { "Wacom Intuos3 4x6", 10, 31496, 19685, 1023, 63, INTUOS3S }, + { "Wacom Cintiq 21UX", 10, 87200, 65600, 1023, 63, CINTIQ }, + { "Wacom Intuos2 6x8", 10, 20320, 16240, 1023, 31, INTUOS }, + { } +}; + +static struct usb_device_id wacom_ids[] = { + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x00) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x10) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x11) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x12) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x13) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x14) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x15) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x16) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x60) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x61) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x62) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x63) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x64) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x20) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x21) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x22) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x23) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x24) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x30) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x31) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x32) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x33) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x34) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x35) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x37) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x38) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x39) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xC0) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xC4) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x03) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x41) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x42) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x43) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x44) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x45) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB0) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB1) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB2) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB3) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB4) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB5) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB7) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x3F) }, + { USB_DEVICE(USB_VENDOR_ID_WACOM, 0x47) }, + { } +}; + +const struct usb_device_id * get_device_table(void) { + const struct usb_device_id * id_table = wacom_ids; + return id_table; +} + +struct wacom_features * get_wacom_feature(const struct usb_device_id * id) { + int index = id - wacom_ids; + struct wacom_features *wf = &wacom_features[index]; + return wf; +} + +MODULE_DEVICE_TABLE(usb, wacom_ids); diff --git a/drivers/input/tablet/wacom_wac.h b/drivers/input/tablet/wacom_wac.h new file mode 100644 index 000000000000..a5e12e8756de --- /dev/null +++ b/drivers/input/tablet/wacom_wac.h @@ -0,0 +1,49 @@ +/* + * drivers/input/tablet/wacom_wac.h + * + * 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. + */ +#ifndef WACOM_WAC_H +#define WACOM_WAC_H + +#define STYLUS_DEVICE_ID 0x02 +#define CURSOR_DEVICE_ID 0x06 +#define ERASER_DEVICE_ID 0x0A +#define PAD_DEVICE_ID 0x0F + +enum { + PENPARTNER = 0, + GRAPHIRE, + WACOM_G4, + PTU, + PL, + INTUOS, + INTUOS3S, + INTUOS3, + INTUOS3L, + CINTIQ, + MAX_TYPE +}; + +struct wacom_features { + char *name; + int pktlen; + int x_max; + int y_max; + int pressure_max; + int distance_max; + int type; +}; + +struct wacom_wac { + unsigned char *data; + int tool[2]; + int id[2]; + __u32 serial[2]; + struct wacom_features *features; +}; + +#endif |