From 29cf28ae8dc244f89e213dc198f2286659d521b5 Mon Sep 17 00:00:00 2001 From: Rafi Rubin Date: Thu, 26 Aug 2010 00:54:54 -0400 Subject: [PATCH 01/22] HID: ntrig: add documention The doctumentation includes a brief introduction to the driver and explanations of the filtering parameters as well as a discussion of the need for and working of the filters. Signed-off-by: Rafi Rubin Signed-off-by: Jiri Kosina --- Documentation/input/ntrig.txt | 126 ++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 Documentation/input/ntrig.txt diff --git a/Documentation/input/ntrig.txt b/Documentation/input/ntrig.txt new file mode 100644 index 000000000000..be1fd981f73f --- /dev/null +++ b/Documentation/input/ntrig.txt @@ -0,0 +1,126 @@ +N-Trig touchscreen Driver +------------------------- + Copyright (c) 2008-2010 Rafi Rubin + Copyright (c) 2009-2010 Stephane Chatty + +This driver provides support for N-Trig pen and multi-touch sensors. Single +and multi-touch events are translated to the appropriate protocols for +the hid and input systems. Pen events are sufficiently hid compliant and +are left to the hid core. The driver also provides additional filtering +and utility functions accessible with sysfs and module parameters. + +This driver has been reported to work properly with multiple N-Trig devices +attached. + + +Parameters +---------- + +Note: values set at load time are global and will apply to all applicable +devices. Adjusting parameters with sysfs will override the load time values, +but only for that one device. + +The following parameters are used to configure filters to reduce noise: + +activate_slack number of fingers to ignore before processing events + +activation_height size threshold to activate immediately +activation_width + +min_height size threshold bellow which fingers are ignored +min_width both to decide activation and during activity + +deactivate_slack the number of "no contact" frames to ignore before + propagating the end of activity events + +When the last finger is removed from the device, it sends a number of empty +frames. By holding off on deactivation for a few frames we can tolerate false +erroneous disconnects, where the sensor may mistakenly not detect a finger that +is still present. Thus deactivate_slack addresses problems where a users might +see breaks in lines during drawing, or drop an object during a long drag. + + +Additional sysfs items +---------------------- + +These nodes just provide easy access to the ranges reported by the device. +sensor_logical_height the range for positions reported during activity +sensor_logical_width + +sensor_physical_height internal ranges not used for normal events but +sensor_physical_width useful for tuning + +All N-Trig devices with product id of 1 report events in the ranges of +X: 0-9600 +Y: 0-7200 +However not all of these devices have the same physical dimensions. Most +seem to be 12" sensors (Dell Latitude XT and XT2 and the HP TX2), and +at least one model (Dell Studio 17) has a 17" sensor. The ratio of physical +to logical sizes is used to adjust the size based filter parameters. + + +Filtering +--------- + +With the release of the early multi-touch firmwares it became increasingly +obvious that these sensors were prone to erroneous events. Users reported +seeing both inappropriately dropped contact and ghosts, contacts reported +where no finger was actually touching the screen. + +Deactivation slack helps prevent dropped contact for single touch use, but does +not address the problem of dropping one of more contacts while other contacts +are still active. Drops in the multi-touch context require additional +processing and should be handled in tandem with tacking. + +As observed ghost contacts are similar to actual use of the sensor, but they +seem to have different profiles. Ghost activity typically shows up as small +short lived touches. As such, I assume that the longer the continuous stream +of events the more likely those events are from a real contact, and that the +larger the size of each contact the more likely it is real. Balancing the +goals of preventing ghosts and accepting real events quickly (to minimize +user observable latency), the filter accumulates confidence for incoming +events until it hits thresholds and begins propagating. In the interest in +minimizing stored state as well as the cost of operations to make a decision, +I've kept that decision simple. + +Time is measured in terms of the number of fingers reported, not frames since +the probability of multiple simultaneous ghosts is expected to drop off +dramatically with increasing numbers. Rather than accumulate weight as a +function of size, I just use it as a binary threshold. A sufficiently large +contact immediately overrides the waiting period and leads to activation. + +Setting the activation size thresholds to large values will result in deciding +primarily on activation slack. If you see longer lived ghosts, turning up the +activation slack while reducing the size thresholds may suffice to eliminate +the ghosts while keeping the screen quite responsive to firm taps. + +Contacts continue to be filtered with min_height and min_width even after +the initial activation filter is satisfied. The intent is to provide +a mechanism for filtering out ghosts in the form of an extra finger while +you actually are using the screen. In practice this sort of ghost has +been far less problematic or relatively rare and I've left the defaults +set to 0 for both parameters, effectively turning off that filter. + +I don't know what the optimal values are for these filters. If the defaults +don't work for you, please play with the parameters. If you do find other +values more comfortable, I would appreciate feedback. + +The calibration of these devices does drift over time. If ghosts or contact +dropping worsen and interfere with the normal usage of your device, try +recalibrating it. + + +Calibration +----------- + +The N-Trig windows tools provide calibration and testing routines. Also an +unofficial unsupported set of user space tools including a calibrator is +available at: +http://code.launchpad.net/~rafi-seas/+junk/ntrig_calib + + +Tracking +-------- + +As of yet, all tested N-Trig firmwares do not track fingers. When multiple +contacts are active they seem to be sorted primarily by Y position. From a52dc34c0eea991115a9d789e9461f06768fa360 Mon Sep 17 00:00:00 2001 From: Rafi Rubin Date: Thu, 26 Aug 2010 00:54:55 -0400 Subject: [PATCH 02/22] HID: ntrig: a bit of whitespace cleanup Signed-off-by: Rafi Rubin Signed-off-by: Jiri Kosina --- drivers/hid/hid-ntrig.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c index fb69b8c4953f..43e95dee9290 100644 --- a/drivers/hid/hid-ntrig.c +++ b/drivers/hid/hid-ntrig.c @@ -377,8 +377,8 @@ static struct attribute_group ntrig_attribute_group = { */ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi, - struct hid_field *field, struct hid_usage *usage, - unsigned long **bit, int *max) + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) { struct ntrig_data *nd = hid_get_drvdata(hdev); @@ -448,13 +448,13 @@ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi, /* width/height mapped on TouchMajor/TouchMinor/Orientation */ case HID_DG_WIDTH: hid_map_usage(hi, usage, bit, max, - EV_ABS, ABS_MT_TOUCH_MAJOR); + EV_ABS, ABS_MT_TOUCH_MAJOR); return 1; case HID_DG_HEIGHT: hid_map_usage(hi, usage, bit, max, - EV_ABS, ABS_MT_TOUCH_MINOR); + EV_ABS, ABS_MT_TOUCH_MINOR); input_set_abs_params(hi->input, ABS_MT_ORIENTATION, - 0, 1, 0, 0); + 0, 1, 0, 0); return 1; } return 0; @@ -468,8 +468,8 @@ static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi, } static int ntrig_input_mapped(struct hid_device *hdev, struct hid_input *hi, - struct hid_field *field, struct hid_usage *usage, - unsigned long **bit, int *max) + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) { /* No special mappings needed for the pen and single touch */ if (field->physical) @@ -489,7 +489,7 @@ static int ntrig_input_mapped(struct hid_device *hdev, struct hid_input *hi, * and call input_mt_sync after each point if necessary */ static int ntrig_event (struct hid_device *hid, struct hid_field *field, - struct hid_usage *usage, __s32 value) + struct hid_usage *usage, __s32 value) { struct input_dev *input = field->hidinput->input; struct ntrig_data *nd = hid_get_drvdata(hid); @@ -860,7 +860,7 @@ err_free: static void ntrig_remove(struct hid_device *hdev) { sysfs_remove_group(&hdev->dev.kobj, - &ntrig_attribute_group); + &ntrig_attribute_group); hid_hw_stop(hdev); kfree(hid_get_drvdata(hdev)); } From cb7cf3da0daa9830e00640da8f7d2380f4b4de42 Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Sun, 29 Aug 2010 12:30:18 +0200 Subject: [PATCH 03/22] HID: roccat: add driver for Roccat Pyra mouse This patch add support for Pyra mobile gaming mouse from Roccat. It provides access to profiles, settings, actual settings etc. through sysfs attributes. This driver is conceptual similar to the existing Kone driver. Userland tools can soon be found at http://sourceforge.net/projects/roccat Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina --- .../ABI/testing/sysfs-driver-hid-roccat-pyra | 98 ++ drivers/hid/Kconfig | 7 + drivers/hid/Makefile | 1 + drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 2 + drivers/hid/hid-roccat-pyra.c | 964 ++++++++++++++++++ drivers/hid/hid-roccat-pyra.h | 186 ++++ 7 files changed, 1259 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-roccat-pyra create mode 100644 drivers/hid/hid-roccat-pyra.c create mode 100644 drivers/hid/hid-roccat-pyra.h diff --git a/Documentation/ABI/testing/sysfs-driver-hid-roccat-pyra b/Documentation/ABI/testing/sysfs-driver-hid-roccat-pyra new file mode 100644 index 000000000000..ad1125b02ff4 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-roccat-pyra @@ -0,0 +1,98 @@ +What: /sys/bus/usb/devices/-:./actual_cpi +Date: August 2010 +Contact: Stefan Achatz +Description: It is possible to switch the cpi setting of the mouse with the + press of a button. + When read, this file returns the raw number of the actual cpi + setting reported by the mouse. This number has to be further + processed to receive the real dpi value. + + VALUE DPI + 1 400 + 2 800 + 4 1600 + + This file is readonly. + +What: /sys/bus/usb/devices/-:./actual_profile +Date: August 2010 +Contact: Stefan Achatz +Description: When read, this file returns the number of the actual profile in + range 0-4. + This file is readonly. + +What: /sys/bus/usb/devices/-:./firmware_version +Date: August 2010 +Contact: Stefan Achatz +Description: When read, this file returns the raw integer version number of the + firmware reported by the mouse. Using the integer value eases + further usage in other programs. To receive the real version + number the decimal point has to be shifted 2 positions to the + left. E.g. a returned value of 138 means 1.38 + This file is readonly. + +What: /sys/bus/usb/devices/-:./profile_settings +Date: August 2010 +Contact: Stefan Achatz +Description: The mouse can store 5 profiles which can be switched by the + press of a button. A profile is split in settings and buttons. + profile_settings holds informations like resolution, sensitivity + and light effects. + When written, this file lets one write the respective profile + settings back to the mouse. The data has to be 13 bytes long. + The mouse will reject invalid data. + Which profile to write is determined by the profile number + contained in the data. + This file is writeonly. + +What: /sys/bus/usb/devices/-:./profile[1-5]_settings +Date: August 2010 +Contact: Stefan Achatz +Description: The mouse can store 5 profiles which can be switched by the + press of a button. A profile is split in settings and buttons. + profile_settings holds informations like resolution, sensitivity + and light effects. + When read, these files return the respective profile settings. + The returned data is 13 bytes in size. + This file is readonly. + +What: /sys/bus/usb/devices/-:./profile_buttons +Date: August 2010 +Contact: Stefan Achatz +Description: The mouse can store 5 profiles which can be switched by the + press of a button. A profile is split in settings and buttons. + profile_buttons holds informations about button layout. + When written, this file lets one write the respective profile + buttons back to the mouse. The data has to be 19 bytes long. + The mouse will reject invalid data. + Which profile to write is determined by the profile number + contained in the data. + This file is writeonly. + +What: /sys/bus/usb/devices/-:./profile[1-5]_buttons +Date: August 2010 +Contact: Stefan Achatz +Description: The mouse can store 5 profiles which can be switched by the + press of a button. A profile is split in settings and buttons. + profile_buttons holds informations about button layout. + When read, these files return the respective profile buttons. + The returned data is 19 bytes in size. + This file is readonly. + +What: /sys/bus/usb/devices/-:./startup_profile +Date: August 2010 +Contact: Stefan Achatz +Description: The integer value of this attribute ranges from 0-4. + When read, this attribute returns the number of the profile + that's active when the mouse is powered on. + This file is readonly. + +What: /sys/bus/usb/devices/-:./settings +Date: August 2010 +Contact: Stefan Achatz +Description: When read, this file returns the settings stored in the mouse. + The size of the data is 3 bytes and holds information on the + startup_profile. + When written, this file lets write settings back to the mouse. + The data has to be 3 bytes long. The mouse will reject invalid + data. diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 6369ba7f96f8..b07440a172b5 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -376,6 +376,13 @@ config HID_ROCCAT_KONE ---help--- Support for Roccat Kone mouse. +config HID_ROCCAT_PYRA + tristate "Roccat Pyra mouse support" + depends on USB_HID + select HID_ROCCAT + ---help--- + Support for Roccat Pyra mouse. + config HID_SAMSUNG tristate "Samsung" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 46f037f3df80..d224fa342187 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_HID_PETALYNX) += hid-petalynx.o obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.o obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o obj-$(CONFIG_HID_ROCCAT_KONE) += hid-roccat-kone.o +obj-$(CONFIG_HID_ROCCAT_PYRA) += hid-roccat-pyra.o obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o obj-$(CONFIG_HID_SONY) += hid-sony.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 0c52899be964..18608b89e76b 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1367,6 +1367,7 @@ static const struct hid_device_id hid_blacklist[] = { { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH) }, { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN) }, { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRED) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 85c6d13c9ffa..5787b1a1339f 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -459,6 +459,8 @@ #define USB_VENDOR_ID_ROCCAT 0x1e7d #define USB_DEVICE_ID_ROCCAT_KONE 0x2ced +#define USB_DEVICE_ID_ROCCAT_PYRA_WIRED 0x2c24 +#define USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS 0x2cf6 #define USB_VENDOR_ID_SAITEK 0x06a3 #define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17 diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c new file mode 100644 index 000000000000..6c09c15d93ed --- /dev/null +++ b/drivers/hid/hid-roccat-pyra.c @@ -0,0 +1,964 @@ +/* + * Roccat Pyra driver for Linux + * + * Copyright (c) 2010 Stefan Achatz + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/* + * Roccat Pyra is a mobile gamer mouse which comes in wired and wireless + * variant. Wireless variant is not tested. + * Userland tools can be found at http://sourceforge.net/projects/roccat + */ + +#include +#include +#include +#include +#include +#include +#include "hid-ids.h" +#include "hid-roccat.h" +#include "hid-roccat-pyra.h" + +static void profile_activated(struct pyra_device *pyra, + unsigned int new_profile) +{ + pyra->actual_profile = new_profile; + pyra->actual_cpi = pyra->profile_settings[pyra->actual_profile].y_cpi; +} + +static int pyra_send_control(struct usb_device *usb_dev, int value, + enum pyra_control_requests request) +{ + int len; + struct pyra_control control; + + if ((request == PYRA_CONTROL_REQUEST_PROFILE_SETTINGS || + request == PYRA_CONTROL_REQUEST_PROFILE_BUTTONS) && + (value < 0 || value > 4)) + return -EINVAL; + + control.command = PYRA_COMMAND_CONTROL; + control.value = value; + control.request = request; + + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_CONFIGURATION, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + PYRA_USB_COMMAND_CONTROL, 0, (char *)&control, + sizeof(struct pyra_control), + USB_CTRL_SET_TIMEOUT); + + if (len != sizeof(struct pyra_control)) + return len; + + return 0; +} + +static int pyra_receive_control_status(struct usb_device *usb_dev) +{ + int len; + struct pyra_control control; + + do { + msleep(10); + + len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_IN, + PYRA_USB_COMMAND_CONTROL, 0, (char *)&control, + sizeof(struct pyra_control), + USB_CTRL_SET_TIMEOUT); + + /* requested too early, try again */ + } while (len == -EPROTO); + + if (len == sizeof(struct pyra_control) && + control.command == PYRA_COMMAND_CONTROL && + control.request == PYRA_CONTROL_REQUEST_STATUS && + control.value == 1) + return 0; + else { + dev_err(&usb_dev->dev, "receive control status: " + "unknown response 0x%x 0x%x\n", + control.request, control.value); + return -EINVAL; + } +} + +static int pyra_get_profile_settings(struct usb_device *usb_dev, + struct pyra_profile_settings *buf, int number) +{ + int retval; + + retval = pyra_send_control(usb_dev, number, + PYRA_CONTROL_REQUEST_PROFILE_SETTINGS); + + if (retval) + return retval; + + retval = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + PYRA_USB_COMMAND_PROFILE_SETTINGS, 0, (char *)buf, + sizeof(struct pyra_profile_settings), + USB_CTRL_SET_TIMEOUT); + + if (retval != sizeof(struct pyra_profile_settings)) + return retval; + + return 0; +} + +static int pyra_get_profile_buttons(struct usb_device *usb_dev, + struct pyra_profile_buttons *buf, int number) +{ + int retval; + + retval = pyra_send_control(usb_dev, number, + PYRA_CONTROL_REQUEST_PROFILE_BUTTONS); + + if (retval) + return retval; + + retval = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + PYRA_USB_COMMAND_PROFILE_BUTTONS, 0, (char *)buf, + sizeof(struct pyra_profile_buttons), + USB_CTRL_SET_TIMEOUT); + + if (retval != sizeof(struct pyra_profile_buttons)) + return retval; + + return 0; +} + +static int pyra_get_settings(struct usb_device *usb_dev, + struct pyra_settings *buf) +{ + int len; + len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + PYRA_USB_COMMAND_SETTINGS, 0, buf, + sizeof(struct pyra_settings), USB_CTRL_SET_TIMEOUT); + if (len != sizeof(struct pyra_settings)) + return -EIO; + return 0; +} + +static int pyra_get_info(struct usb_device *usb_dev, struct pyra_info *buf) +{ + int len; + len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + PYRA_USB_COMMAND_INFO, 0, buf, + sizeof(struct pyra_info), USB_CTRL_SET_TIMEOUT); + if (len != sizeof(struct pyra_info)) + return -EIO; + return 0; +} + +static int pyra_set_profile_settings(struct usb_device *usb_dev, + struct pyra_profile_settings const *settings) +{ + int len; + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_CONFIGURATION, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + PYRA_USB_COMMAND_PROFILE_SETTINGS, 0, (char *)settings, + sizeof(struct pyra_profile_settings), + USB_CTRL_SET_TIMEOUT); + if (len != sizeof(struct pyra_profile_settings)) + return -EIO; + if (pyra_receive_control_status(usb_dev)) + return -EIO; + return 0; +} + +static int pyra_set_profile_buttons(struct usb_device *usb_dev, + struct pyra_profile_buttons const *buttons) +{ + int len; + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_CONFIGURATION, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + PYRA_USB_COMMAND_PROFILE_BUTTONS, 0, (char *)buttons, + sizeof(struct pyra_profile_buttons), + USB_CTRL_SET_TIMEOUT); + if (len != sizeof(struct pyra_profile_buttons)) + return -EIO; + if (pyra_receive_control_status(usb_dev)) + return -EIO; + return 0; +} + +static int pyra_set_settings(struct usb_device *usb_dev, + struct pyra_settings const *settings) +{ + int len; + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_CONFIGURATION, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + PYRA_USB_COMMAND_SETTINGS, 0, (char *)settings, + sizeof(struct pyra_settings), USB_CTRL_SET_TIMEOUT); + if (len != sizeof(struct pyra_settings)) + return -EIO; + if (pyra_receive_control_status(usb_dev)) + return -EIO; + return 0; +} + +static ssize_t pyra_sysfs_read_profilex_settings(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count, int number) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct pyra_profile_settings)) + return 0; + + if (off + count > sizeof(struct pyra_profile_settings)) + count = sizeof(struct pyra_profile_settings) - off; + + mutex_lock(&pyra->pyra_lock); + memcpy(buf, ((char const *)&pyra->profile_settings[number]) + off, + count); + mutex_unlock(&pyra->pyra_lock); + + return count; +} + +static ssize_t pyra_sysfs_read_profile1_settings(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pyra_sysfs_read_profilex_settings(fp, kobj, + attr, buf, off, count, 0); +} + +static ssize_t pyra_sysfs_read_profile2_settings(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pyra_sysfs_read_profilex_settings(fp, kobj, + attr, buf, off, count, 1); +} + +static ssize_t pyra_sysfs_read_profile3_settings(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pyra_sysfs_read_profilex_settings(fp, kobj, + attr, buf, off, count, 2); +} + +static ssize_t pyra_sysfs_read_profile4_settings(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pyra_sysfs_read_profilex_settings(fp, kobj, + attr, buf, off, count, 3); +} + +static ssize_t pyra_sysfs_read_profile5_settings(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pyra_sysfs_read_profilex_settings(fp, kobj, + attr, buf, off, count, 4); +} + +static ssize_t pyra_sysfs_read_profilex_buttons(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count, int number) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct pyra_profile_buttons)) + return 0; + + if (off + count > sizeof(struct pyra_profile_buttons)) + count = sizeof(struct pyra_profile_buttons) - off; + + mutex_lock(&pyra->pyra_lock); + memcpy(buf, ((char const *)&pyra->profile_buttons[number]) + off, + count); + mutex_unlock(&pyra->pyra_lock); + + return count; +} + +static ssize_t pyra_sysfs_read_profile1_buttons(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pyra_sysfs_read_profilex_buttons(fp, kobj, + attr, buf, off, count, 0); +} + +static ssize_t pyra_sysfs_read_profile2_buttons(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pyra_sysfs_read_profilex_buttons(fp, kobj, + attr, buf, off, count, 1); +} + +static ssize_t pyra_sysfs_read_profile3_buttons(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pyra_sysfs_read_profilex_buttons(fp, kobj, + attr, buf, off, count, 2); +} + +static ssize_t pyra_sysfs_read_profile4_buttons(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pyra_sysfs_read_profilex_buttons(fp, kobj, + attr, buf, off, count, 3); +} + +static ssize_t pyra_sysfs_read_profile5_buttons(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return pyra_sysfs_read_profilex_buttons(fp, kobj, + attr, buf, off, count, 4); +} + +static ssize_t pyra_sysfs_write_profile_settings(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval = 0; + int difference; + int profile_number; + struct pyra_profile_settings *profile_settings; + + if (off != 0 || count != sizeof(struct pyra_profile_settings)) + return -EINVAL; + + profile_number = ((struct pyra_profile_settings const *)buf)->number; + profile_settings = &pyra->profile_settings[profile_number]; + + mutex_lock(&pyra->pyra_lock); + difference = memcmp(buf, profile_settings, + sizeof(struct pyra_profile_settings)); + if (difference) { + retval = pyra_set_profile_settings(usb_dev, + (struct pyra_profile_settings const *)buf); + if (!retval) + memcpy(profile_settings, buf, + sizeof(struct pyra_profile_settings)); + } + mutex_unlock(&pyra->pyra_lock); + + if (retval) + return retval; + + return sizeof(struct pyra_profile_settings); +} + +static ssize_t pyra_sysfs_write_profile_buttons(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval = 0; + int difference; + int profile_number; + struct pyra_profile_buttons *profile_buttons; + + if (off != 0 || count != sizeof(struct pyra_profile_buttons)) + return -EINVAL; + + profile_number = ((struct pyra_profile_buttons const *)buf)->number; + profile_buttons = &pyra->profile_buttons[profile_number]; + + mutex_lock(&pyra->pyra_lock); + difference = memcmp(buf, profile_buttons, + sizeof(struct pyra_profile_buttons)); + if (difference) { + retval = pyra_set_profile_buttons(usb_dev, + (struct pyra_profile_buttons const *)buf); + if (!retval) + memcpy(profile_buttons, buf, + sizeof(struct pyra_profile_buttons)); + } + mutex_unlock(&pyra->pyra_lock); + + if (retval) + return retval; + + return sizeof(struct pyra_profile_buttons); +} + +static ssize_t pyra_sysfs_read_settings(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct pyra_settings)) + return 0; + + if (off + count > sizeof(struct pyra_settings)) + count = sizeof(struct pyra_settings) - off; + + mutex_lock(&pyra->pyra_lock); + memcpy(buf, ((char const *)&pyra->settings) + off, count); + mutex_unlock(&pyra->pyra_lock); + + return count; +} + +static ssize_t pyra_sysfs_write_settings(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval = 0; + int difference; + + if (off != 0 || count != sizeof(struct pyra_settings)) + return -EINVAL; + + mutex_lock(&pyra->pyra_lock); + difference = memcmp(buf, &pyra->settings, sizeof(struct pyra_settings)); + if (difference) { + retval = pyra_set_settings(usb_dev, + (struct pyra_settings const *)buf); + if (!retval) + memcpy(&pyra->settings, buf, + sizeof(struct pyra_settings)); + } + mutex_unlock(&pyra->pyra_lock); + + if (retval) + return retval; + + profile_activated(pyra, pyra->settings.startup_profile); + + return sizeof(struct pyra_settings); +} + + +static ssize_t pyra_sysfs_show_actual_cpi(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + return snprintf(buf, PAGE_SIZE, "%d\n", pyra->actual_cpi); +} + +static ssize_t pyra_sysfs_show_actual_profile(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + return snprintf(buf, PAGE_SIZE, "%d\n", pyra->actual_profile); +} + +static ssize_t pyra_sysfs_show_firmware_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + return snprintf(buf, PAGE_SIZE, "%d\n", pyra->firmware_version); +} + +static ssize_t pyra_sysfs_show_startup_profile(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev)); + return snprintf(buf, PAGE_SIZE, "%d\n", pyra->settings.startup_profile); +} + +static DEVICE_ATTR(actual_cpi, 0440, pyra_sysfs_show_actual_cpi, NULL); + +static DEVICE_ATTR(actual_profile, 0440, pyra_sysfs_show_actual_profile, NULL); + +static DEVICE_ATTR(firmware_version, 0440, + pyra_sysfs_show_firmware_version, NULL); + +static DEVICE_ATTR(startup_profile, 0440, + pyra_sysfs_show_startup_profile, NULL); + +static struct attribute *pyra_attributes[] = { + &dev_attr_actual_cpi.attr, + &dev_attr_actual_profile.attr, + &dev_attr_firmware_version.attr, + &dev_attr_startup_profile.attr, + NULL +}; + +static struct attribute_group pyra_attribute_group = { + .attrs = pyra_attributes +}; + +static struct bin_attribute pyra_profile_settings_attr = { + .attr = { .name = "profile_settings", .mode = 0220 }, + .size = sizeof(struct pyra_profile_settings), + .write = pyra_sysfs_write_profile_settings +}; + +static struct bin_attribute pyra_profile1_settings_attr = { + .attr = { .name = "profile1_settings", .mode = 0440 }, + .size = sizeof(struct pyra_profile_settings), + .read = pyra_sysfs_read_profile1_settings +}; + +static struct bin_attribute pyra_profile2_settings_attr = { + .attr = { .name = "profile2_settings", .mode = 0440 }, + .size = sizeof(struct pyra_profile_settings), + .read = pyra_sysfs_read_profile2_settings +}; + +static struct bin_attribute pyra_profile3_settings_attr = { + .attr = { .name = "profile3_settings", .mode = 0440 }, + .size = sizeof(struct pyra_profile_settings), + .read = pyra_sysfs_read_profile3_settings +}; + +static struct bin_attribute pyra_profile4_settings_attr = { + .attr = { .name = "profile4_settings", .mode = 0440 }, + .size = sizeof(struct pyra_profile_settings), + .read = pyra_sysfs_read_profile4_settings +}; + +static struct bin_attribute pyra_profile5_settings_attr = { + .attr = { .name = "profile5_settings", .mode = 0440 }, + .size = sizeof(struct pyra_profile_settings), + .read = pyra_sysfs_read_profile5_settings +}; + +static struct bin_attribute pyra_profile_buttons_attr = { + .attr = { .name = "profile_buttons", .mode = 0220 }, + .size = sizeof(struct pyra_profile_buttons), + .write = pyra_sysfs_write_profile_buttons +}; + +static struct bin_attribute pyra_profile1_buttons_attr = { + .attr = { .name = "profile1_buttons", .mode = 0440 }, + .size = sizeof(struct pyra_profile_buttons), + .read = pyra_sysfs_read_profile1_buttons +}; + +static struct bin_attribute pyra_profile2_buttons_attr = { + .attr = { .name = "profile2_buttons", .mode = 0440 }, + .size = sizeof(struct pyra_profile_buttons), + .read = pyra_sysfs_read_profile2_buttons +}; + +static struct bin_attribute pyra_profile3_buttons_attr = { + .attr = { .name = "profile3_buttons", .mode = 0440 }, + .size = sizeof(struct pyra_profile_buttons), + .read = pyra_sysfs_read_profile3_buttons +}; + +static struct bin_attribute pyra_profile4_buttons_attr = { + .attr = { .name = "profile4_buttons", .mode = 0440 }, + .size = sizeof(struct pyra_profile_buttons), + .read = pyra_sysfs_read_profile4_buttons +}; + +static struct bin_attribute pyra_profile5_buttons_attr = { + .attr = { .name = "profile5_buttons", .mode = 0440 }, + .size = sizeof(struct pyra_profile_buttons), + .read = pyra_sysfs_read_profile5_buttons +}; + +static struct bin_attribute pyra_settings_attr = { + .attr = { .name = "settings", .mode = 0660 }, + .size = sizeof(struct pyra_settings), + .read = pyra_sysfs_read_settings, + .write = pyra_sysfs_write_settings +}; + +static int pyra_create_sysfs_attributes(struct usb_interface *intf) +{ + int retval; + + retval = sysfs_create_group(&intf->dev.kobj, &pyra_attribute_group); + if (retval) + goto exit_1; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile_settings_attr); + if (retval) + goto exit_2; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile1_settings_attr); + if (retval) + goto exit_3; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile2_settings_attr); + if (retval) + goto exit_4; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile3_settings_attr); + if (retval) + goto exit_5; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile4_settings_attr); + if (retval) + goto exit_6; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile5_settings_attr); + if (retval) + goto exit_7; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile_buttons_attr); + if (retval) + goto exit_8; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile1_buttons_attr); + if (retval) + goto exit_9; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile2_buttons_attr); + if (retval) + goto exit_10; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile3_buttons_attr); + if (retval) + goto exit_11; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile4_buttons_attr); + if (retval) + goto exit_12; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_profile5_buttons_attr); + if (retval) + goto exit_13; + + retval = sysfs_create_bin_file(&intf->dev.kobj, + &pyra_settings_attr); + if (retval) + goto exit_14; + + return 0; + +exit_14: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile5_buttons_attr); +exit_13: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile4_buttons_attr); +exit_12: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile3_buttons_attr); +exit_11: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile2_buttons_attr); +exit_10: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile1_buttons_attr); +exit_9: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile_buttons_attr); +exit_8: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile5_settings_attr); +exit_7: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile4_settings_attr); +exit_6: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile3_settings_attr); +exit_5: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile2_settings_attr); +exit_4: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile1_settings_attr); +exit_3: + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile_settings_attr); +exit_2: + sysfs_remove_group(&intf->dev.kobj, &pyra_attribute_group); +exit_1: + return retval; +} + +static void pyra_remove_sysfs_attributes(struct usb_interface *intf) +{ + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_settings_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile5_buttons_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile4_buttons_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile3_buttons_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile2_buttons_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile1_buttons_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile_buttons_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile5_settings_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile4_settings_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile3_settings_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile2_settings_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile1_settings_attr); + sysfs_remove_bin_file(&intf->dev.kobj, &pyra_profile_settings_attr); + sysfs_remove_group(&intf->dev.kobj, &pyra_attribute_group); +} + +static int pyra_init_pyra_device_struct(struct usb_device *usb_dev, + struct pyra_device *pyra) +{ + struct pyra_info *info; + int retval, i; + + mutex_init(&pyra->pyra_lock); + + info = kmalloc(sizeof(struct pyra_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + retval = pyra_get_info(usb_dev, info); + if (retval) { + kfree(info); + return retval; + } + pyra->firmware_version = info->firmware_version; + kfree(info); + + retval = pyra_get_settings(usb_dev, &pyra->settings); + if (retval) + return retval; + + for (i = 0; i < 5; ++i) { + retval = pyra_get_profile_settings(usb_dev, + &pyra->profile_settings[i], i); + if (retval) + return retval; + + retval = pyra_get_profile_buttons(usb_dev, + &pyra->profile_buttons[i], i); + if (retval) + return retval; + } + + profile_activated(pyra, pyra->settings.startup_profile); + + return 0; +} + +static int pyra_init_specials(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct pyra_device *pyra; + int retval; + + if (intf->cur_altsetting->desc.bInterfaceProtocol + == USB_INTERFACE_PROTOCOL_MOUSE) { + + pyra = kzalloc(sizeof(*pyra), GFP_KERNEL); + if (!pyra) { + dev_err(&hdev->dev, "can't alloc device descriptor\n"); + return -ENOMEM; + } + hid_set_drvdata(hdev, pyra); + + retval = pyra_init_pyra_device_struct(usb_dev, pyra); + if (retval) { + dev_err(&hdev->dev, + "couldn't init struct pyra_device\n"); + goto exit_free; + } + + retval = roccat_connect(hdev); + if (retval < 0) { + dev_err(&hdev->dev, "couldn't init char dev\n"); + } else { + pyra->chrdev_minor = retval; + pyra->roccat_claimed = 1; + } + + retval = pyra_create_sysfs_attributes(intf); + if (retval) { + dev_err(&hdev->dev, "cannot create sysfs files\n"); + goto exit_free; + } + } else { + hid_set_drvdata(hdev, NULL); + } + + return 0; +exit_free: + kfree(pyra); + return retval; +} + +static void pyra_remove_specials(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct pyra_device *pyra; + + if (intf->cur_altsetting->desc.bInterfaceProtocol + == USB_INTERFACE_PROTOCOL_MOUSE) { + pyra_remove_sysfs_attributes(intf); + pyra = hid_get_drvdata(hdev); + if (pyra->roccat_claimed) + roccat_disconnect(pyra->chrdev_minor); + kfree(hid_get_drvdata(hdev)); + } +} + +static int pyra_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int retval; + + retval = hid_parse(hdev); + if (retval) { + dev_err(&hdev->dev, "parse failed\n"); + goto exit; + } + + retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (retval) { + dev_err(&hdev->dev, "hw start failed\n"); + goto exit; + } + + retval = pyra_init_specials(hdev); + if (retval) { + dev_err(&hdev->dev, "couldn't install mouse\n"); + goto exit_stop; + } + return 0; + +exit_stop: + hid_hw_stop(hdev); +exit: + return retval; +} + +static void pyra_remove(struct hid_device *hdev) +{ + pyra_remove_specials(hdev); + hid_hw_stop(hdev); +} + +static void pyra_keep_values_up_to_date(struct pyra_device *pyra, + u8 const *data) +{ + struct pyra_mouse_event_button const *button_event; + + switch (data[0]) { + case PYRA_MOUSE_REPORT_NUMBER_BUTTON: + button_event = (struct pyra_mouse_event_button const *)data; + switch (button_event->type) { + case PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2: + profile_activated(pyra, button_event->data1 - 1); + break; + case PYRA_MOUSE_EVENT_BUTTON_TYPE_CPI: + pyra->actual_cpi = button_event->data1; + break; + } + break; + } +} + +static void pyra_report_to_chrdev(struct pyra_device const *pyra, + u8 const *data) +{ + struct pyra_roccat_report roccat_report; + struct pyra_mouse_event_button const *button_event; + + if (data[0] != PYRA_MOUSE_REPORT_NUMBER_BUTTON) + return; + + button_event = (struct pyra_mouse_event_button const *)data; + + switch (button_event->type) { + case PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2: + case PYRA_MOUSE_EVENT_BUTTON_TYPE_CPI: + roccat_report.type = button_event->type; + roccat_report.value = button_event->data1; + roccat_report.key = 0; + roccat_report_event(pyra->chrdev_minor, + (uint8_t const *)&roccat_report, + sizeof(struct pyra_roccat_report)); + break; + case PYRA_MOUSE_EVENT_BUTTON_TYPE_MACRO: + case PYRA_MOUSE_EVENT_BUTTON_TYPE_SHORTCUT: + case PYRA_MOUSE_EVENT_BUTTON_TYPE_QUICKLAUNCH: + if (button_event->data2 == PYRA_MOUSE_EVENT_BUTTON_PRESS) { + roccat_report.type = button_event->type; + roccat_report.key = button_event->data1; + roccat_report.value = pyra->actual_profile; + roccat_report_event(pyra->chrdev_minor, + (uint8_t const *)&roccat_report, + sizeof(struct pyra_roccat_report)); + } + break; + } +} + +static int pyra_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct pyra_device *pyra = hid_get_drvdata(hdev); + + if (intf->cur_altsetting->desc.bInterfaceProtocol + != USB_INTERFACE_PROTOCOL_MOUSE) + return 0; + + pyra_keep_values_up_to_date(pyra, data); + + if (pyra->roccat_claimed) + pyra_report_to_chrdev(pyra, data); + + return 0; +} + +static const struct hid_device_id pyra_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, + USB_DEVICE_ID_ROCCAT_PYRA_WIRED) }, + /* TODO add USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS after testing */ + { } +}; + +MODULE_DEVICE_TABLE(hid, pyra_devices); + +static struct hid_driver pyra_driver = { + .name = "pyra", + .id_table = pyra_devices, + .probe = pyra_probe, + .remove = pyra_remove, + .raw_event = pyra_raw_event +}; + +static int __init pyra_init(void) +{ + return hid_register_driver(&pyra_driver); +} + +static void __exit pyra_exit(void) +{ + hid_unregister_driver(&pyra_driver); +} + +module_init(pyra_init); +module_exit(pyra_exit); + +MODULE_AUTHOR("Stefan Achatz"); +MODULE_DESCRIPTION("USB Roccat Pyra driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hid/hid-roccat-pyra.h b/drivers/hid/hid-roccat-pyra.h new file mode 100644 index 000000000000..22f80a8f26f9 --- /dev/null +++ b/drivers/hid/hid-roccat-pyra.h @@ -0,0 +1,186 @@ +#ifndef __HID_ROCCAT_PYRA_H +#define __HID_ROCCAT_PYRA_H + +/* + * Copyright (c) 2010 Stefan Achatz + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include + +#pragma pack(push) +#pragma pack(1) + +struct pyra_b { + uint8_t command; /* PYRA_COMMAND_B */ + uint8_t size; /* always 3 */ + uint8_t unknown; /* 1 */ +}; + +struct pyra_control { + uint8_t command; /* PYRA_COMMAND_CONTROL */ + /* + * value is profile number for request_settings and request_buttons + * 1 if status ok for request_status + */ + uint8_t value; /* Range 0-4 */ + uint8_t request; +}; + +enum pyra_control_requests { + PYRA_CONTROL_REQUEST_STATUS = 0x00, + PYRA_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10, + PYRA_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20 +}; + +struct pyra_settings { + uint8_t command; /* PYRA_COMMAND_SETTINGS */ + uint8_t size; /* always 3 */ + uint8_t startup_profile; /* Range 0-4! */ +}; + +struct pyra_profile_settings { + uint8_t command; /* PYRA_COMMAND_PROFILE_SETTINGS */ + uint8_t size; /* always 0xd */ + uint8_t number; /* Range 0-4 */ + uint8_t xysync; + uint8_t x_sensitivity; /* 0x1-0xa */ + uint8_t y_sensitivity; + uint8_t x_cpi; /* unused */ + uint8_t y_cpi; /* this value is for x and y */ + uint8_t lightswitch; /* 0 = off, 1 = on */ + uint8_t light_effect; + uint8_t handedness; + uint16_t checksum; /* byte sum */ +}; + +struct pyra_profile_buttons { + uint8_t command; /* PYRA_COMMAND_PROFILE_BUTTONS */ + uint8_t size; /* always 0x13 */ + uint8_t number; /* Range 0-4 */ + uint8_t buttons[14]; + uint16_t checksum; /* byte sum */ +}; + +struct pyra_info { + uint8_t command; /* PYRA_COMMAND_INFO */ + uint8_t size; /* always 6 */ + uint8_t firmware_version; + uint8_t unknown1; /* always 0 */ + uint8_t unknown2; /* always 1 */ + uint8_t unknown3; /* always 0 */ +}; + +enum pyra_commands { + PYRA_COMMAND_CONTROL = 0x4, + PYRA_COMMAND_SETTINGS = 0x5, + PYRA_COMMAND_PROFILE_SETTINGS = 0x6, + PYRA_COMMAND_PROFILE_BUTTONS = 0x7, + PYRA_COMMAND_INFO = 0x9, + PYRA_COMMAND_B = 0xb +}; + +enum pyra_usb_commands { + PYRA_USB_COMMAND_CONTROL = 0x304, + PYRA_USB_COMMAND_SETTINGS = 0x305, + PYRA_USB_COMMAND_PROFILE_SETTINGS = 0x306, + PYRA_USB_COMMAND_PROFILE_BUTTONS = 0x307, + PYRA_USB_COMMAND_INFO = 0x309, + PYRA_USB_COMMAND_B = 0x30b /* writes 3 bytes */ +}; + +enum pyra_mouse_report_numbers { + PYRA_MOUSE_REPORT_NUMBER_HID = 1, + PYRA_MOUSE_REPORT_NUMBER_AUDIO = 2, + PYRA_MOUSE_REPORT_NUMBER_BUTTON = 3, +}; + +struct pyra_mouse_event_button { + uint8_t report_number; /* always 3 */ + uint8_t unknown; /* always 0 */ + uint8_t type; + uint8_t data1; + uint8_t data2; +}; + +struct pyra_mouse_event_audio { + uint8_t report_number; /* always 2 */ + uint8_t type; + uint8_t unused; /* always 0 */ +}; + +/* hid audio controls */ +enum pyra_mouse_event_audio_types { + PYRA_MOUSE_EVENT_AUDIO_TYPE_MUTE = 0xe2, + PYRA_MOUSE_EVENT_AUDIO_TYPE_VOLUME_UP = 0xe9, + PYRA_MOUSE_EVENT_AUDIO_TYPE_VOLUME_DOWN = 0xea, +}; + +enum pyra_mouse_event_button_types { + /* + * Mouse sends tilt events on report_number 1 and 3 + * Tilt events are sent repeatedly with 0.94s between first and second + * event and 0.22s on subsequent + */ + PYRA_MOUSE_EVENT_BUTTON_TYPE_TILT = 0x10, + + /* + * These are sent sequentially + * data1 contains new profile number in range 1-5 + */ + PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_1 = 0x20, + PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2 = 0x30, + + /* + * data1 = button_number (rmp index) + * data2 = pressed/released + */ + PYRA_MOUSE_EVENT_BUTTON_TYPE_MACRO = 0x40, + PYRA_MOUSE_EVENT_BUTTON_TYPE_SHORTCUT = 0x50, + + /* + * data1 = button_number (rmp index) + */ + PYRA_MOUSE_EVENT_BUTTON_TYPE_QUICKLAUNCH = 0x60, + + /* data1 = new cpi */ + PYRA_MOUSE_EVENT_BUTTON_TYPE_CPI = 0xb0, + + /* data1 and data2 = new sensitivity */ + PYRA_MOUSE_EVENT_BUTTON_TYPE_SENSITIVITY = 0xc0, + + PYRA_MOUSE_EVENT_BUTTON_TYPE_MULTIMEDIA = 0xf0, +}; + +enum { + PYRA_MOUSE_EVENT_BUTTON_PRESS = 0, + PYRA_MOUSE_EVENT_BUTTON_RELEASE = 1, +}; + +struct pyra_roccat_report { + uint8_t type; + uint8_t value; + uint8_t key; +}; + +#pragma pack(pop) + +struct pyra_device { + int actual_profile; + int actual_cpi; + int firmware_version; + int roccat_claimed; + int chrdev_minor; + struct mutex pyra_lock; + struct pyra_settings settings; + struct pyra_profile_settings profile_settings[5]; + struct pyra_profile_buttons profile_buttons[5]; +}; + +#endif From d2b570a5d451487f0c5026ea6113842b2eed4894 Mon Sep 17 00:00:00 2001 From: Stefan Achatz Date: Wed, 1 Sep 2010 12:42:23 +0200 Subject: [PATCH 04/22] HID: roccat: Normalized reported profile number for pyra button events. Pyra uses profile numbers in range 0-4 for everything except button events. Using range 1-5 consistent now. Signed-off-by: Stefan Achatz Signed-off-by: Jiri Kosina --- drivers/hid/hid-roccat-pyra.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c index 6c09c15d93ed..9bf23047892a 100644 --- a/drivers/hid/hid-roccat-pyra.c +++ b/drivers/hid/hid-roccat-pyra.c @@ -902,7 +902,11 @@ static void pyra_report_to_chrdev(struct pyra_device const *pyra, if (button_event->data2 == PYRA_MOUSE_EVENT_BUTTON_PRESS) { roccat_report.type = button_event->type; roccat_report.key = button_event->data1; - roccat_report.value = pyra->actual_profile; + /* + * pyra reports profile numbers with range 1-5. + * Keeping this behaviour. + */ + roccat_report.value = pyra->actual_profile + 1; roccat_report_event(pyra->chrdev_minor, (uint8_t const *)&roccat_report, sizeof(struct pyra_roccat_report)); From 0228db70ce6afdcd14164ab8d18137fa319c76e8 Mon Sep 17 00:00:00 2001 From: Chase Douglas Date: Thu, 2 Sep 2010 16:49:52 +0200 Subject: [PATCH 05/22] HID: magicmouse: simplify touch down logic For the MT protocol, we need to properly keep track of each down touch. This change simplifies the logic, and should make things easier when support for the Magic Trackpad is added. Signed-off-by: Chase Douglas Acked-by: Michael Poole Signed-off-by: Jiri Kosina --- drivers/hid/hid-magicmouse.c | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 319b0e57ee41..b74abf202e01 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -98,7 +98,6 @@ struct magicmouse_sc { short scroll_x; short scroll_y; u8 size; - u8 down; } touches[16]; int tracking_ids[16]; }; @@ -226,8 +225,6 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda if (report_touches && down) { int orientation = (misc >> 10) - 32; - msc->touches[id].down = 1; - input_report_abs(input, ABS_MT_TRACKING_ID, id); input_report_abs(input, ABS_MT_TOUCH_MAJOR, tdata[3]); input_report_abs(input, ABS_MT_TOUCH_MINOR, tdata[4]); @@ -240,6 +237,9 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda input_mt_sync(input); } + + if (down) + msc->ntouches++; } static int magicmouse_raw_event(struct hid_device *hdev, @@ -247,7 +247,7 @@ static int magicmouse_raw_event(struct hid_device *hdev, { struct magicmouse_sc *msc = hid_get_drvdata(hdev); struct input_dev *input = msc->input; - int x, y, ts, ii, clicks, last_up; + int x, y, ts, ii, clicks, npoints; switch (data[0]) { case 0x10: @@ -264,22 +264,13 @@ static int magicmouse_raw_event(struct hid_device *hdev, ts = data[3] >> 6 | data[4] << 2 | data[5] << 10; msc->delta_time = (ts - msc->last_timestamp) & 0x3ffff; msc->last_timestamp = ts; - msc->ntouches = (size - 6) / 8; - for (ii = 0; ii < msc->ntouches; ii++) + npoints = (size - 6) / 8; + msc->ntouches = 0; + for (ii = 0; ii < npoints; ii++) magicmouse_emit_touch(msc, ii, data + ii * 8 + 6); - if (report_touches) { - last_up = 1; - for (ii = 0; ii < ARRAY_SIZE(msc->touches); ii++) { - if (msc->touches[ii].down) { - last_up = 0; - msc->touches[ii].down = 0; - } - } - if (last_up) { - input_mt_sync(input); - } - } + if (report_touches && msc->ntouches == 0) + input_mt_sync(input); /* When emulating three-button mode, it is important * to have the current touch information before From c61b7cee672cc7276619ac0edf8f426e2f9e63e9 Mon Sep 17 00:00:00 2001 From: Chase Douglas Date: Thu, 2 Sep 2010 16:51:54 +0200 Subject: [PATCH 06/22] HID: magicmouse: remove timestamp logic The timestamps from the device are currently stored in the private data structure. These aren't used, so remove them. I've left a comment detailing the protocol for future reference. Signed-off-by: Chase Douglas Acked-by: Michael Poole Signed-off-by: Jiri Kosina --- drivers/hid/hid-magicmouse.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index b74abf202e01..945850b42bc1 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -71,11 +71,6 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie * struct magicmouse_sc - Tracks Magic Mouse-specific data. * @input: Input device through which we report events. * @quirks: Currently unused. - * @last_timestamp: Timestamp from most recent (18-bit) touch report - * (units of milliseconds over short windows, but seems to - * increase faster when there are no touches). - * @delta_time: 18-bit difference between the two most recent touch - * reports from the mouse. * @ntouches: Number of touches in most recent touch report. * @scroll_accel: Number of consecutive scroll motions. * @scroll_jiffies: Time of last scroll motion. @@ -86,8 +81,6 @@ struct magicmouse_sc { struct input_dev *input; unsigned long quirks; - int last_timestamp; - int delta_time; int ntouches; int scroll_accel; unsigned long scroll_jiffies; @@ -247,7 +240,7 @@ static int magicmouse_raw_event(struct hid_device *hdev, { struct magicmouse_sc *msc = hid_get_drvdata(hdev); struct input_dev *input = msc->input; - int x, y, ts, ii, clicks, npoints; + int x, y, ii, clicks, npoints; switch (data[0]) { case 0x10: @@ -261,9 +254,6 @@ static int magicmouse_raw_event(struct hid_device *hdev, /* Expect six bytes of prefix, and N*8 bytes of touch data. */ if (size < 6 || ((size - 6) % 8) != 0) return 0; - ts = data[3] >> 6 | data[4] << 2 | data[5] << 10; - msc->delta_time = (ts - msc->last_timestamp) & 0x3ffff; - msc->last_timestamp = ts; npoints = (size - 6) / 8; msc->ntouches = 0; for (ii = 0; ii < npoints; ii++) @@ -279,6 +269,12 @@ static int magicmouse_raw_event(struct hid_device *hdev, x = (int)(((data[3] & 0x0c) << 28) | (data[1] << 22)) >> 22; y = (int)(((data[3] & 0x30) << 26) | (data[2] << 22)) >> 22; clicks = data[3]; + + /* The following bits provide a device specific timestamp. They + * are unused here. + * + * ts = data[3] >> 6 | data[4] << 2 | data[5] << 10; + */ break; case 0x20: /* Theoretically battery status (0-100), but I have * never seen it -- maybe it is only upon request. From 0773590c89fee9c62eaddc5459e52ba96173f930 Mon Sep 17 00:00:00 2001 From: Chase Douglas Date: Tue, 31 Aug 2010 21:56:19 -0400 Subject: [PATCH 07/22] HID: magicmouse: simplify multitouch feature request Only the first feature request is required to put the Magic Mouse into multitouch mode. This is also the case for the Magic Trackpad, for which support will be added in a later commit. Signed-off-by: Chase Douglas Acked-by: Michael Poole Signed-off-by: Jiri Kosina --- drivers/hid/hid-magicmouse.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 945850b42bc1..004c01d9a61a 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -367,8 +367,7 @@ static void magicmouse_setup_input(struct input_dev *input, struct hid_device *h static int magicmouse_probe(struct hid_device *hdev, const struct hid_device_id *id) { - __u8 feature_1[] = { 0xd7, 0x01 }; - __u8 feature_2[] = { 0xf8, 0x01, 0x32 }; + __u8 feature[] = { 0xd7, 0x01 }; struct input_dev *input; struct magicmouse_sc *msc; struct hid_report *report; @@ -408,17 +407,10 @@ static int magicmouse_probe(struct hid_device *hdev, } report->size = 6; - ret = hdev->hid_output_raw_report(hdev, feature_1, sizeof(feature_1), + ret = hdev->hid_output_raw_report(hdev, feature, sizeof(feature), HID_FEATURE_REPORT); - if (ret != sizeof(feature_1)) { - dev_err(&hdev->dev, "unable to request touch data (1:%d)\n", - ret); - goto err_stop_hw; - } - ret = hdev->hid_output_raw_report(hdev, feature_2, - sizeof(feature_2), HID_FEATURE_REPORT); - if (ret != sizeof(feature_2)) { - dev_err(&hdev->dev, "unable to request touch data (2:%d)\n", + if (ret != sizeof(feature)) { + dev_err(&hdev->dev, "unable to request touch data (%d)\n", ret); goto err_stop_hw; } From 6de048bf1dd2ad35fe9b2326bf9d6d23fb2fff7a Mon Sep 17 00:00:00 2001 From: Chase Douglas Date: Tue, 31 Aug 2010 21:56:20 -0400 Subject: [PATCH 08/22] HID: magicmouse: simplify touch data bit manipulation The new format should be easier to read to determine which bits correspond to which data. It also brings all the manipulation logic to the top of the function. This makes size and orientation reading more clear. Note that the impetus for this change is the forthcoming support for the Magic Trackpad, which has a different touch data protocol. Signed-off-by: Chase Douglas Acked-by: Michael Poole Signed-off-by: Jiri Kosina --- drivers/hid/hid-magicmouse.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 004c01d9a61a..402682e0c2f4 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -158,18 +158,21 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tdata) { struct input_dev *input = msc->input; - __s32 x_y = tdata[0] << 8 | tdata[1] << 16 | tdata[2] << 24; - int misc = tdata[5] | tdata[6] << 8; - int id = (misc >> 6) & 15; - int x = x_y << 12 >> 20; - int y = -(x_y >> 20); - int down = (tdata[7] & TOUCH_STATE_MASK) != TOUCH_STATE_NONE; + int id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf; + int x = (tdata[1] << 28 | tdata[0] << 20) >> 20; + int y = -((tdata[2] << 24 | tdata[1] << 16) >> 20); + int size = tdata[5] & 0x3f; + int orientation = (tdata[6] >> 2) - 32; + int touch_major = tdata[3]; + int touch_minor = tdata[4]; + int state = tdata[7] & TOUCH_STATE_MASK; + int down = state != TOUCH_STATE_NONE; /* Store tracking ID and other fields. */ msc->tracking_ids[raw_id] = id; msc->touches[id].x = x; msc->touches[id].y = y; - msc->touches[id].size = misc & 63; + msc->touches[id].size = size; /* If requested, emulate a scroll wheel by detecting small * vertical touch motions. @@ -180,7 +183,7 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda int step_y = msc->touches[id].scroll_y - y; /* Calculate and apply the scroll motion. */ - switch (tdata[7] & TOUCH_STATE_MASK) { + switch (state) { case TOUCH_STATE_START: msc->touches[id].scroll_x = x; msc->touches[id].scroll_y = y; @@ -216,11 +219,9 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda /* Generate the input events for this touch. */ if (report_touches && down) { - int orientation = (misc >> 10) - 32; - input_report_abs(input, ABS_MT_TRACKING_ID, id); - input_report_abs(input, ABS_MT_TOUCH_MAJOR, tdata[3]); - input_report_abs(input, ABS_MT_TOUCH_MINOR, tdata[4]); + input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major); + input_report_abs(input, ABS_MT_TOUCH_MINOR, touch_minor); input_report_abs(input, ABS_MT_ORIENTATION, orientation); input_report_abs(input, ABS_MT_POSITION_X, x); input_report_abs(input, ABS_MT_POSITION_Y, y); From a462230e16acc8664145216da3c928d03556691a Mon Sep 17 00:00:00 2001 From: Chase Douglas Date: Tue, 31 Aug 2010 21:56:23 -0400 Subject: [PATCH 09/22] HID: magicmouse: enable Magic Trackpad support The trackpad speaks a similar, but different, protocol from the magic mouse. However, only small code tweaks here and there are needed to make basic multitouch work. Extra logic is required for single-touch emulation of the touchpad. The changes made here take the approach that only one finger may emulate the single pointer when multiple fingers have touched the screen. Once that finger is raised, all touches must be raised before any further single touch events can be sent. Sometimes the magic trackpad sends two distinct touch reports as one big report. Simply splitting the packet in two and resending them through magicmouse_raw_event ensures they are handled properly. I also added myself to the copyright statement. Signed-off-by: Chase Douglas Acked-by: Michael Poole Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 1 + drivers/hid/hid-magicmouse.c | 190 ++++++++++++++++++++++++++++------- 3 files changed, 154 insertions(+), 38 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 0c52899be964..d2f9c4aa4b52 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1248,6 +1248,7 @@ static const struct hid_device_id hid_blacklist[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICTRACKPAD) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ISO) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ANSI) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 85c6d13c9ffa..5ad4ea02157f 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -63,6 +63,7 @@ #define USB_VENDOR_ID_APPLE 0x05ac #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304 #define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d +#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD 0x030e #define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e #define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO 0x020f #define USB_DEVICE_ID_APPLE_GEYSER_ANSI 0x0214 diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 402682e0c2f4..8791a084693e 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -2,6 +2,7 @@ * Apple "Magic" Wireless Mouse driver * * Copyright (c) 2010 Michael Poole + * Copyright (c) 2010 Chase Douglas */ /* @@ -53,7 +54,9 @@ static bool report_undeciphered; module_param(report_undeciphered, bool, 0644); MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event"); -#define TOUCH_REPORT_ID 0x29 +#define TRACKPAD_REPORT_ID 0x28 +#define MOUSE_REPORT_ID 0x29 +#define DOUBLE_REPORT_ID 0xf7 /* These definitions are not precise, but they're close enough. (Bits * 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem * to be some kind of bit mask -- 0x20 may be a near-field reading, @@ -67,6 +70,15 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define SCROLL_ACCEL_DEFAULT 7 +/* Single touch emulation should only begin when no touches are currently down. + * This is true when single_touch_id is equal to NO_TOUCHES. If multiple touches + * are down and the touch providing for single touch emulation is lifted, + * single_touch_id is equal to SINGLE_TOUCH_UP. While single touch emulation is + * occuring, single_touch_id corresponds with the tracking id of the touch used. + */ +#define NO_TOUCHES -1 +#define SINGLE_TOUCH_UP -2 + /** * struct magicmouse_sc - Tracks Magic Mouse-specific data. * @input: Input device through which we report events. @@ -93,6 +105,7 @@ struct magicmouse_sc { u8 size; } touches[16]; int tracking_ids[16]; + int single_touch_id; }; static int magicmouse_firm_touch(struct magicmouse_sc *msc) @@ -158,15 +171,29 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state) static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tdata) { struct input_dev *input = msc->input; - int id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf; - int x = (tdata[1] << 28 | tdata[0] << 20) >> 20; - int y = -((tdata[2] << 24 | tdata[1] << 16) >> 20); - int size = tdata[5] & 0x3f; - int orientation = (tdata[6] >> 2) - 32; - int touch_major = tdata[3]; - int touch_minor = tdata[4]; - int state = tdata[7] & TOUCH_STATE_MASK; - int down = state != TOUCH_STATE_NONE; + int id, x, y, size, orientation, touch_major, touch_minor, state, down; + + if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) { + id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf; + x = (tdata[1] << 28 | tdata[0] << 20) >> 20; + y = -((tdata[2] << 24 | tdata[1] << 16) >> 20); + size = tdata[5] & 0x3f; + orientation = (tdata[6] >> 2) - 32; + touch_major = tdata[3]; + touch_minor = tdata[4]; + state = tdata[7] & TOUCH_STATE_MASK; + down = state != TOUCH_STATE_NONE; + } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + id = (tdata[7] << 2 | tdata[6] >> 6) & 0xf; + x = (tdata[1] << 27 | tdata[0] << 19) >> 19; + y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19); + size = tdata[6] & 0x3f; + orientation = (tdata[7] >> 2) - 32; + touch_major = tdata[4]; + touch_minor = tdata[5]; + state = tdata[8] & TOUCH_STATE_MASK; + down = state != TOUCH_STATE_NONE; + } /* Store tracking ID and other fields. */ msc->tracking_ids[raw_id] = id; @@ -217,6 +244,13 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda } } + if (down) { + msc->ntouches++; + if (msc->single_touch_id == NO_TOUCHES) + msc->single_touch_id = id; + } else if (msc->single_touch_id == id) + msc->single_touch_id = SINGLE_TOUCH_UP; + /* Generate the input events for this touch. */ if (report_touches && down) { input_report_abs(input, ABS_MT_TRACKING_ID, id); @@ -226,14 +260,15 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda input_report_abs(input, ABS_MT_POSITION_X, x); input_report_abs(input, ABS_MT_POSITION_Y, y); - if (report_undeciphered) - input_event(input, EV_MSC, MSC_RAW, tdata[7]); + if (report_undeciphered) { + if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) + input_event(input, EV_MSC, MSC_RAW, tdata[7]); + else /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + input_event(input, EV_MSC, MSC_RAW, tdata[8]); + } input_mt_sync(input); } - - if (down) - msc->ntouches++; } static int magicmouse_raw_event(struct hid_device *hdev, @@ -241,7 +276,7 @@ static int magicmouse_raw_event(struct hid_device *hdev, { struct magicmouse_sc *msc = hid_get_drvdata(hdev); struct input_dev *input = msc->input; - int x, y, ii, clicks, npoints; + int x = 0, y = 0, ii, clicks = 0, npoints; switch (data[0]) { case 0x10: @@ -251,7 +286,30 @@ static int magicmouse_raw_event(struct hid_device *hdev, y = (__s16)(data[4] | data[5] << 8); clicks = data[1]; break; - case TOUCH_REPORT_ID: + case TRACKPAD_REPORT_ID: + /* Expect four bytes of prefix, and N*9 bytes of touch data. */ + if (size < 4 || ((size - 4) % 9) != 0) + return 0; + npoints = (size - 4) / 9; + msc->ntouches = 0; + for (ii = 0; ii < npoints; ii++) + magicmouse_emit_touch(msc, ii, data + ii * 9 + 4); + + /* We don't need an MT sync here because trackpad emits a + * BTN_TOUCH event in a new frame when all touches are released. + */ + if (msc->ntouches == 0) + msc->single_touch_id = NO_TOUCHES; + + clicks = data[1]; + + /* The following bits provide a device specific timestamp. They + * are unused here. + * + * ts = data[1] >> 6 | data[2] << 2 | data[3] << 10; + */ + break; + case MOUSE_REPORT_ID: /* Expect six bytes of prefix, and N*8 bytes of touch data. */ if (size < 6 || ((size - 6) % 8) != 0) return 0; @@ -277,6 +335,14 @@ static int magicmouse_raw_event(struct hid_device *hdev, * ts = data[3] >> 6 | data[4] << 2 | data[5] << 10; */ break; + case DOUBLE_REPORT_ID: + /* Sometimes the trackpad sends two touch reports in one + * packet. + */ + magicmouse_raw_event(hdev, report, data + 2, data[1]); + magicmouse_raw_event(hdev, report, data + 2 + data[1], + size - 2 - data[1]); + break; case 0x20: /* Theoretically battery status (0-100), but I have * never seen it -- maybe it is only upon request. */ @@ -288,9 +354,25 @@ static int magicmouse_raw_event(struct hid_device *hdev, return 0; } - magicmouse_emit_buttons(msc, clicks & 3); - input_report_rel(input, REL_X, x); - input_report_rel(input, REL_Y, y); + if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) { + magicmouse_emit_buttons(msc, clicks & 3); + input_report_rel(input, REL_X, x); + input_report_rel(input, REL_Y, y); + } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + input_report_key(input, BTN_MOUSE, clicks & 1); + input_report_key(input, BTN_TOUCH, msc->ntouches > 0); + input_report_key(input, BTN_TOOL_FINGER, msc->ntouches == 1); + input_report_key(input, BTN_TOOL_DOUBLETAP, msc->ntouches == 2); + input_report_key(input, BTN_TOOL_TRIPLETAP, msc->ntouches == 3); + input_report_key(input, BTN_TOOL_QUADTAP, msc->ntouches == 4); + if (msc->single_touch_id >= 0) { + input_report_abs(input, ABS_X, + msc->touches[msc->single_touch_id].x); + input_report_abs(input, ABS_Y, + msc->touches[msc->single_touch_id].y); + } + } + input_sync(input); return 1; } @@ -326,18 +408,27 @@ static void magicmouse_setup_input(struct input_dev *input, struct hid_device *h input->dev.parent = hdev->dev.parent; __set_bit(EV_KEY, input->evbit); - __set_bit(BTN_LEFT, input->keybit); - __set_bit(BTN_RIGHT, input->keybit); - if (emulate_3button) - __set_bit(BTN_MIDDLE, input->keybit); - __set_bit(BTN_TOOL_FINGER, input->keybit); - __set_bit(EV_REL, input->evbit); - __set_bit(REL_X, input->relbit); - __set_bit(REL_Y, input->relbit); - if (emulate_scroll_wheel) { - __set_bit(REL_WHEEL, input->relbit); - __set_bit(REL_HWHEEL, input->relbit); + if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) { + __set_bit(BTN_LEFT, input->keybit); + __set_bit(BTN_RIGHT, input->keybit); + if (emulate_3button) + __set_bit(BTN_MIDDLE, input->keybit); + + __set_bit(EV_REL, input->evbit); + __set_bit(REL_X, input->relbit); + __set_bit(REL_Y, input->relbit); + if (emulate_scroll_wheel) { + __set_bit(REL_WHEEL, input->relbit); + __set_bit(REL_HWHEEL, input->relbit); + } + } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + __set_bit(BTN_MOUSE, input->keybit); + __set_bit(BTN_TOOL_FINGER, input->keybit); + __set_bit(BTN_TOOL_DOUBLETAP, input->keybit); + __set_bit(BTN_TOOL_TRIPLETAP, input->keybit); + __set_bit(BTN_TOOL_QUADTAP, input->keybit); + __set_bit(BTN_TOUCH, input->keybit); } if (report_touches) { @@ -347,16 +438,26 @@ static void magicmouse_setup_input(struct input_dev *input, struct hid_device *h input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 4, 0); input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255, 4, 0); input_set_abs_params(input, ABS_MT_ORIENTATION, -32, 31, 1, 0); - input_set_abs_params(input, ABS_MT_POSITION_X, -1100, 1358, - 4, 0); + /* Note: Touch Y position from the device is inverted relative * to how pointer motion is reported (and relative to how USB * HID recommends the coordinates work). This driver keeps * the origin at the same position, and just uses the additive * inverse of the reported Y. */ - input_set_abs_params(input, ABS_MT_POSITION_Y, -1589, 2047, - 4, 0); + if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) { + input_set_abs_params(input, ABS_MT_POSITION_X, -1100, + 1358, 4, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, -1589, + 2047, 4, 0); + } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + input_set_abs_params(input, ABS_X, -2909, 3167, 4, 0); + input_set_abs_params(input, ABS_Y, -2456, 2565, 4, 0); + input_set_abs_params(input, ABS_MT_POSITION_X, -2909, + 3167, 4, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, -2456, + 2565, 4, 0); + } } if (report_undeciphered) { @@ -385,6 +486,8 @@ static int magicmouse_probe(struct hid_device *hdev, msc->quirks = id->driver_data; hid_set_drvdata(hdev, msc); + msc->single_touch_id = NO_TOUCHES; + ret = hid_parse(hdev); if (ret) { dev_err(&hdev->dev, "magicmouse hid parse failed\n"); @@ -400,7 +503,16 @@ static int magicmouse_probe(struct hid_device *hdev, /* we are handling the input ourselves */ hidinput_disconnect(hdev); - report = hid_register_report(hdev, HID_INPUT_REPORT, TOUCH_REPORT_ID); + if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE) + report = hid_register_report(hdev, HID_INPUT_REPORT, + MOUSE_REPORT_ID); + else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + report = hid_register_report(hdev, HID_INPUT_REPORT, + TRACKPAD_REPORT_ID); + report = hid_register_report(hdev, HID_INPUT_REPORT, + DOUBLE_REPORT_ID); + } + if (!report) { dev_err(&hdev->dev, "unable to register touch report\n"); ret = -ENOMEM; @@ -451,8 +563,10 @@ static void magicmouse_remove(struct hid_device *hdev) } static const struct hid_device_id magic_mice[] = { - { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE), - .driver_data = 0 }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_MAGICMOUSE), .driver_data = 0 }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_MAGICTRACKPAD), .driver_data = 0 }, { } }; MODULE_DEVICE_TABLE(hid, magic_mice); From 0277873c05158c5efc97c23d52e6aec6250bde0f Mon Sep 17 00:00:00 2001 From: Rafi Rubin Date: Wed, 8 Sep 2010 11:46:14 +0200 Subject: [PATCH 10/22] HID: ntrig: identify firmware version This adds firmware version polling to the end of probe and reports the version both in the raw form and proccessed to match the formatting used by N-Trig. Signed-off-by: Rafi Rubin Acked-by: Dmitry Torokhov Acked-by: Jiri Slaby Signed-off-by: Jiri Kosina --- drivers/hid/hid-ntrig.c | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c index 43e95dee9290..69169efa1e16 100644 --- a/drivers/hid/hid-ntrig.c +++ b/drivers/hid/hid-ntrig.c @@ -90,6 +90,55 @@ struct ntrig_data { }; +/* + * This function converts the 4 byte raw firmware code into + * a string containing 5 comma separated numbers. + */ +static int ntrig_version_string(unsigned char *raw, char *buf) +{ + __u8 a = (raw[1] & 0x0e) >> 1; + __u8 b = (raw[0] & 0x3c) >> 2; + __u8 c = ((raw[0] & 0x03) << 3) | ((raw[3] & 0xe0) >> 5); + __u8 d = ((raw[3] & 0x07) << 3) | ((raw[2] & 0xe0) >> 5); + __u8 e = raw[2] & 0x07; + + /* + * As yet unmapped bits: + * 0b11000000 0b11110001 0b00011000 0b00011000 + */ + + return sprintf(buf, "%u.%u.%u.%u.%u", a, b, c, d, e); +} + +static void ntrig_report_version(struct hid_device *hdev) +{ + int ret; + char buf[20]; + struct usb_device *usb_dev = hid_to_usb_dev(hdev); + unsigned char *data = kmalloc(8, GFP_KERNEL); + + if (!data) + goto err_free; + + ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_IN, + 0x30c, 1, data, 8, + USB_CTRL_SET_TIMEOUT); + + if (ret == 8) { + ret = ntrig_version_string(&data[2], buf); + + dev_info(&hdev->dev, + "Firmware version: %s (%02x%02x %02x%02x)\n", + buf, data[2], data[3], data[4], data[5]); + } + +err_free: + kfree(data); +} + static ssize_t show_phys_width(struct device *dev, struct device_attribute *attr, char *buf) @@ -848,6 +897,8 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id) if (report) usbhid_submit_report(hdev, report, USB_DIR_OUT); + ntrig_report_version(hdev); + ret = sysfs_create_group(&hdev->dev.kobj, &ntrig_attribute_group); From 24750f3e469bef81a96c0036cd4700df5fb48925 Mon Sep 17 00:00:00 2001 From: Henrik Rydberg Date: Tue, 24 Aug 2010 10:54:44 +0200 Subject: [PATCH 11/22] HID: Add a hid quirk for input sync override As of lately, HID devices which send per-frame data split over several HID reports have started to emerge. This patch adds a quirk which allows the HID driver to take over the input layer synchronization, and hence the control of the frame boundary. Signed-off-by: Henrik Rydberg Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 3 +++ include/linux/hid.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 6c03dcc5760a..b186f6d0feba 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -659,6 +659,9 @@ void hidinput_report_event(struct hid_device *hid, struct hid_report *report) { struct hid_input *hidinput; + if (hid->quirks & HID_QUIRK_NO_INPUT_SYNC) + return; + list_for_each_entry(hidinput, &hid->inputs, list) input_sync(hidinput->input); } diff --git a/include/linux/hid.h b/include/linux/hid.h index 42a0f1d11365..4cfe02c3fa4e 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -316,6 +316,7 @@ struct hid_item { #define HID_QUIRK_FULLSPEED_INTERVAL 0x10000000 #define HID_QUIRK_NO_INIT_REPORTS 0x20000000 #define HID_QUIRK_NO_IGNORE 0x40000000 +#define HID_QUIRK_NO_INPUT_SYNC 0x80000000 /* * This is the global environment of the parser. This information is From 41035901df14e90ab70db826e940712dde2c1a0d Mon Sep 17 00:00:00 2001 From: Henrik Rydberg Date: Tue, 21 Sep 2010 16:11:44 +0200 Subject: [PATCH 12/22] HID: 3m: Adjust to sequential MT HID protocol The multitouch extensions to the HID protocol allows for contact data to be sent over several reports, which is also the case for the 3M M2256PW touchscreen. This patch modifies the logic to only synchronize the input layer when all contacts have been received. Consequentially, the full 60-finger capacity of the device is enabled. Signed-off-by: Henrik Rydberg Acked-by: Stephane Chatty Signed-off-by: Jiri Kosina --- drivers/hid/hid-3m-pct.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-3m-pct.c b/drivers/hid/hid-3m-pct.c index 2a0d56b7a02b..105743068cca 100644 --- a/drivers/hid/hid-3m-pct.c +++ b/drivers/hid/hid-3m-pct.c @@ -24,6 +24,9 @@ MODULE_LICENSE("GPL"); #include "hid-ids.h" +#define MAX_SLOTS 60 +#define MAX_TRKID 59 + struct mmm_finger { __s32 x, y, w, h; __u8 rank; @@ -31,8 +34,9 @@ struct mmm_finger { }; struct mmm_data { - struct mmm_finger f[10]; + struct mmm_finger f[MAX_SLOTS]; __u8 curid, num; + __u8 nexp, nreal; bool touch, valid; }; @@ -93,7 +97,7 @@ static int mmm_input_mapping(struct hid_device *hdev, struct hid_input *hi, 1, 1, 0, 0); return 1; case HID_DG_CONTACTID: - field->logical_maximum = 59; + field->logical_maximum = MAX_TRKID; hid_map_usage(hi, usage, bit, max, EV_ABS, ABS_MT_TRACKING_ID); return 1; @@ -133,7 +137,7 @@ static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) * we need to iterate on all fingers to decide if we have a press * or a release event in our touchscreen emulation. */ - for (i = 0; i < 10; ++i) { + for (i = 0; i < MAX_SLOTS; ++i) { struct mmm_finger *f = &md->f[i]; if (!f->valid) { /* this finger is just placeholder data, ignore */ @@ -190,6 +194,7 @@ static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) } else if (released) { input_event(input, EV_KEY, BTN_TOUCH, 0); } + input_sync(input); } /* @@ -223,10 +228,12 @@ static int mmm_event(struct hid_device *hid, struct hid_field *field, md->f[md->curid].h = value; break; case HID_DG_CONTACTID: + value = clamp_val(value, 0, MAX_SLOTS - 1); if (md->valid) { md->curid = value; md->f[value].touch = md->touch; md->f[value].valid = 1; + md->nreal++; } break; case HID_GD_X: @@ -238,7 +245,12 @@ static int mmm_event(struct hid_device *hid, struct hid_field *field, md->f[md->curid].y = value; break; case HID_DG_CONTACTCOUNT: - mmm_filter_event(md, input); + if (value) + md->nexp = value; + if (md->nreal >= md->nexp) { + mmm_filter_event(md, input); + md->nreal = 0; + } break; } } @@ -255,6 +267,8 @@ static int mmm_probe(struct hid_device *hdev, const struct hid_device_id *id) int ret; struct mmm_data *md; + hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC; + md = kzalloc(sizeof(struct mmm_data), GFP_KERNEL); if (!md) { dev_err(&hdev->dev, "cannot allocate 3M data\n"); From 46c4ba012c9e70b26e4c2072245e3922cca3ef31 Mon Sep 17 00:00:00 2001 From: Henrik Rydberg Date: Tue, 21 Sep 2010 16:16:09 +0200 Subject: [PATCH 13/22] HID: 3m: Output proper orientation range The range of orientation values for height/width devices should be [0, 1], but is currently set to [1, 1]. Having min == max also breaks uinput device setup. Fixed with this patch. Signed-off-by: Henrik Rydberg Acked-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- drivers/hid/hid-3m-pct.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-3m-pct.c b/drivers/hid/hid-3m-pct.c index 105743068cca..d2fda7848f59 100644 --- a/drivers/hid/hid-3m-pct.c +++ b/drivers/hid/hid-3m-pct.c @@ -94,7 +94,7 @@ static int mmm_input_mapping(struct hid_device *hdev, struct hid_input *hi, hid_map_usage(hi, usage, bit, max, EV_ABS, ABS_MT_TOUCH_MINOR); input_set_abs_params(hi->input, ABS_MT_ORIENTATION, - 1, 1, 0, 0); + 0, 1, 0, 0); return 1; case HID_DG_CONTACTID: field->logical_maximum = MAX_TRKID; From 1f01a1fe167f588c8b6b449fde2f5427ca940423 Mon Sep 17 00:00:00 2001 From: Henrik Rydberg Date: Tue, 21 Sep 2010 22:12:12 +0200 Subject: [PATCH 14/22] HID: 3m: Convert to MT slots The Microtouch controller is capable of doing finger tracking on up to 60 fingers. To reduce bandwidth and cpu usage, convert the driver to use the MT slots protocol. On Stephane's suggestion, also insert the additional copyright lines. Signed-off-by: Henrik Rydberg Acked-by: Stephane Chatty Signed-off-by: Jiri Kosina --- drivers/hid/hid-3m-pct.c | 54 ++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-3m-pct.c b/drivers/hid/hid-3m-pct.c index d2fda7848f59..3c380e1c5b60 100644 --- a/drivers/hid/hid-3m-pct.c +++ b/drivers/hid/hid-3m-pct.c @@ -2,6 +2,8 @@ * HID driver for 3M PCT multitouch panels * * Copyright (c) 2009-2010 Stephane Chatty + * Copyright (c) 2010 Henrik Rydberg + * Copyright (c) 2010 Canonical, Ltd. * */ @@ -25,16 +27,24 @@ MODULE_LICENSE("GPL"); #include "hid-ids.h" #define MAX_SLOTS 60 -#define MAX_TRKID 59 +#define MAX_TRKID USHRT_MAX +#define MAX_EVENTS 360 + +/* estimated signal-to-noise ratios */ +#define SN_MOVE 2048 +#define SN_WIDTH 128 struct mmm_finger { __s32 x, y, w, h; + __u16 id; __u8 rank; + bool prev_touch; bool touch, valid; }; struct mmm_data { struct mmm_finger f[MAX_SLOTS]; + __u16 id; __u8 curid, num; __u8 nexp, nreal; bool touch, valid; @@ -44,6 +54,10 @@ static int mmm_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { + int f1 = field->logical_minimum; + int f2 = field->logical_maximum; + int df = f2 - f1; + switch (usage->hid & HID_USAGE_PAGE) { case HID_UP_BUTTON: @@ -54,18 +68,20 @@ static int mmm_input_mapping(struct hid_device *hdev, struct hid_input *hi, case HID_GD_X: hid_map_usage(hi, usage, bit, max, EV_ABS, ABS_MT_POSITION_X); + input_set_abs_params(hi->input, ABS_MT_POSITION_X, + f1, f2, df / SN_MOVE, 0); /* touchscreen emulation */ input_set_abs_params(hi->input, ABS_X, - field->logical_minimum, - field->logical_maximum, 0, 0); + f1, f2, df / SN_MOVE, 0); return 1; case HID_GD_Y: hid_map_usage(hi, usage, bit, max, EV_ABS, ABS_MT_POSITION_Y); + input_set_abs_params(hi->input, ABS_MT_POSITION_Y, + f1, f2, df / SN_MOVE, 0); /* touchscreen emulation */ input_set_abs_params(hi->input, ABS_Y, - field->logical_minimum, - field->logical_maximum, 0, 0); + f1, f2, df / SN_MOVE, 0); return 1; } return 0; @@ -85,14 +101,19 @@ static int mmm_input_mapping(struct hid_device *hdev, struct hid_input *hi, case HID_DG_TIPSWITCH: /* touchscreen emulation */ hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH); + input_set_capability(hi->input, EV_KEY, BTN_TOUCH); return 1; case HID_DG_WIDTH: hid_map_usage(hi, usage, bit, max, EV_ABS, ABS_MT_TOUCH_MAJOR); + input_set_abs_params(hi->input, ABS_MT_TOUCH_MAJOR, + f1, f2, df / SN_WIDTH, 0); return 1; case HID_DG_HEIGHT: hid_map_usage(hi, usage, bit, max, EV_ABS, ABS_MT_TOUCH_MINOR); + input_set_abs_params(hi->input, ABS_MT_TOUCH_MINOR, + f1, f2, df / SN_WIDTH, 0); input_set_abs_params(hi->input, ABS_MT_ORIENTATION, 0, 1, 0, 0); return 1; @@ -100,6 +121,11 @@ static int mmm_input_mapping(struct hid_device *hdev, struct hid_input *hi, field->logical_maximum = MAX_TRKID; hid_map_usage(hi, usage, bit, max, EV_ABS, ABS_MT_TRACKING_ID); + input_set_abs_params(hi->input, ABS_MT_TRACKING_ID, + 0, MAX_TRKID, 0, 0); + if (!hi->input->mt) + input_mt_create_slots(hi->input, MAX_SLOTS); + input_set_events_per_packet(hi->input, MAX_EVENTS); return 1; } /* let hid-input decide for the others */ @@ -117,10 +143,10 @@ static int mmm_input_mapped(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { + /* tell hid-input to skip setup of these event types */ if (usage->type == EV_KEY || usage->type == EV_ABS) - clear_bit(usage->code, *bit); - - return 0; + set_bit(usage->type, hi->input->evbit); + return -1; } /* @@ -141,10 +167,15 @@ static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) struct mmm_finger *f = &md->f[i]; if (!f->valid) { /* this finger is just placeholder data, ignore */ - } else if (f->touch) { + continue; + } + input_mt_slot(input, i); + if (f->touch) { /* this finger is on the screen */ int wide = (f->w > f->h); - input_event(input, EV_ABS, ABS_MT_TRACKING_ID, i); + if (!f->prev_touch) + f->id = md->id++; + input_event(input, EV_ABS, ABS_MT_TRACKING_ID, f->id); input_event(input, EV_ABS, ABS_MT_POSITION_X, f->x); input_event(input, EV_ABS, ABS_MT_POSITION_Y, f->y); input_event(input, EV_ABS, ABS_MT_ORIENTATION, wide); @@ -152,7 +183,6 @@ static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) wide ? f->w : f->h); input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, wide ? f->h : f->w); - input_mt_sync(input); /* * touchscreen emulation: maintain the age rank * of this finger, decide if we have a press @@ -181,7 +211,9 @@ static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) --(md->num); if (md->num == 0) released = true; + input_event(input, EV_ABS, ABS_MT_TRACKING_ID, -1); } + f->prev_touch = f->touch; f->valid = 0; } From 1d958c83c8d77ad4977ae963017e87cec8cca9b5 Mon Sep 17 00:00:00 2001 From: Henrik Rydberg Date: Tue, 21 Sep 2010 23:22:39 +0200 Subject: [PATCH 15/22] HID: 3m: Correct touchscreen emulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current code sometimes misses to report the last BTN_TOUCH event when multiple fingers are lifted simultaneously. With the introduction of MT slots, the tracking id is available to determine the oldest active contact. Use this information to simplify and correct the touchscreen emulation logic. Signed-off-by: Henrik Rydberg Acked-by: Stéphane Chatty Signed-off-by: Jiri Kosina --- drivers/hid/hid-3m-pct.c | 41 +++++----------------------------------- 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/drivers/hid/hid-3m-pct.c b/drivers/hid/hid-3m-pct.c index 3c380e1c5b60..39628e02a428 100644 --- a/drivers/hid/hid-3m-pct.c +++ b/drivers/hid/hid-3m-pct.c @@ -37,7 +37,6 @@ MODULE_LICENSE("GPL"); struct mmm_finger { __s32 x, y, w, h; __u16 id; - __u8 rank; bool prev_touch; bool touch, valid; }; @@ -45,7 +44,7 @@ struct mmm_finger { struct mmm_data { struct mmm_finger f[MAX_SLOTS]; __u16 id; - __u8 curid, num; + __u8 curid; __u8 nexp, nreal; bool touch, valid; }; @@ -156,13 +155,7 @@ static int mmm_input_mapped(struct hid_device *hdev, struct hid_input *hi, static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) { struct mmm_finger *oldest = 0; - bool pressed = false, released = false; int i; - - /* - * we need to iterate on all fingers to decide if we have a press - * or a release event in our touchscreen emulation. - */ for (i = 0; i < MAX_SLOTS; ++i) { struct mmm_finger *f = &md->f[i]; if (!f->valid) { @@ -183,34 +176,11 @@ static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) wide ? f->w : f->h); input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, wide ? f->h : f->w); - /* - * touchscreen emulation: maintain the age rank - * of this finger, decide if we have a press - */ - if (f->rank == 0) { - f->rank = ++(md->num); - if (f->rank == 1) - pressed = true; - } - if (f->rank == 1) + /* touchscreen emulation: pick the oldest contact */ + if (!oldest || ((f->id - oldest->id) & (SHRT_MAX + 1))) oldest = f; } else { /* this finger took off the screen */ - /* touchscreen emulation: maintain age rank of others */ - int j; - - for (j = 0; j < 10; ++j) { - struct mmm_finger *g = &md->f[j]; - if (g->rank > f->rank) { - g->rank--; - if (g->rank == 1) - oldest = g; - } - } - f->rank = 0; - --(md->num); - if (md->num == 0) - released = true; input_event(input, EV_ABS, ABS_MT_TRACKING_ID, -1); } f->prev_touch = f->touch; @@ -219,11 +189,10 @@ static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) /* touchscreen emulation */ if (oldest) { - if (pressed) - input_event(input, EV_KEY, BTN_TOUCH, 1); + input_event(input, EV_KEY, BTN_TOUCH, 1); input_event(input, EV_ABS, ABS_X, oldest->x); input_event(input, EV_ABS, ABS_Y, oldest->y); - } else if (released) { + } else { input_event(input, EV_KEY, BTN_TOUCH, 0); } input_sync(input); From 48216fbdad6cc462b056ce1e876edcc664d32a8d Mon Sep 17 00:00:00 2001 From: Henrik Rydberg Date: Wed, 22 Sep 2010 11:29:07 +0200 Subject: [PATCH 16/22] HID: 3m: Adjust major / minor axes to scale By visual inspection, the reported touch_major and touch_minor axes are a factor of two too large. Presumably the device actually reports the width_major and width_minor, which are generally about a factor of two larger than the touches themselves. Signed-off-by: Henrik Rydberg Signed-off-by: Jiri Kosina --- drivers/hid/hid-3m-pct.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-3m-pct.c b/drivers/hid/hid-3m-pct.c index 39628e02a428..02d8cd3b1b1b 100644 --- a/drivers/hid/hid-3m-pct.c +++ b/drivers/hid/hid-3m-pct.c @@ -166,16 +166,18 @@ static void mmm_filter_event(struct mmm_data *md, struct input_dev *input) if (f->touch) { /* this finger is on the screen */ int wide = (f->w > f->h); + /* divided by two to match visual scale of touch */ + int major = max(f->w, f->h) >> 1; + int minor = min(f->w, f->h) >> 1; + if (!f->prev_touch) f->id = md->id++; input_event(input, EV_ABS, ABS_MT_TRACKING_ID, f->id); input_event(input, EV_ABS, ABS_MT_POSITION_X, f->x); input_event(input, EV_ABS, ABS_MT_POSITION_Y, f->y); input_event(input, EV_ABS, ABS_MT_ORIENTATION, wide); - input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, - wide ? f->w : f->h); - input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, - wide ? f->h : f->w); + input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major); + input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor); /* touchscreen emulation: pick the oldest contact */ if (!oldest || ((f->id - oldest->id) & (SHRT_MAX + 1))) oldest = f; From fe2c91ee245bd81230f1d67645237a684b90be2b Mon Sep 17 00:00:00 2001 From: Alan Ott Date: Wed, 22 Sep 2010 13:19:42 +0200 Subject: [PATCH 17/22] HID: don't Send Feature Reports on Interrupt Endpoint Feature reports should only be sent on the control endpoint. The USB HID standard is unclear and confusing on this issue. It seems to suggest that Feature reports can be sent on a HID device's Interrupt OUT endpoint. This cannot be the case because the report type is not encoded in transfers sent out the Interrput OUT endpoint. If Feature reports were sent on the Interrupt OUT endpint, they would be indistinguishable from Output reports in the case where Report IDs were not used. Further, Windows and Mac OS X do not send Feature reports out the interrupt OUT Endpoint. They will only go out the Control Endpoint. In addition, many devices simply do not hande Feature reports sent out the Interrupt OUT endpoint. Reported-by: simon@mungewell.org Signed-off-by: Alan Ott Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 599041a7f670..39913f51d881 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -807,7 +807,7 @@ static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t co struct usb_host_interface *interface = intf->cur_altsetting; int ret; - if (usbhid->urbout) { + if (usbhid->urbout && report_type != HID_FEATURE_REPORT) { int actual_length; int skipped_report_id = 0; if (buf[0] == 0x0) { From 32c88cbc3080f43c429f6456aa9cd845e37f3778 Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Wed, 22 Sep 2010 13:19:42 +0200 Subject: [PATCH 18/22] HID: Add support for Logitech Speed Force Wireless gaming wheel The following patch adds support for the Logitech Speed Force Wireless gaming wheel. Originally designed for the WII console. Details on the protocol: http://wiibrew.org/wiki/Logitech_USB_steering_wheel This patch relies on previous patch: "Don't Send Feature Reports on Interrupt Endpoint" Logitech as produce a very similar wheel for the PS2/PS3, it is expected that this patch could also support the PS2/PS3 wheel if the USB ID's are added and (if required) the HID descriptor is modified. Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 8 +++ drivers/hid/Makefile | 3 + drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 1 + drivers/hid/hid-lg.c | 38 +++++++++++ drivers/hid/hid-lg.h | 6 ++ drivers/hid/hid-lg4ff.c | 136 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 193 insertions(+) create mode 100644 drivers/hid/hid-lg4ff.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 6369ba7f96f8..6664c573cf33 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -235,6 +235,14 @@ config LOGIG940_FF Say Y here if you want to enable force feedback support for Logitech Flight System G940 devices. +config LOGIWII_FF + bool "Logitech Speed Force Wireless force feedback support" + depends on HID_LOGITECH + select INPUT_FF_MEMLESS + help + Say Y here if you want to enable force feedback support for Logitech + Speed Force Wireless (Wii) devices. + config HID_MAGICMOUSE tristate "Apple MagicMouse multi-touch support" depends on BT_HIDP diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 46f037f3df80..d46a455eefc1 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -21,6 +21,9 @@ endif ifdef CONFIG_LOGIG940_FF hid-logitech-objs += hid-lg3ff.o endif +ifdef CONFIG_LOGIWII_FF + hid-logitech-objs += hid-lg4ff.o +endif obj-$(CONFIG_HID_3M_PCT) += hid-3m-pct.o obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 3f7292486024..19d45473f24f 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1335,6 +1335,7 @@ static const struct hid_device_id hid_blacklist[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 765a4f53eb5c..6b111e1e2df1 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -350,6 +350,7 @@ #define USB_DEVICE_ID_LOGITECH_WINGMAN_FFG 0xc293 #define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL 0xc295 #define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299 +#define USB_DEVICE_ID_LOGITECH_WII_WHEEL 0xc29c #define USB_DEVICE_ID_LOGITECH_ELITE_KBD 0xc30a #define USB_DEVICE_ID_S510_RECEIVER 0xc50c #define USB_DEVICE_ID_S510_RECEIVER_2 0xc517 diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index f6433d8050a9..8989f151e0d7 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -19,6 +19,9 @@ #include #include #include +#include +#include +#include #include "hid-ids.h" #include "hid-lg.h" @@ -35,6 +38,7 @@ #define LG_FF2 0x400 #define LG_RDESC_REL_ABS 0x800 #define LG_FF3 0x1000 +#define LG_FF4 0x2000 /* * Certain Logitech keyboards send in report #3 keys which are far @@ -60,6 +64,17 @@ static void lg_report_fixup(struct hid_device *hdev, __u8 *rdesc, "report descriptor\n"); rdesc[33] = rdesc[50] = 0x02; } + + if ((quirks & LG_FF4) && rsize >= 101 && + rdesc[41] == 0x95 && rdesc[42] == 0x0B && + rdesc[47] == 0x05 && rdesc[48] == 0x09) { + dev_info(&hdev->dev, "fixing up Logitech Speed Force Wireless " + "button descriptor\n"); + rdesc[41] = 0x05; + rdesc[42] = 0x09; + rdesc[47] = 0x95; + rdesc[48] = 0x0B; + } } #define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \ @@ -285,12 +300,33 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id) goto err_free; } + if (quirks & LG_FF4) { + unsigned char buf[] = { 0x00, 0xAF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + ret = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT); + + if (ret >= 0) { + /* insert a little delay of 10 jiffies ~ 40ms */ + wait_queue_head_t wait; + init_waitqueue_head (&wait); + wait_event_interruptible_timeout(wait, 0, 10); + + /* Select random Address */ + buf[1] = 0xB2; + get_random_bytes(&buf[2], 2); + + ret = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT); + } + } + if (quirks & LG_FF) lgff_init(hdev); if (quirks & LG_FF2) lg2ff_init(hdev); if (quirks & LG_FF3) lg3ff_init(hdev); + if (quirks & LG_FF4) + lg4ff_init(hdev); return 0; err_free: @@ -339,6 +375,8 @@ static const struct hid_device_id lg_devices[] = { .driver_data = LG_FF }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL), .driver_data = LG_FF }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL), + .driver_data = LG_FF4 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG ), .driver_data = LG_FF }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2), diff --git a/drivers/hid/hid-lg.h b/drivers/hid/hid-lg.h index ce2ac8672624..b0100ba2ae0b 100644 --- a/drivers/hid/hid-lg.h +++ b/drivers/hid/hid-lg.h @@ -19,4 +19,10 @@ int lg3ff_init(struct hid_device *hdev); static inline int lg3ff_init(struct hid_device *hdev) { return -1; } #endif +#ifdef CONFIG_LOGIWII_FF +int lg4ff_init(struct hid_device *hdev); +#else +static inline int lg4ff_init(struct hid_device *hdev) { return -1; } +#endif + #endif diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c new file mode 100644 index 000000000000..7eef5a2ce948 --- /dev/null +++ b/drivers/hid/hid-lg4ff.c @@ -0,0 +1,136 @@ +/* + * Force feedback support for Logitech Speed Force Wireless + * + * http://wiibrew.org/wiki/Logitech_USB_steering_wheel + * + * Copyright (c) 2010 Simon Wood + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include +#include +#include + +#include "usbhid/usbhid.h" +#include "hid-lg.h" + +struct lg4ff_device { + struct hid_report *report; +}; + +static const signed short ff4_wheel_ac[] = { + FF_CONSTANT, + FF_AUTOCENTER, + -1 +}; + +static int hid_lg4ff_play(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + int x; + +#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff + + switch (effect->type) { + case FF_CONSTANT: + x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */ + CLAMP(x); + report->field[0]->value[0] = 0x11; /* Slot 1 */ + report->field[0]->value[1] = 0x10; + report->field[0]->value[2] = x; + report->field[0]->value[3] = 0x00; + report->field[0]->value[4] = 0x00; + report->field[0]->value[5] = 0x08; + report->field[0]->value[6] = 0x00; + dbg_hid("Autocenter, x=0x%02X\n", x); + + usbhid_submit_report(hid, report, USB_DIR_OUT); + break; + } + return 0; +} + +static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + __s32 *value = report->field[0]->value; + + *value++ = 0xfe; + *value++ = 0x0d; + *value++ = 0x07; + *value++ = 0x07; + *value++ = (magnitude >> 8) & 0xff; + *value++ = 0x00; + *value = 0x00; + + usbhid_submit_report(hid, report, USB_DIR_OUT); +} + + +int lg4ff_init(struct hid_device *hid) +{ + struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; + struct input_dev *dev = hidinput->input; + struct hid_report *report; + struct hid_field *field; + const signed short *ff_bits = ff4_wheel_ac; + int error; + int i; + + /* Find the report to use */ + if (list_empty(report_list)) { + err_hid("No output report found"); + return -1; + } + + /* Check that the report looks ok */ + report = list_entry(report_list->next, struct hid_report, list); + if (!report) { + err_hid("NULL output report"); + return -1; + } + + field = report->field[0]; + if (!field) { + err_hid("NULL field"); + return -1; + } + + for (i = 0; ff_bits[i] >= 0; i++) + set_bit(ff_bits[i], dev->ffbit); + + error = input_ff_create_memless(dev, NULL, hid_lg4ff_play); + + if (error) + return error; + + if (test_bit(FF_AUTOCENTER, dev->ffbit)) + dev->ff->set_autocenter = hid_lg4ff_set_autocenter; + + dev_info(&hid->dev, "Force feedback for Logitech Speed Force Wireless by " + "Simon Wood \n"); + return 0; +} + From 64eb105d7f92fa48798106ac0d8bf17668eb2524 Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Fri, 24 Sep 2010 13:58:18 +0200 Subject: [PATCH 19/22] HID: magicmouse: Use hid-input parsing rather than bypassing it Let the HID core handle input device setup and HID-compliant reports. This driver then only has to worry about the non-standard reports. Signed-off-by: Michael Poole Acked-by: Chase Douglas Signed-off-by: Jiri Kosina --- drivers/hid/hid-magicmouse.c | 81 ++++++++---------------------------- 1 file changed, 18 insertions(+), 63 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 8791a084693e..5e1907a51446 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -279,13 +279,6 @@ static int magicmouse_raw_event(struct hid_device *hdev, int x = 0, y = 0, ii, clicks = 0, npoints; switch (data[0]) { - case 0x10: - if (size != 6) - return 0; - x = (__s16)(data[2] | data[3] << 8); - y = (__s16)(data[4] | data[5] << 8); - clicks = data[1]; - break; case TRACKPAD_REPORT_ID: /* Expect four bytes of prefix, and N*9 bytes of touch data. */ if (size < 4 || ((size - 4) % 9) != 0) @@ -343,13 +336,6 @@ static int magicmouse_raw_event(struct hid_device *hdev, magicmouse_raw_event(hdev, report, data + 2 + data[1], size - 2 - data[1]); break; - case 0x20: /* Theoretically battery status (0-100), but I have - * never seen it -- maybe it is only upon request. - */ - case 0x60: /* Unknown, maybe laser on/off. */ - case 0x61: /* Laser reflection status change. - * data[1]: 0 = spotted, 1 = lost - */ default: return 0; } @@ -377,36 +363,8 @@ static int magicmouse_raw_event(struct hid_device *hdev, return 1; } -static int magicmouse_input_open(struct input_dev *dev) -{ - struct hid_device *hid = input_get_drvdata(dev); - - return hid->ll_driver->open(hid); -} - -static void magicmouse_input_close(struct input_dev *dev) -{ - struct hid_device *hid = input_get_drvdata(dev); - - hid->ll_driver->close(hid); -} - static void magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev) { - input_set_drvdata(input, hdev); - input->event = hdev->ll_driver->hidinput_input_event; - input->open = magicmouse_input_open; - input->close = magicmouse_input_close; - - input->name = hdev->name; - input->phys = hdev->phys; - input->uniq = hdev->uniq; - input->id.bustype = hdev->bus; - input->id.vendor = hdev->vendor; - input->id.product = hdev->product; - input->id.version = hdev->version; - input->dev.parent = hdev->dev.parent; - __set_bit(EV_KEY, input->evbit); if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) { @@ -466,11 +424,22 @@ static void magicmouse_setup_input(struct input_dev *input, struct hid_device *h } } +static int magicmouse_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + struct magicmouse_sc *msc = hid_get_drvdata(hdev); + + if (!msc->input) + msc->input = hi->input; + + return 0; +} + static int magicmouse_probe(struct hid_device *hdev, const struct hid_device_id *id) { __u8 feature[] = { 0xd7, 0x01 }; - struct input_dev *input; struct magicmouse_sc *msc; struct hid_report *report; int ret; @@ -500,8 +469,11 @@ static int magicmouse_probe(struct hid_device *hdev, goto err_free; } - /* we are handling the input ourselves */ - hidinput_disconnect(hdev); + /* We do this after hid-input is done parsing reports so that + * hid-input uses the most natural button and axis IDs. + */ + if (msc->input) + magicmouse_setup_input(msc->input, hdev); if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE) report = hid_register_report(hdev, HID_INPUT_REPORT, @@ -528,24 +500,7 @@ static int magicmouse_probe(struct hid_device *hdev, goto err_stop_hw; } - input = input_allocate_device(); - if (!input) { - dev_err(&hdev->dev, "can't alloc input device\n"); - ret = -ENOMEM; - goto err_stop_hw; - } - magicmouse_setup_input(input, hdev); - - ret = input_register_device(input); - if (ret) { - dev_err(&hdev->dev, "input device registration failed\n"); - goto err_input; - } - msc->input = input; - return 0; -err_input: - input_free_device(input); err_stop_hw: hid_hw_stop(hdev); err_free: @@ -558,7 +513,6 @@ static void magicmouse_remove(struct hid_device *hdev) struct magicmouse_sc *msc = hid_get_drvdata(hdev); hid_hw_stop(hdev); - input_unregister_device(msc->input); kfree(msc); } @@ -577,6 +531,7 @@ static struct hid_driver magicmouse_driver = { .probe = magicmouse_probe, .remove = magicmouse_remove, .raw_event = magicmouse_raw_event, + .input_mapping = magicmouse_input_mapping, }; static int __init magicmouse_init(void) From f51661105c3c8a0afcd69f995a4f4a10e53da153 Mon Sep 17 00:00:00 2001 From: Philipp Merkel Date: Fri, 1 Oct 2010 15:38:59 +0200 Subject: [PATCH 20/22] HID: Fix for problems with eGalax/DWAV multi-touch-screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch fixes three problems with the eGalax/DWAV multi-touch screen found in the Eee PC T101MT: 1) While there is a dedicated multitouch driver for the screen (hid-egalax.c), the MULTI_INPUT quirk is also applied, preventing the hid-egalax driver from working. This patch removes the quirk so the hid-egalax driver can handle the device correctly. 2) The x and y coordinates sent by the screen in multi-touch mode are shifted by three bits from the events sent in single-touch mode, thus the coordinates are out of range, leading to the pointer being stuck in the bottom-right corner if no additional calibration is applied (e.g. in the X evdev driver). This patch shifts the coordinates back. This does not decrease accuracy as the last three bits of the "wrong" coordinates are always 0. 3) Only multi-touch pressure events are sent, single touch emulation is missing pressure information. This patch adds single-touch ABS_PRESSURE events. Signed-off-by: Philipp Merkel Acked-by: Stéphane Chatty Signed-off-by: Jiri Kosina --- drivers/hid/hid-egalax.c | 16 +++++++++++----- drivers/hid/usbhid/hid-quirks.c | 1 - 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-egalax.c b/drivers/hid/hid-egalax.c index 8ca7f65cf2f8..54b017ad258d 100644 --- a/drivers/hid/hid-egalax.c +++ b/drivers/hid/hid-egalax.c @@ -31,7 +31,7 @@ struct egalax_data { bool first; /* is this the first finger in the frame? */ bool valid; /* valid finger data, or just placeholder? */ bool activity; /* at least one active finger previously? */ - __u16 lastx, lasty; /* latest valid (x, y) in the frame */ + __u16 lastx, lasty, lastz; /* latest valid (x, y, z) in the frame */ }; static int egalax_input_mapping(struct hid_device *hdev, struct hid_input *hi, @@ -79,6 +79,10 @@ static int egalax_input_mapping(struct hid_device *hdev, struct hid_input *hi, case HID_DG_TIPPRESSURE: hid_map_usage(hi, usage, bit, max, EV_ABS, ABS_MT_PRESSURE); + /* touchscreen emulation */ + input_set_abs_params(hi->input, ABS_PRESSURE, + field->logical_minimum, + field->logical_maximum, 0, 0); return 1; } return 0; @@ -109,8 +113,8 @@ static void egalax_filter_event(struct egalax_data *td, struct input_dev *input) if (td->valid) { /* emit multitouch events */ input_event(input, EV_ABS, ABS_MT_TRACKING_ID, td->id); - input_event(input, EV_ABS, ABS_MT_POSITION_X, td->x); - input_event(input, EV_ABS, ABS_MT_POSITION_Y, td->y); + input_event(input, EV_ABS, ABS_MT_POSITION_X, td->x >> 3); + input_event(input, EV_ABS, ABS_MT_POSITION_Y, td->y >> 3); input_event(input, EV_ABS, ABS_MT_PRESSURE, td->z); input_mt_sync(input); @@ -121,6 +125,7 @@ static void egalax_filter_event(struct egalax_data *td, struct input_dev *input) */ td->lastx = td->x; td->lasty = td->y; + td->lastz = td->z; } /* @@ -129,8 +134,9 @@ static void egalax_filter_event(struct egalax_data *td, struct input_dev *input) * the oldest on the panel, the one we want for single touch */ if (!td->first && td->activity) { - input_event(input, EV_ABS, ABS_X, td->lastx); - input_event(input, EV_ABS, ABS_Y, td->lasty); + input_event(input, EV_ABS, ABS_X, td->lastx >> 3); + input_event(input, EV_ABS, ABS_Y, td->lasty >> 3); + input_event(input, EV_ABS, ABS_PRESSURE, td->lastz); } if (!td->valid) { diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index 70da3181c8a0..7ff7e3d4b950 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -34,7 +34,6 @@ static const struct hid_blacklist { { USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD, HID_QUIRK_BADPAD }, { USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD, HID_QUIRK_BADPAD }, { USB_VENDOR_ID_DWAV, USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER, HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET }, - { USB_VENDOR_ID_DWAV, USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_MOJO, USB_DEVICE_ID_RETRO_ADAPTER, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING, HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT }, From 921990b7046ab4fb523cbccc5bce2c921762232d Mon Sep 17 00:00:00 2001 From: Henrik Rydberg Date: Tue, 31 Aug 2010 21:56:24 -0400 Subject: [PATCH 21/22] HID: magicmouse: Adjust major / minor axes to scale By visual inspection, the reported touch_major and touch_minor axes are roughly a factor of four too small. The factor is approximate, since the protocol is not known and the HID report encodes touch size with fewer bits than positions. This patch scales the reported values by a factor of four. Signed-off-by: Henrik Rydberg Signed-off-by: Chase Douglas Acked-by: Michael Poole Signed-off-by: Jiri Kosina --- drivers/hid/hid-magicmouse.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 5e1907a51446..e6dc15171664 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -254,8 +254,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda /* Generate the input events for this touch. */ if (report_touches && down) { input_report_abs(input, ABS_MT_TRACKING_ID, id); - input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major); - input_report_abs(input, ABS_MT_TOUCH_MINOR, touch_minor); + input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major << 2); + input_report_abs(input, ABS_MT_TOUCH_MINOR, touch_minor << 2); input_report_abs(input, ABS_MT_ORIENTATION, orientation); input_report_abs(input, ABS_MT_POSITION_X, x); input_report_abs(input, ABS_MT_POSITION_Y, y); From 2c6118e43040034d80894daeba41960bf0035b31 Mon Sep 17 00:00:00 2001 From: Hendrik Iben Date: Mon, 4 Oct 2010 15:39:49 +0200 Subject: [PATCH 22/22] HID: force feedback support for Logitech RumblePad gamepad This patch adds force feedback support for Logitech WingMan RumblePad gamepads by extending the Logitech Rumblepad 2 force feedback code. Signed-off-by: Hendrik Iben Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 4 ++-- drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 1 + drivers/hid/hid-lg.c | 3 +++ drivers/hid/hid-lg2ff.c | 4 ++-- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 6664c573cf33..3892ff5fa11b 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -220,12 +220,12 @@ config LOGITECH_FF force feedback. config LOGIRUMBLEPAD2_FF - bool "Logitech Rumblepad 2 force feedback support" + bool "Logitech RumblePad/Rumblepad 2 force feedback support" depends on HID_LOGITECH select INPUT_FF_MEMLESS help Say Y here if you want to enable force feedback support for Logitech - Rumblepad 2 devices. + RumblePad and Rumblepad 2 devices. config LOGIG940_FF bool "Logitech Flight System G940 force feedback support" diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 19d45473f24f..0120557022bf 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1326,6 +1326,7 @@ static const struct hid_device_id hid_blacklist[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_F3D) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 6b111e1e2df1..79f0304aee8a 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -339,6 +339,7 @@ #define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101 #define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST 0xc110 #define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f +#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD 0xc20a #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD 0xc211 #define USB_DEVICE_ID_LOGITECH_EXTREME_3D 0xc215 #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2 0xc218 diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index 8989f151e0d7..9e92c27002ca 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -7,6 +7,7 @@ * Copyright (c) 2006-2007 Jiri Kosina * Copyright (c) 2007 Paul Walmsley * Copyright (c) 2008 Jiri Slaby + * Copyright (c) 2010 Hendrik Iben */ /* @@ -361,6 +362,8 @@ static const struct hid_device_id lg_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL), .driver_data = LG_NOGET | LG_FF }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD), + .driver_data = LG_FF2 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD), .driver_data = LG_FF }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2), diff --git a/drivers/hid/hid-lg2ff.c b/drivers/hid/hid-lg2ff.c index d888f1e6794f..4258253c36b3 100644 --- a/drivers/hid/hid-lg2ff.c +++ b/drivers/hid/hid-lg2ff.c @@ -1,5 +1,5 @@ /* - * Force feedback support for Logitech Rumblepad 2 + * Force feedback support for Logitech RumblePad and Rumblepad 2 * * Copyright (c) 2008 Anssi Hannula */ @@ -110,7 +110,7 @@ int lg2ff_init(struct hid_device *hid) usbhid_submit_report(hid, report, USB_DIR_OUT); - dev_info(&hid->dev, "Force feedback for Logitech Rumblepad 2 by " + dev_info(&hid->dev, "Force feedback for Logitech RumblePad/Rumblepad 2 by " "Anssi Hannula \n"); return 0;