Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid

Pull HID updates from Jiri Kosina:

 - Surface Aggregator Module support from Maximilian Luz

 - Apple Magic Mouse 2 support from John Chen

 - Support for newer Quad/BT 2.0 Logitech receivers in HID proxy mode
   from Hans de Goede

 - Thinkpad X1 Tablet keyboard support from Hans de Goede

 - Support for FTDI FT260 I2C host adapter from Michael Zaidman

 - other various small device-specific quirks, fixes and cleanups

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (46 commits)
  HID: wacom: Setup pen input capabilities to the targeted tools
  HID: hid-sensor-hub: Move 'hsdev' description to correct struct definition
  HID: hid-sensor-hub: Remove unused struct member 'quirks'
  HID: wacom_sys: Demote kernel-doc abuse
  HID: hid-sensor-custom: Remove unused variable 'ret'
  HID: hid-uclogic-params: Ensure function names are present and correct in kernel-doc headers
  HID: hid-uclogic-rdesc: Kernel-doc is for functions and structs
  HID: hid-logitech-hidpp: Fix conformant kernel-doc header and demote abuses
  HID: hid-picolcd_core: Remove unused variable 'ret'
  HID: hid-kye: Fix incorrect function name for kye_tablet_enable()
  HID: hid-core: Fix incorrect function name in header
  HID: hid-alps: Correct struct misnaming
  HID: usbhid: hid-pidff: Demote a couple kernel-doc abuses
  HID: usbhid: Repair a formatting issue in a struct description
  HID: hid-thrustmaster: Demote a bunch of kernel-doc abuses
  HID: input: map battery capacity (00850065)
  HID: magicmouse: fix reconnection of Magic Mouse 2
  HID: magicmouse: fix 3 button emulation of Mouse 2
  HID: magicmouse: add Apple Magic Mouse 2 support
  HID: lenovo: Add support for Thinkpad X1 Tablet Thin keyboard
  ...
This commit is contained in:
Linus Torvalds 2021-04-30 12:53:02 -07:00
commit efd8929b9e
38 changed files with 2955 additions and 229 deletions

View File

@ -7432,6 +7432,13 @@ F: fs/verity/
F: include/linux/fsverity.h
F: include/uapi/linux/fsverity.h
FT260 FTDI USB-HID TO I2C BRIDGE DRIVER
M: Michael Zaidman <michael.zaidman@gmail.com>
L: linux-i2c@vger.kernel.org
L: linux-input@vger.kernel.org
S: Maintained
F: drivers/hid/hid-ft260.c
FUJITSU LAPTOP EXTRAS
M: Jonathan Woithe <jwoithe@just42.net>
L: platform-driver-x86@vger.kernel.org
@ -12079,6 +12086,13 @@ S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
F: drivers/platform/surface/
MICROSOFT SURFACE HID TRANSPORT DRIVER
M: Maximilian Luz <luzmaximilian@gmail.com>
L: linux-input@vger.kernel.org
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/hid/surface-hid/
MICROSOFT SURFACE HOT-PLUG DRIVER
M: Maximilian Luz <luzmaximilian@gmail.com>
L: platform-driver-x86@vger.kernel.org

View File

@ -351,6 +351,17 @@ config HID_EZKEY
help
Support for Ezkey BTC 8193 keyboard.
config HID_FT260
tristate "FTDI FT260 USB HID to I2C host support"
depends on USB_HID && HIDRAW && I2C
help
Provides I2C host adapter functionality over USB-HID through FT260
device. The customizable USB descriptor fields are exposed as sysfs
attributes.
To compile this driver as a module, choose M here: the module
will be called hid-ft260.
config HID_GEMBIRD
tristate "Gembird Joypad"
depends on HID
@ -1042,10 +1053,11 @@ config HID_THINGM
config HID_THRUSTMASTER
tristate "ThrustMaster devices support"
depends on HID
depends on USB_HID
help
Say Y here if you have a THRUSTMASTER FireStore Dual Power 2 or
a THRUSTMASTER Ferrari GT Rumble Wheel.
Say Y here if you have a THRUSTMASTER FireStore Dual Power 2,
a THRUSTMASTER Ferrari GT Rumble Wheel or Thrustmaster FFB
Wheel (T150RS, T300RS, T300 Ferrari Alcantara Edition, T500RS).
config THRUSTMASTER_FF
bool "ThrustMaster devices force feedback support"
@ -1206,4 +1218,6 @@ source "drivers/hid/intel-ish-hid/Kconfig"
source "drivers/hid/amd-sfh-hid/Kconfig"
source "drivers/hid/surface-hid/Kconfig"
endmenu

View File

@ -46,6 +46,7 @@ obj-$(CONFIG_HID_ELAN) += hid-elan.o
obj-$(CONFIG_HID_ELECOM) += hid-elecom.o
obj-$(CONFIG_HID_ELO) += hid-elo.o
obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o
obj-$(CONFIG_HID_FT260) += hid-ft260.o
obj-$(CONFIG_HID_GEMBIRD) += hid-gembird.o
obj-$(CONFIG_HID_GFRM) += hid-gfrm.o
obj-$(CONFIG_HID_GLORIOUS) += hid-glorious.o
@ -112,7 +113,8 @@ obj-$(CONFIG_HID_STEAM) += hid-steam.o
obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o
obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o
obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o
obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o
obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o hid-thrustmaster.o
obj-$(CONFIG_HID_TMINIT) += hid-tminit.o
obj-$(CONFIG_HID_TIVO) += hid-tivo.o
obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o
obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o
@ -145,3 +147,5 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/
obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/

View File

@ -74,7 +74,7 @@ enum dev_num {
UNKNOWN,
};
/**
* struct u1_data
* struct alps_dev
*
* @input: pointer to the kernel input device
* @input2: pointer to the kernel input2 device

View File

@ -2129,7 +2129,7 @@ struct hid_dynid {
};
/**
* store_new_id - add a new HID device ID to this driver and re-probe devices
* new_id_store - add a new HID device ID to this driver and re-probe devices
* @drv: target device driver
* @buf: buffer for scanning device ID data
* @count: input size

View File

@ -417,6 +417,7 @@ static const struct hid_usage_entry hid_usage_table[] = {
{ 0x85, 0x44, "Charging" },
{ 0x85, 0x45, "Discharging" },
{ 0x85, 0x4b, "NeedReplacement" },
{ 0x85, 0x65, "AbsoluteStateOfCharge" },
{ 0x85, 0x66, "RemainingCapacity" },
{ 0x85, 0x68, "RunTimeToEmpty" },
{ 0x85, 0x6a, "AverageTimeToFull" },

View File

@ -410,15 +410,6 @@ static int elan_start_multitouch(struct hid_device *hdev)
return 0;
}
static enum led_brightness elan_mute_led_get_brigtness(struct led_classdev *led_cdev)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hdev = to_hid_device(dev);
struct elan_drvdata *drvdata = hid_get_drvdata(hdev);
return drvdata->mute_led_state;
}
static int elan_mute_led_set_brigtness(struct led_classdev *led_cdev,
enum led_brightness value)
{
@ -445,8 +436,9 @@ static int elan_mute_led_set_brigtness(struct led_classdev *led_cdev,
kfree(dmabuf);
if (ret != ELAN_LED_REPORT_SIZE) {
hid_err(hdev, "Failed to set mute led brightness: %d\n", ret);
return ret;
if (ret != -ENODEV)
hid_err(hdev, "Failed to set mute led brightness: %d\n", ret);
return ret < 0 ? ret : -EIO;
}
drvdata->mute_led_state = led_state;
@ -459,9 +451,10 @@ static int elan_init_mute_led(struct hid_device *hdev)
struct led_classdev *mute_led = &drvdata->mute_led;
mute_led->name = "elan:red:mute";
mute_led->brightness_get = elan_mute_led_get_brigtness;
mute_led->default_trigger = "audio-mute";
mute_led->brightness_set_blocking = elan_mute_led_set_brigtness;
mute_led->max_brightness = LED_ON;
mute_led->flags = LED_HW_PLUGGABLE;
mute_led->dev = &hdev->dev;
return devm_led_classdev_register(&hdev->dev, mute_led);

1054
drivers/hid/hid-ft260.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -93,6 +93,7 @@
#define BT_VENDOR_ID_APPLE 0x004c
#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304
#define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d
#define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD 0x030e
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 0x0265
#define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e
@ -431,6 +432,7 @@
#define USB_VENDOR_ID_FUTURE_TECHNOLOGY 0x0403
#define USB_DEVICE_ID_RETRODE2 0x97c1
#define USB_DEVICE_ID_FT260 0x6030
#define USB_VENDOR_ID_ESSENTIAL_REALITY 0x0d7f
#define USB_DEVICE_ID_ESSENTIAL_REALITY_P5 0x0100
@ -808,6 +810,7 @@
#define USB_DEVICE_ID_LOGITECH_27MHZ_MOUSE_RECEIVER 0xc51b
#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER 0xc52b
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER 0xc52f
#define USB_DEVICE_ID_LOGITECH_G700_RECEIVER 0xc531
#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2 0xc532
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_2 0xc534
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1 0xc539
@ -816,8 +819,14 @@
#define USB_DEVICE_ID_SPACETRAVELLER 0xc623
#define USB_DEVICE_ID_SPACENAVIGATOR 0xc626
#define USB_DEVICE_ID_DINOVO_DESKTOP 0xc704
#define USB_DEVICE_ID_DINOVO_EDGE 0xc714
#define USB_DEVICE_ID_DINOVO_MINI 0xc71f
#define USB_DEVICE_ID_MX5000_RECEIVER_MOUSE_DEV 0xc70a
#define USB_DEVICE_ID_MX5000_RECEIVER_KBD_DEV 0xc70e
#define USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_KBD_DEV 0xc713
#define USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_MOUSE_DEV 0xc714
#define USB_DEVICE_ID_MX5500_RECEIVER_KBD_DEV 0xc71b
#define USB_DEVICE_ID_MX5500_RECEIVER_MOUSE_DEV 0xc71c
#define USB_DEVICE_ID_DINOVO_MINI_RECEIVER_KBD_DEV 0xc71e
#define USB_DEVICE_ID_DINOVO_MINI_RECEIVER_MOUSE_DEV 0xc71f
#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2 0xca03
#define USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL 0xca04
@ -946,6 +955,7 @@
#define USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S 0x8003
#define USB_VENDOR_ID_PLANTRONICS 0x047f
#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES 0xc056
#define USB_VENDOR_ID_PANASONIC 0x04da
#define USB_DEVICE_ID_PANABOARD_UBT780 0x1044

View File

@ -435,7 +435,8 @@ static int hidinput_get_battery_property(struct power_supply *psy,
return ret;
}
static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, struct hid_field *field)
static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
struct hid_field *field, bool is_percentage)
{
struct power_supply_desc *psy_desc;
struct power_supply_config psy_cfg = { .drv_data = dev, };
@ -475,7 +476,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
min = field->logical_minimum;
max = field->logical_maximum;
if (quirks & HID_BATTERY_QUIRK_PERCENT) {
if (is_percentage || (quirks & HID_BATTERY_QUIRK_PERCENT)) {
min = 0;
max = 100;
}
@ -552,7 +553,7 @@ static void hidinput_update_battery(struct hid_device *dev, int value)
}
#else /* !CONFIG_HID_BATTERY_STRENGTH */
static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
struct hid_field *field)
struct hid_field *field, bool is_percentage)
{
return 0;
}
@ -806,7 +807,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
break;
case 0x3b: /* Battery Strength */
hidinput_setup_battery(device, HID_INPUT_REPORT, field);
hidinput_setup_battery(device, HID_INPUT_REPORT, field, false);
usage->type = EV_PWR;
return;
@ -1068,7 +1069,16 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
case HID_UP_GENDEVCTRLS:
switch (usage->hid) {
case HID_DC_BATTERYSTRENGTH:
hidinput_setup_battery(device, HID_INPUT_REPORT, field);
hidinput_setup_battery(device, HID_INPUT_REPORT, field, false);
usage->type = EV_PWR;
return;
}
goto unknown;
case HID_UP_BATTERY:
switch (usage->hid) {
case HID_BAT_ABSOLUTESTATEOFCHARGE:
hidinput_setup_battery(device, HID_INPUT_REPORT, field, true);
usage->type = EV_PWR;
return;
}
@ -1672,7 +1682,7 @@ static void report_features(struct hid_device *hid)
/* Verify if Battery Strength feature is available */
if (usage->hid == HID_DC_BATTERYSTRENGTH)
hidinput_setup_battery(hid, HID_FEATURE_REPORT,
rep->field[i]);
rep->field[i], false);
if (drv->feature_mapping)
drv->feature_mapping(hid, rep->field[i], usage);

