text:
#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);


Strong communication skills in business (and life in general) give people the edge. Leadership and teamwork require good communication for objectives to be met. Here are speakers who are renowned as experts on communication.


text:
/* ------------------------------------------------------------------
 * Copyright (C) 1998-2009 PacketVideo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 * -------------------------------------------------------------------
 */
/*
------------------------------------------------------------------------------
   PacketVideo Corp.
   MP3 Decoder Library

   Filename: pvmp3_dec_defs.h

     Date: 09/21/2007

------------------------------------------------------------------------------
 REVISION HISTORY

 Description:

------------------------------------------------------------------------------
 INCLUDE DESCRIPTION

 This include file has the mp3 decoder common defines.

------------------------------------------------------------------------------
*/

/*----------------------------------------------------------------------------
; CONTINUE ONLY IF NOT ALREADY DEFINED
----------------------------------------------------------------------------*/
#ifndef PVMP3_DEC_DEFS_H
#define PVMP3_DEC_DEFS_H

/*----------------------------------------------------------------------------
; INCLUDES
----------------------------------------------------------------------------*/
#include "pvmp3_audio_type_defs.h"
#include "pvmp3decoder_api.h"

/*----------------------------------------------------------------------------
; MACROS
; Define module specific macros here
----------------------------------------------------------------------------*/
#define module(x, POW2)   ((x)&((POW2)-1))

/*----------------------------------------------------------------------------
; DEFINES
; Include all pre-processor statements here.
----------------------------------------------------------------------------*/
#define BUFSIZE   8192   // big enough to hold 4608 bytes == biggest mp3 frame

#define CHAN           2
#define GRAN           2

#define SUBBANDS_NUMBER        32
#define FILTERBANK_BANDS       18
#define HAN_SIZE              512

/* MPEG Header Definitions - ID Bit Values */

#define MPEG_1              0
#define MPEG_2              1
#define MPEG_2_5            2
#define INVALID_VERSION   (-1)

/* MPEG Header Definitions - Mode Values */

#define MPG_MD_STEREO           0
#define MPG_MD_JOINT_STEREO     1
#define MPG_MD_DUAL_CHANNEL     2
#define MPG_MD_MONO             3

#define LEFT        0
#define RIGHT       1

#define SYNC_WORD         (int32)0x7ff
#define SYNC_WORD_LNGTH   11

/*----------------------------------------------------------------------------
; EXTERNAL VARIABLES REFERENCES
; Declare variables used in this module but defined elsewhere
----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------
; SIMPLE TYPEDEF'S
----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------
; ENUMERATED TYPEDEF'S
----------------------------------------------------------------------------*/
#ifdef __cplusplus
extern "C"
{
#endif

    /*----------------------------------------------------------------------------
    ; STRUCTURES TYPEDEF'S
    ----------------------------------------------------------------------------*/

    /* Header Information Structure */

    typedef struct
    {
        int32 version_x;
        int32 layer_description;
        int32 error_protection;
        int32 bitrate_index;
        int32 sampling_frequency;
        int32 padding;
        int32 extension;
        int32 mode;
        int32 mode_ext;
        int32 copyright;
        int32 original;
        int32 emphasis;
    } mp3Header;

    /* Layer III side information. */

    typedef  struct
    {
        uint32 part2_3_length;
        uint32 big_values;
        int32 global_gain;
        uint32 scalefac_compress;
        uint32 window_switching_flag;
        uint32 block_type;
        uint32 mixed_block_flag;
        uint32 table_select[3];
        uint32 subblock_gain[3];
        uint32 region0_count;
        uint32 region1_count;
        uint32 preflag;
        uint32 scalefac_scale;
        uint32 count1table_select;

    } granuleInfo;

    typedef  struct
    {
        uint32      scfsi[4];
        granuleInfo gran[2];

    } channelInfo;

    /* Layer III side info. */

    typedef struct
    {
        uint32      main_data_begin;
        uint32      private_bits;
        channelInfo ch[2];

    } mp3SideInfo;

    /* Layer III scale factors. */
    typedef struct
    {
        int32 l[23];            /* [cb] */
        int32 s[3][13];         /* [window][cb] */

    } mp3ScaleFactors;

#ifdef __cplusplus
}
#endif

/*----------------------------------------------------------------------------
; GLOBAL FUNCTION DEFINITIONS
; Function Prototype declaration
----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------
; END
----------------------------------------------------------------------------*/

#endif

redpajama_set_name: RedPajamaGithub


text:
Female Sex Drive
Everything you always wanted to know about the FEMALE SEX DRIVE and a little more...Sex is the spark that ignites many relationships. But that heart racing enthusiasm you first have can diminish with time - and for some reason it seems to fade more quickly in women.
From the controversial new theory that its monogamy that kills a lady's lust, to whether taking testosterone can restore it, Dr Newby investigates the science of female libido.
Duration: 27min 4sec
Broadcast: Tue 3 Nov 2015, 1:00am
Published: Tue 3 Nov 2015, 1:00am
Sex is the spark that ignites many relationships. But that heart-racing enthusiasm you first have can diminish with time, and for some reason, it seems to fade more quickly in women. So, this program is about desire - female desire. Why it comes, why it goes and what, if anything, you should do about it. From the controversial new theory it's monogamy that kills a lady's lust...
Daniel Bergner
Female desire, after a few years, seems to quite literally plummet.
..to whether taking testosterone can restore it...
Dr Rosie King
They'd cut my throat if I tried to take away their testosterone.
Dr Jonica Newby
Right, OK. Looks like I'm gonna try it.
Join me as I put my hormones on the line and investigate the science behind female libido, and what it takes to reignite a healthy sex life...
FILE FOOTAGE
Female educator: They're called the reproductive organs.
..in a culture that used to make us feel guilty if we liked sex...
Female educator: Now, we don't let anybody touch anywhere near these special parts of our body.
..and now makes us feel guilty if we don't, it's no wonder some women feel conflicted about sex. But I want to start with our biology, with a hormone we've always associated with men but which may equally influence desire in women. It is, of course, testosterone, and I'm on my way to get mine tested now because I'm in my 40s, I have a partner, and this libido question has become a real worry in my life.
Prof Susan Davis
So, first off, tell me what are your concerns and what are you hoping that I can help you with.
I just notice that I don't have the same sensitivity anymore - sexually - I don't have the same responsiveness, it's like it's gone.
OK, so what we've got now is your results from your recent blood test. So, you're really healthy, but, in fact, your testosterone now is on the very lowest limit of normal.
Right. I'm in the unusual situation that I first did a testosterone story with Professor Davis ten years ago, and my levels were taken then, so, in my case, we can see clearly what's happened over time.
Your testosterone's about 30% of what it was 10 years ago. It's declined. That's not abnormal - it's a part of reproductive ageing.
So, why does testosterone matter? Contrary to popular belief, testosterone is vital to being female. Without it, we can't fully develop into women. That's partly because we need testosterone to make oestrogen. But testosterone has vital functions all of itself. Like in men, it influences our energy levels, bone density and sexual interest.
So, if we talk about the menstrual cycle, at the middle of the cycle when the woman ovulates, testosterone levels go up, and that's why women often say that around mid-cycle, they're actually feeling more sexually aroused and interested.
Other hormones go up then too, but Professor Davis believes it's that testosterone spike that helps drive the feeling of female libido.
So, with respect to sexuality in women, testosterone has effects on the brain - sexual thoughts, fantasies, motivation. But what is often not recognised is testosterone is a blood-vessel dilator, so it, in fact, dilates blood vessels in the genital area and increases the capacity for a woman to have an orgasm, simply by blood flow.
But as I've just discovered, on average, by our 40s, we have half the testosterone we had in our 20s. In some women, it doesn't cause much in the way of symptoms. In others, it can make a big difference.
In this setting, what I would do is suggest a trial of testosterone. And the reason I say a trial is because it doesn't fix the issue for everyone.
Oh, it's a cream.
It's a cream.
OK. And what are the risk of side effects?
I think that's what most people would worry about - popping muscles and... (Deep voice) ..talking like this.
The reason I monitor your blood levels is I wanna know if, in error, you're overusing it. But if your level remains in the normal female range, you will not have side effects.
Well, it will take a few weeks to find out if it helps, so I'll let you know how I get on, in the most G-rated way, of course. It's not surprising I'm hitting a few libido speed bumps as I'm already halfway through my potential sex life. But even when we're young and just starting out, some women's sex drive never even gets into first gear. They hit a roadblock. It's called the pill.
I gather a few of you are on the pill. Is that right?
(All agree)
When did you first start thinking about taking it? How old were you?
I was 16, I think, 'cause my skin started breaking out.
Oh, right, yeah.
My mum got me on it when I was 15 'cause I had really irregular periods, so...
Oh, right, OK. Did any of you ever hear that the pill, in some women, can affect your enjoyment of sex - your libido?
I haven't heard.
No, I've never heard that.
It is in the list of side effects but easily missed.
I can't believe the doctor never mentioned it.
But it's not something I've ever really thought about. Obviously... I presume it's not been an issue.
Mustn't be.
Assoc Prof John Eden
Well, most women like their contraceptive pill and actually prefer to be on the pill than even have a natural cycle, but there's definitely a significant minority of women who completely lose their sex drive when they go on the contraceptive pill.
Completely lose it?
Completely lose it.
Really? So, this side effect - possible side effect - has been known about for a long time and still the message isn't getting out there.
Yes, but we're talking about a taboo subject here. For some young women, it's a problem right from the start, though if they've started the pill young, they may never know the difference.
Like, how would you know? Like, the girls... we don't get an erection like guys do, so we wouldn't know if we're suffering or whether we're not sexually happening, I guess, if that's the way to put it.
But, surprisingly, it can also affect women in their 40s, for whom the pill may have been fine until now, but it's suddenly a problem. As we've seen, with age testosterone declines. Professor Davis believes this means for some women the additional impost of the pill gradually becomes too much for their sex drive.
Many women would not know this, no. And I think many doctors probably don't think about it too often either, because we tend to think, 'Oh, I've taken that for years. It can't possibly affect me.' But our bodies change.
Changing pills can help, but for many, an effective alternative contraception, such as a modern, safe IUD like this, is a recommended option.
I don't think we should go round saying, 'By the way, if you go on the pill, you'll have no libido,' because the power of suggestion's amazing, and most women won't be affected. However, I think there should be a conversation and I think that if women are aware that they're not feeling as sexual as they think they should feel, they should go back and speak to their doctor about it.
Of course, there are other roadblocks to young women's desire for sex. On trouble is many don't even have a good roadmap to their own bodies. The potential problems for girls start here...
Voice-over: An egg cell leaves one of the mother's ovaries in which it has been developing.
..with sex education, and how we teach, or don't teach, female anatomy.
Cyndi Darnell
We can see this emphasis on the fallopian tubes, the uterus, the ovary, the cervix, but there's one part that's fundamentally missing.
Which is...
Which is the clitoris.
By contrast, the penis is shown in all its detailed anatomical glory. Sex therapist Cyndi Darnell is trying to rectify this by publishing the first up-to-date, anatomical atlas of arousal. Because the truth is scientists have discovered the clitoris is huge.
Now, that's a clitoris.
So, that is clitoris. So, the clitoris, a lot of folk probably don't know this, but the clitoris is somewhere between 11 and 12cm-long.
So, it is quite a large organ that spans the entire length of the vulva.
As much of this structure lies internally, it helps to see how it sits in this 3-D model. And this is a side view which shows a clitoris becoming erect.
We can see that the head has moved. So, we've gone from here, and it's moved up to here...
It's very similar to the penis.
It's really similar to the penis.
So, how many people actually know about this incredible anatomy? Well, let's find out.
So, can you tell me... what that shape is?
It looks a bit like a penis.
Penis and balls, yeah.
Penis, balls.
I think I've got one myself.
And do you recognise this shape?
Maybe a flower.
Is it the lungs?
It kind of looks like the logo for, like, a GM sort of food company. (Laughs)
I think that's the best answer yet.
It's extraordinary to think that the true size and extent of the clitoris was only discovered as recently as 15 years ago. But it's clear word is not yet getting out.
I thought I'd learnt what I was gonna learn today, but nope... (Laughs)
You never stop learning.
And if you don't understand your own anatomy, your chances of enjoyable sex are diminished.
I met a girl, 20, the other day, who confessed to me she can't have an orgasm through sex, and she felt terribly guilty.
Most women do not have orgasms through intercourse because, as we've just seen, with the clitoris and the anatomy the way it is, the vagina actually doesn't have a lot of nerve endings in it. Most of them are in the clitoris. So it's the equivalent of, um... touching a man's testicles and asking him why he hasn't had an orgasm yet.
And it's not just a lack of knowledge that can supress female libido. We women face another struggle men don't. For many of us, there seems to be a disconnect between what's happening between the legs and between the ears. In other words, between arousal and desire.
Take a look at this. This is what's called a photoplethysmograph, and it's a way of measuring arousal in the genitals. So, there's a little light here which can detect the volume of blood flowing through tissue. Without being too graphic, it's placed in the appropriate area and scientists can measure to the second what's really going on.
Professor Meredith Chivers pioneered this ground breaking research on female arousal. She found when men were asked whether they were feeling turned on while watching porn, their answers perfectly matched what the instruments were showing of their physical arousal. But the women were highly variable.
Prof Meredith Chivers
For some women, there's an almost perfect relationship between their physical response and how they feel, and for other women, there's a perfect disconnect when she's becoming more physically aroused but actually feeling turned off.
It's called nonconcordance. So what's the explanation. Well, a big one is probably our double-standard culture as we grow up.
I think for a lot of women, their physical-sexual responses are something they're not supposed to feel and be in tune with, and especially their genitals, messages have been paired with shame surrounding, for example, menstruation. And so, I think for a lot of women there's been a real barrier in terms of being able to connect with their bodies sexually.
So, you've successfully navigated the sex drive of your 20s. You've sorted your contraception and hopefully found your own roadmap to delight. It's full steam ahead. You meet a great guy, pair up, kids and...
So, ladies, how's your sex life?
Oh, my God, who has the time?!
Really? What's the problem?
(Women laugh)
Pregnancy and lactation produce hormones that supress libido. And once that's past, well, there are a few other desire-distractors...
Mum!
..tiredness, stress, time, which will resolve eventually, but for some women, unknown to them, there's another factor going on too. This is where testosterone comes back into the story. For all that we joke about finding time for sex after kids, when the issue becomes really serious. there's a culture of silence, even of shame. And if I had any doubts about difficult a topic this is for women, they were dispelled after spending three months talking to women who'd been through hell before finding a solution, not one of whom was prepared to talk openly on camera. So, I recorded an interview under strict conditions of anonymity and asked an actress to replicate the words and emotions as faithfully as she could. The woman was in her mid 30s when, despite her love for her husband, her sex drive just died.
Actress: Every now and then I'd say in my head, 'It's been six months, you gotta push yourself.' So, I would, I'd push myself to do it. But I just didn't enjoy it. I just... I felt nothing. And then... I would start to question myself, you know, I mean, 'What's wrong with me? I mean, what am I missing as person?' You know? It got to the point where I said to him that he should go and find somebody else, because I can't give him what he wants. And I don't want him to be unhappy for the rest of his life.
In many of the couples I get to talk to, it's the problems they have in intimacy, has a devastating effect on their relationship. Of course, from the male perspective too, a lot of men misinterpret their partner's loss of sex drive - or even just pain with sex - as, 'She doesn't love me anymore.'
For our young woman, things only changed when she was referred to Dr John Eden for testosterone therapy.
Actress: I felt it - I actually felt like having sex. And, you know, I felt better in myself as well, not just... I mean, I felt better as a person. And I felt like me again, finally.
When you hear a story like this, it's hard not to be compelled into believing that testosterone works. But one story is not scientific evidence. So, what is the evidence?
In the research that we've done, about 60% of women will respond and improve. And responders respond pretty well. But 20% of women experience absolutely no difference. And, in fact, about 20% of women get worse. And the reason they get worse is testosterone has nothing to do with their problem, and their problem's just perpetuating and increasing. So, if the relationship's sour, I can't fix that with hormones. But while Doctors Davis and Eden are convinced testosterone works, other experts are far more sceptical.
Prof Bronwyn Stuckey
Well, my take is it's highly overrated in women for libido. I think that there's not really an entity of testosterone-deficiency in women. If you look at the time when women's sexual function declines, it's mostly at menopause, and at that time, the sexual-function decline is not at all related to changes in testosterone - it's all related to changes in oestrogen.
Oestrogen is, of course, vitally related to sexual function, particularly through maintaining vaginal tissues and lubrication. Long-term studies have shown hormone-replacement therapy helps maintain sexual function later in life. But Professor Stuckey is less convinced by the studies on testosterone.
You know, the question is, in the controlled trials, testosterone versus placebo, was there clear evidence that testosterone improves libido in women? And I don't... I don't think there is clear evidence.
And this divide in medical opinion is one of the reason many doctors don't mention testosterone. Because the truth is testosterone for women has been deeply controversial. Back when I first reported on this ten years ago, there were fears that this would happen - that the big pharmaceutical companies would push an unnecessary and ineffective drug on to women.
Dr Ray Moynihan
I think there's a real concern that ordinary sexual lives will be portrayed as if they're somehow not good enough, that women with very ordinary fluctuations in desire, women in long-term relationships where desire clearly changes, will be portrayed, will feel as if maybe there's something wrong with them.
Another concern is the potential that testosterone could pose unknown long-term health risks. The longest trial to date lasted just three years and was run by a pharmaceutical company.
This is the debate - how much more satisfying sex is worth the risk of the potential side effects? You know, and in a sense, um, that decision may be the sort of the decision that individual women and their partners make.
But in the meantime, Professor Sue Davis has spent the last 15 years trying to address these valid concerns through ongoing research.
So, unfortunately we don't have long-term studies that are being conducted to say absolutely, categorically, black-and-white, that if a women uses testosterone, it will not change any risk. However, having said that, we have what we call observational studies that are incredibly reassuring.
We did a 20-year follow-up observational study and found there was no increased risk of breast cancer in the 20-year follow-up.
But what if women have plenty of desire, just not for their partners? Is the real problem not medical but monogamy? Well, I'm about to meet a man who argues, controversially, that long-term relationships are even more of a libido-killer for women than for men.
Neither men nor women are particularly well suited to monogamy. The idea that women are somehow better suited really is a myth - particularly a comforting myth to men, a comforting myth for society, because it casts women as a kind of stabilising force. Now, is libido for women even less well suited to monogamy? I think there's some evidence for this.
Exhibit one - he cites a German study of committed couples which found desire drops off much faster in women than men. Then there's this Australian study of menopausal women that found the way to restore their sex drive was a new relationship.
New partner trumps hormones predictably consistently, and again, it's just shifting our expectations.
But perhaps the cultural concept that angers Bergner most is that all women want is intimacy, that desire is sparked by doing the dishes, and intimacy and closeness will fuel lust.
The idea that building better communication and intimacy is going to spark lust is, I think, worse than misleading 'cause it leads to real disappointment. 'Hey, we're having these great conversations, we're doing all the exercises, but nothing's really happening for me.' But that spark, I think, depends, on a level of distance, a level of seeing one's self as other, as separate. Hence right now I live six blocks away from my long-term girlfriend and I'm hoping that will do the trick.
Well, I'm not convinced by Daniel there's enough evidence to say that monogamy is any worse for women's desire than it is for men's. But the truth is long-term relationships are a challenge for libido. So, what's the answer? Well, some chose dalliance, others to live apart, many follow serial monogamy. But what about the people who want to maintain a happy sex life with their long-term love? Well, for the last stop in our sex drive, I want to turn to one of Australia's best-known sex therapists,Dr Rosie King.
I think the difficulty is that people don't understand what happens when relationships mature, because when they come and see me, what they want is the passion and the excitement that they experienced in the early phase of their relationship. They think that that's something that should go on forever.
The trouble is something called 'limerence'. Limerence is like a smattering of magic dust over you and your new beloved. Your brain is actually awash with a potent cocktail of feel-good chemicals - dopamine, noradrenaline.
And it is a form of madness. We are obsessed with our partner, we can't keep our hands off them.
And limerence always, always fades.
Limerence has to fade otherwise we would never get anything done.
And in Rosie's experience, limerence generally fades faster in women than men, which can lead to a toxic cycle where the man starts to pursue the woman for sex.
The more he pursues, the more she distances. The more she distances, the more he pursues. Right? From time to time, he'll get really cranky. Now, her only option, what does she do? She gives mercy sex. It is the sex that you have when you really don't want to have sex. And mercy sex actually reduces libido even further.
So, how do you break the cycle and regain at least a glimmer of limerence? Well, firstly, instead of concentrating on things that enhance female libido, Rosie focuses on solving everything that blocks it. Rosie, it's a very long list.
Yeah, there's a lot of things that will turn down your libido.
Let's see. 'Fatigue, problems with sleep, prescribed medications including antidepressants, antipsychotics, chemotherapy, low testosterone.'
From the physical to the practical to the quality of affection in the relationship, Rosie addresses it all to try and bring the magic back.
And I say, 'When was the last time you had a really frank discussion about what you like during sex and what you don't like during sex?' And most couples say, 'Oh, we've never had that conversation.' I say, 'Well, I think now's a good time.'
But what if a woman has no libido left as she's got older, she decides she's had enough, she's done with sex - isn't that her right?
Yes, it is her right to decide not to have sex, however, she needs to realise there will be consequences, right? It may mean ongoing unhappiness in the relationship.
Unless he has low libido too, which would be perfect.
Then they would be a perfect match.
And so, to my little adventure in the libido stakes. It's been seven weeks since I started the testosterone treatment. How do I feel it's gone?
Look, it is subtle, but I think that the testosterone has helped me. I feel like it's given me a back a sensitivity and a responsiveness that I had lost, and that is quite a big deal for me, so I've decided that I am staying with it for now.
This is a very personal decision, and I'm aware there's still a question about long-term health risks, but no-one should even consider a controversial treatment like this without full discussion of the risks and benefits with a medical professional. So, we've navigated a range of tips for keeping the female sex drive serviced through life. You can try a hormone top-up, refresh your relationship or even trade it in. But even the best-maintained libido will start to wear with age. Still, age 70, 50% of Australian women remain sexually active, so if you're still enjoying the ride, I guess just keep on driving.
Presenter Dr Jonica Newby
Producer Dr Jonica Newby
I/V Professor Helen O'Connell talks about the anatomy of the clitoris
Tue 3 Nov 2015, 1:00am
external link The variation of sexual motivation with duration of partnership is analyzed in data from a survey of German students by Dietrich Klusmann
external link Dennerstein L, Lehert P, Burger H. The relative effects of hormones and relationship factors on sexual functioning of women through the natural menopausal transition. Fertility and Sterility 2005; 84:174-80
external link Professor Meredith Chivers, Queen's University
external link Daniel Bergner, author of What Do Women Want?
external link Ray Moynihan, Author and Researcher

redpajama_set_name: RedPajamaCommonCrawl