]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
HID: logitech-dj: Add support for a new lightspeed receiver iteration
authorMavroudis Chatzilazaridis <mavchatz@protonmail.com>
Sat, 27 Sep 2025 23:49:45 +0000 (23:49 +0000)
committerJiri Kosina <jkosina@suse.com>
Tue, 14 Oct 2025 09:52:24 +0000 (11:52 +0200)
This lightspeed receiver uses 13 byte mouse reports without an ID.
There are 5 additional vendor defined bytes appended to the report.
The workaround for such cases has been adjusted to handle these larger
reports.

The keyboard reports have a distinct layout with differing minimums and
maximums. Additionally, the LED reports now require a report ID of 1.

New keyboard and mouse descriptors have been added to reflect these
changes.

The devices attached to this receiver now report the status of their
battery in wireless mode, libratbag communicates with them and they can
be configured with Piper.

Fixes: 9d1bd9346241 ("HID: logitech-dj: Add support for a new lightspeed receiver iteration")
Link: https://bugzilla.kernel.org/show_bug.cgi?id=218172
Link: https://bugzilla.kernel.org/show_bug.cgi?id=218094
Co-developed-by: Filipe Laíns <lains@riseup.net>
Signed-off-by: Filipe Laíns <lains@riseup.net>
Signed-off-by: Mavroudis Chatzilazaridis <mavchatz@protonmail.com>
Tested-by: Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
drivers/hid/hid-ids.h
drivers/hid/hid-logitech-dj.c

index 1d23e5c8f8df28ed789af8b0256efdd7f0d79873..bad860af560ac8f251eee95af381dfbf392b0ce4 100644 (file)
 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1      0xc539
 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1    0xc53f
 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2    0xc543
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3    0xc547
 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a
 #define USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER   0xc548
 #define USB_DEVICE_ID_SPACETRAVELLER   0xc623