View File

@ -655,7 +655,7 @@ static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc,
}
/**
* Enable fully-functional tablet mode by setting a special feature report.
* kye_tablet_enable() - Enable fully-functional tablet mode by setting a special feature report.
*
* @hdev: HID device
*

View File

@ -33,6 +33,9 @@
#include "hid-ids.h"
/* Userspace expects F20 for mic-mute KEY_MICMUTE does not work */
#define LENOVO_KEY_MICMUTE KEY_F20
struct lenovo_drvdata {
u8 led_report[3]; /* Must be first for proper alignment */
int led_state;
@ -62,8 +65,8 @@ struct lenovo_drvdata {
#define TP10UBKBD_LED_OFF 1
#define TP10UBKBD_LED_ON 2
static void lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code,
enum led_brightness value)
static int lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code,
enum led_brightness value)
{
struct lenovo_drvdata *data = hid_get_drvdata(hdev);
int ret;
@ -75,10 +78,18 @@ static void lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code,
data->led_report[2] = value ? TP10UBKBD_LED_ON : TP10UBKBD_LED_OFF;
ret = hid_hw_raw_request(hdev, data->led_report[0], data->led_report, 3,
HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
if (ret)
hid_err(hdev, "Set LED output report error: %d\n", ret);
if (ret != 3) {
if (ret != -ENODEV)
hid_err(hdev, "Set LED output report error: %d\n", ret);
ret = ret < 0 ? ret : -EIO;
} else {
ret = 0;
}
mutex_unlock(&data->led_report_mutex);
return ret;
}
static void lenovo_tp10ubkbd_sync_fn_lock(struct work_struct *work)
@ -126,7 +137,7 @@ static int lenovo_input_mapping_tpkbd(struct hid_device *hdev,
if (usage->hid == (HID_UP_BUTTON | 0x0010)) {
/* This sub-device contains trackpoint, mark it */
hid_set_drvdata(hdev, (void *)1);
map_key_clear(KEY_MICMUTE);
map_key_clear(LENOVO_KEY_MICMUTE);
return 1;
}
return 0;
@ -141,7 +152,7 @@ static int lenovo_input_mapping_cptkbd(struct hid_device *hdev,
(usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) {
switch (usage->hid & HID_USAGE) {
case 0x00f1: /* Fn-F4: Mic mute */
map_key_clear(KEY_MICMUTE);
map_key_clear(LENOVO_KEY_MICMUTE);
return 1;
case 0x00f2: /* Fn-F5: Brightness down */
map_key_clear(KEY_BRIGHTNESSDOWN);
@ -231,7 +242,7 @@ static int lenovo_input_mapping_tp10_ultrabook_kbd(struct hid_device *hdev,
map_key_clear(KEY_FN_ESC);
return 1;
case 9: /* Fn-F4: Mic mute */
map_key_clear(KEY_MICMUTE);
map_key_clear(LENOVO_KEY_MICMUTE);
return 1;
case 10: /* Fn-F7: Control panel */
map_key_clear(KEY_CONFIG);
@ -255,6 +266,54 @@ static int lenovo_input_mapping_tp10_ultrabook_kbd(struct hid_device *hdev,
return 0;
}
static int lenovo_input_mapping_x1_tab_kbd(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
/*
* The ThinkPad X1 Tablet Thin Keyboard uses 0x000c0001 usage for
* a bunch of keys which have no standard consumer page code.
*/
if (usage->hid == 0x000c0001) {
switch (usage->usage_index) {
case 0: /* Fn-F10: Enable/disable bluetooth */
map_key_clear(KEY_BLUETOOTH);
return 1;
case 1: /* Fn-F11: Keyboard settings */
map_key_clear(KEY_KEYBOARD);
return 1;
case 2: /* Fn-F12: User function / Cortana */
map_key_clear(KEY_MACRO1);
return 1;
case 3: /* Fn-PrtSc: Snipping tool */
map_key_clear(KEY_SELECTIVE_SCREENSHOT);
return 1;
case 8: /* Fn-Esc: Fn-lock toggle */
map_key_clear(KEY_FN_ESC);
return 1;
case 9: /* Fn-F4: Mute/unmute microphone */
map_key_clear(KEY_MICMUTE);
return 1;
case 10: /* Fn-F9: Settings */
map_key_clear(KEY_CONFIG);
return 1;
case 13: /* Fn-F7: Manage external displays */
map_key_clear(KEY_SWITCHVIDEOMODE);
return 1;
case 14: /* Fn-F8: Enable/disable wifi */
map_key_clear(KEY_WLAN);
return 1;
}
}
if (usage->hid == (HID_UP_KEYBOARD | 0x009a)) {
map_key_clear(KEY_SYSRQ);
return 1;
}
return 0;
}
static int lenovo_input_mapping(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
@ -278,6 +337,8 @@ static int lenovo_input_mapping(struct hid_device *hdev,
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
return lenovo_input_mapping_tp10_ultrabook_kbd(hdev, hi, field,
usage, bit, max);
case USB_DEVICE_ID_LENOVO_X1_TAB:
return lenovo_input_mapping_x1_tab_kbd(hdev, hi, field, usage, bit, max);
default:
return 0;
}
@ -349,7 +410,7 @@ static ssize_t attr_fn_lock_store(struct device *dev,
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data = hid_get_drvdata(hdev);
int value;
int value, ret;
if (kstrtoint(buf, 10, &value))
return -EINVAL;
@ -364,7 +425,10 @@ static ssize_t attr_fn_lock_store(struct device *dev,
lenovo_features_set_cptkbd(hdev);
break;
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value);
case USB_DEVICE_ID_LENOVO_X1_TAB:
ret = lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value);
if (ret)
return ret;
break;
}
@ -498,11 +562,15 @@ static int lenovo_event_cptkbd(struct hid_device *hdev,
static int lenovo_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
if (!hid_get_drvdata(hdev))
return 0;
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_CUSBKBD:
case USB_DEVICE_ID_LENOVO_CBTKBD:
return lenovo_event_cptkbd(hdev, field, usage, value);
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
return lenovo_event_tp10ubkbd(hdev, field, usage, value);
default:
return 0;
@ -761,23 +829,7 @@ static void lenovo_led_set_tpkbd(struct hid_device *hdev)
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
}
static enum led_brightness lenovo_led_brightness_get(
struct led_classdev *led_cdev)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
int led_nr = 0;
if (led_cdev == &data_pointer->led_micmute)
led_nr = 1;
return data_pointer->led_state & (1 << led_nr)
? LED_FULL
: LED_OFF;
}
static void lenovo_led_brightness_set(struct led_classdev *led_cdev,
static int lenovo_led_brightness_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct device *dev = led_cdev->dev->parent;
@ -785,6 +837,7 @@ static void lenovo_led_brightness_set(struct led_classdev *led_cdev,
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
u8 tp10ubkbd_led[] = { TP10UBKBD_MUTE_LED, TP10UBKBD_MICMUTE_LED };
int led_nr = 0;
int ret = 0;
if (led_cdev == &data_pointer->led_micmute)
led_nr = 1;
@ -799,9 +852,12 @@ static void lenovo_led_brightness_set(struct led_classdev *led_cdev,
lenovo_led_set_tpkbd(hdev);
break;
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value);
case USB_DEVICE_ID_LENOVO_X1_TAB:
ret = lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value);
break;
}
return ret;
}
static int lenovo_register_leds(struct hid_device *hdev)
@ -821,16 +877,20 @@ static int lenovo_register_leds(struct hid_device *hdev)
snprintf(name_micm, name_sz, "%s:amber:micmute", dev_name(&hdev->dev));
data->led_mute.name = name_mute;
data->led_mute.brightness_get = lenovo_led_brightness_get;
data->led_mute.brightness_set = lenovo_led_brightness_set;
data->led_mute.default_trigger = "audio-mute";
data->led_mute.brightness_set_blocking = lenovo_led_brightness_set;
data->led_mute.max_brightness = 1;
data->led_mute.flags = LED_HW_PLUGGABLE;
data->led_mute.dev = &hdev->dev;
ret = led_classdev_register(&hdev->dev, &data->led_mute);
if (ret < 0)
return ret;
data->led_micmute.name = name_micm;
data->led_micmute.brightness_get = lenovo_led_brightness_get;
data->led_micmute.brightness_set = lenovo_led_brightness_set;
data->led_micmute.default_trigger = "audio-micmute";
data->led_micmute.brightness_set_blocking = lenovo_led_brightness_set;
data->led_micmute.max_brightness = 1;
data->led_micmute.flags = LED_HW_PLUGGABLE;
data->led_micmute.dev = &hdev->dev;
ret = led_classdev_register(&hdev->dev, &data->led_micmute);
if (ret < 0) {
@ -952,11 +1012,24 @@ static const struct attribute_group lenovo_attr_group_tp10ubkbd = {
static int lenovo_probe_tp10ubkbd(struct hid_device *hdev)
{
struct hid_report_enum *rep_enum;
struct lenovo_drvdata *data;
struct hid_report *rep;
bool found;
int ret;
/* All the custom action happens on the USBMOUSE device for USB */
if (hdev->type != HID_TYPE_USBMOUSE)
/*
* The LEDs and the Fn-lock functionality use output report 9,
* with an application of 0xffa0001, add the LEDs on the interface
* with this output report.
*/
found = false;
rep_enum = &hdev->report_enum[HID_OUTPUT_REPORT];
list_for_each_entry(rep, &rep_enum->report_list, list) {
if (rep->application == 0xffa00001)
found = true;
}
if (!found)
return 0;
data = devm_kzalloc(&hdev->dev, sizeof(*data), GFP_KERNEL);
@ -1018,6 +1091,7 @@ static int lenovo_probe(struct hid_device *hdev,
ret = lenovo_probe_cptkbd(hdev);
break;
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
ret = lenovo_probe_tp10ubkbd(hdev);
break;
default:
@ -1083,6 +1157,7 @@ static void lenovo_remove(struct hid_device *hdev)
lenovo_remove_cptkbd(hdev);
break;
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
lenovo_remove_tp10ubkbd(hdev);
break;
}
@ -1122,6 +1197,12 @@ static const struct hid_device_id lenovo_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TP10UBKBD) },
/*
* Note bind to the HID_GROUP_GENERIC group, so that we only bind to the keyboard
* part, while letting hid-multitouch.c handle the touchpad and trackpoint.
*/
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_TAB) },
{ }
};

View File

@ -568,22 +568,6 @@ static int lg_ultrax_remote_mapping(struct hid_input *hi,
return 1;
}
static int lg_dinovo_mapping(struct hid_input *hi, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
return 0;
switch (usage->hid & HID_USAGE) {
case 0x00d: lg_map_key_clear(KEY_MEDIA); break;
default:
return 0;
}
return 1;
}
static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage,
unsigned long **bit, int *max)
{
@ -668,10 +652,6 @@ static int lg_input_mapping(struct hid_device *hdev, struct hid_input *hi,
lg_ultrax_remote_mapping(hi, usage, bit, max))
return 1;
if (hdev->product == USB_DEVICE_ID_DINOVO_MINI &&
lg_dinovo_mapping(hi, usage, bit, max))
return 1;
if ((drv_data->quirks & LG_WIRELESS) && lg_wireless_mapping(hi, usage, bit, max))
return 1;
@ -879,10 +859,6 @@ static const struct hid_device_id lg_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP),
.driver_data = LG_DUPLICATE_USAGES },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE),
.driver_data = LG_DUPLICATE_USAGES },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI),
.driver_data = LG_DUPLICATE_USAGES },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD),
.driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },

View File

