[PATCH 15/28] HID: logitech-dj: add support for 27 MHz receivers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Most Logitech wireless keyboard and mice using the 27 MHz are hidpp10
devices, add support to logitech-dj for their receivers.

Doing so leads to 2 improvements:

1) All these devices share the same USB product-id for their receiver,
making it impossible to properly map some special keys / buttons
which differ from device to device. Adding support to logitech-dj to
see these as hidpp10 devices allows us to get the actual device-id
from the keyboard / mouse.

2) It enables battery-monitoring of these devices

Note this commits also sets up a better name for 27 MHz hidpp devices,
this is done because unlike other hidpp devices, the logitech-hidpp code
will not override the name, so having a good name is important.

Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx>
---
 drivers/hid/hid-lg.c          |   2 -
 drivers/hid/hid-logitech-dj.c | 136 ++++++++++++++++++++++++++++++++--
 drivers/hid/hid-quirks.c      |   1 -
 3 files changed, 130 insertions(+), 9 deletions(-)

diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c
index 596227ddb6e0..c8f1e24d38ec 100644
--- a/drivers/hid/hid-lg.c
+++ b/drivers/hid/hid-lg.c
@@ -818,8 +818,6 @@ static const struct hid_device_id lg_devices[] = {
 		.driver_data = LG_RDESC | LG_WIRELESS },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER),
 		.driver_data = LG_RDESC | LG_WIRELESS },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2),
-		.driver_data = LG_RDESC | LG_WIRELESS },
 
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER),
 		.driver_data = LG_BAD_RELATIVE_KEYS },
diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c
index 9af5479c66ed..f9c88820f684 100644
--- a/drivers/hid/hid-logitech-dj.c
+++ b/drivers/hid/hid-logitech-dj.c
@@ -106,6 +106,7 @@
 #define HIDPP_PARAM_DEVICE_INFO			0x01
 #define HIDPP_PARAM_EQUAD_LSB			0x02
 #define HIDPP_PARAM_EQUAD_MSB			0x03
+#define HIDPP_PARAM_27MHZ_DEVID			0x03
 #define HIDPP_DEVICE_TYPE_MASK			GENMASK(3, 0)
 #define HIDPP_LINK_STATUS_MASK			BIT(6)
 
