ChangeSet 1.1731, 2004/05/17 15:31:31-07:00, bwheadley@earthlink.net [PATCH] USB: Aiptek.c Driver patch drivers/usb/input/aiptek.c | 2500 +++++++++++++++++++++++++++++++++++++++---- drivers/usb/input/hid-core.c | 16 2 files changed, 2295 insertions(+), 221 deletions(-) diff -Nru a/drivers/usb/input/aiptek.c b/drivers/usb/input/aiptek.c --- a/drivers/usb/input/aiptek.c Mon May 17 16:37:40 2004 +++ b/drivers/usb/input/aiptek.c Mon May 17 16:37:40 2004 @@ -1,7 +1,9 @@ /* - * Native support for the Aiptek 8000U - * - * Copyright (c) 2001 Chris Atenasio + * Native support for the Aiptek HyperPen USB Tablets + * (4000U/5000U/6000U/8000U/12000U) + * + * Copyright (c) 2001 Chris Atenasio + * Copyright (c) 2002-2004 Bryan W. Headley * * based on wacom.c by * Vojtech Pavlik @@ -11,17 +13,48 @@ * James E. Blair * Daniel Egger * - * * Many thanks to Oliver Kuechemann for his support. * * ChangeLog: * v0.1 - Initial release - * v0.2 - Hack to get around fake event 28's. + * v0.2 - Hack to get around fake event 28's. (Bryan W. Headley) * v0.3 - Make URB dynamic (Bryan W. Headley, Jun-8-2002) - * (kernel 2.5.x variant, June-14-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 @@ -37,373 +70,2402 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include #include #include #include #include #include #include +#include +#include #include -#include + /* * Version Information */ -#define DRIVER_VERSION "v0.3" -#define DRIVER_AUTHOR "Chris Atenasio " -#define DRIVER_DESC "USB Aiptek 6000U/8000U tablet driver (Linux 2.5.x)" - -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); +#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 DV IR + * 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 Resolution500LPI 0 - * 0x17/0x00 FilterOn 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 command Resolution500LPI - * (2) Option Commands (GetXExtension, GetYExtension) - * (3) Send command SwitchToTablet + * (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_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 { - char *name; - int pktlen; - int x_max; - int y_max; - int pressure_min; - int pressure_max; - usb_complete_t irq; - unsigned long evbit; - unsigned long absbit; - unsigned long relbit; - unsigned long btnbit; - unsigned long digibit; + 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 */ + char manuName[64 + 1]; /* manufacturer name */ + char prodName[64 + 1]; /* product name */ +}; + +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 dev; - struct usb_device *usbdev; - struct urb *irq; - struct aiptek_features *features; - int tool; - int open; + 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 openCount; /* module use counter */ + 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 */ +}; - signed char *data; - dma_addr_t data_dma; +/* + * 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 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 }; -static void -aiptek_irq(struct urb *urb, struct pt_regs *regs) +/*********************************************************************** + * 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 occured 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 pt_regs *regs) { struct aiptek *aiptek = urb->context; unsigned char *data = aiptek->data; - struct input_dev *dev = &aiptek->dev; - int x; - int y; - int pressure; - int proximity; - int retval; + 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 */ + { + /* 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; + { + /* 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; + { + dbg("%s - nonzero urb status received: %d", + __FUNCTION__, urb->status); + goto exit; + } } - if ((data[0] & 2) == 0) { - dbg("received unknown report #%d", data[0]); + /* See if we are in a delay loop -- throw out report if true. + */ + if (aiptek->inDelay == 1 && time_after(aiptek->endDelay, jiffies)) { + goto exit; } - input_regs(dev, regs); - - proximity = data[5] & 0x01; - input_report_key(dev, BTN_TOOL_PEN, proximity); - - x = le16_to_cpu(get_unaligned((u16 *) &data[1])); - y = le16_to_cpu(get_unaligned((u16 *) &data[3])); - pressure = le16_to_cpu(*(u16 *) &data[6]); - pressure -= aiptek->features->pressure_min; + aiptek->inDelay = 0; + aiptek->eventCount++; - if (pressure < 0) { - pressure = 0; + /* 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 { + input_regs(inputdev, regs); + + 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); + } } - - if (proximity) { - 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, data[5] & 0x04); - input_report_key(dev, BTN_STYLUS, data[5] & 0x08); - input_report_key(dev, BTN_STYLUS2, data[5] & 0x10); + /* 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 { + input_regs(inputdev, regs); + + x = le16_to_cpu(get_unaligned((__u16 *) (data + 1))); + y = le16_to_cpu(get_unaligned((__u16 *) (data + 3))); + z = le16_to_cpu(get_unaligned((__u16 *) (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 { + input_regs(inputdev, regs); + x = le16_to_cpu(get_unaligned((__u16 *) (data + 1))); + y = le16_to_cpu(get_unaligned((__u16 *) (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((__u16 *) (data + 4))); + + if (dv != 0) { + input_regs(inputdev, regs); + + /* 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) { + input_regs(inputdev, regs); + + /* 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((__u16 *) (data + 1))); + input_regs(inputdev, regs); + + 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]); } - input_sync(dev); + /* 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) - err ("%s - usb_submit_urb failed with result %d", - __FUNCTION__, retval); + exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval != 0) { + err("%s - usb_submit_urb failed with result %d", + __FUNCTION__, retval); + } } -static struct aiptek_features aiptek_features[] = { - {"Aiptek 6000U/8000U", - 8, 3000, 2250, 26, 511, aiptek_irq, 0, 0, 0, 0}, - {NULL, 0} -}; - -static struct usb_device_id aiptek_ids[] = { - {USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x20), .driver_info = 0}, +/*********************************************************************** + * 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. + */ +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); -static int -aiptek_open(struct input_dev *dev) +/*********************************************************************** + * Open an instance of the tablet driver. + */ +static int aiptek_open(struct input_dev *inputdev) { - struct aiptek *aiptek = dev->private; + struct aiptek *aiptek = inputdev->private; - if (aiptek->open++) + if (aiptek->openCount++ > 0) { return 0; + } - aiptek->irq->dev = aiptek->usbdev; - if (usb_submit_urb(aiptek->irq, GFP_KERNEL)) { - aiptek->open--; + aiptek->urb->dev = aiptek->usbdev; + if (usb_submit_urb(aiptek->urb, GFP_KERNEL) != 0) { + aiptek->openCount--; return -EIO; } return 0; } -static void -aiptek_close(struct input_dev *dev) +/*********************************************************************** + * Close an instance of the tablet driver. + */ +static void aiptek_close(struct input_dev *inputdev) { - struct aiptek *aiptek = dev->private; + struct aiptek *aiptek = inputdev->private; + + if (--aiptek->openCount == 0) { + usb_unlink_urb(aiptek->urb); + } +} - if (!--aiptek->open) - usb_unlink_urb(aiptek->irq); +/*********************************************************************** + * 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, 5 * HZ); } -#define USB_REQ_SET_REPORT 0x09 static int -usb_set_report(struct usb_device *dev, struct usb_host_interface *inter, unsigned char type, - unsigned char id, void *buf, int size) +aiptek_get_report(struct aiptek *aiptek, + unsigned char report_type, + unsigned char report_id, void *buffer, int size) { - return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - USB_REQ_SET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE, - (type << 8) + id, inter->desc.bInterfaceNumber, buf, size, HZ); + 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, 5 * HZ); } +/*********************************************************************** + * Send a command to the tablet. + */ static int -aiptek_command(struct usb_device *dev, struct usb_host_interface *inter, - unsigned char command, unsigned char data) +aiptek_command(struct aiptek *aiptek, unsigned char command, unsigned char data) { + const int sizeof_buf = 3 * sizeof(u8); + int ret; u8 *buf; - int err; - - buf = kmalloc(3, GFP_KERNEL); - if (!buf) + + buf = kmalloc(sizeof_buf, GFP_KERNEL); + if (!buf) { return -ENOMEM; + } - buf[0] = 4; + buf[0] = 2; buf[1] = command; buf[2] = data; - if ((err = usb_set_report(dev, inter, 3, 2, buf, 3)) != 3) { - dbg("aiptek_command: 0x%x 0x%x\n", command, 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 err < 0 ? err : 0; + return ret < 0 ? ret : 0; } -static int -aiptek_probe(struct usb_interface *intf, - const struct usb_device_id *id) +/*********************************************************************** + * 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) { - struct usb_device *dev = interface_to_usbdev (intf); - struct usb_host_interface *interface = intf->cur_altsetting; - struct usb_endpoint_descriptor *endpoint; - struct aiptek *aiptek; - int err = -ENOMEM; + const int sizeof_buf = 3 * sizeof(u8); + int ret; + u8 *buf; - if (!(aiptek = kmalloc(sizeof (struct aiptek), GFP_KERNEL))) - goto error_out_noalloc; + buf = kmalloc(sizeof_buf, GFP_KERNEL); + if (!buf) { + return -ENOMEM; + } - memset(aiptek, 0, sizeof (struct aiptek)); + buf[0] = 2; + buf[1] = command; + buf[2] = data; - aiptek->data = usb_buffer_alloc(dev, 10, GFP_KERNEL, &aiptek->data_dma); - if (!aiptek->data) { - goto error_out_nobuf; + if (aiptek_command(aiptek, command, data) != 0) { + kfree(buf); + return -EIO; } + wait_ms(aiptek->curSetting.programmableDelay); - aiptek->irq = usb_alloc_urb(0, GFP_KERNEL); - if (!aiptek->irq) { - goto error_out_nourb; + 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((__u16 *) (buf + 1))); } + kfree(buf); + return ret; +} - /* Resolution500LPI */ - err = aiptek_command(dev, interface, 0x18, 0x04); - if (err) - goto error_out; +/*********************************************************************** + * 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; + } - /* SwitchToTablet */ - err = aiptek_command(dev, interface, 0x10, 0x01); - if (err) - goto error_out; + /* Query getModelCode */ + if ((ret = aiptek_query(aiptek, 0x02, 0x00)) < 0) { + return ret; + } + aiptek->features.modelCode = ret & 0xff; - aiptek->features = aiptek_features + id->driver_info; + /* Query getODMCode */ + if ((ret = aiptek_query(aiptek, 0x03, 0x00)) < 0) { + return ret; + } + aiptek->features.odmCode = ret; - aiptek->dev.evbit[0] |= BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_MSC) | - aiptek->features->evbit; + /* Query getFirmwareCode */ + if ((ret = aiptek_query(aiptek, 0x04, 0x00)) < 0) { + return ret; + } + aiptek->features.firmwareCode = ret; - aiptek->dev.absbit[0] |= BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE) | - BIT(ABS_MISC) | aiptek->features->absbit; + /* 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; - aiptek->dev.relbit[0] |= aiptek->features->relbit; + /* 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; - aiptek->dev.keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT) | - BIT(BTN_MIDDLE) | aiptek->features->btnbit; + /* 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; - aiptek->dev.keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_PEN) | - BIT(BTN_TOOL_MOUSE) | BIT(BTN_TOUCH) | - BIT(BTN_STYLUS) | BIT(BTN_STYLUS2) | aiptek->features->digibit; + /* 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; + } + } - aiptek->dev.mscbit[0] = BIT(MSC_SERIAL); + /* 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 - aiptek->dev.absmax[ABS_X] = aiptek->features->x_max; - aiptek->dev.absmax[ABS_Y] = aiptek->features->y_max; - aiptek->dev.absmax[ABS_PRESSURE] = aiptek->features->pressure_max - - aiptek->features->pressure_min; + /* Execute AutoGainOn */ + if ((ret = aiptek_command(aiptek, 0x12, 0xff)) < 0) { + return ret; + } - aiptek->dev.absfuzz[ABS_X] = 0; - aiptek->dev.absfuzz[ABS_Y] = 0; + /* Reset the eventCount, so we track events from last (re)programming + */ + aiptek->diagnostic = AIPTEK_DIAGNOSTIC_NA; + aiptek->eventCount = 0; - aiptek->dev.private = aiptek; - aiptek->dev.open = aiptek_open; - aiptek->dev.close = aiptek_close; + return 0; +} - aiptek->dev.name = aiptek->features->name; - aiptek->dev.id.bustype = BUS_USB; - aiptek->dev.id.vendor = dev->descriptor.idVendor; - aiptek->dev.id.product = dev->descriptor.idProduct; - aiptek->dev.id.version = dev->descriptor.bcdDevice; - aiptek->dev.dev = &intf->dev; - aiptek->usbdev = dev; +/*********************************************************************** + * 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. + */ - endpoint = &intf->altsetting[0].endpoint[0].desc; +/*********************************************************************** + * support the 'size' file -- display support + */ +static ssize_t show_tabletSize(struct device *dev, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); - if (aiptek->features->pktlen > 10) - BUG(); + if (aiptek == NULL) { + return 0; + } - usb_fill_int_urb(aiptek->irq, dev, - usb_rcvintpipe(dev, endpoint->bEndpointAddress), - aiptek->data, aiptek->features->pktlen, - aiptek->features->irq, aiptek, endpoint->bInterval); - aiptek->irq->transfer_dma = aiptek->data_dma; - aiptek->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + return snprintf(buf, PAGE_SIZE, "%dx%d\n", + aiptek->inputdev.absmax[ABS_X] + 1, + aiptek->inputdev.absmax[ABS_Y] + 1); +} - input_register_device(&aiptek->dev); +/* 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); - printk(KERN_INFO "input: %s on usb%d:%d\n", - aiptek->features->name, dev->bus->busnum, dev->devnum); +/*********************************************************************** + * support routines for the 'product_id' file + */ +static ssize_t show_tabletProductId(struct device *dev, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); - usb_set_intfdata(intf, aiptek); - return 0; + if (aiptek == NULL) { + return 0; + } -error_out: - usb_free_urb(aiptek->irq); -error_out_nourb: - usb_buffer_free(dev, 10, aiptek->data, aiptek->data_dma); -error_out_nobuf: - kfree(aiptek); -error_out_noalloc: - return err; - + return snprintf(buf, PAGE_SIZE, "0x%04x\n", + aiptek->inputdev.id.product); } -static void -aiptek_disconnect(struct usb_interface *intf) +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, char *buf) { - struct aiptek *aiptek = usb_get_intfdata (intf); + struct aiptek *aiptek = dev_get_drvdata(dev); - usb_set_intfdata(intf, NULL); - if (aiptek) { - usb_unlink_urb(aiptek->irq); - input_unregister_device(&aiptek->dev); - usb_free_urb(aiptek->irq); - usb_buffer_free(interface_to_usbdev(intf), 10, aiptek->data, aiptek->data_dma); + 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, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int retval; + + if (aiptek == NULL) { + return 0; + } + + retval = snprintf(buf, PAGE_SIZE, "%s\n", aiptek->features.manuName); + 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, char *buf) +{ + struct aiptek *aiptek = dev_get_drvdata(dev); + int retval; + + if (aiptek == NULL) { + return 0; + } + + retval = snprintf(buf, PAGE_SIZE, "%s\n", aiptek->features.prodName); + 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, 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, 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, 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, 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, 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, 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, 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, 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, 0, 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, 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, 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, 0, 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, 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, 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, 0, 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, 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, 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, 0, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0, 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, 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, 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, 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, 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, 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; + char path[64 + 1]; + 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 + }; + + /* 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; + + if ((aiptek = kmalloc(sizeof(struct aiptek), GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + memset(aiptek, 0, sizeof(struct aiptek)); + + aiptek->data = usb_buffer_alloc(usbdev, AIPTEK_PACKET_LENGTH, + SLAB_ATOMIC, &aiptek->data_dma); + if (aiptek->data == NULL) { + kfree(aiptek); + return -ENOMEM; + } + + aiptek->urb = usb_alloc_urb(0, GFP_KERNEL); + if (aiptek->urb == NULL) { + usb_buffer_free(usbdev, AIPTEK_PACKET_LENGTH, aiptek->data, + aiptek->data_dma); kfree(aiptek); + return -ENOMEM; } + + /* 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 + */ + memcpy(&aiptek->newSetting, &aiptek->curSetting, + sizeof(struct aiptek_settings)); + + /* Now program the capacities of the tablet, in terms of being + * an input device. + */ + aiptek->inputdev.evbit[0] |= BIT(EV_KEY) + | BIT(EV_ABS) + | BIT(EV_REL) + | BIT(EV_MSC); + + aiptek->inputdev.absbit[0] |= + (BIT(ABS_X) | + BIT(ABS_Y) | + BIT(ABS_PRESSURE) | + BIT(ABS_TILT_X) | + BIT(ABS_TILT_Y) | BIT(ABS_WHEEL) | BIT(ABS_MISC)); + + aiptek->inputdev.relbit[0] |= + (BIT(REL_X) | BIT(REL_Y) | BIT(REL_WHEEL) | BIT(REL_MISC)); + + aiptek->inputdev.keybit[LONG(BTN_LEFT)] |= + (BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE)); + + aiptek->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)); + + aiptek->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 < sizeof(macroKeyEvents) / sizeof(macroKeyEvents[0]); ++i) { + set_bit(macroKeyEvents[i], aiptek->inputdev.keybit); + } + + /* Set up client data, pointers to open and close routines + * for the input device. + */ + aiptek->inputdev.private = aiptek; + aiptek->inputdev.open = aiptek_open; + aiptek->inputdev.close = aiptek_close; + + /* 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... + */ + if (usb_make_path(usbdev, path, 64) > 0) { + sprintf(aiptek->features.usbPath, "%s/input0", path); + } + + /* 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. + */ + aiptek->inputdev.absmin[ABS_X] = 0; + aiptek->inputdev.absmax[ABS_X] = 2999; + aiptek->inputdev.absmin[ABS_Y] = 0; + aiptek->inputdev.absmax[ABS_Y] = 2249; + aiptek->inputdev.absmin[ABS_PRESSURE] = 0; + aiptek->inputdev.absmax[ABS_PRESSURE] = 511; + aiptek->inputdev.absmin[ABS_TILT_X] = AIPTEK_TILT_MIN; + aiptek->inputdev.absmax[ABS_TILT_X] = AIPTEK_TILT_MAX; + aiptek->inputdev.absmin[ABS_TILT_Y] = AIPTEK_TILT_MIN; + aiptek->inputdev.absmax[ABS_TILT_Y] = AIPTEK_TILT_MAX; + aiptek->inputdev.absmin[ABS_WHEEL] = AIPTEK_WHEEL_MIN; + aiptek->inputdev.absmax[ABS_WHEEL] = AIPTEK_WHEEL_MAX - 1; + aiptek->inputdev.absfuzz[ABS_X] = 0; + aiptek->inputdev.absfuzz[ABS_Y] = 0; + aiptek->inputdev.absfuzz[ABS_PRESSURE] = 0; + aiptek->inputdev.absfuzz[ABS_TILT_X] = 0; + aiptek->inputdev.absfuzz[ABS_TILT_Y] = 0; + aiptek->inputdev.absfuzz[ABS_WHEEL] = 0; + aiptek->inputdev.absflat[ABS_X] = 0; + aiptek->inputdev.absflat[ABS_Y] = 0; + aiptek->inputdev.absflat[ABS_PRESSURE] = 0; + aiptek->inputdev.absflat[ABS_TILT_X] = 0; + aiptek->inputdev.absflat[ABS_TILT_Y] = 0; + aiptek->inputdev.absflat[ABS_WHEEL] = 0; + aiptek->inputdev.name = "Aiptek"; + aiptek->inputdev.phys = aiptek->features.usbPath; + aiptek->inputdev.id.bustype = BUS_USB; + aiptek->inputdev.id.vendor = usbdev->descriptor.idVendor; + aiptek->inputdev.id.product = usbdev->descriptor.idProduct; + aiptek->inputdev.id.version = usbdev->descriptor.bcdDevice; + + aiptek->usbdev = usbdev; + aiptek->ifnum = intf->altsetting[0].desc.bInterfaceNumber; + aiptek->inDelay = 0; + aiptek->endDelay = 0; + aiptek->previousJitterable = 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; + + /* Register the tablet as an Input Device + */ + input_register_device(&aiptek->inputdev); + + /* Go and decode the USB representation of the tablet's manufacturer + * name and product name. They only change once every hotplug event, + * which is why we put it here instead of in the sysfs interface. + */ + usb_string(usbdev, + usbdev->descriptor.iManufacturer, + aiptek->features.manuName, + sizeof(aiptek->features.manuName)); + usb_string(usbdev, + usbdev->descriptor.iProduct, + aiptek->features.prodName, + sizeof(aiptek->features.prodName)); + + /* 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 /{devfs}/input/eventN. + */ + inputdev = &aiptek->inputdev; + 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; + } + } + + info("input: Aiptek on %s (%s)\n", path, aiptek->features.inputPath); + + /* 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 < sizeof(speeds) / sizeof(speeds[0]); ++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; + } + } + + /* 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; } +/* Forward declaration */ +static void aiptek_disconnect(struct usb_interface *intf); + static struct usb_driver aiptek_driver = { - .owner = THIS_MODULE, - .name = "aiptek", - .probe = aiptek_probe, - .disconnect = aiptek_disconnect, - .id_table = aiptek_ids, + .owner = THIS_MODULE, + .name = "aiptek", + .probe = aiptek_probe, + .disconnect = aiptek_disconnect, + .id_table = aiptek_ids, }; -static int __init -aiptek_init(void) +/*********************************************************************** + * 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_unlink_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); + aiptek = NULL; + } +} + +static int __init aiptek_init(void) { int result = usb_register(&aiptek_driver); if (result == 0) { - info(DRIVER_VERSION " " DRIVER_AUTHOR); + info(DRIVER_VERSION ": " DRIVER_AUTHOR); info(DRIVER_DESC); } return result; } -static void __exit -aiptek_exit(void) +static void __exit aiptek_exit(void) { usb_deregister(&aiptek_driver); } + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +MODULE_PARM(programmableDelay, "i"); +MODULE_PARM_DESC(programmableDelay, "delay used during tablet programming"); +MODULE_PARM(jitterDelay, "i"); +MODULE_PARM_DESC(jitterDelay, "stylus/mouse settlement delay"); module_init(aiptek_init); module_exit(aiptek_exit); diff -Nru a/drivers/usb/input/hid-core.c b/drivers/usb/input/hid-core.c --- a/drivers/usb/input/hid-core.c Mon May 17 16:37:40 2004 +++ b/drivers/usb/input/hid-core.c Mon May 17 16:37:40 2004 @@ -1359,7 +1359,13 @@ #define USB_DEVICE_ID_KBGEAR_JAMSTUDIO 0x1001 #define USB_VENDOR_ID_AIPTEK 0x08ca -#define USB_DEVICE_ID_AIPTEK_6000 0x0020 +#define USB_DEVICE_ID_AIPTEK_01 0x0001 +#define USB_DEVICE_ID_AIPTEK_10 0x0010 +#define USB_DEVICE_ID_AIPTEK_20 0x0020 +#define USB_DEVICE_ID_AIPTEK_21 0x0021 +#define USB_DEVICE_ID_AIPTEK_22 0x0022 +#define USB_DEVICE_ID_AIPTEK_23 0x0023 +#define USB_DEVICE_ID_AIPTEK_24 0x0024 #define USB_VENDOR_ID_GRIFFIN 0x077d #define USB_DEVICE_ID_POWERMATE 0x0410 @@ -1428,7 +1434,13 @@ unsigned quirks; } hid_blacklist[] = { - { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_6000, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_01, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_10, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_20, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_21, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_22, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_23, HID_QUIRK_IGNORE }, + { USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_24, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_BERKSHIRE, USB_DEVICE_ID_BERKSHIRE_PCWD, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_KBGEAR, USB_DEVICE_ID_KBGEAR_JAMSTUDIO, HID_QUIRK_IGNORE },