CHROMEOS: HID: Support for Logitech T651 Touchpad
authorAndrew de los Reyes <adlr@chromium.org>
Sun, 24 Feb 2013 18:45:11 +0000 (10:45 -0800)
committerChromeBot <chrome-bot@google.com>
Thu, 28 Mar 2013 02:59:30 +0000 (19:59 -0700)
This touchpad is similar to the T650, but has a few notable
differences:

- It connects via Bluetooth as opposed to the Unifying Receiver

- The raw data reports are in a unique format appended to normal mouse
  reports. The mouse reports must be ignored. Putting this pad into
  raw data mode will work as the T650 does, but Nestor Lopez Casado of
  Logitech advises against as that mode as it's not officially
  supported.

BUG=chromium:223138
TEST=Manually tested on device. Will add base regression tests.

Signed-off-by: Andrew de los Reyes <adlr@chromium.org>
Change-Id: I9c6e3359f08508c54c47e79bec1bbc0121d0b06c
Reviewed-on: https://gerrit.chromium.org/gerrit/46683
Reviewed-by: Yufeng Shen <miletus@chromium.org>
drivers/hid/hid-core.c
drivers/hid/hid-ids.h
drivers/hid/hid-logitech-hidpp.c
drivers/hid/hid-logitech-hidpp.h
drivers/hid/hid-logitech-wtp.c

index f4d5996..4ad213f 100644 (file)
@@ -1480,6 +1480,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR) },
+       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_WIRELESS_TOUCHPAD_T651) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LUMIO, USB_DEVICE_ID_CRYSTALTOUCH) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LUMIO, USB_DEVICE_ID_CRYSTALTOUCH_DUAL) },
        { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) },
index c50cc9c..9972797 100644 (file)
 #define USB_DEVICE_ID_DINOVO_EDGE      0xc714
 #define USB_DEVICE_ID_DINOVO_MINI      0xc71f
 #define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2     0xca03
+#define USB_DEVICE_ID_WIRELESS_TOUCHPAD_T651   0xb00c
 
 #define USB_VENDOR_ID_LUMIO            0x202e
 #define USB_DEVICE_ID_CRYSTALTOUCH     0x0006
index cbab03e..586f0f1 100644 (file)
@@ -29,6 +29,7 @@
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/sched.h>
+#include "hid-ids.h"
 #include "hid-logitech-hidpp.h"
 
 MODULE_LICENSE("GPL");
@@ -74,7 +75,9 @@ static int __hidpp_send_report(struct hid_device *hdev,
                                                 sizeof(struct hidpp_report),
                                                 HID_OUTPUT_REPORT);
 
-       return (sent_bytes < 0) ? sent_bytes : 0;
+       /* It seems that sending via bluetooth can return -EIO even
+        * when the message is delivered, so we have this hack: */
+       return (sent_bytes < 0 && sent_bytes != -EIO) ? sent_bytes : 0;
 }
 
 static int hidpp_send_message_sync(struct hidpp_device *hidpp_dev,
@@ -154,6 +157,9 @@ int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
 
        memset(&message, 0, sizeof(message));
        message.report_id = report_id;
+       /* If sending to a non-DJ device, device expects 0xff. If sending to
+        * a DJ device, this device_index will be overwritten by the DJ code: */
+       message.device_index = 0xff;
        message.rap.sub_id = sub_id;
        message.rap.reg_address = reg_address;
        memcpy(&message.rap.params, params, param_count);
@@ -174,7 +180,7 @@ int hidpp_get_hidpp2_feature_index(struct hidpp_device *hidpp_dev,
        params[0] = feature_id >> 8;
        params[1] = feature_id & 0xff;
        ret = hidpp_send_hidpp2_sync(hidpp_dev,
-                                       REPORT_ID_HIDPP_SHORT,
+                                       REPORT_ID_HIDPP_LONG,
                                        0,
                                        0,
                                        software_id,
@@ -334,8 +340,10 @@ int hidpp_raw_event(struct hid_device *hdev, struct hid_report *hid_report,
 
        hidpp_print_raw_event("hidpp_raw_event", data, size);
 
-       if ((report->report_id != REPORT_ID_HIDPP_LONG) &&
-           (report->report_id != REPORT_ID_HIDPP_SHORT)) {
+       if (!(report->report_id == REPORT_ID_HIDPP_LONG ||
+             report->report_id == REPORT_ID_HIDPP_SHORT ||
+             (report->report_id == T651_REPORT_TYPE_MOUSE &&
+              hdev->product == USB_DEVICE_ID_WIRELESS_TOUCHPAD_T651))) {
                dbg_hid("hid-logitech-hidpp.c:%s: ignore report_id:%d\n",
                        __func__, report->report_id);
                return 0;
index 66e8ce6..ce127bd 100644 (file)
@@ -164,4 +164,6 @@ extern int hidpp_get_hidpp2_feature_index(struct hidpp_device *hidpp_dev,
 #define HIDPP_TYPE_PRESENTER                   0x06
 #define HIDPP_TYPE_RECEIVER                    0x07
 
+#define T651_REPORT_TYPE_MOUSE                 0x02
+
 #endif
index 79e3aae..3b12ab4 100644 (file)
@@ -105,6 +105,8 @@ struct wtp_data {
        __u16 next_tracking_id;
        __u16 current_slots_used;  /* slots = device IDs. Bitmask. */
        __u16 prev_slots_used;  /* slots = device IDs. Bitmask. */
+
+       __u8 fingers_seen_this_frame;
 };
 
 static void wtp_touch_event(struct wtp_data *fd,
@@ -114,6 +116,7 @@ static void wtp_touch_event(struct wtp_data *fd,
 
        bool new_finger = !(fd->prev_slots_used & (1 << slot));
        fd->current_slots_used |= 1 << slot;
+       fd->fingers_seen_this_frame++;
 
        input_mt_slot(fd->input, slot);
        if (new_finger) {
@@ -148,7 +151,10 @@ static int wtp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev,
                        wtp_touch_event(fd, &event->fingers[i]);
        }
 
-       if (event->end_of_frame || finger_count <= 2) {
+       if (event->end_of_frame || finger_count < 2 ||
+               (finger_count == 2 && hidpp_dev->hid_dev->product ==
+                UNIFYING_DEVICE_ID_WIRELESS_TOUCHPAD)) {
+               u8 fingers_this_frame = fd->fingers_seen_this_frame;
                for (i = 0; i < SLOT_COUNT; i++) {
                        __u16 slot_mask = 1 << i;
                        bool released = (fd->prev_slots_used & slot_mask) &&
@@ -161,15 +167,15 @@ static int wtp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev,
                }
                input_mt_report_pointer_emulation(fd->input, true);
                input_report_key(fd->input, BTN_TOOL_FINGER,
-                                finger_count == 1);
+                                fingers_this_frame == 1);
                input_report_key(fd->input, BTN_TOOL_DOUBLETAP,
-                                finger_count == 2);
+                                fingers_this_frame == 2);
                input_report_key(fd->input, BTN_TOOL_TRIPLETAP,
-                                finger_count == 3);
+                                fingers_this_frame == 3);
                input_report_key(fd->input, BTN_TOOL_QUADTAP,
-                                finger_count == 4);
+                                fingers_this_frame == 4);
                input_report_key(fd->input, BTN_TOOL_QUINTTAP,
-                                finger_count == 5);
+                                fingers_this_frame == 5);
                /* WTP Uses normal mouse reports for button state */
                if (hidpp_dev->hid_dev->product !=
                        UNIFYING_DEVICE_ID_WIRELESS_TOUCHPAD)
@@ -179,6 +185,7 @@ static int wtp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev,
 
                fd->prev_slots_used = fd->current_slots_used;
                fd->current_slots_used = 0;
+               fd->fingers_seen_this_frame = 0;
        }
        return 1;
 }
@@ -219,13 +226,50 @@ static int hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_device,
 
        /* Ensure we get the proper raw data report here. We do this after
           the parsing above to avoid mixed declarations and code. */
-       if (hidpp_report->report_id != REPORT_ID_HIDPP_LONG ||
+       if ((hidpp_report->report_id != REPORT_ID_HIDPP_LONG ||
                hidpp_report->rap.sub_id != fd->mt_feature_index ||
-               (hidpp_report->rap.reg_address >> 4) != WTP_RAW_XY_EVENT_INDEX) {
+               (hidpp_report->rap.reg_address >> 4) != WTP_RAW_XY_EVENT_INDEX) &&
+               !(hidpp_report->report_id == T651_REPORT_TYPE_MOUSE &&
+                hidpp_device->hid_dev->product == USB_DEVICE_ID_WIRELESS_TOUCHPAD_T651)) {
                dbg_hid("Unhandled event type\n");
                return 0;
        }
 
+       if (hidpp_report->report_id == T651_REPORT_TYPE_MOUSE &&
+               hidpp_device->hid_dev->product == USB_DEVICE_ID_WIRELESS_TOUCHPAD_T651) {
+               /* Approximate area as (w^2 + h^2) / 2: */
+               u8 c1_area = ((buf[10] & 0xf) * (buf[10] & 0xf) +
+                               (buf[10] >> 4) * (buf[10] >> 4)) / 2;
+               u8 c2_area = ((buf[16] & 0xf) * (buf[16] & 0xf) +
+                               (buf[16] >> 4) * (buf[16] >> 4)) / 2;
+               struct hidpp_touchpad_raw_xy raw_xy_t651 = {
+                       buf[4],  /* Timestamp */
+                       { {
+                               0,  /* Contact type */
+                               c1_area > 0,  /* Contact status */
+                               (buf[7] << 8) | buf[6],  /* X */
+                               (buf[9] << 8) | buf[8],  /* Y */
+                               c1_area,  /* Z/Force */
+                               c1_area,  /* Area */
+                               buf[5]  /* Finger ID */
+                       }, {
+                               0,  /* Contact type */
+                               c2_area > 0,  /* Contact status */
+                               (buf[13] << 8) | buf[12],  /* X */
+                               (buf[15] << 8) | buf[14],  /* Y */
+                               c2_area,  /* Z/Force */
+                               c2_area,  /* Area */
+                               buf[11]  /* Finger ID */
+                       } },
+                       (buf[5] != 0) + (buf[11] != 0),  /* Fingers this frame */
+                       0,  /* Proximity */
+                       buf[3] & 1,  /* Mechanical button */
+                       0,  /* Spurious flag */
+                       (buf[3] >> 7) == 0  /* EOF */
+               };
+               raw_xy = raw_xy_t651;
+       }
+
        dbg_hid("EVT: %d {ty: %d, st: %d, (%d,%d,%d,%d) id:%d} {ty: %d, st: %d, (%d,%d,%d,%d) id:%d} cnt:%d pr:%d but:%d sf:%d eof:%d\n",
                raw_xy.timestamp,
                raw_xy.fingers[0].contact_type,
@@ -291,6 +335,11 @@ static int hidpp_touchpad_set_raw_report_state(struct hidpp_device *hidpp_dev)
                remaining bits - reserved */
        u8 params = 0x5;
 
+       if (hidpp_dev->hid_dev->product == USB_DEVICE_ID_WIRELESS_TOUCHPAD_T651) {
+               dbg_hid("not going to raw for T651\n");
+               return 0;
+       }
+
        ret = hidpp_send_fap_command_sync(hidpp_dev, fd->mt_feature_index,
                CMD_TOUCHPAD_SET_RAW_REPORT_STATE, &params, 1, &response);
 
@@ -414,7 +463,7 @@ static int wtp_probe(struct hid_device *hdev, const struct hid_device_id *id)
        hid_device_io_start(hdev);
 
        /* Get hid++ version number */
-       ret = hidpp_send_hidpp2_sync(hidpp_device, REPORT_ID_HIDPP_SHORT,
+       ret = hidpp_send_hidpp2_sync(hidpp_device, REPORT_ID_HIDPP_LONG,
                                        0, 1,
                                        SOFTWARE_ID,
                                        NULL, 0, &response);
@@ -477,6 +526,7 @@ static void wtp_remove(struct hid_device *hdev)
 static const struct hid_device_id wtp_devices[] = {
        {HID_DEVICE(BUS_DJ, USB_VENDOR_ID_LOGITECH, UNIFYING_DEVICE_ID_WIRELESS_TOUCHPAD) },
        {HID_DEVICE(BUS_DJ, USB_VENDOR_ID_LOGITECH, UNIFYING_DEVICE_ID_WIRELESS_TOUCHPAD_T650) },
+       {HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_WIRELESS_TOUCHPAD_T651) },
        { }
 };
 MODULE_DEVICE_TABLE(hid, wtp_devices);