@@ -120,6 +121,7 @@ enum recvr_type {
 	recvr_type_dj,
 	recvr_type_hidpp,
 	recvr_type_gaming_hidpp,
+	recvr_type_27mhz,
 };
 
 struct dj_report {
@@ -248,6 +250,44 @@ static const char mse_descriptor[] = {
 	0xC0,			/*  END_COLLECTION                      */
 };
 
+/* Mouse descriptor (2) for 27 MHz receiver, only 8 buttons */
+static const char mse_27mhz_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)             */
+	0x05, 0x09,		/*      USAGE_PAGE (buttons)            */
+	0x19, 0x01,		/*      USAGE_MIN (1)                   */
+	0x29, 0x08,		/*      USAGE_MAX (8)                   */
+	0x15, 0x00,		/*      LOGICAL_MIN (0)                 */
+	0x25, 0x01,		/*      LOGICAL_MAX (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_MIN (-2047)             */
+	0x26, 0xFF, 0x07,	/*      LOGICAL_MAX (2047)              */
+	0x75, 0x0C,		/*      REPORT_SIZE (12)                */
+	0x95, 0x02,		/*      REPORT_COUNT (2)                */
+	0x09, 0x30,		/*      USAGE (X)                       */
+	0x09, 0x31,		/*      USAGE (Y)                       */
+	0x81, 0x06,		/*      INPUT                           */
+	0x15, 0x81,		/*      LOGICAL_MIN (-127)              */
+	0x25, 0x7F,		/*      LOGICAL_MAX (127)               */
+	0x75, 0x08,		/*      REPORT_SIZE (8)                 */
+	0x95, 0x01,		/*      REPORT_COUNT (1)                */
+	0x09, 0x38,		/*      USAGE (wheel)                   */
+	0x81, 0x06,		/*      INPUT                           */
+	0x05, 0x0C,		/*      USAGE_PAGE(consumer)            */
+	0x0A, 0x38, 0x02,	/*      USAGE(AC Pan)                   */
+	0x95, 0x01,		/*      REPORT_COUNT (1)                */
+	0x81, 0x06,		/*      INPUT                           */
+	0xC0,			/*    END_COLLECTION                    */
+	0xC0,			/*  END_COLLECTION                      */
+};
+
 /* Gaming Mouse descriptor (2) */
 static const char mse_high_res_descriptor[] = {
 	0x05, 0x01,		/*  USAGE_PAGE (Generic Desktop)        */
@@ -286,7 +326,7 @@ static const char mse_high_res_descriptor[] = {
 	0xC0,			/*  END_COLLECTION                      */
 };
 
-/* Consumer Control descriptor (3) */
+/* Consumer Control descriptor (3) normal, usages 1 - 652 */
 static const char consumer_descriptor[] = {
 	0x05, 0x0C,		/* USAGE_PAGE (Consumer Devices)       */
 	0x09, 0x01,		/* USAGE (Consumer Control)            */
@@ -302,6 +342,25 @@ static const char consumer_descriptor[] = {
 	0xC0,			/* END_COLLECTION                      */
 };				/*                                     */
 
+/*
+ * Consumer Control descriptor (3) 27 MHz devices, usages 1 - 4173 (0x104d)
+ * Usages > 0x1000 are used for Logitech custom keys
+ */
+static const char consumer_27mhz_descriptor[] = {
+	0x05, 0x0C,		/* USAGE_PAGE (Consumer Devices)       */
+	0x09, 0x01,		/* USAGE (Consumer Control)            */
+	0xA1, 0x01,		/* COLLECTION (Application)            */
+	0x85, 0x03,		/* REPORT_ID = 3                       */
+	0x75, 0x10,		/* REPORT_SIZE (16)                    */
+	0x95, 0x02,		/* REPORT_COUNT (2)                    */
+	0x15, 0x01,		/* LOGICAL_MIN (1)                     */
+	0x26, 0x4D, 0x10,	/* LOGICAL_MAX (4173)                  */
+	0x19, 0x01,		/* USAGE_MIN (1)                       */
+	0x2A, 0x4D, 0x10,	/* USAGE_MAX (4173)                    */
+	0x81, 0x00,		/* INPUT (Data Ary Abs)                */
+	0xC0,			/* END_COLLECTION                      */
+};				/*                                     */
+
 /* System control descriptor (4) */
 static const char syscontrol_descriptor[] = {
 	0x05, 0x01,		/*   USAGE_PAGE (Generic Desktop)      */
@@ -387,6 +446,8 @@ static const char hidpp_descriptor[] = {
 	0x91, 0x00,		/*   Output (Data,Arr,Abs)             */
 	0xc0,			/* End Collection                      */
 };
+/* Some receivers only support short reports (only support report id 16) */
+#define HIDPP_DESC_SHORT_REPORT_SIZE 27
 
 /* Maximum size of all defined hid reports in bytes (including report id) */
 #define MAX_REPORT_SIZE 8
@@ -592,9 +653,16 @@ static void logi_dj_recv_add_djhid_device(struct dj_receiver_dev *djrcv_dev,
 	dj_hiddev->vendor = djrcv_hdev->vendor;
 	dj_hiddev->product = (workitem->quad_id_msb << 8) |
 			      workitem->quad_id_lsb;
-	snprintf(dj_hiddev->name, sizeof(dj_hiddev->name),
-		"Logitech Unifying Device. Wireless PID:%04x",
-		dj_hiddev->product);
+	if (djrcv_dev->type == recvr_type_27mhz) {
+		snprintf(dj_hiddev->name, sizeof(dj_hiddev->name),
+			"Logitech 27 MHz %s. Wireless PID:%04x",
+			(device_index == 1) ? "Mouse" : "Keyboard",
+			dj_hiddev->product);
+	} else {
+		snprintf(dj_hiddev->name, sizeof(dj_hiddev->name),
+			"Logitech Unifying Device. Wireless PID:%04x",
+			dj_hiddev->product);
+	}
 
 	dj_hiddev->group = HID_GROUP_LOGITECH_DJ_DEVICE;
 
@@ -781,6 +849,26 @@ static void logi_hidpp_dev_conn_notif_equad(struct hidpp_event *hidpp_report,
 	}
 }
 
+static void logi_hidpp_dev_conn_notif_27mhz(struct hid_device *hdev,
+					    struct hidpp_event *hidpp_report,
+					    struct dj_workitem *workitem)
+{
+	workitem->type = WORKITEM_TYPE_PAIRED;
+	workitem->quad_id_lsb = hidpp_report->params[HIDPP_PARAM_27MHZ_DEVID];
+	switch (hidpp_report->device_index) {
+	case 1: /* Device 1 is always the mouse */
+		workitem->reports_supported |= STD_MOUSE;
+		break;
+	case 3: /* Device 3 is always the keyboard */
+		workitem->reports_supported |= STD_KEYBOARD | MULTIMEDIA |
+					       POWER_KEYS;
+		break;
+	default:
+		hid_warn(hdev, "%s: unexpected device-index %d", __func__,
+			 hidpp_report->device_index);
+	}
+}
+
 static void logi_hidpp_recv_queue_notif(struct hid_device *hdev,
 					struct hidpp_event *hidpp_report)
 {
@@ -798,6 +886,7 @@ static void logi_hidpp_recv_queue_notif(struct hid_device *hdev,
 		break;
 	case 0x02:
 		device_type = "27 Mhz";
+		logi_hidpp_dev_conn_notif_27mhz(hdev, hidpp_report, &workitem);
 		break;
 	case 0x03:
 		device_type = "QUAD or eQUAD";
@@ -1185,6 +1274,9 @@ static int logi_dj_ll_parse(struct hid_device *hid)
 		if (djdev->dj_receiver_dev->type == recvr_type_gaming_hidpp)
 			rdcat(rdesc, &rsize, mse_high_res_descriptor,
 			      sizeof(mse_high_res_descriptor));
+		else if (djdev->dj_receiver_dev->type == recvr_type_27mhz)
+			rdcat(rdesc, &rsize, mse_27mhz_descriptor,
+			      sizeof(mse_27mhz_descriptor));
 		else
 			rdcat(rdesc, &rsize, mse_descriptor,
 			      sizeof(mse_descriptor));
@@ -1193,7 +1285,12 @@ static int logi_dj_ll_parse(struct hid_device *hid)
 	if (djdev->reports_supported & MULTIMEDIA) {
 		dbg_hid("%s: sending a multimedia report descriptor: %x\n",
 			__func__, djdev->reports_supported);
-		rdcat(rdesc, &rsize, consumer_descriptor, sizeof(consumer_descriptor));
+		if (djdev->dj_receiver_dev->type == recvr_type_27mhz)
+			rdcat(rdesc, &rsize, consumer_27mhz_descriptor,
+			      sizeof(consumer_27mhz_descriptor));
+		else
+			rdcat(rdesc, &rsize, consumer_descriptor,
+			      sizeof(consumer_descriptor));
 	}
 
 	if (djdev->reports_supported & POWER_KEYS) {
@@ -1213,7 +1310,11 @@ static int logi_dj_ll_parse(struct hid_device *hid)
 			__func__, djdev->reports_supported);
 	}
 
-	rdcat(rdesc, &rsize, hidpp_descriptor, sizeof(hidpp_descriptor));
+	/* 27 MHz receivers only support short reports */
+	if (djdev->dj_receiver_dev->type == recvr_type_27mhz)
+		rdcat(rdesc, &rsize, hidpp_descriptor, HIDPP_DESC_SHORT_REPORT_SIZE);
+	else
+		rdcat(rdesc, &rsize, hidpp_descriptor, sizeof(hidpp_descriptor));
 
 	retval = hid_parse_report(hid, rdesc, rsize);
 	kfree(rdesc);
@@ -1356,6 +1457,25 @@ static int logi_dj_hidpp_event(struct hid_device *hdev,
 	spin_lock_irqsave(&djrcv_dev->lock, flags);
 
 	dj_dev = djrcv_dev->paired_dj_devices[device_index];
+
+	/*
+	 * With 27 MHz receivers, we do not get an explicit unpair event,
+	 * remove the old device if the user has paired a *different* device.
+	 */
+	if (djrcv_dev->type == recvr_type_27mhz && dj_dev &&
+	    hidpp_report->sub_id == REPORT_TYPE_NOTIF_DEVICE_CONNECTED &&
+	    hidpp_report->params[HIDPP_PARAM_PROTO_TYPE] == 0x02 &&
+	    hidpp_report->params[HIDPP_PARAM_27MHZ_DEVID] !=
+						dj_dev->hdev->product) {
+		struct dj_workitem workitem = {
+			.device_index = hidpp_report->device_index,
+			.type = WORKITEM_TYPE_UNPAIRED,
+		};
+		kfifo_in(&djrcv_dev->notif_fifo, &workitem, sizeof(workitem));
+		/* logi_hidpp_recv_queue_notif will queue the work */
+		dj_dev = NULL;
+	}
+
 	if (dj_dev) {
 		logi_dj_recv_forward_report(dj_dev, data, size);
 	} else {
@@ -1623,6 +1743,10 @@ static const struct hid_device_id logi_dj_receivers[] = {
 	  HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
 		USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_GAMING),
 	 .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),
+	 .driver_data = recvr_type_27mhz},
 	{}
 };
 
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index a46d7ab7db91..6ef3bd23fbdc 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -436,7 +436,6 @@ static const struct hid_device_id hid_have_special_driver[] = {
 #if IS_ENABLED(CONFIG_HID_LOGITECH)
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
-	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2) },
 	{ 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) },
-- 
2.21.0




[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux