+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
+ };
+
+ /* 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,
+ SLAB_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->cdev.dev = &intf->dev;
+ inputdev->private = 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
+ */
+ input_register_device(aiptek->inputdev);
+
+ /* 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 -ENOMEM;
+}
+
+/* Forward declaration */
+static void aiptek_disconnect(struct usb_interface *intf);
+
+static struct usb_driver aiptek_driver = {
+ .name = "aiptek",
+ .probe = aiptek_probe,
+ .disconnect = aiptek_disconnect,
+ .id_table = aiptek_ids,
+};
+
+/***********************************************************************
+ * 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 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");
+