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:
Коммит
efd8929b9e
14
MAINTAINERS
14
MAINTAINERS
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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);
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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) },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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},
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
/*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) },
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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");
|
|
@ -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");
|
|
@ -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 */
|
|
@ -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");
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -300,6 +300,7 @@ struct hid_data {
|
|||
bool tipswitch;
|
||||
bool barrelswitch;
|
||||
bool barrelswitch2;
|
||||
bool serialhi;
|
||||
int x;
|
||||
int y;
|
||||
int pressure;
|
||||
|
|
|
@ -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!
|
||||
|
|
Загрузка…
Ссылка в новой задаче