index cce54dd9884a3e4b0f3544c7b7deda72fbbf83ac..d66f4807311a32de0480a1627a0d02028b937eef 100644 (file)
@@ -116,6 +116,7 @@ enum recvr_type {
        recvr_type_dj,
        recvr_type_hidpp,
        recvr_type_gaming_hidpp,
+       recvr_type_gaming_hidpp_ls_1_3,
        recvr_type_mouse_only,
        recvr_type_27mhz,
        recvr_type_bluetooth,
@@ -211,6 +212,44 @@ static const char kbd_descriptor[] = {
        0xC0
 };
 
+/* Gaming Keyboard descriptor (1) */
+static const char kbd_lightspeed_1_3_descriptor[] = {
+       0x05, 0x01,             /* Usage Page (Generic Desktop) */
+       0x09, 0x06,             /* Usage (Keyboard)             */
+       0xA1, 0x01,             /* Collection (Application)     */
+       0x85, 0x01,             /*   Report ID (1)              */
+       0x05, 0x07,             /*   Usage Page (Kbrd/Keypad)   */
+       0x19, 0xE0,             /*   Usage Minimum (0xE0)       */
+       0x29, 0xE7,             /*   Usage Maximum (0xE7)       */
+       0x15, 0x00,             /*   Logical Minimum (0)        */
+       0x25, 0x01,             /*   Logical Maximum (1)        */
+       0x75, 0x01,             /*   Report Size (1)            */
+       0x95, 0x08,             /*   Report Count (8)           */
+       0x81, 0x02,             /*   Input (Data,Var)           */
+       0x95, 0x70,             /*   Report Count (112)         */
+       0x19, 0x04,             /*   Usage Minimum (0x04)       */
+       0x29, 0x73,             /*   Usage Maximum (0x73)       */
+       0x81, 0x02,             /*   Input (Data,Var,Abs)       */
+       0x95, 0x05,             /*   Report Count (5)           */
+       0x19, 0x87,             /*   Usage Minimum (0x87)       */
+       0x29, 0x8B,             /*   Usage Maximum (0x8B)       */
+       0x81, 0x02,             /*   Input (Data,Var,Abs)       */
+       0x95, 0x03,             /*   Report Count (3)           */
+       0x19, 0x90,             /*   Usage Minimum (0x90)       */
+       0x29, 0x92,             /*   Usage Maximum (0x92)       */
+       0x81, 0x02,             /*   Input (Data,Var,Abs)       */
+       0x95, 0x05,             /*   Report Count (5)           */
+       0x85, 0x0E,             /*   Report ID (14)             */
+       0x05, 0x08,             /*   Usage Page (LEDs)          */
+       0x19, 0x01,             /*   Usage Minimum (Num Lock)   */
+       0x29, 0x05,             /*   Usage Maximum (Kana)       */
+       0x91, 0x02,             /*   Output (Data,Var,Abs)      */
+       0x95, 0x01,             /*   Report Count (1)           */
+       0x75, 0x03,             /*   Report Size (3)            */
+       0x91, 0x03,             /*   Output (Const,Var,Abs)     */
+       0xC0,                   /* End Collection               */
+};
+
 /* Mouse descriptor (2)     */
 static const char mse_descriptor[] = {
        0x05, 0x01,             /*  USAGE_PAGE (Generic Desktop)        */
@@ -415,6 +454,51 @@ static const char mse_high_res_descriptor[] = {
        0xC0,                   /*  END_COLLECTION                      */
 };
 
+/* Gaming Mouse descriptor with vendor data (2) */
+static const char mse_high_res_ls_1_3_descriptor[] = {
+       0x05, 0x01,             /* Usage Page (Generic Desktop)         */
+       0x09, 0x02,             /* Usage (Mouse)                        */
+       0xA1, 0x01,             /* Collection (Application)             */
+       0x85, 0x02,             /*   Report ID (2)                      */
+       0x09, 0x01,             /*   Usage (Pointer)                    */
+       0xA1, 0x00,             /*   Collection (Physical)              */
+       0x95, 0x10,             /*     Report Count (16)                */
+       0x75, 0x01,             /*     Report Size (1)                  */
+       0x15, 0x00,             /*     Logical Minimum (0)              */
+       0x25, 0x01,             /*     Logical Maximum (1)              */
+       0x05, 0x09,             /*     Usage Page (Button)              */
+       0x19, 0x01,             /*     Usage Minimum (0x01)             */
+       0x29, 0x10,             /*     Usage Maximum (0x10)             */
+       0x81, 0x02,             /*     Input (Data,Var,Abs)             */
+       0x95, 0x02,             /*     Report Count (2)                 */
+       0x75, 0x10,             /*     Report Size (16)                 */
+       0x16, 0x01, 0x80,       /*     Logical Minimum (-32767)         */
+       0x26, 0xFF, 0x7F,       /*     Logical Maximum (32767)          */
+       0x05, 0x01,             /*     Usage Page (Generic Desktop)     */
+       0x09, 0x30,             /*     Usage (X)                        */
+       0x09, 0x31,             /*     Usage (Y)                        */
+       0x81, 0x06,             /*     Input (Data,Var,Rel)             */
+       0x95, 0x01,             /*     Report Count (1)                 */
+       0x75, 0x08,             /*     Report Size (8)                  */
+       0x15, 0x81,             /*     Logical Minimum (-127)           */
+       0x25, 0x7F,             /*     Logical Maximum (127)            */
+       0x09, 0x38,             /*     Usage (Wheel)                    */
+       0x81, 0x06,             /*     Input (Data,Var,Rel)             */
+       0x95, 0x01,             /*     Report Count (1)                 */
+       0x05, 0x0C,             /*     Usage Page (Consumer)            */
+       0x0A, 0x38, 0x02,       /*     Usage (AC Pan)                   */
+       0x81, 0x06,             /*     Input (Data,Var,Rel)             */
+       0xC0,                   /*   End Collection                     */
+       0x06, 0x00, 0xFF,       /*   Usage Page (Vendor Defined 0xFF00) */
+       0x09, 0xF1,             /*   Usage (0xF1)                       */
+       0x75, 0x08,             /*   Report Size (8)                    */
+       0x95, 0x05,             /*   Report Count (5)                   */
+       0x15, 0x00,             /*   Logical Minimum (0)                */
+       0x26, 0xFF, 0x00,       /*   Logical Maximum (255)              */
+       0x81, 0x00,             /*   Input (Data,Array,Abs)             */
+       0xC0,                   /* End Collection                       */
+};
+
 /* Consumer Control descriptor (3) */
 static const char consumer_descriptor[] = {
        0x05, 0x0C,             /* USAGE_PAGE (Consumer Devices)       */
@@ -520,9 +604,9 @@ static const char hidpp_descriptor[] = {
 /* Maximum size of all defined hid reports in bytes (including report id) */
 #define MAX_REPORT_SIZE 8
 
-/* Make sure all descriptors are present here */
+/* Make sure the largest of each descriptor type is present here */
 #define MAX_RDESC_SIZE                         \
-       (sizeof(kbd_descriptor) +               \
+       (sizeof(kbd_lightspeed_1_3_descriptor) +\
         sizeof(mse_bluetooth_descriptor) +     \
         sizeof(mse5_bluetooth_descriptor) +    \
         sizeof(consumer_descriptor) +          \
@@ -1374,12 +1458,19 @@ static int logi_dj_ll_raw_request(struct hid_device *hid,
                return -EINVAL;
 
        if (djrcv_dev->type != recvr_type_dj && count >= 2) {
+               unsigned char led_report_id = 0;
+
                if (!djrcv_dev->keyboard) {
                        hid_warn(hid, "Received REPORT_TYPE_LEDS request before the keyboard interface was enumerated\n");
                        return 0;
                }
+
+               /* This Lightspeed receiver expects LED reports with report ID 1 */
+               if (djrcv_dev->type == recvr_type_gaming_hidpp_ls_1_3)
+                       led_report_id = 1;
+
                /* usbhid overrides the report ID and ignores the first byte */
-               return hid_hw_raw_request(djrcv_dev->keyboard, 0, buf, count,
+               return hid_hw_raw_request(djrcv_dev->keyboard, led_report_id, buf, count,
                                          report_type, reqtype);
        }
 
@@ -1426,7 +1517,11 @@ static int logi_dj_ll_parse(struct hid_device *hid)
        if (djdev->reports_supported & STD_KEYBOARD) {
                dbg_hid("%s: sending a kbd descriptor, reports_supported: %llx\n",
                        __func__, djdev->reports_supported);
-               rdcat(rdesc, &rsize, kbd_descriptor, sizeof(kbd_descriptor));
+               if (djdev->dj_receiver_dev->type == recvr_type_gaming_hidpp_ls_1_3)
+                       rdcat(rdesc, &rsize, kbd_lightspeed_1_3_descriptor,
+                             sizeof(kbd_lightspeed_1_3_descriptor));
+               else
+                       rdcat(rdesc, &rsize, kbd_descriptor, sizeof(kbd_descriptor));
        }
 
        if (djdev->reports_supported & STD_MOUSE) {
@@ -1436,6 +1531,9 @@ static int logi_dj_ll_parse(struct hid_device *hid)
                    djdev->dj_receiver_dev->type == recvr_type_mouse_only)
                        rdcat(rdesc, &rsize, mse_high_res_descriptor,
                              sizeof(mse_high_res_descriptor));
+               else if (djdev->dj_receiver_dev->type == recvr_type_gaming_hidpp_ls_1_3)
+                       rdcat(rdesc, &rsize, mse_high_res_ls_1_3_descriptor,
+                             sizeof(mse_high_res_ls_1_3_descriptor));
                else if (djdev->dj_receiver_dev->type == recvr_type_27mhz)
                        rdcat(rdesc, &rsize, mse_27mhz_descriptor,
                              sizeof(mse_27mhz_descriptor));
@@ -1695,11 +1793,12 @@ static int logi_dj_raw_event(struct hid_device *hdev,
                }
                /*
                 * Mouse-only receivers send unnumbered mouse data. The 27 MHz
-                * receiver uses 6 byte packets, the nano receiver 8 bytes.
+                * receiver uses 6 byte packets, the nano receiver 8 bytes,
+                * the lightspeed receiver (Pro X Superlight) 13 bytes.
                 */
                if (djrcv_dev->unnumbered_application == HID_GD_MOUSE &&
-                   size <= 8) {
-                       u8 mouse_report[9];
+                   size <= 13){
+                       u8 mouse_report[14];
 
                        /* Prepend report id */
                        mouse_report[0] = REPORT_TYPE_MOUSE;
@@ -1776,6 +1875,7 @@ static int logi_dj_probe(struct hid_device *hdev,
        case recvr_type_dj:             no_dj_interfaces = 3; break;
        case recvr_type_hidpp:          no_dj_interfaces = 2; break;
        case recvr_type_gaming_hidpp:   no_dj_interfaces = 3; break;
+       case recvr_type_gaming_hidpp_ls_1_3: no_dj_interfaces = 3; break;
        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;
@@ -1987,6 +2087,10 @@ static const struct hid_device_id logi_dj_receivers[] = {
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
                USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2),
         .driver_data = recvr_type_gaming_hidpp},
+       { /* Logitech lightspeed receiver (0xc547) */
+         HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+               USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3),
+        .driver_data = recvr_type_gaming_hidpp_ls_1_3},
 
        { /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),