@ -84,6 +84,7 @@
#define STD_MOUSE BIT(2)
#define MULTIMEDIA BIT(3)
#define POWER_KEYS BIT(4)
#define KBD_MOUSE BIT(5)
#define MEDIA_CENTER BIT(8)
#define KBD_LEDS BIT(14)
/* Fake (bitnr > NUMBER_OF_HID_REPORTS) bit to track HID++ capability */
@ -117,6 +118,7 @@ enum recvr_type {
recvr_type_mouse_only,
recvr_type_27mhz,
recvr_type_bluetooth,
recvr_type_dinovo,
};
struct dj_report {
@ -333,6 +335,47 @@ static const char mse_bluetooth_descriptor[] = {
0xC0, /* END_COLLECTION */
};
/* Mouse descriptor (5) for Bluetooth receiver, normal-res hwheel, 8 buttons */
static const char mse5_bluetooth_descriptor[] = {
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
0x09, 0x02, /* Usage (Mouse) */
0xa1, 0x01, /* Collection (Application) */
0x85, 0x05, /* Report ID (5) */
0x09, 0x01, /* Usage (Pointer) */
0xa1, 0x00, /* Collection (Physical) */
0x05, 0x09, /* Usage Page (Button) */
0x19, 0x01, /* Usage Minimum (1) */
0x29, 0x08, /* Usage Maximum (8) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x01, /* Logical Maximum (1) */
0x95, 0x08, /* Report Count (8) */
0x75, 0x01, /* Report Size (1) */
0x81, 0x02, /* Input (Data,Var,Abs) */
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x16, 0x01, 0xf8, /* Logical Minimum (-2047) */
0x26, 0xff, 0x07, /* Logical Maximum (2047) */
0x75, 0x0c, /* Report Size (12) */
0x95, 0x02, /* Report Count (2) */
0x09, 0x30, /* Usage (X) */
0x09, 0x31, /* Usage (Y) */
0x81, 0x06, /* Input (Data,Var,Rel) */
0x15, 0x81, /* Logical Minimum (-127) */
0x25, 0x7f, /* Logical Maximum (127) */
0x75, 0x08, /* Report Size (8) */
0x95, 0x01, /* Report Count (1) */
0x09, 0x38, /* Usage (Wheel) */
0x81, 0x06, /* Input (Data,Var,Rel) */
0x05, 0x0c, /* Usage Page (Consumer Devices) */
0x0a, 0x38, 0x02, /* Usage (AC Pan) */
0x15, 0x81, /* Logical Minimum (-127) */
0x25, 0x7f, /* Logical Maximum (127) */
0x75, 0x08, /* Report Size (8) */
0x95, 0x01, /* Report Count (1) */
0x81, 0x06, /* Input (Data,Var,Rel) */
0xc0, /* End Collection */
0xc0, /* End Collection */
};
/* Gaming Mouse descriptor (2) */
static const char mse_high_res_descriptor[] = {
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
@ -480,6 +523,7 @@ static const char hidpp_descriptor[] = {
#define MAX_RDESC_SIZE \
(sizeof(kbd_descriptor) + \
sizeof(mse_bluetooth_descriptor) + \
sizeof(mse5_bluetooth_descriptor) + \
sizeof(consumer_descriptor) + \
sizeof(syscontrol_descriptor) + \
sizeof(media_descriptor) + \
@ -517,6 +561,11 @@ static void delayedwork_callback(struct work_struct *work);
static LIST_HEAD(dj_hdev_list);
static DEFINE_MUTEX(dj_hdev_list_lock);
static bool recvr_type_is_bluetooth(enum recvr_type type)
{
return type == recvr_type_bluetooth || type == recvr_type_dinovo;
}
/*
* dj/HID++ receivers are really a single logical entity, but for BIOS/Windows
* compatibility they have multiple USB interfaces. On HID++ receivers we need
@ -534,7 +583,7 @@ static struct dj_receiver_dev *dj_find_receiver_dev(struct hid_device *hdev,
* The bluetooth receiver contains a built-in hub and has separate
* USB-devices for the keyboard and mouse interfaces.
*/
sep = (type == recvr_type_bluetooth) ? '.' : '/';
sep = recvr_type_is_bluetooth(type) ? '.' : '/';
/* Try to find an already-probed interface from the same device */
list_for_each_entry(djrcv_dev, &dj_hdev_list, list) {
@ -872,6 +921,14 @@ static void logi_dj_recv_queue_notification(struct dj_receiver_dev *djrcv_dev,
* touchpad to work we must also forward mouse input reports to the dj_hiddev
* created for the keyboard (instead of forwarding them to a second paired
* device with a device_type of REPORT_TYPE_MOUSE as we normally would).
*
* On Dinovo receivers the keyboard's touchpad and an optional paired actual
* mouse send separate input reports, INPUT(2) aka STD_MOUSE for the mouse
* and INPUT(5) aka KBD_MOUSE for the keyboard's touchpad.
*
* On MX5x00 receivers (which can also be paired with a Dinovo keyboard)
* INPUT(2) is used for both an optional paired actual mouse and for the
* keyboard's touchpad.
*/
static const u16 kbd_builtin_touchpad_ids[] = {
0xb309, /* Dinovo Edge */
@ -898,7 +955,10 @@ static void logi_hidpp_dev_conn_notif_equad(struct hid_device *hdev,
id = (workitem->quad_id_msb << 8) | workitem->quad_id_lsb;
for (i = 0; i < ARRAY_SIZE(kbd_builtin_touchpad_ids); i++) {
if (id == kbd_builtin_touchpad_ids[i]) {
workitem->reports_supported |= STD_MOUSE;
if (djrcv_dev->type == recvr_type_dinovo)
workitem->reports_supported |= KBD_MOUSE;
else
workitem->reports_supported |= STD_MOUSE;
break;
}
}
@ -1367,7 +1427,7 @@ static int logi_dj_ll_parse(struct hid_device *hid)
else if (djdev->dj_receiver_dev->type == recvr_type_27mhz)
rdcat(rdesc, &rsize, mse_27mhz_descriptor,
sizeof(mse_27mhz_descriptor));
else if (djdev->dj_receiver_dev->type == recvr_type_bluetooth)
else if (recvr_type_is_bluetooth(djdev->dj_receiver_dev->type))
rdcat(rdesc, &rsize, mse_bluetooth_descriptor,
sizeof(mse_bluetooth_descriptor));
else
@ -1375,6 +1435,13 @@ static int logi_dj_ll_parse(struct hid_device *hid)
sizeof(mse_descriptor));
}
if (djdev->reports_supported & KBD_MOUSE) {
dbg_hid("%s: sending a kbd-mouse descriptor, reports_supported: %llx\n",
__func__, djdev->reports_supported);
rdcat(rdesc, &rsize, mse5_bluetooth_descriptor,
sizeof(mse5_bluetooth_descriptor));
}
if (djdev->reports_supported & MULTIMEDIA) {
dbg_hid("%s: sending a multimedia report descriptor: %llx\n",
__func__, djdev->reports_supported);
@ -1692,6 +1759,7 @@ static int logi_dj_probe(struct hid_device *hdev,
case recvr_type_mouse_only: no_dj_interfaces = 2; break;
case recvr_type_27mhz: no_dj_interfaces = 2; break;
case recvr_type_bluetooth: no_dj_interfaces = 2; break;
case recvr_type_dinovo: no_dj_interfaces = 2; break;
}
if (hid_is_using_ll_driver(hdev, &usb_hid_driver)) {
intf = to_usb_interface(hdev->dev.parent);
@ -1857,23 +1925,27 @@ static void logi_dj_remove(struct hid_device *hdev)
}
static const struct hid_device_id logi_dj_receivers[] = {
{HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
{ /* Logitech unifying receiver (0xc52b) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER),
.driver_data = recvr_type_dj},
{HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
{ /* Logitech unifying receiver (0xc532) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2),
.driver_data = recvr_type_dj},
{ /* Logitech Nano mouse only receiver */
{ /* Logitech Nano mouse only receiver (0xc52f) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER),
.driver_data = recvr_type_mouse_only},
{ /* Logitech Nano (non DJ) receiver */
{ /* Logitech Nano (non DJ) receiver (0xc534) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_2),
.driver_data = recvr_type_hidpp},
{ /* Logitech G700(s) receiver (0xc531) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
0xc531),
USB_DEVICE_ID_LOGITECH_G700_RECEIVER),
.driver_data = recvr_type_gaming_hidpp},
{ /* Logitech G602 receiver (0xc537) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
@ -1883,17 +1955,18 @@ static const struct hid_device_id logi_dj_receivers[] = {
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1),
.driver_data = recvr_type_gaming_hidpp},
{ /* Logitech lightspeed receiver (0xc53f) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1),
.driver_data = recvr_type_gaming_hidpp},
{ /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
.driver_data = recvr_type_27mhz},
{ /* Logitech powerplay receiver (0xc53a) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY),
.driver_data = recvr_type_gaming_hidpp},
{ /* Logitech lightspeed receiver (0xc53f) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1),
.driver_data = recvr_type_gaming_hidpp},
{ /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
.driver_data = recvr_type_27mhz},
{ /* Logitech 27 MHz HID++ 1.0 receiver (0xc517) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_S510_RECEIVER_2),
@ -1902,22 +1975,40 @@ static const struct hid_device_id logi_dj_receivers[] = {
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_27MHZ_MOUSE_RECEIVER),
.driver_data = recvr_type_27mhz},
{ /* Logitech MX5000 HID++ / bluetooth receiver keyboard intf. */
{ /* Logitech MX5000 HID++ / bluetooth receiver keyboard intf. (0xc70e) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
0xc70e),
USB_DEVICE_ID_MX5000_RECEIVER_KBD_DEV),
.driver_data = recvr_type_bluetooth},
{ /* Logitech MX5000 HID++ / bluetooth receiver mouse intf. */
{ /* Logitech MX5000 HID++ / bluetooth receiver mouse intf. (0xc70a) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
0xc70a),
USB_DEVICE_ID_MX5000_RECEIVER_MOUSE_DEV),
.driver_data = recvr_type_bluetooth},
{ /* Logitech MX5500 HID++ / bluetooth receiver keyboard intf. */
{ /* Logitech MX5500 HID++ / bluetooth receiver keyboard intf. (0xc71b) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
0xc71b),
USB_DEVICE_ID_MX5500_RECEIVER_KBD_DEV),
.driver_data = recvr_type_bluetooth},
{ /* Logitech MX5500 HID++ / bluetooth receiver mouse intf. */
{ /* Logitech MX5500 HID++ / bluetooth receiver mouse intf. (0xc71c) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
0xc71c),
USB_DEVICE_ID_MX5500_RECEIVER_MOUSE_DEV),
.driver_data = recvr_type_bluetooth},
{ /* Logitech Dinovo Edge HID++ / bluetooth receiver keyboard intf. (0xc713) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_KBD_DEV),
.driver_data = recvr_type_dinovo},
{ /* Logitech Dinovo Edge HID++ / bluetooth receiver mouse intf. (0xc714) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_MOUSE_DEV),
.driver_data = recvr_type_dinovo},
{ /* Logitech DiNovo Mini HID++ / bluetooth receiver mouse intf. (0xc71e) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_DINOVO_MINI_RECEIVER_KBD_DEV),
.driver_data = recvr_type_dinovo},
{ /* Logitech DiNovo Mini HID++ / bluetooth receiver keyboard intf. (0xc71f) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_DINOVO_MINI_RECEIVER_MOUSE_DEV),
.driver_data = recvr_type_dinovo},
{}
};

View File

@ -261,7 +261,7 @@ static int __hidpp_send_report(struct hid_device *hdev,
return ret == fields_count ? 0 : -1;
}
/**
/*
* hidpp_send_message_sync() returns 0 in case of success, and something else
* in case of a failure.
* - If ' something else' is positive, that means that an error has been raised
@ -423,7 +423,7 @@ static inline bool hidpp_report_is_connect_event(struct hidpp_device *hidpp,
(report->rap.sub_id == 0x41));
}
/**
/*
* hidpp_prefix_name() prefixes the current given name with "Logitech ".
*/
static void hidpp_prefix_name(char **name, int name_length)
@ -454,6 +454,7 @@ static void hidpp_prefix_name(char **name, int name_length)
* hidpp_scroll_counter_handle_scroll() - Send high- and low-resolution scroll
* events given a high-resolution wheel
* movement.
* @input_dev: Pointer to the input device
* @counter: a hid_scroll_counter struct describing the wheel.
* @hi_res_value: the movement of the wheel, in the mouse's high-resolution
* units.
@ -1884,7 +1885,7 @@ struct hidpp_touchpad_fw_items {
uint8_t persistent;
};
/**
/*
* send a set state command to the device by reading the current items->state
* field. items is then filled with the current state.
*/

View File

@ -16,6 +16,7 @@
#include <linux/input/mt.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include "hid-ids.h"
@ -54,6 +55,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
#define TRACKPAD2_USB_REPORT_ID 0x02
#define TRACKPAD2_BT_REPORT_ID 0x31
#define MOUSE_REPORT_ID 0x29
#define MOUSE2_REPORT_ID 0x12
#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
@ -127,6 +129,9 @@ struct magicmouse_sc {
u8 size;
} touches[16];
int tracking_ids[16];
struct hid_device *hdev;
struct delayed_work work;
};
static int magicmouse_firm_touch(struct magicmouse_sc *msc)
@ -195,7 +200,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
int id, x, y, size, orientation, touch_major, touch_minor, state, down;
int pressure = 0;
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf;
x = (tdata[1] << 28 | tdata[0] << 20) >> 20;
y = -((tdata[2] << 24 | tdata[1] << 16) >> 20);
@ -296,7 +302,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
input_report_abs(input, ABS_MT_PRESSURE, pressure);
if (report_undeciphered) {
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2)
input_event(input, EV_MSC, MSC_RAW, tdata[7]);
else if (input->id.product !=
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
@ -380,6 +387,34 @@ static int magicmouse_raw_event(struct hid_device *hdev,
* ts = data[3] >> 6 | data[4] << 2 | data[5] << 10;
*/
break;
case MOUSE2_REPORT_ID:
/* Size is either 8 or (14 + 8 * N) */
if (size != 8 && (size < 14 || (size - 14) % 8 != 0))
return 0;
npoints = (size - 14) / 8;
if (npoints > 15) {
hid_warn(hdev, "invalid size value (%d) for MOUSE2_REPORT_ID\n",
size);
return 0;
}
msc->ntouches = 0;
for (ii = 0; ii < npoints; ii++)
magicmouse_emit_touch(msc, ii, data + ii * 8 + 14);
/* When emulating three-button mode, it is important
* to have the current touch information before
* generating a click event.
*/
x = (int)((data[3] << 24) | (data[2] << 16)) >> 16;
y = (int)((data[5] << 24) | (data[4] << 16)) >> 16;
clicks = data[1];
/* The following bits provide a device specific timestamp. They
* are unused here.
*
* ts = data[11] >> 6 | data[12] << 2 | data[13] << 10;
*/
break;
case DOUBLE_REPORT_ID:
/* Sometimes the trackpad sends two touch reports in one
* packet.
@ -392,7 +427,8 @@ static int magicmouse_raw_event(struct hid_device *hdev,
return 0;
}
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
magicmouse_emit_buttons(msc, clicks & 3);
input_report_rel(input, REL_X, x);
input_report_rel(input, REL_Y, y);
@ -408,6 +444,23 @@ static int magicmouse_raw_event(struct hid_device *hdev,
return 1;
}
static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
if (msc->input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 &&
field->report->id == MOUSE2_REPORT_ID) {
/*
* magic_mouse_raw_event has done all the work. Skip hidinput.
*
* Specifically, hidinput may modify BTN_LEFT and BTN_RIGHT,
* breaking emulate_3button.
*/
return 1;
}
return 0;
}
static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
{
int error;
@ -415,7 +468,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
__set_bit(EV_KEY, input->evbit);
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
__set_bit(BTN_LEFT, input->keybit);
__set_bit(BTN_RIGHT, input->keybit);
if (emulate_3button)
@ -480,7 +534,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
* the origin at the same position, and just uses the additive
* inverse of the reported Y.
*/
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
input_set_abs_params(input, ABS_MT_POSITION_X,
MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
@ -580,19 +635,60 @@ static int magicmouse_input_configured(struct hid_device *hdev,
return 0;
}
static int magicmouse_enable_multitouch(struct hid_device *hdev)
{
const u8 *feature;
const u8 feature_mt[] = { 0xD7, 0x01 };
const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 };
const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
u8 *buf;
int ret;
int feature_size;
if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
if (hdev->vendor == BT_VENDOR_ID_APPLE) {
feature_size = sizeof(feature_mt_trackpad2_bt);
feature = feature_mt_trackpad2_bt;
} else { /* USB_VENDOR_ID_APPLE */
feature_size = sizeof(feature_mt_trackpad2_usb);
feature = feature_mt_trackpad2_usb;
}
} else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
feature_size = sizeof(feature_mt_mouse2);
feature = feature_mt_mouse2;
} else {
feature_size = sizeof(feature_mt);
feature = feature_mt;
}
buf = kmemdup(feature, feature_size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
kfree(buf);
return ret;
}
static void magicmouse_enable_mt_work(struct work_struct *work)
{
struct magicmouse_sc *msc =
container_of(work, struct magicmouse_sc, work.work);
int ret;
ret = magicmouse_enable_multitouch(msc->hdev);
if (ret < 0)
hid_err(msc->hdev, "unable to request touch data (%d)\n", ret);
}
static int magicmouse_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
const u8 *feature;
const u8 feature_mt[] = { 0xD7, 0x01 };
const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
u8 *buf;
struct magicmouse_sc *msc;
struct hid_report *report;
int ret;
int feature_size;
if (id->vendor == USB_VENDOR_ID_APPLE &&
id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
@ -606,6 +702,8 @@ static int magicmouse_probe(struct hid_device *hdev,
}
msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
msc->hdev = hdev;
INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work);
msc->quirks = id->driver_data;
hid_set_drvdata(hdev, msc);
@ -631,6 +729,9 @@ static int magicmouse_probe(struct hid_device *hdev,
if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
report = hid_register_report(hdev, HID_INPUT_REPORT,
MOUSE_REPORT_ID, 0);
else if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2)
report = hid_register_report(hdev, HID_INPUT_REPORT,
MOUSE2_REPORT_ID, 0);
else if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
if (id->vendor == BT_VENDOR_ID_APPLE)
report = hid_register_report(hdev, HID_INPUT_REPORT,
@ -652,25 +753,6 @@ static int magicmouse_probe(struct hid_device *hdev,
}
report->size = 6;
if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
if (id->vendor == BT_VENDOR_ID_APPLE) {
feature_size = sizeof(feature_mt_trackpad2_bt);
feature = feature_mt_trackpad2_bt;
} else { /* USB_VENDOR_ID_APPLE */
feature_size = sizeof(feature_mt_trackpad2_usb);
feature = feature_mt_trackpad2_usb;
}
} else {
feature_size = sizeof(feature_mt);
feature = feature_mt;
}
buf = kmemdup(feature, feature_size, GFP_KERNEL);
if (!buf) {
ret = -ENOMEM;
goto err_stop_hw;
}
/*
* Some devices repond with 'invalid report id' when feature
* report switching it into multitouch mode is sent to it.
@ -679,13 +761,14 @@ static int magicmouse_probe(struct hid_device *hdev,
* but there seems to be no other way of switching the mode.
* Thus the super-ugly hacky success check below.
*/
ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
kfree(buf);
if (ret != -EIO && ret != feature_size) {
ret = magicmouse_enable_multitouch(hdev);
if (ret != -EIO && ret < 0) {
hid_err(hdev, "unable to request touch data (%d)\n", ret);
goto err_stop_hw;
}
if (ret == -EIO && id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
}
return 0;
err_stop_hw:
@ -693,9 +776,18 @@ err_stop_hw:
return ret;
}
static void magicmouse_remove(struct hid_device *hdev)
{
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
cancel_delayed_work_sync(&msc->work);
hid_hw_stop(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(BT_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICMOUSE2), .driver_data = 0 },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICTRACKPAD), .driver_data = 0 },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
@ -710,7 +802,9 @@ static struct hid_driver magicmouse_driver = {
.name = "magicmouse",
.id_table = magic_mice,
.probe = magicmouse_probe,
.remove = magicmouse_remove,
.raw_event = magicmouse_raw_event,
.event = magicmouse_event,
.input_mapping = magicmouse_input_mapping,
.input_configured = magicmouse_input_configured,
};

View File

@ -329,7 +329,6 @@ static int picolcd_raw_event(struct hid_device *hdev,
{
struct picolcd_data *data = hid_get_drvdata(hdev);
unsigned long flags;
int ret = 0;
if (!data)
return 1;
@ -342,9 +341,9 @@ static int picolcd_raw_event(struct hid_device *hdev,
if (report->id == REPORT_KEY_STATE) {
if (data->input_keys)
ret = picolcd_raw_keypad(data, report, raw_data+1, size-1);
picolcd_raw_keypad(data, report, raw_data+1, size-1);
} else if (report->id == REPORT_IR_DATA) {
ret = picolcd_raw_cir(data, report, raw_data+1, size-1);
picolcd_raw_cir(data, report, raw_data+1, size-1);
} else {
spin_lock_irqsave(&data->lock, flags);
/*

View File

@ -13,6 +13,7 @@
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/jiffies.h>
#define PLT_HID_1_0_PAGE 0xffa00000
#define PLT_HID_2_0_PAGE 0xffa20000
@ -36,6 +37,16 @@
#define PLT_ALLOW_CONSUMER (field->application == HID_CP_CONSUMERCONTROL && \
(usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER)
#define PLT_QUIRK_DOUBLE_VOLUME_KEYS BIT(0)
#define PLT_DOUBLE_KEY_TIMEOUT 5 /* ms */
struct plt_drv_data {
unsigned long device_type;
unsigned long last_volume_key_ts;
u32 quirks;
};
static int plantronics_input_mapping(struct hid_device *hdev,
struct hid_input *hi,
struct hid_field *field,
@ -43,7 +54,8 @@ static int plantronics_input_mapping(struct hid_device *hdev,
unsigned long **bit, int *max)
{
unsigned short mapped_key;
unsigned long plt_type = (unsigned long)hid_get_drvdata(hdev);
struct plt_drv_data *drv_data = hid_get_drvdata(hdev);
unsigned long plt_type = drv_data->device_type;
/* special case for PTT products */
if (field->application == HID_GD_JOYSTICK)
@ -105,6 +117,30 @@ mapped:
return 1;
}
static int plantronics_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct plt_drv_data *drv_data = hid_get_drvdata(hdev);
if (drv_data->quirks & PLT_QUIRK_DOUBLE_VOLUME_KEYS) {
unsigned long prev_ts, cur_ts;
/* Usages are filtered in plantronics_usages. */
if (!value) /* Handle key presses only. */
return 0;
prev_ts = drv_data->last_volume_key_ts;
cur_ts = jiffies;
if (jiffies_to_msecs(cur_ts - prev_ts) <= PLT_DOUBLE_KEY_TIMEOUT)
return 1; /* Ignore the repeated key. */
drv_data->last_volume_key_ts = cur_ts;
}
return 0;
}
static unsigned long plantronics_device_type(struct hid_device *hdev)
{
unsigned i, col_page;
@ -133,15 +169,24 @@ exit:
static int plantronics_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
struct plt_drv_data *drv_data;
int ret;
drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
if (!drv_data)
return -ENOMEM;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err;
}
hid_set_drvdata(hdev, (void *)plantronics_device_type(hdev));
drv_data->device_type = plantronics_device_type(hdev);
drv_data->quirks = id->driver_data;
drv_data->last_volume_key_ts = jiffies - msecs_to_jiffies(PLT_DOUBLE_KEY_TIMEOUT);
hid_set_drvdata(hdev, drv_data);
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT |
HID_CONNECT_HIDINPUT_FORCE | HID_CONNECT_HIDDEV_FORCE);
@ -153,15 +198,26 @@ err:
}
static const struct hid_device_id plantronics_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES),
.driver_data = PLT_QUIRK_DOUBLE_VOLUME_KEYS },
{ HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) },
{ }
};
MODULE_DEVICE_TABLE(hid, plantronics_devices);
static const struct hid_usage_id plantronics_usages[] = {
{ HID_CP_VOLUMEUP, EV_KEY, HID_ANY_ID },
{ HID_CP_VOLUMEDOWN, EV_KEY, HID_ANY_ID },
{ HID_TERMINATOR, HID_TERMINATOR, HID_TERMINATOR }
};
static struct hid_driver plantronics_driver = {
.name = "plantronics",
.id_table = plantronics_devices,
.usage_table = plantronics_usages,
.input_mapping = plantronics_input_mapping,
.event = plantronics_event,
.probe = plantronics_probe,
};
module_hid_driver(plantronics_driver);

View File

@ -445,8 +445,6 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD) },
{ 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) },
@ -661,6 +659,9 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb654) },
{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb65a) },
#endif
#if IS_ENABLED(CONFIG_HID_TMINIT)
{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb65d) },
#endif
#if IS_ENABLED(CONFIG_HID_TIVO)
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_BT) },
{ HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE) },

View File

@ -397,15 +397,14 @@ static ssize_t store_value(struct device *dev, struct device_attribute *attr,
if (!strncmp(name, "value", strlen("value"))) {
u32 report_id;
int ret;
if (kstrtoint(buf, 0, &value) != 0)
return -EINVAL;
report_id = sensor_inst->fields[field_index].attribute.
report_id;
ret = sensor_hub_set_feature(sensor_inst->hsdev, report_id,
index, sizeof(value), &value);
sensor_hub_set_feature(sensor_inst->hsdev, report_id,
index, sizeof(value), &value);
} else
return -EINVAL;

View File

@ -18,7 +18,6 @@
/**
* struct sensor_hub_data - Hold a instance data for a HID hub device
* @hsdev: Stored hid instance for current hub device.
* @mutex: Mutex to serialize synchronous request.
* @lock: Spin lock to protect pending request structure.
* @dyn_callback_list: Holds callback function
@ -34,7 +33,6 @@ struct sensor_hub_data {
spinlock_t dyn_callback_lock;
struct mfd_cell *hid_sensor_hub_client_devs;
int hid_sensor_client_cnt;
unsigned long quirks;
int ref_cnt;
};
@ -42,6 +40,7 @@ struct sensor_hub_data {
* struct hid_sensor_hub_callbacks_list - Stores callback list
* @list: list head.
* @usage_id: usage id for a physical device.
* @hsdev: Stored hid instance for current hub device.
* @usage_callback: Stores registered callback functions.
* @priv: Private data for a physical device.
*/
@ -615,7 +614,6 @@ static int sensor_hub_probe(struct hid_device *hdev,
}
hid_set_drvdata(hdev, sd);
sd->quirks = id->driver_data;
spin_lock_init(&sd->lock);
spin_lock_init(&sd->dyn_callback_lock);

View File

@ -0,0 +1,371 @@
// SPDX-License-Identifier: GPL-2.0
/*
* When connected to the machine, the Thrustmaster wheels appear as
* a «generic» hid gamepad called "Thrustmaster FFB Wheel".
*
* When in this mode not every functionality of the wheel, like the force feedback,
* are available. To enable all functionalities of a Thrustmaster wheel we have to send
* to it a specific USB CONTROL request with a code different for each wheel.
*
* This driver tries to understand which model of Thrustmaster wheel the generic
* "Thrustmaster FFB Wheel" really is and then sends the appropriate control code.
*
* Copyright (c) 2020-2021 Dario Pagani <dario.pagani.146+linuxk@gmail.com>
* Copyright (c) 2020-2021 Kim Kuparinen <kimi.h.kuparinen@gmail.com>
*/
#include <linux/hid.h>
#include <linux/usb.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/module.h>
/*
* These interrupts are used to prevent a nasty crash when initializing the
* T300RS. Used in thrustmaster_interrupts().
*/
static const u8 setup_0[] = { 0x42, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const u8 setup_1[] = { 0x0a, 0x04, 0x90, 0x03, 0x00, 0x00, 0x00, 0x00 };
static const u8 setup_2[] = { 0x0a, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00 };
static const u8 setup_3[] = { 0x0a, 0x04, 0x12, 0x10, 0x00, 0x00, 0x00, 0x00 };
static const u8 setup_4[] = { 0x0a, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00 };
static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4 };
static const unsigned int setup_arr_sizes[] = {
ARRAY_SIZE(setup_0),
ARRAY_SIZE(setup_1),
ARRAY_SIZE(setup_2),
ARRAY_SIZE(setup_3),
ARRAY_SIZE(setup_4)
};
/*
* This struct contains for each type of
* Thrustmaster wheel
*
* Note: The values are stored in the CPU
* endianness, the USB protocols always use
* little endian; the macro cpu_to_le[BIT]()
* must be used when preparing USB packets
* and vice-versa
*/
struct tm_wheel_info {
uint16_t wheel_type;
/*
* See when the USB control out packet is prepared...
* @TODO The TMX seems to require multiple control codes to switch.
*/
uint16_t switch_value;
char const *const wheel_name;
};
/*
* Known wheels.
* Note: TMX does not work as it requires 2 control packets
*/
static const struct tm_wheel_info tm_wheels_infos[] = {
{0x0306, 0x0006, "Thrustmaster T150RS"},
{0x0206, 0x0005, "Thrustmaster T300RS"},
{0x0204, 0x0005, "Thrustmaster T300 Ferrari Alcantara Edition"},
{0x0002, 0x0002, "Thrustmaster T500RS"}
//{0x0407, 0x0001, "Thrustmaster TMX"}
};
static const uint8_t tm_wheels_infos_length = 4;
/*
* This structs contains (in little endian) the response data
* of the wheel to the request 73
*
* A sufficient research to understand what each field does is not
* beign conducted yet. The position and meaning of fields are a
* just a very optimistic guess based on instinct....
*/
struct __packed tm_wheel_response
{
/*
* Seems to be the type of packet
* - 0x0049 if is data.a (15 bytes)
* - 0x0047 if is data.b (7 bytes)
*/
uint16_t type;
union {
struct __packed {
uint16_t field0;
uint16_t field1;
/*
* Seems to be the model code of the wheel
* Read table thrustmaster_wheels to values
*/
uint16_t model;
uint16_t field2;
uint16_t field3;
uint16_t field4;
uint16_t field5;
} a;
struct __packed {
uint16_t field0;
uint16_t field1;
uint16_t model;
} b;
} data;
};
struct tm_wheel {
struct usb_device *usb_dev;
struct urb *urb;
struct usb_ctrlrequest *model_request;
struct tm_wheel_response *response;
struct usb_ctrlrequest *change_request;
};
/* The control packet to send to wheel */
static const struct usb_ctrlrequest model_request = {
.bRequestType = 0xc1,
.bRequest = 73,
.wValue = 0,
.wIndex = 0,
.wLength = cpu_to_le16(0x0010)
};
static const struct usb_ctrlrequest change_request = {
.bRequestType = 0x41,
.bRequest = 83,
.wValue = 0, // Will be filled by the driver
.wIndex = 0,
.wLength = 0
};
/*
* On some setups initializing the T300RS crashes the kernel,
* these interrupts fix that particular issue. So far they haven't caused any
* adverse effects in other wheels.
*/
static void thrustmaster_interrupts(struct hid_device *hdev)
{
int ret, trans, i, b_ep;
u8 *send_buf = kmalloc(256, GFP_KERNEL);
struct usb_host_endpoint *ep;
struct device *dev = &hdev->dev;
struct usb_interface *usbif = to_usb_interface(dev->parent);
struct usb_device *usbdev = interface_to_usbdev(usbif);
if (!send_buf) {
hid_err(hdev, "failed allocating send buffer\n");
return;
}
ep = &usbif->cur_altsetting->endpoint[1];
b_ep = ep->desc.bEndpointAddress;
for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) {
memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]);
ret = usb_interrupt_msg(usbdev,
usb_sndintpipe(usbdev, b_ep),
send_buf,
setup_arr_sizes[i],
&trans,
USB_CTRL_SET_TIMEOUT);
if (ret) {
hid_err(hdev, "setup data couldn't be sent\n");
return;
}
}
kfree(send_buf);
}
static void thrustmaster_change_handler(struct urb *urb)
{
struct hid_device *hdev = urb->context;
// The wheel seems to kill himself before answering the host and therefore is violating the USB protocol...
if (urb->status == 0 || urb->status == -EPROTO || urb->status == -EPIPE)
hid_info(hdev, "Success?! The wheel should have been initialized!\n");
else
hid_warn(hdev, "URB to change wheel mode seems to have failed with error %d\n", urb->status);
}
/*
* Called by the USB subsystem when the wheel responses to our request
* to get [what it seems to be] the wheel's model.
*
* If the model id is recognized then we send an opportune USB CONTROL REQUEST
* to switch the wheel to its full capabilities
*/
static void thrustmaster_model_handler(struct urb *urb)
{
struct hid_device *hdev = urb->context;
struct tm_wheel *tm_wheel = hid_get_drvdata(hdev);
uint16_t model = 0;
int i, ret;
const struct tm_wheel_info *twi = 0;
if (urb->status) {
hid_err(hdev, "URB to get model id failed with error %d\n", urb->status);
return;
}
if (tm_wheel->response->type == cpu_to_le16(0x49))
model = le16_to_cpu(tm_wheel->response->data.a.model);
else if (tm_wheel->response->type == cpu_to_le16(0x47))
model = le16_to_cpu(tm_wheel->response->data.b.model);
else {
hid_err(hdev, "Unknown packet type 0x%x, unable to proceed further with wheel init\n", tm_wheel->response->type);
return;
}
for (i = 0; i < tm_wheels_infos_length && !twi; i++)
if (tm_wheels_infos[i].wheel_type == model)
twi = tm_wheels_infos + i;
if (twi)
hid_info(hdev, "Wheel with model id 0x%x is a %s\n", model, twi->wheel_name);
else {
hid_err(hdev, "Unknown wheel's model id 0x%x, unable to proceed further with wheel init\n", model);
return;
}
tm_wheel->change_request->wValue = cpu_to_le16(twi->switch_value);
usb_fill_control_urb(
tm_wheel->urb,
tm_wheel->usb_dev,
usb_sndctrlpipe(tm_wheel->usb_dev, 0),
(char *)tm_wheel->change_request,
0, 0, // We do not expect any response from the wheel
thrustmaster_change_handler,
hdev
);
ret = usb_submit_urb(tm_wheel->urb, GFP_ATOMIC);
if (ret)
hid_err(hdev, "Error %d while submitting the change URB. I am unable to initialize this wheel...\n", ret);
}
static void thrustmaster_remove(struct hid_device *hdev)
{
struct tm_wheel *tm_wheel = hid_get_drvdata(hdev);
usb_kill_urb(tm_wheel->urb);
kfree(tm_wheel->response);
kfree(tm_wheel->model_request);
usb_free_urb(tm_wheel->urb);
kfree(tm_wheel);
hid_hw_stop(hdev);
}
/*
* Function called by HID when a hid Thrustmaster FFB wheel is connected to the host.
* This function starts the hid dev, tries to allocate the tm_wheel data structure and
* finally send an USB CONTROL REQUEST to the wheel to get [what it seems to be] its
* model type.
*/
static int thrustmaster_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret = 0;
struct tm_wheel *tm_wheel = 0;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed with error %d\n", ret);
goto error0;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret) {
hid_err(hdev, "hw start failed with error %d\n", ret);
goto error0;
}
// Now we allocate the tm_wheel
tm_wheel = kzalloc(sizeof(struct tm_wheel), GFP_KERNEL);
if (!tm_wheel) {
ret = -ENOMEM;
goto error1;
}
tm_wheel->urb = usb_alloc_urb(0, GFP_ATOMIC);
if (!tm_wheel->urb) {
ret = -ENOMEM;
goto error2;
}
tm_wheel->model_request = kmemdup(&model_request,
sizeof(struct usb_ctrlrequest),
GFP_KERNEL);
if (!tm_wheel->model_request) {
ret = -ENOMEM;
goto error3;
}
tm_wheel->response = kzalloc(sizeof(struct tm_wheel_response), GFP_KERNEL);
if (!tm_wheel->response) {
ret = -ENOMEM;
goto error4;
}
tm_wheel->change_request = kzalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
if (!tm_wheel->model_request) {
ret = -ENOMEM;
goto error5;
}
memcpy(tm_wheel->change_request, &change_request, sizeof(struct usb_ctrlrequest));
tm_wheel->usb_dev = interface_to_usbdev(to_usb_interface(hdev->dev.parent));
hid_set_drvdata(hdev, tm_wheel);
thrustmaster_interrupts(hdev);
usb_fill_control_urb(
tm_wheel->urb,
tm_wheel->usb_dev,
usb_rcvctrlpipe(tm_wheel->usb_dev, 0),
(char *)tm_wheel->model_request,
tm_wheel->response,
sizeof(struct tm_wheel_response),
thrustmaster_model_handler,
hdev
);
ret = usb_submit_urb(tm_wheel->urb, GFP_ATOMIC);
if (ret)
hid_err(hdev, "Error %d while submitting the URB. I am unable to initialize this wheel...\n", ret);
return ret;
error5: kfree(tm_wheel->response);
error4: kfree(tm_wheel->model_request);
error3: usb_free_urb(tm_wheel->urb);
error2: kfree(tm_wheel);
error1: hid_hw_stop(hdev);
error0:
return ret;
}
static const struct hid_device_id thrustmaster_devices[] = {
{ HID_USB_DEVICE(0x044f, 0xb65d)},
{}
};
MODULE_DEVICE_TABLE(hid, thrustmaster_devices);
static struct hid_driver thrustmaster_driver = {
.name = "hid-thrustmaster",
.id_table = thrustmaster_devices,
.probe = thrustmaster_probe,
.remove = thrustmaster_remove,
};
module_hid_driver(thrustmaster_driver);
MODULE_AUTHOR("Dario Pagani <dario.pagani.146+linuxk@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Driver to initialize some steering wheel joysticks from Thrustmaster");

