From 6d97010eb9ff2d00955c42595e4a1a7fff988992 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 26 Feb 2021 21:32:22 +0200 Subject: HID: i2c-hid: acpi: Get ACPI companion only once and reuse it Currently the ACPI companion and handle are retrieved and checked a few times in different functions. Instead get ACPI companion only once and reuse it everywhere. Signed-off-by: Andy Shevchenko Acked-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-acpi.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c index bb8c00e6be78..7225a6bc42f0 100644 --- a/drivers/hid/i2c-hid/i2c-hid-acpi.c +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c @@ -30,7 +30,7 @@ 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,29 +42,22 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = { { }, }; -static int i2c_hid_acpi_get_descriptor(struct i2c_client *client) +static int i2c_hid_acpi_get_descriptor(struct acpi_device *adev) { static guid_t i2c_hid_guid = GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555, 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE); + acpi_handle handle = acpi_device_handle(adev); union acpi_object *obj; - struct acpi_device *adev; - acpi_handle handle; u16 hid_descriptor_address; - 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; - } - if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0) return -ENODEV; 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,10 +69,9 @@ 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, @@ -91,21 +83,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); -- cgit v1.2.3-59-g8ed1b From 4cecff8f1d0e52300e925ea6056acfd12cbc1bf2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 26 Feb 2021 21:32:23 +0200 Subject: HID: i2c-hid: acpi: Switch to new style i2c-driver probe function Switch to the new style i2c-driver probe_new probe function. Signed-off-by: Andy Shevchenko Acked-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-acpi.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c index 7225a6bc42f0..70955f21349a 100644 --- a/drivers/hid/i2c-hid/i2c-hid-acpi.c +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c @@ -74,8 +74,7 @@ static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *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; @@ -127,7 +126,7 @@ static struct i2c_driver i2c_hid_acpi_driver = { .acpi_match_table = ACPI_PTR(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, }; -- cgit v1.2.3-59-g8ed1b From a3836a02c5f20c4cbc09e463e9f7a7555c77d70d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 26 Feb 2021 21:32:24 +0200 Subject: HID: i2c-hid: acpi: Move GUID out of function and described it Move static GUID variable out of the function and add a comment how it looks like in the human readable representation. While at it, include uuid.h since the guid_t type is defined in it. Signed-off-by: Andy Shevchenko Acked-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-acpi.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c index 70955f21349a..a4810f199d59 100644 --- a/drivers/hid/i2c-hid/i2c-hid-acpi.c +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "i2c-hid.h" @@ -42,11 +43,13 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = { { }, }; +/* 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); + static int i2c_hid_acpi_get_descriptor(struct acpi_device *adev) { - static guid_t i2c_hid_guid = - GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555, - 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE); acpi_handle handle = acpi_device_handle(adev); union acpi_object *obj; u16 hid_descriptor_address; -- cgit v1.2.3-59-g8ed1b From 0960941043a19c0413b30dc5e88a55c593fda1cb Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 26 Feb 2021 21:32:25 +0200 Subject: HID: i2c-hid: acpi: Drop redundant ACPI_PTR() The driver depends on ACPI, ACPI_PTR() resolution is always the same. Otherwise a compiler may produce a warning. That said, the rule of thumb either ugly ifdeffery with ACPI_PTR or none should be used in a driver. Signed-off-by: Andy Shevchenko Acked-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c index a4810f199d59..a6f0257a26de 100644 --- a/drivers/hid/i2c-hid/i2c-hid-acpi.c +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c @@ -126,7 +126,7 @@ 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_new = i2c_hid_acpi_probe, -- cgit v1.2.3-59-g8ed1b From 751cb6518ceffa44263a9a1ea89d92830497d35d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 4 Feb 2021 21:56:16 +0100 Subject: HID: logitech-dj: Use hid-ids.h defines for USB device-ids for all supported devices The logitech-dj code already uses hid-ids.h defines for almost all devices it supports. Lets be consistent: add and use hid-ids.h defines for the G700, MX5000 and MX5500 receivers too. Also add / update some comments to make the comment style in the hid_device_id table consistent too. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 5 +++++ drivers/hid/hid-logitech-dj.c | 40 +++++++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index e42aaae3138f..7d7b3a0cf8c4 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -806,6 +806,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,6 +817,10 @@ #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_MX5500_RECEIVER_KBD_DEV 0xc71b +#define USB_DEVICE_ID_MX5500_RECEIVER_MOUSE_DEV 0xc71c #define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2 0xca03 #define USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL 0xca04 diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 271bd8d24339..f753c80faf83 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -1857,23 +1857,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 +1887,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 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 powerplay receiver (0xc53a) */ - HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, - USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY), - .driver_data = recvr_type_gaming_hidpp}, { /* Logitech 27 MHz HID++ 1.0 receiver (0xc517) */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2), @@ -1902,21 +1907,22 @@ 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}, {} }; -- cgit v1.2.3-59-g8ed1b From 434f77092eeb42d3ab4484f71921450d7f8966e5 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 4 Feb 2021 21:56:17 +0100 Subject: HID: logitech-dj: Handle newer quad/bt2.0 receivers in HID proxy mode The Dinovo Edge and Dinovo Mini keyboards with builtin touchpad come with a different version of the quad/bt2.0 combo receivers shipped with the MX5000 and MX5500 keyboards. These receivers are compatible with one another, e.g. the Dinovo Edge keyboard can be paired with the MX5000 receiver. Like the MX5x00 receivers in HID proxy mode these receivers present themselves as a hub with multiple USB-HID devices, one for the keyboard and one for the mouse. Where they differ is that the mouse USB-device has 2 input reports for reporting mice events. It has the exact same INPUT(2) report as the MX5x00 receivers, but it also has a second INPUT(5) mouse report which is different; and when the Dinovo receivers are paired with the Dinovo keyboards the second INPUT(5) mouse report is actually used for events on the builtin touchpad. Add support for handling the Dinovo quad/bluetooth-2.0 combo receivers in HID proxy mode to logitech-dj, like we already do for the similar MX5000 and MX5500 receivers. This adds battery monitoring functionality (through logitech-hidpp) and fixes the Phone (Fn + F1) and "[A]" - "[D]" (Fn + F9 - F12) hotkeys not working on the Dinovo Edge. Note these receivers present themselves as a hub with 2 separate USB devices for the keyboard and mouse; and the logitech-dj code needs to bind to both devices (just as with the MX5x00 receivers). Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 6 ++- drivers/hid/hid-lg.c | 24 ------------ drivers/hid/hid-logitech-dj.c | 91 +++++++++++++++++++++++++++++++++++++++++-- drivers/hid/hid-quirks.c | 2 - 4 files changed, 92 insertions(+), 31 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 7d7b3a0cf8c4..e2da38e5df74 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -815,12 +815,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 diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index 0dc7cdfc56f7..d40af911df63 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -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 }, diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index f753c80faf83..fa835d565982 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -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); @@ -1924,6 +1992,23 @@ static const struct hid_device_id logi_dj_receivers[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 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}, {} }; diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 1a9daf03dbfa..3816be700f49 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -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) }, -- cgit v1.2.3-59-g8ed1b From f567d6ef8606fb427636e824c867229ecb5aefab Mon Sep 17 00:00:00 2001 From: Maxim Mikityanskiy Date: Sun, 7 Feb 2021 16:47:40 +0200 Subject: HID: plantronics: Workaround for double volume key presses Plantronics Blackwire 3220 Series (047f:c056) sends HID reports twice for each volume key press. This patch adds a quirk to hid-plantronics for this product ID, which will ignore the second volume key press if it happens within 5 ms from the last one that was handled. The patch was tested on the mentioned model only, it shouldn't affect other models, however, this quirk might be needed for them too. Auto-repeat (when a key is held pressed) is not affected, because the rate is about 3 times per second, which is far less frequent than once in 5 ms. Fixes: 81bb773faed7 ("HID: plantronics: Update to map volume up/down controls") Signed-off-by: Maxim Mikityanskiy Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-plantronics.c | 60 +++++++++++++++++++++++++++++++++++++++++-- include/linux/hid.h | 2 ++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index e42aaae3138f..4c74d8706b51 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -944,6 +944,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 diff --git a/drivers/hid/hid-plantronics.c b/drivers/hid/hid-plantronics.c index 85b685efc12f..e81b7cec2d12 100644 --- a/drivers/hid/hid-plantronics.c +++ b/drivers/hid/hid-plantronics.c @@ -13,6 +13,7 @@ #include #include +#include #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); diff --git a/include/linux/hid.h b/include/linux/hid.h index ef702b3f56e3..3e33eb14118c 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -262,6 +262,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 -- cgit v1.2.3-59-g8ed1b From 9951bb259dd0794db39beb63899104da9579efcc Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Thu, 11 Feb 2021 11:30:59 -0800 Subject: HID: hiddev: Return specific error codes on connect failure The only caller of this function only cares about gross success/failure but we still might as well resolve the following smatch warning and fix the other error paths as well: hiddev.c:894 hiddev_connect() warn: returning -1 instead of -ENOMEM is sloppy Signed-off-by: Jason Gerecke Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hiddev.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 45e0b1c75cb1..2fb2991dbe4c 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -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; } /* -- cgit v1.2.3-59-g8ed1b From 6a82582d9fa438045191074856f47165334f2777 Mon Sep 17 00:00:00 2001 From: Michael Zaidman Date: Fri, 19 Feb 2021 18:36:44 +0200 Subject: HID: ft260: add usb hid to i2c host bridge driver The FTDI FT260 chip implements USB to I2C/UART bridges through two USB HID class interfaces. The first - for I2C, and the second for UART. Each interface is independent, and the kernel detects it as a separate USB hidraw device. This commit adds I2C host adapter support. Signed-off-by: Michael Zaidman Tested-by: Aaron Jones (FTDI-UK) --- MAINTAINERS | 7 + drivers/hid/Kconfig | 11 + drivers/hid/Makefile | 1 + drivers/hid/hid-ft260.c | 1053 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-ids.h | 1 + 5 files changed, 1073 insertions(+) create mode 100644 drivers/hid/hid-ft260.c diff --git a/MAINTAINERS b/MAINTAINERS index dace4bd105d5..cbfe57e03e89 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7278,6 +7278,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 +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 L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 786b71ef7738..b279c6130bc2 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -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 diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index c4f6d5c613dc..6e24c37132ec 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -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 diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c new file mode 100644 index 000000000000..047aa85a7c83 --- /dev/null +++ b/drivers/hid/hid-ft260.c @@ -0,0 +1,1053 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * hid-ft260.c - FTDI FT260 USB HID to I2C host bridge + * + * Copyright (c) 2021, Michael Zaidman + * + * Data Sheet: + * https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT260.pdf + */ + +#include "hid-ids.h" +#include +#include +#include +#include + +#ifdef DEBUG +static int ft260_debug = 1; +#else +static int ft260_debug; +#endif +module_param_named(debug, ft260_debug, int, 0600); +MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages"); + +#define ft260_dbg(format, arg...) \ + do { \ + if (ft260_debug) \ + pr_info("%s: " format, __func__, ##arg); \ + } while (0) + +#define FT260_REPORT_MAX_LENGTH (64) +#define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / 4) +/* + * The input report format assigns 62 bytes for the data payload, but ft260 + * returns 60 and 2 in two separate transactions. To minimize transfer time + * in reading chunks mode, set the maximum read payload length to 60 bytes. + */ +#define FT260_RD_DATA_MAX (60) +#define FT260_WR_DATA_MAX (60) + +/* + * Device interface configuration. + * The FT260 has 2 interfaces that are controlled by DCNF0 and DCNF1 pins. + * First implementes USB HID to I2C bridge function and + * second - USB HID to UART bridge function. + */ +enum { + FT260_MODE_ALL = 0x00, + FT260_MODE_I2C = 0x01, + FT260_MODE_UART = 0x02, + FT260_MODE_BOTH = 0x03, +}; + +/* Control pipe */ +enum { + FT260_GET_RQST_TYPE = 0xA1, + FT260_GET_REPORT = 0x01, + FT260_SET_RQST_TYPE = 0x21, + FT260_SET_REPORT = 0x09, + FT260_FEATURE = 0x03, +}; + +/* Report IDs / Feature In */ +enum { + FT260_CHIP_VERSION = 0xA0, + FT260_SYSTEM_SETTINGS = 0xA1, + FT260_I2C_STATUS = 0xC0, + FT260_I2C_READ_REQ = 0xC2, + FT260_I2C_REPORT_MIN = 0xD0, + FT260_I2C_REPORT_MAX = 0xDE, + FT260_GPIO = 0xB0, + FT260_UART_INTERRUPT_STATUS = 0xB1, + FT260_UART_STATUS = 0xE0, + FT260_UART_RI_DCD_STATUS = 0xE1, + FT260_UART_REPORT = 0xF0, +}; + +/* Feature Out */ +enum { + FT260_SET_CLOCK = 0x01, + FT260_SET_I2C_MODE = 0x02, + FT260_SET_UART_MODE = 0x03, + FT260_ENABLE_INTERRUPT = 0x05, + FT260_SELECT_GPIO2_FUNC = 0x06, + FT260_ENABLE_UART_DCD_RI = 0x07, + FT260_SELECT_GPIOA_FUNC = 0x08, + FT260_SELECT_GPIOG_FUNC = 0x09, + FT260_SET_INTERRUPT_TRIGGER = 0x0A, + FT260_SET_SUSPEND_OUT_POLAR = 0x0B, + FT260_ENABLE_UART_RI_WAKEUP = 0x0C, + FT260_SET_UART_RI_WAKEUP_CFG = 0x0D, + FT260_SET_I2C_RESET = 0x20, + FT260_SET_I2C_CLOCK_SPEED = 0x22, + FT260_SET_UART_RESET = 0x40, + FT260_SET_UART_CONFIG = 0x41, + FT260_SET_UART_BAUD_RATE = 0x42, + FT260_SET_UART_DATA_BIT = 0x43, + FT260_SET_UART_PARITY = 0x44, + FT260_SET_UART_STOP_BIT = 0x45, + FT260_SET_UART_BREAKING = 0x46, + FT260_SET_UART_XON_XOFF = 0x49, +}; + +/* Response codes in I2C status report */ +enum { + FT260_I2C_STATUS_SUCCESS = 0x00, + FT260_I2C_STATUS_CTRL_BUSY = 0x01, + FT260_I2C_STATUS_ERROR = 0x02, + FT260_I2C_STATUS_ADDR_NO_ACK = 0x04, + FT260_I2C_STATUS_DATA_NO_ACK = 0x08, + FT260_I2C_STATUS_ARBITR_LOST = 0x10, + FT260_I2C_STATUS_CTRL_IDLE = 0x20, + FT260_I2C_STATUS_BUS_BUSY = 0x40, +}; + +/* I2C Conditions flags */ +enum { + FT260_FLAG_NONE = 0x00, + FT260_FLAG_START = 0x02, + FT260_FLAG_START_REPEATED = 0x03, + FT260_FLAG_STOP = 0x04, + FT260_FLAG_START_STOP = 0x06, + FT260_FLAG_START_STOP_REPEATED = 0x07, +}; + +#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | report_id) + +/* Feature In reports */ + +struct ft260_get_chip_version_report { + u8 report; /* FT260_CHIP_VERSION */ + u8 chip_code[4]; /* FTDI chip identification code */ + u8 reserved[8]; +} __packed; + +struct ft260_get_system_status_report { + u8 report; /* FT260_SYSTEM_SETTINGS */ + u8 chip_mode; /* DCNF0 and DCNF1 status, bits 0-1 */ + u8 clock_ctl; /* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */ + u8 suspend_status; /* 0 - not suspended, 1 - suspended */ + u8 pwren_status; /* 0 - FT260 is not ready, 1 - ready */ + u8 i2c_enable; /* 0 - disabled, 1 - enabled */ + u8 uart_mode; /* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */ + /* 3 - XON_XOFF, 4 - No flow control */ + u8 hid_over_i2c_en; /* 0 - disabled, 1 - enabled */ + u8 gpio2_function; /* 0 - GPIO, 1 - SUSPOUT, */ + /* 2 - PWREN, 4 - TX_LED */ + u8 gpioA_function; /* 0 - GPIO, 3 - TX_ACTIVE, 4 - TX_LED */ + u8 gpioG_function; /* 0 - GPIO, 2 - PWREN, */ + /* 5 - RX_LED, 6 - BCD_DET */ + u8 suspend_out_pol; /* 0 - active-high, 1 - active-low */ + u8 enable_wakeup_int; /* 0 - disabled, 1 - enabled */ + u8 intr_cond; /* Interrupt trigger conditions */ + u8 power_saving_en; /* 0 - disabled, 1 - enabled */ + u8 reserved[10]; +} __packed; + +struct ft260_get_i2c_status_report { + u8 report; /* FT260_I2C_STATUS */ + u8 bus_status; /* I2C bus status */ + __le16 clock; /* I2C bus clock in range 60-3400 KHz */ + u8 reserved; +} __packed; + +/* Feature Out reports */ + +struct ft260_set_system_clock_report { + u8 report; /* FT260_SYSTEM_SETTINGS */ + u8 request; /* FT260_SET_CLOCK */ + u8 clock_ctl; /* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */ +} __packed; + +struct ft260_set_i2c_mode_report { + u8 report; /* FT260_SYSTEM_SETTINGS */ + u8 request; /* FT260_SET_I2C_MODE */ + u8 i2c_enable; /* 0 - disabled, 1 - enabled */ +} __packed; + +struct ft260_set_uart_mode_report { + u8 report; /* FT260_SYSTEM_SETTINGS */ + u8 request; /* FT260_SET_UART_MODE */ + u8 uart_mode; /* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */ + /* 3 - XON_XOFF, 4 - No flow control */ +} __packed; + +struct ft260_set_i2c_reset_report { + u8 report; /* FT260_SYSTEM_SETTINGS */ + u8 request; /* FT260_SET_I2C_RESET */ +} __packed; + +struct ft260_set_i2c_speed_report { + u8 report; /* FT260_SYSTEM_SETTINGS */ + u8 request; /* FT260_SET_I2C_CLOCK_SPEED */ + __le16 clock; /* I2C bus clock in range 60-3400 KHz */ +} __packed; + +/* Data transfer reports */ + +struct ft260_i2c_write_request_report { + u8 report; /* FT260_I2C_REPORT */ + u8 address; /* 7-bit I2C address */ + u8 flag; /* I2C transaction condition */ + u8 length; /* data payload length */ + u8 data[60]; /* data payload */ +} __packed; + +struct ft260_i2c_read_request_report { + u8 report; /* FT260_I2C_READ_REQ */ + u8 address; /* 7-bit I2C address */ + u8 flag; /* I2C transaction condition */ + __le16 length; /* data payload length */ +} __packed; + +struct ft260_i2c_input_report { + u8 report; /* FT260_I2C_REPORT */ + u8 length; /* data payload length */ + u8 data[2]; /* data payload */ +} __packed; + +static const struct hid_device_id ft260_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY, + USB_DEVICE_ID_FT260) }, + { /* END OF LIST */ } +}; +MODULE_DEVICE_TABLE(hid, ft260_devices); + +struct ft260_device { + struct i2c_adapter adap; + struct hid_device *hdev; + struct completion wait; + struct mutex lock; + u8 write_buf[FT260_REPORT_MAX_LENGTH]; + u8 *read_buf; + u16 read_idx; + u16 read_len; + u16 clock; +}; + +static int ft260_hid_feature_report_get(struct hid_device *hdev, + unsigned char report_id, u8 *data, + size_t len) +{ + u8 *buf; + int ret; + + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, report_id, buf, len, HID_FEATURE_REPORT, + HID_REQ_GET_REPORT); + memcpy(data, buf, len); + kfree(buf); + return ret; +} + +static int ft260_hid_feature_report_set(struct hid_device *hdev, u8 *data, + size_t len) +{ + u8 *buf; + int ret; + + buf = kmemdup(data, len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = FT260_SYSTEM_SETTINGS; + + ret = hid_hw_raw_request(hdev, buf[0], buf, len, HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + + kfree(buf); + return ret; +} + +static int ft260_i2c_reset(struct hid_device *hdev) +{ + struct ft260_set_i2c_reset_report report; + int ret; + + report.request = FT260_SET_I2C_RESET; + + ret = ft260_hid_feature_report_set(hdev, (u8 *)&report, sizeof(report)); + if (ret < 0) { + hid_err(hdev, "failed to reset I2C controller: %d\n", ret); + return ret; + } + + ft260_dbg("done\n"); + return ret; +} + +static int ft260_xfer_status(struct ft260_device *dev) +{ + struct hid_device *hdev = dev->hdev; + struct ft260_get_i2c_status_report report; + int ret; + + ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS, + (u8 *)&report, sizeof(report)); + if (ret < 0) { + hid_err(hdev, "failed to retrieve status: %d\n", ret); + return ret; + } + + dev->clock = le16_to_cpu(report.clock); + ft260_dbg("bus_status %#02x, clock %u\n", report.bus_status, + dev->clock); + + if (report.bus_status & FT260_I2C_STATUS_CTRL_BUSY) + return -EAGAIN; + + if (report.bus_status & FT260_I2C_STATUS_BUS_BUSY) + return -EBUSY; + + if (report.bus_status & FT260_I2C_STATUS_ERROR) + return -EIO; + + ret = -EIO; + + if (report.bus_status & FT260_I2C_STATUS_ADDR_NO_ACK) + ft260_dbg("unacknowledged address\n"); + + if (report.bus_status & FT260_I2C_STATUS_DATA_NO_ACK) + ft260_dbg("unacknowledged data\n"); + + if (report.bus_status & FT260_I2C_STATUS_ARBITR_LOST) + ft260_dbg("arbitration loss\n"); + + if (report.bus_status & FT260_I2C_STATUS_CTRL_IDLE) + ret = 0; + + return ret; +} + +static int ft260_hid_output_report(struct hid_device *hdev, u8 *data, + size_t len) +{ + u8 *buf; + int ret; + + buf = kmemdup(data, len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = hid_hw_output_report(hdev, buf, len); + + kfree(buf); + return ret; +} + +static int ft260_hid_output_report_check_status(struct ft260_device *dev, + u8 *data, int len) +{ + int ret, usec, try = 3; + struct hid_device *hdev = dev->hdev; + + ret = ft260_hid_output_report(hdev, data, len); + if (ret < 0) { + hid_err(hdev, "%s: failed to start transfer, ret %d\n", + __func__, ret); + ft260_i2c_reset(hdev); + return ret; + } + + /* transfer time = 1 / clock(KHz) * 10 bits * bytes */ + usec = 10000 / dev->clock * len; + usleep_range(usec, usec + 100); + ft260_dbg("wait %d usec, len %d\n", usec, len); + do { + ret = ft260_xfer_status(dev); + if (ret != -EAGAIN) + break; + } while (--try); + + if (ret == 0 || ret == -EBUSY) + return 0; + + ft260_i2c_reset(hdev); + return -EIO; +} + +static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data, + int data_len, u8 flag) +{ + int len, ret, idx = 0; + struct hid_device *hdev = dev->hdev; + struct ft260_i2c_write_request_report *rep = + (struct ft260_i2c_write_request_report *)dev->write_buf; + + do { + if (data_len <= FT260_WR_DATA_MAX) + len = data_len; + else + len = FT260_WR_DATA_MAX; + + rep->report = FT260_I2C_DATA_REPORT_ID(len); + rep->address = addr; + rep->length = len; + rep->flag = flag; + + memcpy(rep->data, &data[idx], len); + + ft260_dbg("rep %#02x addr %#02x off %d len %d d[0] %#02x\n", + rep->report, addr, idx, len, data[0]); + + ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, + len + 4); + if (ret < 0) { + hid_err(hdev, "%s: failed to start transfer, ret %d\n", + __func__, ret); + return ret; + } + + data_len -= len; + idx += len; + + } while (data_len > 0); + + return 0; +} + +static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd, + u8 *data, u8 data_len, u8 flag) +{ + int ret = 0; + int len = 4; + + struct ft260_i2c_write_request_report *rep = + (struct ft260_i2c_write_request_report *)dev->write_buf; + + rep->address = addr; + rep->data[0] = cmd; + rep->length = data_len + 1; + rep->flag = flag; + len += rep->length; + + rep->report = FT260_I2C_DATA_REPORT_ID(len); + + if (data_len > 0) + memcpy(&rep->data[1], data, data_len); + + ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n", + rep->report, addr, cmd, rep->length, len); + + ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len); + + return ret; +} + +static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data, + u16 len, u8 flag) +{ + struct ft260_i2c_read_request_report rep; + struct hid_device *hdev = dev->hdev; + int timeout; + int ret; + + if (len > FT260_RD_DATA_MAX) { + hid_err(hdev, "%s: unsupported rd len: %d\n", __func__, len); + return -EINVAL; + } + + dev->read_idx = 0; + dev->read_buf = data; + dev->read_len = len; + + rep.report = FT260_I2C_READ_REQ; + rep.length = cpu_to_le16(len); + rep.address = addr; + rep.flag = flag; + + ft260_dbg("rep %#02x addr %#02x len %d\n", rep.report, rep.address, + rep.length); + + reinit_completion(&dev->wait); + + ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep)); + if (ret < 0) { + hid_err(hdev, "%s: failed to start transaction, ret %d\n", + __func__, ret); + return ret; + } + + timeout = msecs_to_jiffies(5000); + if (!wait_for_completion_timeout(&dev->wait, timeout)) { + ft260_i2c_reset(hdev); + return -ETIMEDOUT; + } + + ret = ft260_xfer_status(dev); + if (ret == 0) + return 0; + + ft260_i2c_reset(hdev); + return -EIO; +} + +/* + * A random read operation is implemented as a dummy write operation, followed + * by a current address read operation. The dummy write operation is used to + * load the target byte address into the current byte address counter, from + * which the subsequent current address read operation then reads. + */ +static int ft260_i2c_write_read(struct ft260_device *dev, struct i2c_msg *msgs) +{ + int len, ret; + u16 left_len = msgs[1].len; + u8 *read_buf = msgs[1].buf; + u8 addr = msgs[0].addr; + u16 read_off = 0; + struct hid_device *hdev = dev->hdev; + + if (msgs[0].len > 2) { + hid_err(hdev, "%s: unsupported wr len: %d\n", __func__, len); + return -EOPNOTSUPP; + } + + memcpy(&read_off, msgs[0].buf, msgs[0].len); + + do { + if (left_len <= FT260_RD_DATA_MAX) + len = left_len; + else + len = FT260_RD_DATA_MAX; + + ft260_dbg("read_off %#x left_len %d len %d\n", read_off, + left_len, len); + + ret = ft260_i2c_write(dev, addr, (u8 *)&read_off, msgs[0].len, + FT260_FLAG_START); + if (ret < 0) + return ret; + + ret = ft260_i2c_read(dev, addr, read_buf, len, + FT260_FLAG_START_STOP); + if (ret < 0) + return ret; + + left_len -= len; + read_buf += len; + read_off += len; + + } while (left_len > 0); + + return 0; +} + +static int ft260_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, + int num) +{ + int ret; + struct ft260_device *dev = i2c_get_adapdata(adapter); + struct hid_device *hdev = dev->hdev; + + mutex_lock(&dev->lock); + + ret = hid_hw_power(hdev, PM_HINT_FULLON); + if (ret < 0) { + hid_err(hdev, "failed to enter FULLON power mode: %d\n", ret); + mutex_unlock(&dev->lock); + return ret; + } + + if (num == 1) { + if (msgs->flags & I2C_M_RD) + ret = ft260_i2c_read(dev, msgs->addr, msgs->buf, + msgs->len, FT260_FLAG_START_STOP); + else + ret = ft260_i2c_write(dev, msgs->addr, msgs->buf, + msgs->len, FT260_FLAG_START_STOP); + if (ret < 0) + goto i2c_exit; + + } else { + /* Combined write then read message */ + ret = ft260_i2c_write_read(dev, msgs); + if (ret < 0) + goto i2c_exit; + } + + ret = num; +i2c_exit: + hid_hw_power(hdev, PM_HINT_NORMAL); + mutex_unlock(&dev->lock); + return ret; +} + +static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags, + char read_write, u8 cmd, int size, + union i2c_smbus_data *data) +{ + int ret; + struct ft260_device *dev = i2c_get_adapdata(adapter); + struct hid_device *hdev = dev->hdev; + + ft260_dbg("smbus size %d\n", size); + + mutex_lock(&dev->lock); + + ret = hid_hw_power(hdev, PM_HINT_FULLON); + if (ret < 0) { + hid_err(hdev, "power management error: %d\n", ret); + mutex_unlock(&dev->lock); + return ret; + } + + switch (size) { + case I2C_SMBUS_QUICK: + if (read_write == I2C_SMBUS_READ) + ret = ft260_i2c_read(dev, addr, &data->byte, 0, + FT260_FLAG_START_STOP); + else + ret = ft260_smbus_write(dev, addr, cmd, NULL, 0, + FT260_FLAG_START_STOP); + break; + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_READ) + ret = ft260_i2c_read(dev, addr, &data->byte, 1, + FT260_FLAG_START_STOP); + else + ret = ft260_smbus_write(dev, addr, cmd, NULL, 0, + FT260_FLAG_START_STOP); + break; + case I2C_SMBUS_BYTE_DATA: + if (read_write == I2C_SMBUS_READ) { + ret = ft260_smbus_write(dev, addr, cmd, NULL, 0, + FT260_FLAG_START); + if (ret) + goto smbus_exit; + + ret = ft260_i2c_read(dev, addr, &data->byte, 1, + FT260_FLAG_START_STOP_REPEATED); + } else { + ret = ft260_smbus_write(dev, addr, cmd, &data->byte, 1, + FT260_FLAG_START_STOP); + } + break; + case I2C_SMBUS_WORD_DATA: + if (read_write == I2C_SMBUS_READ) { + ret = ft260_smbus_write(dev, addr, cmd, NULL, 0, + FT260_FLAG_START); + if (ret) + goto smbus_exit; + + ret = ft260_i2c_read(dev, addr, (u8 *)&data->word, 2, + FT260_FLAG_START_STOP_REPEATED); + } else { + ret = ft260_smbus_write(dev, addr, cmd, + (u8 *)&data->word, 2, + FT260_FLAG_START_STOP); + } + break; + case I2C_SMBUS_BLOCK_DATA: + if (read_write == I2C_SMBUS_READ) { + ret = ft260_smbus_write(dev, addr, cmd, NULL, 0, + FT260_FLAG_START); + if (ret) + goto smbus_exit; + + ret = ft260_i2c_read(dev, addr, data->block, + data->block[0] + 1, + FT260_FLAG_START_STOP_REPEATED); + } else { + ret = ft260_smbus_write(dev, addr, cmd, data->block, + data->block[0] + 1, + FT260_FLAG_START_STOP); + } + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + if (read_write == I2C_SMBUS_READ) { + ret = ft260_smbus_write(dev, addr, cmd, NULL, 0, + FT260_FLAG_START); + if (ret) + goto smbus_exit; + + ret = ft260_i2c_read(dev, addr, data->block + 1, + data->block[0], + FT260_FLAG_START_STOP_REPEATED); + } else { + ret = ft260_smbus_write(dev, addr, cmd, data->block + 1, + data->block[0], + FT260_FLAG_START_STOP); + } + break; + default: + hid_err(hdev, "unsupported smbus transaction size %d\n", size); + ret = -EOPNOTSUPP; + } + +smbus_exit: + hid_hw_power(hdev, PM_HINT_NORMAL); + mutex_unlock(&dev->lock); + return ret; +} + +static u32 ft260_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_QUICK | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK; +} + +static const struct i2c_adapter_quirks ft260_i2c_quirks = { + .flags = I2C_AQ_COMB_WRITE_THEN_READ, + .max_comb_1st_msg_len = 2, +}; + +static const struct i2c_algorithm ft260_i2c_algo = { + .master_xfer = ft260_i2c_xfer, + .smbus_xfer = ft260_smbus_xfer, + .functionality = ft260_functionality, +}; + +static int ft260_get_system_config(struct hid_device *hdev, + struct ft260_get_system_status_report *cfg) +{ + int ret; + int len = sizeof(struct ft260_get_system_status_report); + + ret = ft260_hid_feature_report_get(hdev, FT260_SYSTEM_SETTINGS, + (u8 *)cfg, len); + if (ret != len) { + hid_err(hdev, "failed to retrieve system status\n"); + if (ret >= 0) + return -EIO; + } + return 0; +} + +static int ft260_is_interface_enabled(struct hid_device *hdev) +{ + struct ft260_get_system_status_report cfg; + struct usb_interface *usbif = to_usb_interface(hdev->dev.parent); + int interface = usbif->cur_altsetting->desc.bInterfaceNumber; + int ret; + + ret = ft260_get_system_config(hdev, &cfg); + if (ret) + return ret; + + ft260_dbg("interface: 0x%02x\n", interface); + ft260_dbg("chip mode: 0x%02x\n", cfg.chip_mode); + ft260_dbg("clock_ctl: 0x%02x\n", cfg.clock_ctl); + ft260_dbg("i2c_enable: 0x%02x\n", cfg.i2c_enable); + ft260_dbg("uart_mode: 0x%02x\n", cfg.uart_mode); + + switch (cfg.chip_mode) { + case FT260_MODE_ALL: + case FT260_MODE_BOTH: + if (interface == 1) { + hid_info(hdev, "uart interface is not supported\n"); + return 0; + } + ret = 1; + break; + case FT260_MODE_UART: + if (interface == 0) { + hid_info(hdev, "uart is unsupported on interface 0\n"); + ret = 0; + } + break; + case FT260_MODE_I2C: + if (interface == 1) { + hid_info(hdev, "i2c is unsupported on interface 1\n"); + ret = 0; + } + break; + } + return ret; +} + +static int ft260_byte_show(struct hid_device *hdev, int id, u8 *cfg, int len, + u8 *field, u8 *buf) +{ + int ret; + + ret = ft260_hid_feature_report_get(hdev, id, cfg, len); + if (ret != len && ret >= 0) + return -EIO; + + return scnprintf(buf, PAGE_SIZE, "%hi\n", *field); +} + +static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len, + u16 *field, u8 *buf) +{ + int ret; + + ret = ft260_hid_feature_report_get(hdev, id, cfg, len); + if (ret != len && ret >= 0) + return -EIO; + + return scnprintf(buf, PAGE_SIZE, "%hi\n", le16_to_cpu(*field)); +} + +#define FT260_ATTR_SHOW(name, reptype, id, type, func) \ + static ssize_t name##_show(struct device *kdev, \ + struct device_attribute *attr, char *buf) \ + { \ + struct reptype rep; \ + struct hid_device *hdev = to_hid_device(kdev); \ + type *field = &rep.name; \ + int len = sizeof(rep); \ + \ + return func(hdev, id, (u8 *)&rep, len, field, buf); \ + } + +#define FT260_SSTAT_ATTR_SHOW(name) \ + FT260_ATTR_SHOW(name, ft260_get_system_status_report, \ + FT260_SYSTEM_SETTINGS, u8, ft260_byte_show) + +#define FT260_I2CST_ATTR_SHOW(name) \ + FT260_ATTR_SHOW(name, ft260_get_i2c_status_report, \ + FT260_I2C_STATUS, u16, ft260_word_show) + +#define FT260_ATTR_STORE(name, reptype, id, req, type, func) \ + static ssize_t name##_store(struct device *kdev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct reptype rep; \ + struct hid_device *hdev = to_hid_device(kdev); \ + type name; \ + int ret; \ + \ + if (!func(buf, 10, &name)) { \ + rep.name = name; \ + rep.report = id; \ + rep.request = req; \ + ret = ft260_hid_feature_report_set(hdev, (u8 *)&rep, \ + sizeof(rep)); \ + if (!ret) \ + ret = count; \ + } else { \ + ret = -EINVAL; \ + } \ + return ret; \ + } + +#define FT260_BYTE_ATTR_STORE(name, reptype, req) \ + FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req, \ + u8, kstrtou8) + +#define FT260_WORD_ATTR_STORE(name, reptype, req) \ + FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req, \ + u16, kstrtou16) + +FT260_SSTAT_ATTR_SHOW(chip_mode); +static DEVICE_ATTR_RO(chip_mode); + +FT260_SSTAT_ATTR_SHOW(pwren_status); +static DEVICE_ATTR_RO(pwren_status); + +FT260_SSTAT_ATTR_SHOW(suspend_status); +static DEVICE_ATTR_RO(suspend_status); + +FT260_SSTAT_ATTR_SHOW(hid_over_i2c_en); +static DEVICE_ATTR_RO(hid_over_i2c_en); + +FT260_SSTAT_ATTR_SHOW(power_saving_en); +static DEVICE_ATTR_RO(power_saving_en); + +FT260_SSTAT_ATTR_SHOW(i2c_enable); +FT260_BYTE_ATTR_STORE(i2c_enable, ft260_set_i2c_mode_report, + FT260_SET_I2C_MODE); +static DEVICE_ATTR_RW(i2c_enable); + +FT260_SSTAT_ATTR_SHOW(uart_mode); +FT260_BYTE_ATTR_STORE(uart_mode, ft260_set_uart_mode_report, + FT260_SET_UART_MODE); +static DEVICE_ATTR_RW(uart_mode); + +FT260_SSTAT_ATTR_SHOW(clock_ctl); +FT260_BYTE_ATTR_STORE(clock_ctl, ft260_set_system_clock_report, + FT260_SET_CLOCK); +static DEVICE_ATTR_RW(clock_ctl); + +FT260_I2CST_ATTR_SHOW(clock); +FT260_WORD_ATTR_STORE(clock, ft260_set_i2c_speed_report, + FT260_SET_I2C_CLOCK_SPEED); +static DEVICE_ATTR_RW(clock); + +static ssize_t i2c_reset_store(struct device *kdev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct hid_device *hdev = to_hid_device(kdev); + int ret = ft260_i2c_reset(hdev); + + if (ret) + return ret; + return count; +} +static DEVICE_ATTR_WO(i2c_reset); + +static const struct attribute_group ft260_attr_group = { + .attrs = (struct attribute *[]) { + &dev_attr_chip_mode.attr, + &dev_attr_pwren_status.attr, + &dev_attr_suspend_status.attr, + &dev_attr_hid_over_i2c_en.attr, + &dev_attr_power_saving_en.attr, + &dev_attr_i2c_enable.attr, + &dev_attr_uart_mode.attr, + &dev_attr_clock_ctl.attr, + &dev_attr_i2c_reset.attr, + &dev_attr_clock.attr, + NULL + } +}; + +static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct ft260_device *dev; + struct ft260_get_chip_version_report version; + int ret; + + dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "failed to parse HID\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "failed to start HID HW\n"); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "failed to open HID HW\n"); + goto err_hid_stop; + } + + ret = ft260_hid_feature_report_get(hdev, FT260_CHIP_VERSION, + (u8 *)&version, sizeof(version)); + if (ret != sizeof(version)) { + hid_err(hdev, "failed to retrieve chip version\n"); + if (ret >= 0) + ret = -EIO; + goto err_hid_close; + } + + hid_info(hdev, "chip code: %02x%02x %02x%02x\n", + version.chip_code[0], version.chip_code[1], + version.chip_code[2], version.chip_code[3]); + + ret = ft260_is_interface_enabled(hdev); + if (ret <= 0) + goto err_hid_close; + + hid_set_drvdata(hdev, dev); + dev->hdev = hdev; + dev->adap.owner = THIS_MODULE; + dev->adap.class = I2C_CLASS_HWMON; + dev->adap.algo = &ft260_i2c_algo; + dev->adap.quirks = &ft260_i2c_quirks; + dev->adap.dev.parent = &hdev->dev; + snprintf(dev->adap.name, sizeof(dev->adap.name), + "FT260 usb-i2c bridge on hidraw%d", + ((struct hidraw *)hdev->hidraw)->minor); + + mutex_init(&dev->lock); + init_completion(&dev->wait); + + ret = i2c_add_adapter(&dev->adap); + if (ret) { + hid_err(hdev, "failed to add i2c adapter\n"); + goto err_hid_close; + } + + i2c_set_adapdata(&dev->adap, dev); + + ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group); + if (ret < 0) { + hid_err(hdev, "failed to create sysfs attrs\n"); + goto err_i2c_free; + } + + ret = ft260_xfer_status(dev); + if (ret) + ft260_i2c_reset(hdev); + + return 0; + +err_i2c_free: + i2c_del_adapter(&dev->adap); +err_hid_close: + hid_hw_close(hdev); +err_hid_stop: + hid_hw_stop(hdev); + return ret; +} + +static void ft260_remove(struct hid_device *hdev) +{ + int ret; + struct ft260_device *dev = hid_get_drvdata(hdev); + + ret = ft260_is_interface_enabled(hdev); + if (ret <= 0) + return; + + sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group); + i2c_del_adapter(&dev->adap); + + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct ft260_device *dev = hid_get_drvdata(hdev); + struct ft260_i2c_input_report *xfer = (void *)data; + + if (xfer->report >= FT260_I2C_REPORT_MIN && + xfer->report <= FT260_I2C_REPORT_MAX) { + ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report, + xfer->length); + + memcpy(&dev->read_buf[dev->read_idx], &xfer->data, + xfer->length); + dev->read_idx += xfer->length; + + if (dev->read_idx == dev->read_len) + complete(&dev->wait); + + } else { + hid_err(hdev, "unknown report: %#02x\n", xfer->report); + return 0; + } + return 1; +} + +static struct hid_driver ft260_driver = { + .name = "ft260", + .id_table = ft260_devices, + .probe = ft260_probe, + .remove = ft260_remove, + .raw_event = ft260_raw_event, +}; + +module_hid_driver(ft260_driver); +MODULE_DESCRIPTION("FTDI FT260 USB HID to I2C host bridge"); +MODULE_AUTHOR("Michael Zaidman "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index e42aaae3138f..daa4c0318cbc 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -430,6 +430,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 -- cgit v1.2.3-59-g8ed1b From c49c33637802a2c6957a78119eb8be3b055dd9e9 Mon Sep 17 00:00:00 2001 From: Dario Pagani Date: Sun, 31 Jan 2021 10:00:45 +0100 Subject: HID: support for initialization of some Thrustmaster wheels Add support for proper initialization of some Thrustmaster wheels that appear like a "Thrustmaster FFB Wheel" (044f:b65d) to the host. When the device is connected a special usb request is sent, this request makes the wheel disconnect and reappear to the host as the "real wheel". For example: a T150 will re-appear as 044f:b677 and a T300 as 044f:b66e [jkosina@suse.cz: renamed driver to hid-thrustmaster] Link: https://github.com/scarburato/hid-tminit Signed-off-by: Dario Pagani Co-developed-by: Kim Kuparinen Signed-off-by: Kim Kuparinen Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 5 +- drivers/hid/Makefile | 3 +- drivers/hid/hid-quirks.c | 3 + drivers/hid/hid-thrustmaster.c | 370 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 378 insertions(+), 3 deletions(-) create mode 100644 drivers/hid/hid-thrustmaster.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 786b71ef7738..4001c125e2ce 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1044,8 +1044,9 @@ config HID_THRUSTMASTER tristate "ThrustMaster devices support" depends on 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" diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index c4f6d5c613dc..bca89f7c1b1a 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -112,7 +112,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 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 1a9daf03dbfa..849c71302ab0 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -661,6 +661,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) }, diff --git a/drivers/hid/hid-thrustmaster.c b/drivers/hid/hid-thrustmaster.c new file mode 100644 index 000000000000..bfcd08759ba0 --- /dev/null +++ b/drivers/hid/hid-thrustmaster.c @@ -0,0 +1,370 @@ +// 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 + * Copyright (c) 2020-2021 Kim Kuparinen + */ +#include +#include +#include +#include +#include + +/** + * 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 = kzalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (!tm_wheel->model_request) { + ret = -ENOMEM; + goto error3; + } + memcpy(tm_wheel->model_request, &model_request, sizeof(struct usb_ctrlrequest)); + + 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 "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Driver to initialize some steering wheel joysticks from Thrustmaster"); + -- cgit v1.2.3-59-g8ed1b From 2076b7bdc5290141cea809c8462b769836bd2757 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 18 Mar 2021 13:39:34 +0300 Subject: HID: ft260: fix an error message in ft260_i2c_write_read() The "len" variable is uninitialize. Fixes: 6a82582d9fa4 ("HID: ft260: add usb hid to i2c host bridge driver") Signed-off-by: Dan Carpenter Signed-off-by: Jiri Kosina --- drivers/hid/hid-ft260.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c index 047aa85a7c83..a5751607ce24 100644 --- a/drivers/hid/hid-ft260.c +++ b/drivers/hid/hid-ft260.c @@ -512,7 +512,8 @@ static int ft260_i2c_write_read(struct ft260_device *dev, struct i2c_msg *msgs) struct hid_device *hdev = dev->hdev; if (msgs[0].len > 2) { - hid_err(hdev, "%s: unsupported wr len: %d\n", __func__, len); + hid_err(hdev, "%s: unsupported wr len: %d\n", __func__, + msgs[0].len); return -EOPNOTSUPP; } -- cgit v1.2.3-59-g8ed1b From dfe74f550810cf7480d8759956d850ce40512f7a Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Fri, 19 Mar 2021 19:49:18 +0100 Subject: HID: thrustmaster: introduce proper USB dependency HID_THRUSTMASTER now requires USB_HID support. Reflect that dependency in Kconfig. Fixes: c49c33637802a2c ("HID: support for initialization of some Thrustmaster wheels") Reported-by: kernel test robot Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 4001c125e2ce..f1acb6510ccc 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1042,7 +1042,7 @@ 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, a THRUSTMASTER Ferrari GT Rumble Wheel or Thrustmaster FFB -- cgit v1.2.3-59-g8ed1b From b05ff1002a5c19f2fd511c6eada6f475ff701841 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Wed, 10 Mar 2021 23:53:28 +0100 Subject: HID: Add support for Surface Aggregator Module HID transport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a HID transport driver to support integrated HID devices on newer Microsoft Surface models (specifically 7th-generation, i.e. Surface Laptop 3, Surface Book 3, and later). On those models, the internal keyboard and touchpad (as well as some other HID devices with currently unknown function) are connected via the generic HID subsystem (TC=0x15) of the Surface System Aggregator Module (SSAM). This subsystem provides a generic HID transport layer, support for which is implemented by this driver. Co-developed-by: Blaž Hrastnik Signed-off-by: Blaž Hrastnik Signed-off-by: Maximilian Luz Signed-off-by: Jiri Kosina --- MAINTAINERS | 7 + drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 2 + drivers/hid/surface-hid/Kconfig | 28 +++ drivers/hid/surface-hid/Makefile | 6 + drivers/hid/surface-hid/surface_hid.c | 253 +++++++++++++++++++++++++++ drivers/hid/surface-hid/surface_hid_core.c | 272 +++++++++++++++++++++++++++++ drivers/hid/surface-hid/surface_hid_core.h | 77 ++++++++ 8 files changed, 647 insertions(+) create mode 100644 drivers/hid/surface-hid/Kconfig create mode 100644 drivers/hid/surface-hid/Makefile create mode 100644 drivers/hid/surface-hid/surface_hid.c create mode 100644 drivers/hid/surface-hid/surface_hid_core.c create mode 100644 drivers/hid/surface-hid/surface_hid_core.h diff --git a/MAINTAINERS b/MAINTAINERS index 4d433fd526c1..d6a1924afdcc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11876,6 +11876,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 +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 L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 786b71ef7738..26e06097ba08 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1206,4 +1206,6 @@ source "drivers/hid/intel-ish-hid/Kconfig" source "drivers/hid/amd-sfh-hid/Kconfig" +source "drivers/hid/surface-hid/Kconfig" + endmenu diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index c4f6d5c613dc..1044ed238856 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -145,3 +145,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/ diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig new file mode 100644 index 000000000000..642c7f0e64fe --- /dev/null +++ b/drivers/hid/surface-hid/Kconfig @@ -0,0 +1,28 @@ +# 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. + +endmenu + +config SURFACE_HID_CORE + tristate + select HID diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile new file mode 100644 index 000000000000..62fc04632d3d --- /dev/null +++ b/drivers/hid/surface-hid/Makefile @@ -0,0 +1,6 @@ +# 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 diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c new file mode 100644 index 000000000000..3477b31611ae --- /dev/null +++ b/drivers/hid/surface-hid/surface_hid.c @@ -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 , + * Maximilian Luz + */ + +#include +#include +#include +#include +#include + +#include +#include + +#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 "); +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c new file mode 100644 index 000000000000..7b27ec392232 --- /dev/null +++ b/drivers/hid/surface-hid/surface_hid_core.c @@ -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 + */ + +#include +#include +#include +#include +#include +#include + +#include + +#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 "); +MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/surface-hid/surface_hid_core.h b/drivers/hid/surface-hid/surface_hid_core.h new file mode 100644 index 000000000000..4b1a7b57e035 --- /dev/null +++ b/drivers/hid/surface-hid/surface_hid_core.h @@ -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 + */ + +#ifndef SURFACE_HID_CORE_H +#define SURFACE_HID_CORE_H + +#include +#include +#include + +#include +#include + +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 */ -- cgit v1.2.3-59-g8ed1b From 35a927f2848bd79586c6374ebb99e4207f3b0c7f Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Wed, 10 Mar 2021 23:53:29 +0100 Subject: HID: surface-hid: Add support for legacy keyboard interface Add support for the legacy keyboard (KBD/TC=0x08) HID transport layer of the Surface System Aggregator Module (SSAM) to the Surface HID driver. On Surface Laptops 1 and 2, this interface is used to connect the integrated keyboard. Note that this subsystem interface essentially provides a limited HID transport layer. In contrast to the generic HID interface (TC=0x15) used on newer Surface models, this interface only allows (as far as we know) for a single device to be connected and is otherwise severely limited in terms of support for feature- and output-reports. Specifically, only caps-lock-LED output-reports and a single read-only feature-report are supported. Signed-off-by: Maximilian Luz Signed-off-by: Jiri Kosina --- drivers/hid/surface-hid/Kconfig | 14 ++ drivers/hid/surface-hid/Makefile | 1 + drivers/hid/surface-hid/surface_kbd.c | 300 ++++++++++++++++++++++++++++++++++ 3 files changed, 315 insertions(+) create mode 100644 drivers/hid/surface-hid/surface_kbd.c diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig index 642c7f0e64fe..7ce9b5d641eb 100644 --- a/drivers/hid/surface-hid/Kconfig +++ b/drivers/hid/surface-hid/Kconfig @@ -21,6 +21,20 @@ config SURFACE_HID 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 diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile index 62fc04632d3d..4ae11cf09b25 100644 --- a/drivers/hid/surface-hid/Makefile +++ b/drivers/hid/surface-hid/Makefile @@ -4,3 +4,4 @@ # obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o obj-$(CONFIG_SURFACE_HID) += surface_hid.o +obj-$(CONFIG_SURFACE_KBD) += surface_kbd.o diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c new file mode 100644 index 000000000000..0635341bc517 --- /dev/null +++ b/drivers/hid/surface-hid/surface_kbd.c @@ -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 + */ + +#include +#include +#include +#include +#include +#include + +#include + +#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 "); +MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 66ff89946e6a71e48acaecfd5190038922b8e96a Mon Sep 17 00:00:00 2001 From: kernel test robot Date: Thu, 25 Mar 2021 23:49:56 +0800 Subject: HID: fix memdup.cocci warnings drivers/hid/hid-thrustmaster.c:300:27-34: WARNING opportunity for kmemdup Use kmemdup rather than duplicating its implementation Generated by: scripts/coccinelle/api/memdup.cocci Fixes: c49c33637802 ("HID: support for initialization of some Thrustmaster wheels") CC: Dario Pagani Reported-by: kernel test robot Signed-off-by: kernel test robot Signed-off-by: Jiri Kosina --- drivers/hid/hid-thrustmaster.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-thrustmaster.c b/drivers/hid/hid-thrustmaster.c index bfcd08759ba0..d38304a35199 100644 --- a/drivers/hid/hid-thrustmaster.c +++ b/drivers/hid/hid-thrustmaster.c @@ -297,12 +297,13 @@ static int thrustmaster_probe(struct hid_device *hdev, const struct hid_device_i goto error2; } - tm_wheel->model_request = kzalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + tm_wheel->model_request = kmemdup(&model_request, + sizeof(struct usb_ctrlrequest), + GFP_KERNEL); if (!tm_wheel->model_request) { ret = -ENOMEM; goto error3; } - memcpy(tm_wheel->model_request, &model_request, sizeof(struct usb_ctrlrequest)); tm_wheel->response = kzalloc(sizeof(struct tm_wheel_response), GFP_KERNEL); if (!tm_wheel->response) { -- cgit v1.2.3-59-g8ed1b From efb618fba54d7df0414f395b6213a469c075f859 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 09:52:50 +0200 Subject: HID: elan: Silence mute LED errors being logged when the device is unplugged MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Being integrated into an USB keyboard-dock the mute LED can go away at any time, leading to the following errors: [ 918.667671] elan 0003:04F3:0755.0002: Failed to set mute led brightness: -19 [ 918.667737] leds elan:red:mute: Setting an LED's brightness failed (-19) Fix this by making the following changes: 1. Don't log an error from elan_mute_led_set_brigtness() when ret == -ENODEV 2. Set the LED_HW_PLUGGABLE flag on the mute LED led_classdev While at it also make sure that elan_mute_led_set_brigtness() returns an error (-EIO) when ret != 3 but it is not an error (>= 0). Reviewed-by: Marek Behún Acked-by: Pavel Machek Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-elan.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-elan.c b/drivers/hid/hid-elan.c index dae193749d44..473d8528319d 100644 --- a/drivers/hid/hid-elan.c +++ b/drivers/hid/hid-elan.c @@ -445,8 +445,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; @@ -462,6 +463,7 @@ static int elan_init_mute_led(struct hid_device *hdev) mute_led->brightness_get = elan_mute_led_get_brigtness; 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); -- cgit v1.2.3-59-g8ed1b From 2601371e1082ca30577556d381afacc11a11157b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 09:52:51 +0200 Subject: HID: elan: Set default_trigger for the mute LED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mute LED should be automatically turned on/off based on the audio-card's mixer settings. Add the standardized default-trigger name for this, so that the alsa code can turn the LED on/off as appropriate (on supported audio cards). Reviewed-by: Marek Behún Acked-by: Pavel Machek Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-elan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hid/hid-elan.c b/drivers/hid/hid-elan.c index 473d8528319d..5173f50d474d 100644 --- a/drivers/hid/hid-elan.c +++ b/drivers/hid/hid-elan.c @@ -460,6 +460,7 @@ 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->default_trigger = "audio-mute"; mute_led->brightness_get = elan_mute_led_get_brigtness; mute_led->brightness_set_blocking = elan_mute_led_set_brigtness; mute_led->max_brightness = LED_ON; -- cgit v1.2.3-59-g8ed1b From ba3f92938f2c01623546d4570ecc19a8b7786fa7 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 09:52:52 +0200 Subject: HID: elan: Remove elan_mute_led_get_brigtness() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The led_classdev already contains a cached value of the last set brightness, the brightness_get callback is only meant for LED drivers which can read back the actual / current brightness from the hardware. Since elan_mute_led_get_brigtness() just returns the last set value it does not add any functionality, so we can just remove it. Reviewed-by: Marek Behún Acked-by: Pavel Machek Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-elan.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/hid/hid-elan.c b/drivers/hid/hid-elan.c index 5173f50d474d..021049805bb7 100644 --- a/drivers/hid/hid-elan.c +++ b/drivers/hid/hid-elan.c @@ -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) { @@ -461,7 +452,6 @@ static int elan_init_mute_led(struct hid_device *hdev) mute_led->name = "elan:red:mute"; mute_led->default_trigger = "audio-mute"; - mute_led->brightness_get = elan_mute_led_get_brigtness; mute_led->brightness_set_blocking = elan_mute_led_set_brigtness; mute_led->max_brightness = LED_ON; mute_led->flags = LED_HW_PLUGGABLE; -- cgit v1.2.3-59-g8ed1b From bbf62645255f120bc2e7488c237e3f04da42ec70 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 10:04:24 +0200 Subject: HID: lenovo: Use brightness_set_blocking callback for setting LEDs brightness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lenovo_led_brightness_set function may sleep, so we should have the the led_class_dev's brightness_set_blocking callback point to it, rather then the regular brightness_set callback. When toggled through sysfs this is not a problem, but the brightness_set callback may be called from atomic context when using LED-triggers. Fixes: bc04b37ea0ec ("HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support") Reviewed-by: Marek Behún Acked-by: Pavel Machek Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index c6c8e20f3e8d..4dc5e5f932ed 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -777,7 +777,7 @@ static enum led_brightness lenovo_led_brightness_get( : 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; @@ -802,6 +802,8 @@ static void lenovo_led_brightness_set(struct led_classdev *led_cdev, lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value); break; } + + return 0; } static int lenovo_register_leds(struct hid_device *hdev) @@ -822,7 +824,7 @@ static int lenovo_register_leds(struct hid_device *hdev) 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.brightness_set_blocking = lenovo_led_brightness_set; data->led_mute.dev = &hdev->dev; ret = led_classdev_register(&hdev->dev, &data->led_mute); if (ret < 0) @@ -830,7 +832,7 @@ static int lenovo_register_leds(struct hid_device *hdev) 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.brightness_set_blocking = lenovo_led_brightness_set; data->led_micmute.dev = &hdev->dev; ret = led_classdev_register(&hdev->dev, &data->led_micmute); if (ret < 0) { -- cgit v1.2.3-59-g8ed1b From 658d04e6eb6be1601ae95d7bee92bbf4096cdc1e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 10:04:25 +0200 Subject: HID: lenovo: Fix lenovo_led_set_tp10ubkbd() error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the following issues with lenovo_led_set_tp10ubkbd() error handling: 1. On success hid_hw_raw_request() returns the number of bytes sent. So we should check for (ret != 3) rather then for (ret != 0). 2. Actually propagate errors to the caller. 3. Since the LEDs are part of an USB keyboard-dock the mute LEDs can go away at any time. Don't log an error when ret == -ENODEV and set the LED_HW_PLUGGABLE flag to avoid errors getting logged when the USB gets disconnected. Fixes: bc04b37ea0ec ("HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support") Reviewed-by: Marek Behún Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index 4dc5e5f932ed..ee175ab54281 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -62,8 +62,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 +75,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) @@ -349,7 +357,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 +372,9 @@ 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); + ret = lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value); + if (ret) + return ret; break; } @@ -785,6 +795,7 @@ static int 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,11 +810,11 @@ static int 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); + ret = lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value); break; } - return 0; + return ret; } static int lenovo_register_leds(struct hid_device *hdev) @@ -825,6 +836,7 @@ static int lenovo_register_leds(struct hid_device *hdev) data->led_mute.name = name_mute; data->led_mute.brightness_get = lenovo_led_brightness_get; data->led_mute.brightness_set_blocking = lenovo_led_brightness_set; + 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) @@ -833,6 +845,7 @@ static int lenovo_register_leds(struct hid_device *hdev) data->led_micmute.name = name_micm; data->led_micmute.brightness_get = lenovo_led_brightness_get; data->led_micmute.brightness_set_blocking = lenovo_led_brightness_set; + 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) { -- cgit v1.2.3-59-g8ed1b From 34348a8661e3cd67dcf6938f08c8bb77522301f7 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 10:04:26 +0200 Subject: HID: lenovo: Check hid_get_drvdata() returns non NULL in lenovo_event() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HID lenovo probe function only attaches drvdata to one of the USB interfaces, but lenovo_event() will get called for all USB interfaces to which hid-lenovo is bound. This allows a malicious device to fake being a device handled by hid-lenovo, which generates events for which lenovo_event() has special handling (and thus dereferences hid_get_drvdata()) on another interface triggering a NULL pointer exception. Add a check for hid_get_drvdata() returning NULL, avoiding this possible NULL pointer exception. Fixes: bc04b37ea0ec ("HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support") Reviewed-by: Marek Behún Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index ee175ab54281..b2596ed37880 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -508,6 +508,9 @@ 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: -- cgit v1.2.3-59-g8ed1b From 033c978680255a81c6284cb6ea4de20bd2bdbe5c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 10:04:27 +0200 Subject: HID: lenovo: Remove lenovo_led_brightness_get() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The led_classdev already contains a cached value of the last set brightness, the brightness_get callback is only meant for LED drivers which can read back the actual / current brightness from the hardware. Since lenovo_led_brightness_get() just returns the last set value it does not add any functionality, so we can just remove it. Reviewed-by: Marek Behún Acked-by: Pavel Machek Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index b2596ed37880..1b8dd85ceb05 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -774,22 +774,6 @@ 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 int lenovo_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness value) { @@ -837,7 +821,6 @@ 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_blocking = lenovo_led_brightness_set; data->led_mute.flags = LED_HW_PLUGGABLE; data->led_mute.dev = &hdev->dev; @@ -846,7 +829,6 @@ static int lenovo_register_leds(struct hid_device *hdev) return ret; data->led_micmute.name = name_micm; - data->led_micmute.brightness_get = lenovo_led_brightness_get; data->led_micmute.brightness_set_blocking = lenovo_led_brightness_set; data->led_micmute.flags = LED_HW_PLUGGABLE; data->led_micmute.dev = &hdev->dev; -- cgit v1.2.3-59-g8ed1b From 8744eeec539aa760c41a64b3efbb77868b1d700a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 10:04:28 +0200 Subject: HID: lenovo: Set LEDs max_brightness value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LEDs can only by turned on/off, so max_brightness should be set to 1. Without this the max_brightness sysfs-attribute will report 255 which is wrong. Reviewed-by: Marek Behún Acked-by: Pavel Machek Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index 1b8dd85ceb05..668762663f69 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -822,6 +822,7 @@ static int lenovo_register_leds(struct hid_device *hdev) data->led_mute.name = name_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); @@ -830,6 +831,7 @@ static int lenovo_register_leds(struct hid_device *hdev) data->led_micmute.name = name_micm; 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); -- cgit v1.2.3-59-g8ed1b From 617103246cfd19af837e4cb614ba9f877c4f7779 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 10:04:29 +0200 Subject: HID: lenovo: Map mic-mute button to KEY_F20 instead of KEY_MICMUTE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mapping the mic-mute button to KEY_MICMUTE is technically correct but KEY_MICMUTE translates to a scancode of 256 (248 + 8) under X, which does not fit in 8 bits, so it does not work. Because of this userspace is expecting KEY_F20 instead, theoretically KEY_MICMUTE should work under Wayland but even there it does not work, because the desktop-environment is listening only for KEY_F20 and not for KEY_MICMUTE. Fixes: bc04b37ea0ec ("HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support") Reviewed-by: Marek Behún Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index 668762663f69..2cf89b880ac5 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -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; @@ -134,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; @@ -149,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); @@ -239,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); -- cgit v1.2.3-59-g8ed1b From e2da5ff435d64b59880648269c136484bea5c312 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 10:04:30 +0200 Subject: HID: lenovo: Set default_triggers for the mute and micmute LEDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mute and mic-mute LEDs should be automatically turned on/off based on the audio-card's mixer settings. Add the standardized default-trigger names for this, so that the alsa code can turn the LEDs on/off as appropriate (on supported audio cards). This brings the mute/mic-mute LED support inline with the thinkpad_acpi support for the same LEDs in keyboards directly connected to the laptop's embedded-controller. Reviewed-by: Marek Behún Acked-by: Pavel Machek Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index 2cf89b880ac5..2287142116b9 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -824,6 +824,7 @@ 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.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; @@ -833,6 +834,7 @@ static int lenovo_register_leds(struct hid_device *hdev) return ret; data->led_micmute.name = name_micm; + 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; -- cgit v1.2.3-59-g8ed1b From 2d0f1c071ab1ce2f75ac3e52b99775ce574f6416 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 10:04:31 +0200 Subject: HID: lenovo: Rework how the tp10ubkbd code decides which USB interface to use Instead of looking for a hdev with a type of HID_TYPE_USBMOUSE find the interface for the mute/mic-mute/fn-lock LEDs by checking for the output-report which is used to set them. This is a preparation patch for adding support for the LEDs on the X1 tablet thin keyboard which uses the same output-report, but has a separate (third) USB interface for the touchpad/mouse functionality. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index 2287142116b9..a33de5022ec3 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -959,11 +959,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); -- cgit v1.2.3-59-g8ed1b From c158c2afc99f9d1086f6d53b9c6c9813fcac1a10 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 4 Apr 2021 10:04:32 +0200 Subject: HID: lenovo: Add support for Thinkpad X1 Tablet Thin keyboard The Thinkpad X1 Tablet Thin keyboard's HID interface for the media-keys and other special functions, is quite similar to the Thinkpad 10 ultrabook keyboard's mouse/media-keys HID interface. The only difference is that it needs a bit different key mappings. Add support for the mute-LED and the non-standard media-keys on this keyboard by re-using the tp10_ultrabook_kbd code combined with a new lenovo_input_mapping_x1_tab_kbd() function. Co-authored-by: Alexander Kobel Tested-by: Alexander Kobel Signed-off-by: Alexander Kobel Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index a33de5022ec3..93b1f935e526 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -266,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) @@ -289,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; } @@ -375,6 +425,7 @@ static ssize_t attr_fn_lock_store(struct device *dev, lenovo_features_set_cptkbd(hdev); break; case USB_DEVICE_ID_LENOVO_TP10UBKBD: + case USB_DEVICE_ID_LENOVO_X1_TAB: ret = lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value); if (ret) return ret; @@ -519,6 +570,7 @@ static int lenovo_event(struct hid_device *hdev, struct hid_field *field, 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; @@ -800,6 +852,7 @@ static int lenovo_led_brightness_set(struct led_classdev *led_cdev, lenovo_led_set_tpkbd(hdev); break; case USB_DEVICE_ID_LENOVO_TP10UBKBD: + case USB_DEVICE_ID_LENOVO_X1_TAB: ret = lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value); break; } @@ -1038,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: @@ -1103,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; } @@ -1142,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) }, { } }; -- cgit v1.2.3-59-g8ed1b From 2b0c086cd6f40648df73c30ea785e5c7777948da Mon Sep 17 00:00:00 2001 From: John Chen Date: Tue, 30 Mar 2021 19:33:16 +0800 Subject: HID: magicmouse: add Apple Magic Mouse 2 support Bluetooth device Vendor 004c (Apple) Device 0269 (Magic Mouse 2) Add support for Apple Magic Mouse 2, putting the device in multi-touch mode. Co-authored-by: Rohit Pidaparthi Co-authored-by: RicardoEPRodrigues Signed-off-by: John Chen Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-magicmouse.c | 53 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index e42aaae3138f..fa0edf03570a 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -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 diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index abd86903875f..7aad6ca56780 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -54,6 +54,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 @@ -195,7 +196,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 +298,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 +383,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 +423,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); @@ -415,7 +447,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 +513,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); @@ -586,6 +620,7 @@ static int magicmouse_probe(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; @@ -631,6 +666,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, @@ -660,6 +698,9 @@ static int magicmouse_probe(struct hid_device *hdev, feature_size = sizeof(feature_mt_trackpad2_usb); feature = feature_mt_trackpad2_usb; } + } else if (id->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; @@ -696,6 +737,8 @@ err_stop_hw: 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, -- cgit v1.2.3-59-g8ed1b From 3dcc5f7b45e037b0c6e84e4f757d16c55535db28 Mon Sep 17 00:00:00 2001 From: John Chen Date: Tue, 30 Mar 2021 19:33:17 +0800 Subject: HID: magicmouse: fix 3 button emulation of Mouse 2 It is observed that, with 3 button emulation, when middle button is clicked, either the left button or right button is clicked as well. It is caused by hidinput "correctly" acting on the event, oblivious to the 3 button emulation. As raw_event has taken care of everything, no further processing is needed. However, the only way to stop at raw_event is to return an error (negative) value. Therefore, the processing is stopped at event instead. [jkosina@suse.cz: fix comment style] Signed-off-by: John Chen Signed-off-by: Jiri Kosina --- drivers/hid/hid-magicmouse.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 7aad6ca56780..6740bda725d1 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -440,6 +440,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; @@ -754,6 +771,7 @@ static struct hid_driver magicmouse_driver = { .id_table = magic_mice, .probe = magicmouse_probe, .raw_event = magicmouse_raw_event, + .event = magicmouse_event, .input_mapping = magicmouse_input_mapping, .input_configured = magicmouse_input_configured, }; -- cgit v1.2.3-59-g8ed1b From c0dc5582812dfaf122a6eb188b0cd8e5ae4b0387 Mon Sep 17 00:00:00 2001 From: John Chen Date: Tue, 30 Mar 2021 19:33:18 +0800 Subject: HID: magicmouse: fix reconnection of Magic Mouse 2 It is observed that the Magic Mouse 2 would not enter multi-touch mode unless the mouse is connected before loading the module. It seems to be a quirk specific to Magic Mouse 2 Retrying after 500ms fixes the problem for me. The delay can't be reduced much further --- 300ms didn't work for me. Retrying immediately after receiving an event didn't work either. Signed-off-by: John Chen Signed-off-by: Jiri Kosina --- drivers/hid/hid-magicmouse.c | 93 ++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 30 deletions(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 6740bda725d1..2bb473d8c424 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "hid-ids.h" @@ -128,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) @@ -631,9 +635,7 @@ static int magicmouse_input_configured(struct hid_device *hdev, return 0; } - -static int magicmouse_probe(struct hid_device *hdev, - const struct hid_device_id *id) +static int magicmouse_enable_multitouch(struct hid_device *hdev) { const u8 *feature; const u8 feature_mt[] = { 0xD7, 0x01 }; @@ -641,10 +643,52 @@ static int magicmouse_probe(struct hid_device *hdev, 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) +{ 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 && @@ -658,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); @@ -707,28 +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 if (id->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) { - 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. @@ -737,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: @@ -751,6 +776,13 @@ 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 }, @@ -770,6 +802,7 @@ 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, -- cgit v1.2.3-59-g8ed1b From 9de07a4e8d4cb269f9876b2ffa282b5ffd09e05b Mon Sep 17 00:00:00 2001 From: John Chen Date: Tue, 30 Mar 2021 19:33:19 +0800 Subject: HID: input: map battery capacity (00850065) This is the capacity in percentage, relative to design capacity. Specifically, it is present in Apple Magic Mouse 2. In contrast, usage 00850064 is also the capacity in percentage, but is relative to full capacity. It is not mapped here because I don't have such device. Signed-off-by: John Chen Signed-off-by: Jiri Kosina --- drivers/hid/hid-debug.c | 1 + drivers/hid/hid-input.c | 22 ++++++++++++++++------ include/linux/hid.h | 3 +++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index d7eaf9100370..59f8d716d78f 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -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" }, diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 236bccd37760..18f5e28d475c 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -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); diff --git a/include/linux/hid.h b/include/linux/hid.h index ef702b3f56e3..b40e1abbe11d 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -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 @@ -297,6 +298,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! -- cgit v1.2.3-59-g8ed1b From e9bb37ff35bb6faed53c5a2ae0329589a8b02939 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:51 +0000 Subject: HID: hid-thrustmaster: Demote a bunch of kernel-doc abuses Fixes the following W=1 kernel build warning(s): drivers/hid/hid-thrustmaster.c:23: warning: Incorrect use of kernel-doc format: * These interrupts are used to prevent a nasty crash when initializing the drivers/hid/hid-thrustmaster.c:26: warning: cannot understand function prototype: 'const u8 setup_0[] = ' drivers/hid/hid-thrustmaster.c:49: warning: cannot understand function prototype: 'struct tm_wheel_info ' drivers/hid/hid-thrustmaster.c:62: warning: wrong kernel-doc identifier on line: drivers/hid/hid-thrustmaster.c:84: warning: cannot understand function prototype: 'struct __packed tm_wheel_response ' drivers/hid/hid-thrustmaster.c:143: warning: wrong kernel-doc identifier on line: drivers/hid/hid-thrustmaster.c:202: warning: Function parameter or member 'urb' not described in 'thrustmaster_model_handler' drivers/hid/hid-thrustmaster.c:202: warning: expecting prototype for Called by the USB subsystem when the wheel responses to our request(). Prototype was for thrustmaster_model_handler() instead drivers/hid/hid-thrustmaster.c:265: warning: wrong kernel-doc identifier on line: Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: Maxime Coquelin Cc: Alexandre Torgue Cc: Dario Pagani Cc: Kim Kuparinen Cc: linux-input@vger.kernel.org Cc: linux-stm32@st-md-mailman.stormreply.com Cc: linux-arm-kernel@lists.infradead.org Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-thrustmaster.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/hid/hid-thrustmaster.c b/drivers/hid/hid-thrustmaster.c index d38304a35199..2e452c6e8ef4 100644 --- a/drivers/hid/hid-thrustmaster.c +++ b/drivers/hid/hid-thrustmaster.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -/** +/* * When connected to the machine, the Thrustmaster wheels appear as * a «generic» hid gamepad called "Thrustmaster FFB Wheel". * @@ -19,7 +19,7 @@ #include #include -/** +/* * These interrupts are used to prevent a nasty crash when initializing the * T300RS. Used in thrustmaster_interrupts(). */ @@ -36,7 +36,7 @@ static const unsigned int setup_arr_sizes[] = { ARRAY_SIZE(setup_3), ARRAY_SIZE(setup_4) }; -/** +/* * This struct contains for each type of * Thrustmaster wheel * @@ -49,7 +49,7 @@ static const unsigned int setup_arr_sizes[] = { 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. */ @@ -58,7 +58,7 @@ struct tm_wheel_info { char const *const wheel_name; }; -/** +/* * Known wheels. * Note: TMX does not work as it requires 2 control packets */ @@ -72,7 +72,7 @@ static const struct tm_wheel_info tm_wheels_infos[] = { 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 * @@ -82,7 +82,7 @@ static const uint8_t tm_wheels_infos_length = 4; */ 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) @@ -93,7 +93,7 @@ struct __packed tm_wheel_response struct __packed { uint16_t field0; uint16_t field1; - /** + /* * Seems to be the model code of the wheel * Read table thrustmaster_wheels to values */ @@ -122,7 +122,7 @@ struct tm_wheel { struct usb_ctrlrequest *change_request; }; -/** The control packet to send to wheel */ +/* The control packet to send to wheel */ static const struct usb_ctrlrequest model_request = { .bRequestType = 0xc1, .bRequest = 73, @@ -139,7 +139,7 @@ static const struct usb_ctrlrequest change_request = { .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. @@ -191,7 +191,7 @@ static void thrustmaster_change_handler(struct urb *urb) 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. * @@ -261,7 +261,7 @@ static void thrustmaster_remove(struct hid_device *hdev) 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 -- cgit v1.2.3-59-g8ed1b From 4cbf8aa75cf3ce860805e04d6fae0031caf75478 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:39 +0000 Subject: HID: usbhid: Repair a formatting issue in a struct description Fixes the following W=1 kernel build warning(s): drivers/hid/usbhid/usbkbd.c:66: warning: bad line: should be on Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: message to Cc: linux-usb@vger.kernel.org Cc: linux-input@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/usbkbd.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c index d5b7a696a68c..e22434dfc9ef 100644 --- a/drivers/hid/usbhid/usbkbd.c +++ b/drivers/hid/usbhid/usbkbd.c @@ -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; -- cgit v1.2.3-59-g8ed1b From fbe352aa7bbd6160ab9c04eee87dac4f507d958b Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:41 +0000 Subject: HID: usbhid: hid-pidff: Demote a couple kernel-doc abuses Fixes the following W=1 kernel build warning(s): drivers/hid/usbhid/hid-pidff.c:512: warning: Function parameter or member 'dev' not described in 'pidff_playback' drivers/hid/usbhid/hid-pidff.c:512: warning: Function parameter or member 'effect_id' not described in 'pidff_playback' drivers/hid/usbhid/hid-pidff.c:512: warning: Function parameter or member 'value' not described in 'pidff_playback' drivers/hid/usbhid/hid-pidff.c:512: warning: expecting prototype for value times(). Prototype was for pidff_playback() instead drivers/hid/usbhid/hid-pidff.c:1005: warning: Function parameter or member 'pidff' not described in 'pidff_find_effects' drivers/hid/usbhid/hid-pidff.c:1005: warning: Function parameter or member 'dev' not described in 'pidff_find_effects' drivers/hid/usbhid/hid-pidff.c:1005: warning: expecting prototype for Find the implemented effect types(). Prototype was for pidff_find_effects() instead Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: Anssi Hannula Cc: linux-usb@vger.kernel.org Cc: linux-input@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires --- drivers/hid/usbhid/hid-pidff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index fddac7c72f64..ea126c50acc3 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -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, -- cgit v1.2.3-59-g8ed1b From 8802d1546b79296afc3a3ae803c9dc869eb2ad52 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:42 +0000 Subject: HID: hid-alps: Correct struct misnaming Fixes the following W=1 kernel build warning(s): drivers/hid/hid-alps.c:113: warning: expecting prototype for struct u1_data. Prototype was for struct alps_dev instead Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: Henrik Rydberg Cc: Masaki Ota Cc: linux-input@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-alps.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 3feaece13ade..8b86058f6380 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -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 -- cgit v1.2.3-59-g8ed1b From cd2bb7b73e0bc395f01196e4b9c4b20f4a723750 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:44 +0000 Subject: HID: hid-core: Fix incorrect function name in header Fixes the following W=1 kernel build warning(s): drivers/hid/hid-core.c:2142: warning: expecting prototype for store_new_id(). Prototype was for new_id_store() instead Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: Vojtech Pavlik Cc: Michael Haboustak Cc: linux-input@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 097cb1ee3126..0ae9f6df59d1 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -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 -- cgit v1.2.3-59-g8ed1b From a3760cd75e330d994e3b4cd4a61e40b4aecf34b1 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:47 +0000 Subject: HID: hid-kye: Fix incorrect function name for kye_tablet_enable() Fixes the following W=1 kernel build warning(s): drivers/hid/hid-kye.c:666: warning: expecting prototype for Enable fully(). Prototype was for kye_tablet_enable() instead Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-kye.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c index c8b40c07eca6..f46616390a98 100644 --- a/drivers/hid/hid-kye.c +++ b/drivers/hid/hid-kye.c @@ -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 * -- cgit v1.2.3-59-g8ed1b From ae71db0d4d68f39b16f970af21c9c695fea9f5c9 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:48 +0000 Subject: HID: hid-picolcd_core: Remove unused variable 'ret' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the following W=1 kernel build warning(s): drivers/hid/hid-picolcd_core.c: In function ‘picolcd_raw_event’: drivers/hid/hid-picolcd_core.c:332:6: warning: variable ‘ret’ set but not used [-Wunused-but-set-variable] Cc: "Bruno Prémont" Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: "L. Vinyard, Jr" Cc: linux-input@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-picolcd_core.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c index 1b5c63241af0..bbda231a7ce3 100644 --- a/drivers/hid/hid-picolcd_core.c +++ b/drivers/hid/hid-picolcd_core.c @@ -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); /* -- cgit v1.2.3-59-g8ed1b From 3f37fdcd313d2c9843aec01cef6512929a33a677 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:49 +0000 Subject: HID: hid-logitech-hidpp: Fix conformant kernel-doc header and demote abuses Fixes the following W=1 kernel build warning(s): drivers/hid/hid-logitech-hidpp.c:275: warning: Function parameter or member 'hidpp' not described in 'hidpp_send_message_sync' drivers/hid/hid-logitech-hidpp.c:275: warning: Function parameter or member 'message' not described in 'hidpp_send_message_sync' drivers/hid/hid-logitech-hidpp.c:275: warning: Function parameter or member 'response' not described in 'hidpp_send_message_sync' drivers/hid/hid-logitech-hidpp.c:275: warning: expecting prototype for and something else(). Prototype was for hidpp_send_message_sync() instead drivers/hid/hid-logitech-hidpp.c:427: warning: wrong kernel-doc identifier on line: drivers/hid/hid-logitech-hidpp.c:470: warning: Function parameter or member 'input_dev' not described in 'hidpp_scroll_counter_handle_scroll' drivers/hid/hid-logitech-hidpp.c:1894: warning: Function parameter or member 'hidpp' not described in 'hidpp_touchpad_fw_items_set' drivers/hid/hid-logitech-hidpp.c:1894: warning: Function parameter or member 'feature_index' not described in 'hidpp_touchpad_fw_items_set' drivers/hid/hid-logitech-hidpp.c:1894: warning: Function parameter or member 'items' not described in 'hidpp_touchpad_fw_items_set' drivers/hid/hid-logitech-hidpp.c:1894: warning: expecting prototype for send a set state command to the device by reading the current items(). Prototype was for hidpp_touchpad_fw_items_set() instead Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: Henrik Rydberg Cc: Lopez Casado Cc: linux-input@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index d459e2dbe647..d598094dadd0 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -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. */ -- cgit v1.2.3-59-g8ed1b From a62ce117ac189abccffb81f66e32f1694d1271de Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:50 +0000 Subject: HID: hid-uclogic-rdesc: Kernel-doc is for functions and structs Fixes the following W=1 kernel build warning(s): drivers/hid/hid-uclogic-rdesc.c:645: warning: wrong kernel-doc identifier on line: Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-uclogic-rdesc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index bf5da6de7bba..6dd6dcd09c8b 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -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 -- cgit v1.2.3-59-g8ed1b From 5abb54458242659552046e84ccd889577e2fd971 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:52 +0000 Subject: HID: hid-uclogic-params: Ensure function names are present and correct in kernel-doc headers Fixes the following W=1 kernel build warning(s): drivers/hid/hid-uclogic-params.c:33: warning: expecting prototype for Convert a pen in(). Prototype was for uclogic_params_pen_inrange_to_str() instead drivers/hid/hid-uclogic-params.c:519: warning: wrong kernel-doc identifier on line: drivers/hid/hid-uclogic-params.c:706: warning: expecting prototype for uclogic_params_init(). Prototype was for uclogic_params_huion_init() instead Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-uclogic-params.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 6af25c38b9cc..3d67b748a3b9 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -21,7 +21,8 @@ #include /** - * 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 -- cgit v1.2.3-59-g8ed1b From dbf104fdd42e49310851fa258ce0493e69dd2221 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:53 +0000 Subject: HID: hid-sensor-custom: Remove unused variable 'ret' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the following W=1 kernel build warning(s): drivers/hid/hid-sensor-custom.c: In function ‘store_value’: drivers/hid/hid-sensor-custom.c:400:7: warning: variable ‘ret’ set but not used [-Wunused-but-set-variable] Cc: Jiri Kosina Cc: Jonathan Cameron Cc: Srinivas Pandruvada Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Cc: linux-iio@vger.kernel.org Signed-off-by: Lee Jones Acked-by: Jonathan Cameron Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sensor-custom.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c index 2628bc53ed80..2e6662173a79 100644 --- a/drivers/hid/hid-sensor-custom.c +++ b/drivers/hid/hid-sensor-custom.c @@ -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; -- cgit v1.2.3-59-g8ed1b From 1784cd57e35f8cc5981cb1e632561e74956cd007 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:54 +0000 Subject: HID: wacom_sys: Demote kernel-doc abuse Fixes the following W=1 kernel build warning(s): drivers/hid/wacom_sys.c:1505: warning: Function parameter or member 'wacom' not described in 'wacom_led_next' drivers/hid/wacom_sys.c:1505: warning: Function parameter or member 'cur' not described in 'wacom_led_next' Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Signed-off-by: Lee Jones Signed-off-by: Benjamin Tissoires --- drivers/hid/wacom_sys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 8328ef155c46..57bfa0ae9836 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -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 -- cgit v1.2.3-59-g8ed1b From 07b34ddd759b7b1580eee6fd754dec824a280f0d Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:55 +0000 Subject: HID: hid-sensor-hub: Remove unused struct member 'quirks' Commit b0f847e16c1ea ("HID: hid-sensor-hub: Force logical minimum to 1 for power and report state") removed the last used quirk handled by this driver. Fixes the following W=1 kernel build warning(s): drivers/hid/hid-sensor-hub.c:39: warning: Function parameter or member 'quirks' not described in 'sensor_hub_data' Cc: Jiri Kosina Cc: Jonathan Cameron Cc: Srinivas Pandruvada Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Cc: linux-iio@vger.kernel.org Signed-off-by: Lee Jones Acked-by: Jonathan Cameron Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sensor-hub.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 3dd7d3246737..36b6852f75dd 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -34,7 +34,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; }; @@ -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); -- cgit v1.2.3-59-g8ed1b From ff0e9ee3a6d40c8a1c6e19ea8620ef94816eb51c Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Fri, 26 Mar 2021 14:34:56 +0000 Subject: HID: hid-sensor-hub: Move 'hsdev' description to correct struct definition Fixes the following W=1 kernel build warning(s): drivers/hid/hid-sensor-hub.c:54: warning: Function parameter or member 'hsdev' not described in 'hid_sensor_hub_callbacks_list' Cc: Jiri Kosina Cc: Jonathan Cameron Cc: Srinivas Pandruvada Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Cc: linux-iio@vger.kernel.org Signed-off-by: Lee Jones Acked-by: Jonathan Cameron Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-sensor-hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 36b6852f75dd..95cf88f3bafb 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -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 @@ -41,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. */ -- cgit v1.2.3-59-g8ed1b From 46fc466e01a187ef7c1e21448a89b831c3f5b519 Mon Sep 17 00:00:00 2001 From: Ping Cheng Date: Mon, 5 Apr 2021 20:46:34 -0700 Subject: HID: wacom: Setup pen input capabilities to the targeted tools Only set the bits that are supported by the targeted devices. This patch also removes duplicated set_bit calls. Signed-off-by: Ping Cheng Reviewed-by: Jason Gerecke Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 50 +++++++++++++++++++++++++------------------------ drivers/hid/wacom_wac.h | 1 + 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 44d715c12f6a..5dfc5dfbbd19 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -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; } @@ -3584,11 +3588,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; - } __set_bit(BTN_TOUCH, input_dev->keybit); __set_bit(ABS_MISC, input_dev->absbit); diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 195910dd2154..71c886245dbf 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -300,6 +300,7 @@ struct hid_data { bool tipswitch; bool barrelswitch; bool barrelswitch2; + bool serialhi; int x; int y; int pressure; -- cgit v1.2.3-59-g8ed1b