View File

@ -21,7 +21,8 @@
#include <asm/unaligned.h>
/**
* Convert a pen in-range reporting type to a string.
* uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type
* to a string.
*
* @inrange: The in-range reporting type to convert.
*
@ -516,7 +517,8 @@ void uclogic_params_cleanup(struct uclogic_params *params)
}
/**
* Get a replacement report descriptor for a tablet's interface.
* uclogic_params_get_desc() - Get a replacement report descriptor for a
* tablet's interface.
*
* @params: The parameters of a tablet interface to get report
* descriptor for. Cannot be NULL.
@ -689,7 +691,7 @@ static void uclogic_params_init_with_pen_unused(struct uclogic_params *params)
}
/**
* uclogic_params_init() - initialize a Huion tablet interface and discover
* uclogic_params_huion_init() - initialize a Huion tablet interface and discover
* its parameters.
*
* @params: Parameters to fill in (to be cleaned with

View File

@ -641,7 +641,7 @@ const __u8 uclogic_rdesc_pen_v2_template_arr[] = {
const size_t uclogic_rdesc_pen_v2_template_size =
sizeof(uclogic_rdesc_pen_v2_template_arr);
/**
/*
* Expand to the contents of a generic buttonpad report descriptor.
*
* @_padding: Padding from the end of button bits at bit 44, until

View File

@ -25,12 +25,13 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/uuid.h>
#include "i2c-hid.h"
struct i2c_hid_acpi {
struct i2chid_ops ops;
struct i2c_client *client;
struct acpi_device *adev;
};
static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
@ -42,21 +43,16 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
{ },
};
static int i2c_hid_acpi_get_descriptor(struct i2c_client *client)
{
static guid_t i2c_hid_guid =
GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
union acpi_object *obj;
struct acpi_device *adev;
acpi_handle handle;
u16 hid_descriptor_address;
/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */
static guid_t i2c_hid_guid =
GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
handle = ACPI_HANDLE(&client->dev);
if (!handle || acpi_bus_get_device(handle, &adev)) {
dev_err(&client->dev, "Error could not get ACPI device\n");
return -ENODEV;
}
static int i2c_hid_acpi_get_descriptor(struct acpi_device *adev)
{
acpi_handle handle = acpi_device_handle(adev);
union acpi_object *obj;
u16 hid_descriptor_address;
if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
return -ENODEV;
@ -64,7 +60,7 @@ static int i2c_hid_acpi_get_descriptor(struct i2c_client *client)
obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL,
ACPI_TYPE_INTEGER);
if (!obj) {
dev_err(&client->dev, "Error _DSM call to get HID descriptor address failed\n");
acpi_handle_err(handle, "Error _DSM call to get HID descriptor address failed\n");
return -ENODEV;
}
@ -76,14 +72,12 @@ static int i2c_hid_acpi_get_descriptor(struct i2c_client *client)
static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
{
struct i2c_hid_acpi *ihid_acpi =
container_of(ops, struct i2c_hid_acpi, ops);
struct device *dev = &ihid_acpi->client->dev;
acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D3_COLD);
struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
acpi_device_set_power(ihid_acpi->adev, ACPI_STATE_D3_COLD);
}
static int i2c_hid_acpi_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
static int i2c_hid_acpi_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct i2c_hid_acpi *ihid_acpi;
@ -91,21 +85,25 @@ static int i2c_hid_acpi_probe(struct i2c_client *client,
u16 hid_descriptor_address;
int ret;
adev = ACPI_COMPANION(dev);
if (!adev) {
dev_err(&client->dev, "Error could not get ACPI device\n");
return -ENODEV;
}
ihid_acpi = devm_kzalloc(&client->dev, sizeof(*ihid_acpi), GFP_KERNEL);
if (!ihid_acpi)
return -ENOMEM;
ihid_acpi->client = client;
ihid_acpi->adev = adev;
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
ret = i2c_hid_acpi_get_descriptor(client);
ret = i2c_hid_acpi_get_descriptor(adev);
if (ret < 0)
return ret;
hid_descriptor_address = ret;
adev = ACPI_COMPANION(dev);
if (adev)
acpi_device_fix_up_power(adev);
acpi_device_fix_up_power(adev);
if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) {
device_set_wakeup_capable(dev, true);
@ -128,10 +126,10 @@ static struct i2c_driver i2c_hid_acpi_driver = {
.name = "i2c_hid_acpi",
.pm = &i2c_hid_core_pm,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
.acpi_match_table = ACPI_PTR(i2c_hid_acpi_match),
.acpi_match_table = i2c_hid_acpi_match,
},
.probe = i2c_hid_acpi_probe,
.probe_new = i2c_hid_acpi_probe,
.remove = i2c_hid_core_remove,
.shutdown = i2c_hid_core_shutdown,
};

View File

@ -0,0 +1,42 @@
# SPDX-License-Identifier: GPL-2.0+
menu "Surface System Aggregator Module HID support"
depends on SURFACE_AGGREGATOR
depends on INPUT
config SURFACE_HID
tristate "HID transport driver for Surface System Aggregator Module"
depends on SURFACE_AGGREGATOR_REGISTRY
select SURFACE_HID_CORE
help
Driver to support integrated HID devices on newer Microsoft Surface
models.
This driver provides support for the HID transport protocol provided
by the Surface Aggregator Module (i.e. the embedded controller) on
7th-generation Microsoft Surface devices, i.e. Surface Book 3 and
Surface Laptop 3. On those models, it is mainly used to connect the
integrated touchpad and keyboard.
Say M or Y here, if you want support for integrated HID devices, i.e.
integrated touchpad and keyboard, on 7th generation Microsoft Surface
models.
config SURFACE_KBD
tristate "HID keyboard transport driver for Surface System Aggregator Module"
select SURFACE_HID_CORE
help
Driver to support HID keyboards on Surface Laptop 1 and 2 devices.
This driver provides support for the HID transport protocol provided
by the Surface Aggregator Module (i.e. the embedded controller) on
Microsoft Surface Laptops 1 and 2. It is used to connect the
integrated keyboard on those devices.
Say M or Y here, if you want support for the integrated keyboard on
Microsoft Surface Laptops 1 and 2.
endmenu
config SURFACE_HID_CORE
tristate
select HID

View File

@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Makefile - Surface System Aggregator Module (SSAM) HID transport driver.
#
obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o
obj-$(CONFIG_SURFACE_HID) += surface_hid.o
obj-$(CONFIG_SURFACE_KBD) += surface_kbd.o

View File

@ -0,0 +1,253 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Surface System Aggregator Module (SSAM) HID transport driver for the
* generic HID interface (HID/TC=0x15 subsystem). Provides support for
* integrated HID devices on Surface Laptop 3, Book 3, and later.
*
* Copyright (C) 2019-2021 Blaž Hrastnik <blaz@mxxn.io>,
* Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <asm/unaligned.h>
#include <linux/hid.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/surface_aggregator/controller.h>
#include <linux/surface_aggregator/device.h>
#include "surface_hid_core.h"
/* -- SAM interface. -------------------------------------------------------- */
struct surface_hid_buffer_slice {
__u8 entry;
__le32 offset;
__le32 length;
__u8 end;
__u8 data[];
} __packed;
static_assert(sizeof(struct surface_hid_buffer_slice) == 10);
enum surface_hid_cid {
SURFACE_HID_CID_OUTPUT_REPORT = 0x01,
SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02,
SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03,
SURFACE_HID_CID_GET_DESCRIPTOR = 0x04,
};
static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
{
u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76];
struct surface_hid_buffer_slice *slice;
struct ssam_request rqst;
struct ssam_response rsp;
u32 buffer_len, offset, length;
int status;
/*
* Note: The 0x76 above has been chosen because that's what's used by
* the Windows driver. Together with the header, this leads to a 128
* byte payload in total.
*/
buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice);
rqst.target_category = shid->uid.category;
rqst.target_id = shid->uid.target;
rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR;
rqst.instance_id = shid->uid.instance;
rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
rqst.length = sizeof(struct surface_hid_buffer_slice);
rqst.payload = buffer;
rsp.capacity = ARRAY_SIZE(buffer);
rsp.pointer = buffer;
slice = (struct surface_hid_buffer_slice *)buffer;
slice->entry = entry;
slice->end = 0;
offset = 0;
length = buffer_len;
while (!slice->end && offset < len) {
put_unaligned_le32(offset, &slice->offset);
put_unaligned_le32(length, &slice->length);
rsp.length = 0;
status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp,
sizeof(*slice));
if (status)
return status;
offset = get_unaligned_le32(&slice->offset);
length = get_unaligned_le32(&slice->length);
/* Don't mess stuff up in case we receive garbage. */
if (length > buffer_len || offset > len)
return -EPROTO;
if (offset + length > len)
length = len - offset;
memcpy(buf + offset, &slice->data[0], length);
offset += length;
length = buffer_len;
}
if (offset != len) {
dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n",
offset, len);
return -EPROTO;
}
return 0;
}
static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature,
u8 *buf, size_t len)
{
struct ssam_request rqst;
u8 cid;
if (feature)
cid = SURFACE_HID_CID_SET_FEATURE_REPORT;
else
cid = SURFACE_HID_CID_OUTPUT_REPORT;
rqst.target_category = shid->uid.category;
rqst.target_id = shid->uid.target;
rqst.instance_id = shid->uid.instance;
rqst.command_id = cid;
rqst.flags = 0;
rqst.length = len;
rqst.payload = buf;
buf[0] = rprt_id;
return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL);
}
static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
{
struct ssam_request rqst;
struct ssam_response rsp;
rqst.target_category = shid->uid.category;
rqst.target_id = shid->uid.target;
rqst.instance_id = shid->uid.instance;
rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT;
rqst.flags = 0;
rqst.length = sizeof(rprt_id);
rqst.payload = &rprt_id;
rsp.capacity = len;
rsp.length = 0;
rsp.pointer = buf;
return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id));
}
static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
{
struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
if (event->command_id != 0x00)
return 0;
hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
return SSAM_NOTIF_HANDLED;
}
/* -- Transport driver. ----------------------------------------------------- */
static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
{
int status;
status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len);
return status >= 0 ? len : status;
}
static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
{
int status;
status = ssam_hid_get_raw_report(shid, rprt_id, buf, len);
return status >= 0 ? len : status;
}
static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
{
int status;
status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len);
return status >= 0 ? len : status;
}
/* -- Driver setup. --------------------------------------------------------- */
static int surface_hid_probe(struct ssam_device *sdev)
{
struct surface_hid_device *shid;
shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL);
if (!shid)
return -ENOMEM;
shid->dev = &sdev->dev;
shid->ctrl = sdev->ctrl;
shid->uid = sdev->uid;
shid->notif.base.priority = 1;
shid->notif.base.fn = ssam_hid_event_fn;
shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG;
shid->notif.event.id.target_category = sdev->uid.category;
shid->notif.event.id.instance = sdev->uid.instance;
shid->notif.event.mask = SSAM_EVENT_MASK_STRICT;
shid->notif.event.flags = 0;
shid->ops.get_descriptor = ssam_hid_get_descriptor;
shid->ops.output_report = shid_output_report;
shid->ops.get_feature_report = shid_get_feature_report;
shid->ops.set_feature_report = shid_set_feature_report;
ssam_device_set_drvdata(sdev, shid);
return surface_hid_device_add(shid);
}
static void surface_hid_remove(struct ssam_device *sdev)
{
surface_hid_device_destroy(ssam_device_get_drvdata(sdev));
}
static const struct ssam_device_id surface_hid_match[] = {
{ SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) },
{ },
};
MODULE_DEVICE_TABLE(ssam, surface_hid_match);
static struct ssam_device_driver surface_hid_driver = {
.probe = surface_hid_probe,
.remove = surface_hid_remove,
.match_table = surface_hid_match,
.driver = {
.name = "surface_hid",
.pm = &surface_hid_pm_ops,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
module_ssam_device_driver(surface_hid_driver);
MODULE_AUTHOR("Blaž Hrastnik <blaz@mxxn.io>");
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,272 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Common/core components for the Surface System Aggregator Module (SSAM) HID
* transport driver. Provides support for integrated HID devices on Microsoft
* Surface models.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <asm/unaligned.h>
#include <linux/hid.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/usb/ch9.h>
#include <linux/surface_aggregator/controller.h>
#include "surface_hid_core.h"
/* -- Device descriptor access. --------------------------------------------- */
static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid)
{
int status;
status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID,
(u8 *)&shid->hid_desc, sizeof(shid->hid_desc));
if (status)
return status;
if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) {
dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n",
shid->hid_desc.desc_len, sizeof(shid->hid_desc));
return -EPROTO;
}
if (shid->hid_desc.desc_type != HID_DT_HID) {
dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n",
shid->hid_desc.desc_type, HID_DT_HID);
return -EPROTO;
}
if (shid->hid_desc.num_descriptors != 1) {
dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n",
shid->hid_desc.num_descriptors);
return -EPROTO;
}
if (shid->hid_desc.report_desc_type != HID_DT_REPORT) {
dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n",
shid->hid_desc.report_desc_type, HID_DT_REPORT);
return -EPROTO;
}
return 0;
}
static int surface_hid_load_device_attributes(struct surface_hid_device *shid)
{
int status;
status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS,
(u8 *)&shid->attrs, sizeof(shid->attrs));
if (status)
return status;
if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) {
dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n",
get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs));
return -EPROTO;
}
return 0;
}
/* -- Transport driver (common). -------------------------------------------- */
static int surface_hid_start(struct hid_device *hid)
{
struct surface_hid_device *shid = hid->driver_data;
return ssam_notifier_register(shid->ctrl, &shid->notif);
}
static void surface_hid_stop(struct hid_device *hid)
{
struct surface_hid_device *shid = hid->driver_data;
/* Note: This call will log errors for us, so ignore them here. */
ssam_notifier_unregister(shid->ctrl, &shid->notif);
}
static int surface_hid_open(struct hid_device *hid)
{
return 0;
}
static void surface_hid_close(struct hid_device *hid)
{
}
static int surface_hid_parse(struct hid_device *hid)
{
struct surface_hid_device *shid = hid->driver_data;
size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len);
u8 *buf;
int status;
buf = kzalloc(len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len);
if (!status)
status = hid_parse_report(hid, buf, len);
kfree(buf);
return status;
}
static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf,
size_t len, unsigned char rtype, int reqtype)
{
struct surface_hid_device *shid = hid->driver_data;
if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT)
return shid->ops.output_report(shid, reportnum, buf, len);
else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT)
return shid->ops.get_feature_report(shid, reportnum, buf, len);
else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT)
return shid->ops.set_feature_report(shid, reportnum, buf, len);
return -EIO;
}
static struct hid_ll_driver surface_hid_ll_driver = {
.start = surface_hid_start,
.stop = surface_hid_stop,
.open = surface_hid_open,
.close = surface_hid_close,
.parse = surface_hid_parse,
.raw_request = surface_hid_raw_request,
};
/* -- Common device setup. -------------------------------------------------- */
int surface_hid_device_add(struct surface_hid_device *shid)
{
int status;
status = surface_hid_load_hid_descriptor(shid);
if (status)
return status;
status = surface_hid_load_device_attributes(shid);
if (status)
return status;
shid->hid = hid_allocate_device();
if (IS_ERR(shid->hid))
return PTR_ERR(shid->hid);
shid->hid->dev.parent = shid->dev;
shid->hid->bus = BUS_HOST;
shid->hid->vendor = cpu_to_le16(shid->attrs.vendor);
shid->hid->product = cpu_to_le16(shid->attrs.product);
shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version);
shid->hid->country = shid->hid_desc.country_code;
snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
shid->hid->vendor, shid->hid->product);
strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys));
shid->hid->driver_data = shid;
shid->hid->ll_driver = &surface_hid_ll_driver;
status = hid_add_device(shid->hid);
if (status)
hid_destroy_device(shid->hid);
return status;
}
EXPORT_SYMBOL_GPL(surface_hid_device_add);
void surface_hid_device_destroy(struct surface_hid_device *shid)
{
hid_destroy_device(shid->hid);
}
EXPORT_SYMBOL_GPL(surface_hid_device_destroy);
/* -- PM ops. --------------------------------------------------------------- */
#ifdef CONFIG_PM_SLEEP
static int surface_hid_suspend(struct device *dev)
{
struct surface_hid_device *d = dev_get_drvdata(dev);
if (d->hid->driver && d->hid->driver->suspend)
return d->hid->driver->suspend(d->hid, PMSG_SUSPEND);
return 0;
}
static int surface_hid_resume(struct device *dev)
{
struct surface_hid_device *d = dev_get_drvdata(dev);
if (d->hid->driver && d->hid->driver->resume)
return d->hid->driver->resume(d->hid);
return 0;
}
static int surface_hid_freeze(struct device *dev)
{
struct surface_hid_device *d = dev_get_drvdata(dev);
if (d->hid->driver && d->hid->driver->suspend)
return d->hid->driver->suspend(d->hid, PMSG_FREEZE);
return 0;
}
static int surface_hid_poweroff(struct device *dev)
{
struct surface_hid_device *d = dev_get_drvdata(dev);
if (d->hid->driver && d->hid->driver->suspend)
return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE);
return 0;
}
static int surface_hid_restore(struct device *dev)
{
struct surface_hid_device *d = dev_get_drvdata(dev);
if (d->hid->driver && d->hid->driver->reset_resume)
return d->hid->driver->reset_resume(d->hid);
return 0;
}
const struct dev_pm_ops surface_hid_pm_ops = {
.freeze = surface_hid_freeze,
.thaw = surface_hid_resume,
.suspend = surface_hid_suspend,
.resume = surface_hid_resume,
.poweroff = surface_hid_poweroff,
.restore = surface_hid_restore,
};
EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
#else /* CONFIG_PM_SLEEP */
const struct dev_pm_ops surface_hid_pm_ops = { };
EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
#endif /* CONFIG_PM_SLEEP */
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Common/core components for the Surface System Aggregator Module (SSAM) HID
* transport driver. Provides support for integrated HID devices on Microsoft
* Surface models.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef SURFACE_HID_CORE_H
#define SURFACE_HID_CORE_H
#include <linux/hid.h>
#include <linux/pm.h>
#include <linux/types.h>
#include <linux/surface_aggregator/controller.h>
#include <linux/surface_aggregator/device.h>
enum surface_hid_descriptor_entry {
SURFACE_HID_DESC_HID = 0,
SURFACE_HID_DESC_REPORT = 1,
SURFACE_HID_DESC_ATTRS = 2,
};
struct surface_hid_descriptor {
__u8 desc_len; /* = 9 */
__u8 desc_type; /* = HID_DT_HID */
__le16 hid_version;
__u8 country_code;
__u8 num_descriptors; /* = 1 */
__u8 report_desc_type; /* = HID_DT_REPORT */
__le16 report_desc_len;
} __packed;
static_assert(sizeof(struct surface_hid_descriptor) == 9);
struct surface_hid_attributes {
__le32 length;
__le16 vendor;
__le16 product;
__le16 version;
__u8 _unknown[22];
} __packed;
static_assert(sizeof(struct surface_hid_attributes) == 32);
struct surface_hid_device;
struct surface_hid_device_ops {
int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len);
int (*output_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
int (*get_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
int (*set_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
};
struct surface_hid_device {
struct device *dev;
struct ssam_controller *ctrl;
struct ssam_device_uid uid;
struct surface_hid_descriptor hid_desc;
struct surface_hid_attributes attrs;
struct ssam_event_notifier notif;
struct hid_device *hid;
struct surface_hid_device_ops ops;
};
int surface_hid_device_add(struct surface_hid_device *shid);
void surface_hid_device_destroy(struct surface_hid_device *shid);
extern const struct dev_pm_ops surface_hid_pm_ops;
#endif /* SURFACE_HID_CORE_H */

View File

@ -0,0 +1,300 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Surface System Aggregator Module (SSAM) HID transport driver for the legacy
* keyboard interface (KBD/TC=0x08 subsystem). Provides support for the
* integrated HID keyboard on Surface Laptops 1 and 2.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <asm/unaligned.h>
#include <linux/hid.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/surface_aggregator/controller.h>
#include "surface_hid_core.h"
/* -- SAM interface (KBD). -------------------------------------------------- */
#define KBD_FEATURE_REPORT_SIZE 7 /* 6 + report ID */
enum surface_kbd_cid {
SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00,
SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01,
SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03,
SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04,
SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b,
};
static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
{
struct ssam_request rqst;
struct ssam_response rsp;
int status;
rqst.target_category = shid->uid.category;
rqst.target_id = shid->uid.target;
rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR;
rqst.instance_id = shid->uid.instance;
rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
rqst.length = sizeof(entry);
rqst.payload = &entry;
rsp.capacity = len;
rsp.length = 0;
rsp.pointer = buf;
status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry));
if (status)
return status;
if (rsp.length != len) {
dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n",
rsp.length, len);
return -EPROTO;
}
return 0;
}
static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value)
{
struct ssam_request rqst;
u8 value_u8 = value;
rqst.target_category = shid->uid.category;
rqst.target_id = shid->uid.target;
rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED;
rqst.instance_id = shid->uid.instance;
rqst.flags = 0;
rqst.length = sizeof(value_u8);
rqst.payload = &value_u8;
return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8));
}
static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len)
{
struct ssam_request rqst;
struct ssam_response rsp;
u8 payload = 0;
int status;
rqst.target_category = shid->uid.category;
rqst.target_id = shid->uid.target;
rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT;
rqst.instance_id = shid->uid.instance;
rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
rqst.length = sizeof(payload);
rqst.payload = &payload;
rsp.capacity = len;
rsp.length = 0;
rsp.pointer = buf;
status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload));
if (status)
return status;
if (rsp.length != len) {
dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n",
rsp.length, len);
return -EPROTO;
}
return 0;
}
static bool ssam_kbd_is_input_event(const struct ssam_event *event)
{
if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC)
return true;
if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS)
return true;
return false;
}
static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
{
struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
/*
* Check against device UID manually, as registry and device target
* category doesn't line up.
*/
if (shid->uid.category != event->target_category)
return 0;
if (shid->uid.target != event->target_id)
return 0;
if (shid->uid.instance != event->instance_id)
return 0;
if (!ssam_kbd_is_input_event(event))
return 0;
hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
return SSAM_NOTIF_HANDLED;
}
/* -- Transport driver (KBD). ----------------------------------------------- */
static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len)
{
struct hid_field *field;
unsigned int offset, size;
int i;
/* Get LED field. */
field = hidinput_get_led_field(hid);
if (!field)
return -ENOENT;
/* Check if we got the correct report. */
if (len != hid_report_len(field->report))
return -ENOENT;
if (rprt_id != field->report->id)
return -ENOENT;
/* Get caps lock LED index. */
for (i = 0; i < field->report_count; i++)
if ((field->usage[i].hid & 0xffff) == 0x02)
break;
if (i == field->report_count)
return -ENOENT;
/* Extract value. */
size = field->report_size;
offset = field->report_offset + i * size;
return !!hid_field_extract(hid, buf + 1, size, offset);
}
static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
{
int caps_led;
int status;
caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len);
if (caps_led < 0)
return -EIO; /* Only caps LED output reports are supported. */
status = ssam_kbd_set_caps_led(shid, caps_led);
if (status < 0)
return status;
return len;
}
static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
{
u8 report[KBD_FEATURE_REPORT_SIZE];
int status;
/*
* The keyboard only has a single hard-coded read-only feature report
* of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its
* report ID against the requested one.
*/
if (len < ARRAY_SIZE(report))
return -ENOSPC;
status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report));
if (status < 0)
return status;
if (rprt_id != report[0])
return -ENOENT;
memcpy(buf, report, ARRAY_SIZE(report));
return len;
}
static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
{
/* Not supported. See skbd_get_feature_report() for details. */
return -EIO;
}
/* -- Driver setup. --------------------------------------------------------- */
static int surface_kbd_probe(struct platform_device *pdev)
{
struct ssam_controller *ctrl;
struct surface_hid_device *shid;
/* Add device link to EC. */
ctrl = ssam_client_bind(&pdev->dev);
if (IS_ERR(ctrl))
return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL);
if (!shid)
return -ENOMEM;
shid->dev = &pdev->dev;
shid->ctrl = ctrl;
shid->uid.domain = SSAM_DOMAIN_SERIALHUB;
shid->uid.category = SSAM_SSH_TC_KBD;
shid->uid.target = 2;
shid->uid.instance = 0;
shid->uid.function = 0;
shid->notif.base.priority = 1;
shid->notif.base.fn = ssam_kbd_event_fn;
shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
shid->notif.event.id.target_category = shid->uid.category;
shid->notif.event.id.instance = shid->uid.instance;
shid->notif.event.mask = SSAM_EVENT_MASK_NONE;
shid->notif.event.flags = 0;
shid->ops.get_descriptor = ssam_kbd_get_descriptor;
shid->ops.output_report = skbd_output_report;
shid->ops.get_feature_report = skbd_get_feature_report;
shid->ops.set_feature_report = skbd_set_feature_report;
platform_set_drvdata(pdev, shid);
return surface_hid_device_add(shid);
}
static int surface_kbd_remove(struct platform_device *pdev)
{
surface_hid_device_destroy(platform_get_drvdata(pdev));
return 0;
}
static const struct acpi_device_id surface_kbd_match[] = {
{ "MSHW0096" },
{ },
};
MODULE_DEVICE_TABLE(acpi, surface_kbd_match);
static struct platform_driver surface_kbd_driver = {
.probe = surface_kbd_probe,
.remove = surface_kbd_remove,
.driver = {
.name = "surface_keyboard",
.acpi_match_table = surface_kbd_match,
.pm = &surface_hid_pm_ops,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
module_platform_driver(surface_kbd_driver);
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module");
MODULE_LICENSE("GPL");

View File

@ -505,7 +505,7 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n)
HID_REQ_SET_REPORT);
}
/**
/*
* Play the effect with effect id @effect_id for @value times
*/
static int pidff_playback(struct input_dev *dev, int effect_id, int value)
@ -997,7 +997,7 @@ static int pidff_find_special_fields(struct pidff_device *pidff)
return 0;
}
/**
/*
* Find the implemented effect types
*/
static int pidff_find_effects(struct pidff_device *pidff,

View File

@ -887,11 +887,11 @@ int hiddev_connect(struct hid_device *hid, unsigned int force)
break;
if (i == hid->maxcollection)
return -1;
return -EINVAL;
}
if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL)))
return -1;
return -ENOMEM;
init_waitqueue_head(&hiddev->wait);
INIT_LIST_HEAD(&hiddev->list);
@ -905,7 +905,7 @@ int hiddev_connect(struct hid_device *hid, unsigned int force)
hid_err(hid, "Not able to get a minor for this device\n");
hid->hiddev = NULL;
kfree(hiddev);
return -1;
return retval;
}
/*

View File

@ -63,7 +63,7 @@ static const unsigned char usb_kbd_keycode[256] = {
* new key is pressed or a key that was pressed is released.
* @led: URB for sending LEDs (e.g. numlock, ...)
* @newleds: data that will be sent with the @led URB representing which LEDs
should be on
* should be on
* @name: Name of the keyboard. @dev's name field points to this buffer
* @phys: Physical path of the keyboard. @dev's phys field points to this
* buffer
@ -91,7 +91,7 @@ struct usb_kbd {
unsigned char *leds;
dma_addr_t new_dma;
dma_addr_t leds_dma;
spinlock_t leds_lock;
bool led_urb_submitted;
@ -175,15 +175,15 @@ static int usb_kbd_event(struct input_dev *dev, unsigned int type,
}
*(kbd->leds) = kbd->newleds;
kbd->led->dev = kbd->usbdev;
if (usb_submit_urb(kbd->led, GFP_ATOMIC))
pr_err("usb_submit_urb(leds) failed\n");
else
kbd->led_urb_submitted = true;
spin_unlock_irqrestore(&kbd->leds_lock, flags);
return 0;
}
@ -205,14 +205,14 @@ static void usb_kbd_led(struct urb *urb)
}
*(kbd->leds) = kbd->newleds;
kbd->led->dev = kbd->usbdev;
if (usb_submit_urb(kbd->led, GFP_ATOMIC)){
hid_err(urb->dev, "usb_submit_urb(leds) failed\n");
kbd->led_urb_submitted = false;
}
spin_unlock_irqrestore(&kbd->leds_lock, flags);
}
static int usb_kbd_open(struct input_dev *dev)
@ -358,9 +358,9 @@ static int usb_kbd_probe(struct usb_interface *iface,
device_set_wakeup_enable(&dev->dev, 1);
return 0;
fail2:
fail2:
usb_kbd_free_mem(dev, kbd);
fail1:
fail1:
input_free_device(input_dev);
kfree(kbd);
return error;

View File

@ -1495,7 +1495,7 @@ struct wacom_led *wacom_led_find(struct wacom *wacom, unsigned int group_id,
return &group->leds[id];
}
/**
/*
* wacom_led_next: gives the next available led with a wacom trigger.
*
* returns the next available struct wacom_led which has its default trigger

View File

@ -1860,8 +1860,6 @@ static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
usage->type = type;
usage->code = code;
set_bit(type, input->evbit);
switch (type) {
case EV_ABS:
input_set_abs_params(input, code, fmin, fmax, fuzz, 0);
@ -1869,13 +1867,9 @@ static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
hidinput_calc_abs_res(field, resolution_code));
break;
case EV_KEY:
input_set_capability(input, EV_KEY, code);
break;
case EV_MSC:
input_set_capability(input, EV_MSC, code);
break;
case EV_SW:
input_set_capability(input, EV_SW, code);
input_set_capability(input, type, code);
break;
}
}
@ -2187,6 +2181,18 @@ static void wacom_wac_pad_report(struct hid_device *hdev,
}
}
static void wacom_set_barrel_switch3_usage(struct wacom_wac *wacom_wac)
{
struct input_dev *input = wacom_wac->pen_input;
struct wacom_features *features = &wacom_wac->features;
if (!(features->quirks & WACOM_QUIRK_AESPEN) &&
wacom_wac->hid_data.barrelswitch &&
wacom_wac->hid_data.barrelswitch2 &&
wacom_wac->hid_data.serialhi)
input_set_capability(input, EV_KEY, BTN_STYLUS3);
}
static void wacom_wac_pen_usage_mapping(struct hid_device *hdev,
struct hid_field *field, struct hid_usage *usage)
{
@ -2227,13 +2233,21 @@ static void wacom_wac_pen_usage_mapping(struct hid_device *hdev,
wacom_map_usage(input, usage, field, EV_ABS, ABS_Z, 0);
break;
case HID_DG_ERASER:
input_set_capability(input, EV_KEY, BTN_TOOL_RUBBER);
wacom_map_usage(input, usage, field, EV_KEY, BTN_TOUCH, 0);
break;
case HID_DG_TIPSWITCH:
input_set_capability(input, EV_KEY, BTN_TOOL_PEN);
wacom_map_usage(input, usage, field, EV_KEY, BTN_TOUCH, 0);
break;
case HID_DG_BARRELSWITCH:
wacom_wac->hid_data.barrelswitch = true;
wacom_set_barrel_switch3_usage(wacom_wac);
wacom_map_usage(input, usage, field, EV_KEY, BTN_STYLUS, 0);
break;
case HID_DG_BARRELSWITCH2:
wacom_wac->hid_data.barrelswitch2 = true;
wacom_set_barrel_switch3_usage(wacom_wac);
wacom_map_usage(input, usage, field, EV_KEY, BTN_STYLUS2, 0);
break;
case HID_DG_TOOLSERIALNUMBER:
@ -2245,22 +2259,12 @@ static void wacom_wac_pen_usage_mapping(struct hid_device *hdev,
wacom_map_usage(input, usage, field, EV_KEY, BTN_TOOL_PEN, 0);
break;
case WACOM_HID_WD_SERIALHI:
wacom_wac->hid_data.serialhi = true;
wacom_set_barrel_switch3_usage(wacom_wac);
wacom_map_usage(input, usage, field, EV_ABS, ABS_MISC, 0);
if (!(features->quirks & WACOM_QUIRK_AESPEN)) {
set_bit(EV_KEY, input->evbit);
input_set_capability(input, EV_KEY, BTN_TOOL_PEN);
input_set_capability(input, EV_KEY, BTN_TOOL_RUBBER);
input_set_capability(input, EV_KEY, BTN_TOOL_BRUSH);
input_set_capability(input, EV_KEY, BTN_TOOL_PENCIL);
input_set_capability(input, EV_KEY, BTN_TOOL_AIRBRUSH);
if (!(features->device_type & WACOM_DEVICETYPE_DIRECT)) {
input_set_capability(input, EV_KEY, BTN_TOOL_MOUSE);
input_set_capability(input, EV_KEY, BTN_TOOL_LENS);
}
}
break;
case WACOM_HID_WD_FINGERWHEEL:
input_set_capability(input, EV_KEY, BTN_TOOL_AIRBRUSH);
wacom_map_usage(input, usage, field, EV_ABS, ABS_WHEEL, 0);
break;
}
@ -3582,11 +3586,9 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev,
else
__set_bit(INPUT_PROP_POINTER, input_dev->propbit);
if (features->type == HID_GENERIC) {
/* setup has already been done; apply otherwise-undetectible quirks */
input_set_capability(input_dev, EV_KEY, BTN_STYLUS3);
if (features->type == HID_GENERIC)
/* setup has already been done */
return 0;
}
input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
__set_bit(BTN_TOUCH, input_dev->keybit);

View File

@ -300,6 +300,7 @@ struct hid_data {
bool tipswitch;
bool barrelswitch;
bool barrelswitch2;
bool serialhi;
int x;
int y;
int pressure;

View File

@ -153,6 +153,7 @@ struct hid_item {
#define HID_UP_CONSUMER 0x000c0000
#define HID_UP_DIGITIZER 0x000d0000
#define HID_UP_PID 0x000f0000
#define HID_UP_BATTERY 0x00850000
#define HID_UP_HPVENDOR 0xff7f0000
#define HID_UP_HPVENDOR2 0xff010000
#define HID_UP_MSVENDOR 0xff000000
@ -262,6 +263,8 @@ struct hid_item {
#define HID_CP_SELECTION 0x000c0080
#define HID_CP_MEDIASELECTION 0x000c0087
#define HID_CP_SELECTDISC 0x000c00ba
#define HID_CP_VOLUMEUP 0x000c00e9
#define HID_CP_VOLUMEDOWN 0x000c00ea
#define HID_CP_PLAYBACKSPEED 0x000c00f1
#define HID_CP_PROXIMITY 0x000c0109
#define HID_CP_SPEAKERSYSTEM 0x000c0160
@ -297,6 +300,8 @@ struct hid_item {
#define HID_DG_TOOLSERIALNUMBER 0x000d005b
#define HID_DG_LATENCYMODE 0x000d0060
#define HID_BAT_ABSOLUTESTATEOFCHARGE 0x00850065
#define HID_VD_ASUS_CUSTOM_MEDIA_KEYS 0xff310076
/*
* HID report types --- Ouch! HID spec says 1 2 3!