| /* |
| * HID driver for Logitech Wireless Touchpad device |
| * |
| * Copyright (c) 2011 Logitech (c) |
| */ |
| |
| /* |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| * Should you need to contact me, the author, you can do so by e-mail send |
| * your message to Benjamin Tissoires <benjamin.tissoires at gmail com> |
| * |
| */ |
| |
| #include <linux/device.h> |
| #include <linux/hid.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/input/mt.h> |
| |
| MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); |
| MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>"); |
| MODULE_DESCRIPTION("Logitech Wireless Touchpad"); |
| MODULE_LICENSE("GPL"); |
| |
| #include "hid-ids.h" |
| #include "hid-logitech-hidpp.h" |
| |
| #define SOFTWARE_ID 0xB |
| |
| #define ORIGIN_LOWER_LEFT 0x1 |
| #define ORIGIN_LOWER_RIGHT 0x2 |
| #define ORIGIN_UPPER_LEFT 0x3 |
| #define ORIGIN_UPPER_RIGHT 0x4 |
| #define ORIGIN_IS_HIGH(origin) ((origin - 1) & 2) |
| #define ORIGIN_IS_RIGHT(origin) ((origin - 1) & 1) |
| |
| /* Converts a value in DPI to dots-per-millimeter */ |
| #define DPI_TO_DPMM(dpi) (((dpi) * 5) / 127) |
| |
| #define SLOT_COUNT 16 |
| |
| #define CONTACT_STATUS_RELEASED 0 |
| #define CONTACT_STATUS_TOUCH 1 |
| #define CONTACT_STATUS_HOVER 2 |
| #define CONTACT_STATUS_RESERVED 3 |
| |
| #define BUTTON_LEFT 0 |
| #define BUTTON_RIGHT 1 |
| #define BUTTON_MIDDLE 2 |
| /* T400 reports 2 different buttons for middle clicks, |
| but we report both as BTN_MIDDLE input_event */ |
| #define BUTTON_MIDDLE_ALT 3 |
| |
| #define BUTTON_LEFT_MASK (1 << BUTTON_LEFT) |
| #define BUTTON_RIGHT_MASK (1 << BUTTON_RIGHT) |
| #define BUTTON_MIDDLE_MASK (1 << BUTTON_MIDDLE) |
| #define BUTTON_MIDDLE_ALT_MASK (1 << BUTTON_MIDDLE_ALT) |
| |
| #define UNIFYING_KBD_INPUT_REPORT_SIZE 8 |
| |
| #define ARRAYSIZE(array) (sizeof(array) / sizeof(*(array))) |
| |
| /* Supported Devices */ |
| static const struct hid_device_id wtp_devices[] = { |
| {HID_DJ_DEVICE(USB_VENDOR_ID_LOGITECH, UNIFYING_DEVICE_ID_WIRELESS_TOUCHPAD) }, |
| {HID_DJ_DEVICE(USB_VENDOR_ID_LOGITECH, UNIFYING_DEVICE_ID_WIRELESS_TOUCHPAD_T650) }, |
| {HID_DJ_DEVICE(USB_VENDOR_ID_LOGITECH, UNIFYING_DEVICE_ID_ALLINONE_KBD_TK820) }, |
| {HID_DJ_DEVICE(USB_VENDOR_ID_LOGITECH, UNIFYING_DEVICE_ID_ZONE_MOUSE_T400) }, |
| {HID_DJ_DEVICE(USB_VENDOR_ID_LOGITECH, UNIFYING_DEVICE_ID_TOUCH_MOUSE_T620) }, |
| {HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_WIRELESS_TOUCHPAD_T651) }, |
| {HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_BLUETOOTH_TOUCHMOUSE) }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(hid, wtp_devices); |
| |
| |
| /* This struct represents a finger including location, pressure and |
| its status */ |
| struct wtp_event_finger { |
| u8 status; |
| u16 abs_x; |
| u16 abs_y; |
| u8 pressure; |
| u8 id; |
| }; |
| |
| /* This struct represents a touch data message from a touchpad */ |
| struct wtp_event { |
| struct wtp_event_finger fingers[4]; |
| |
| /* relative mouse movement */ |
| s16 rel_x; |
| s16 rel_y; |
| |
| /* bitmask of button states. 1=down, 0=up. */ |
| u8 buttons; |
| |
| /* stores an incoming keyboard report to transmit to hid_input */ |
| u8 kbd_report[UNIFYING_KBD_INPUT_REPORT_SIZE]; |
| |
| /* bitmask of buttons included within this event */ |
| u8 has_buttons; |
| |
| /* true if this event includes finger data */ |
| bool has_abs:1; |
| |
| /* true if this event includes mouse movement data */ |
| bool has_rel:1; |
| |
| /* true if this event includes keyboard input */ |
| bool has_keys:1; |
| |
| /* false if there are events following with more data, |
| true if this is the last one */ |
| bool end_of_frame:1; |
| }; |
| |
| /* Struct describing the touch devices axis properties */ |
| struct wtp_device_info { |
| u16 abs_max_x; |
| u16 abs_res_x; |
| |
| u16 abs_max_y; |
| u16 abs_res_y; |
| |
| bool has_rel:1; |
| |
| u8 origin; |
| |
| u16 abs_min_pressure; |
| u16 abs_max_pressure; |
| |
| u8 max_contacts; |
| }; |
| |
| struct wtp_data; |
| |
| /* Struct storing feature-specific information. Each feature_id |
| has methods assigned that process messages from this feature. */ |
| struct wtp_feature { |
| u16 id; |
| u8 index; |
| u8 event_format; |
| int (*init)(struct hidpp_device *); |
| int (*probe)(struct hidpp_device *, struct wtp_device_info *); |
| int (*parse_feature_event)(struct wtp_data *, struct hidpp_report *, |
| struct wtp_event *); |
| int (*parse_other_event)(struct wtp_data *, struct hidpp_report *, |
| struct wtp_event *); |
| }; |
| |
| /* Structure containing device data */ |
| struct wtp_data { |
| struct input_dev *input; |
| |
| struct wtp_device_info info; |
| |
| /* the touch feature supported by this device */ |
| struct wtp_feature feature; |
| |
| /* keep track of which buttons are down */ |
| u8 buttons; |
| |
| /* For assigning tracking IDs for MT-B protocol. */ |
| 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; |
| }; |
| |
| /* Bit Operations Helper */ |
| static u16 make_u16(u8 high, u8 low) |
| { |
| return (high << 8) | low; |
| } |
| static u8 low_nib(u8 val) |
| { |
| return val & 0xf; |
| } |
| static u8 high_nib(u8 val) |
| { |
| return val >> 4; |
| } |
| static bool get_bit(u8 mask, u8 idx) |
| { |
| return mask & (1 << idx); |
| } |
| static u16 make_u12_4_8(u8 high, u8 low) |
| { |
| return make_u16(high, low) & 0x0fff; |
| } |
| static u16 make_u12_8_4(u8 high, u8 low) |
| { |
| return make_u16(high, low << 4) >> 4; |
| } |
| static s16 u12_to_s12(u16 val) |
| { |
| return ((s16)(val << 4)) >> 4; |
| } |
| /* |
| Helper methods for parsing mouse events. Some devices |
| use mouse events to report buttons. |
| */ |
| |
| #define GENERIC_EVENT_MOUSE 0x02 |
| #define GENERIC_EVENT_KEYBOARD 0x01 |
| |
| static void generic_parse_mouse_button(u8 raw, |
| struct wtp_event *event) { |
| event->has_buttons = BUTTON_LEFT_MASK | BUTTON_RIGHT_MASK | |
| BUTTON_MIDDLE_MASK; |
| event->buttons = raw & event->has_buttons; |
| } |
| |
| static void generic_parse_mouse_rel(u8 *raw, |
| struct wtp_event *event) { |
| event->rel_x = u12_to_s12(make_u12_4_8(low_nib(raw[1]), raw[0])); |
| event->rel_y = u12_to_s12(make_u12_8_4(raw[2], high_nib(raw[1]))); |
| event->has_rel = true; |
| } |
| |
| static void generic_parse_kbd_keys(u8 *raw, |
| struct wtp_event *event) { |
| dbg_hid("%s\n", __func__); |
| memcpy(event->kbd_report, raw, UNIFYING_KBD_INPUT_REPORT_SIZE); |
| event->has_keys = true; |
| } |
| |
| /* |
| TouchPadRawXY (TPRXY) Feature |
| */ |
| |
| #define TPRXY_FEATURE 0x6100 |
| #define TPRXY_CMD_GET_TOUCHPAD_INFO (0x00 | SOFTWARE_ID) |
| #define TPRXY_CMD_GET_RAW_REPORT_STATE (0x10 | SOFTWARE_ID) |
| #define TPRXY_CMD_SET_RAW_REPORT_STATE (0x20 | SOFTWARE_ID) |
| #define TPRXY_EVENT_TOUCHPAD_RAW_TOUCH_POINTS 0x00 |
| |
| #define TPRXY_FORMAT_RAW 0x01 |
| #define TPRXY_FORMAT_RAW_SEPARATE_BUTTON_REPORT 0x02 |
| #define TPRXY_FORMAT_MOUSE_EXTENDED 0x03 |
| |
| #define TPRXY_DEFAULT_RES 1000 /* DPI */ |
| |
| #define TPRXY_SLOTS_PER_FRAME 2 |
| |
| /* Initialize TouchPadRawXY feature: |
| Set touchpad into raw mode (except for T651, which allows for raw touch |
| data appended to mouse events). */ |
| static int tprxy_init(struct hidpp_device *hidpp_dev) |
| { |
| struct wtp_data *fd = hidpp_dev->driver_data; |
| struct hidpp_report response; |
| int ret; |
| u8 params; |
| |
| dbg_hid("%s\n", __func__); |
| |
| if (hidpp_dev->hid_dev->product == |
| USB_DEVICE_ID_WIRELESS_TOUCHPAD_T651) { |
| params = 0x4; /* enhanced sensitivity */ |
| fd->feature.event_format = TPRXY_FORMAT_MOUSE_EXTENDED; |
| } else { |
| params = 0x5; /* enhanced sensitivity + raw */ |
| fd->feature.event_format = TPRXY_FORMAT_RAW; |
| if (hidpp_dev->hid_dev->product == UNIFYING_DEVICE_ID_WIRELESS_TOUCHPAD) |
| fd->feature.event_format = TPRXY_FORMAT_RAW_SEPARATE_BUTTON_REPORT; |
| } |
| |
| ret = hidpp_send_fap_command_sync(hidpp_dev, fd->feature.index, |
| TPRXY_CMD_SET_RAW_REPORT_STATE, ¶ms, 1, &response); |
| |
| return -ret; |
| } |
| |
| /* Probe TouchPadRawXY feature */ |
| static int tprxy_probe(struct hidpp_device *hidpp_dev, |
| struct wtp_device_info *info) |
| { |
| struct wtp_data *fd = hidpp_dev->driver_data; |
| struct hidpp_report response; |
| int ret; |
| u16 res; |
| u8 *params = (u8 *)response.fap.params; |
| |
| dbg_hid("%s\n", __func__); |
| |
| ret = hidpp_send_fap_command_sync(hidpp_dev, fd->feature.index, |
| TPRXY_CMD_GET_TOUCHPAD_INFO, NULL, 0, &response); |
| |
| if (ret) |
| return -ret; |
| |
| info->abs_max_x = make_u16(params[0], params[1]); |
| info->abs_max_y = make_u16(params[2], params[3]); |
| info->abs_max_pressure = params[5]; |
| info->max_contacts = params[7]; |
| info->origin = params[8]; |
| |
| res = make_u16(params[13], params[14]); |
| if (!res) |
| res = TPRXY_DEFAULT_RES; |
| info->abs_res_x = res; |
| info->abs_res_y = res; |
| return ret; |
| } |
| |
| /* Parse other events while using TouchPadRawXY feature: |
| Mouse events might still be received in the following cases: |
| - Touchpad with separate buttons send mouse events on click |
| - Touchpad using extended mouse events send touch data as |
| mouse events */ |
| static int tprxy_parse_other_event(struct wtp_data *wtp, |
| struct hidpp_report *report, |
| struct wtp_event *event) { |
| int i; |
| u8 *raw = (u8 *)report; |
| u8 *buf = &report->rap.params[0]; |
| |
| dbg_hid("%s:report_id:%x\n", __func__,report->report_id); |
| |
| if (report->report_id == GENERIC_EVENT_KEYBOARD) { |
| generic_parse_kbd_keys(&raw[0], event); |
| return 0; |
| } |
| |
| if (report->report_id != GENERIC_EVENT_MOUSE) |
| return -1; |
| |
| if (wtp->feature.event_format != TPRXY_FORMAT_RAW) |
| generic_parse_mouse_button(raw[1], event); |
| |
| if (wtp->feature.event_format != TPRXY_FORMAT_MOUSE_EXTENDED) |
| return 0; |
| |
| for (i = 0; i < TPRXY_SLOTS_PER_FRAME; ++i) { |
| struct wtp_event_finger *finger = &event->fingers[i]; |
| u8 *raw = buf + (5 + i * 6); |
| u8 width = low_nib(raw[5]); |
| u8 height = high_nib(raw[5]); |
| |
| finger->pressure = (width * width + height * height) / 2; |
| finger->status = finger->pressure > 0; |
| finger->abs_x = make_u16(raw[2], raw[1]); |
| finger->abs_y = make_u16(raw[4], raw[3]); |
| finger->id = raw[0]; |
| } |
| event->end_of_frame = !get_bit(buf[3], 7); |
| event->has_abs = true; |
| |
| return 0; |
| } |
| |
| /* Parse TouchPadRawXY events */ |
| static int tprxy_parse_feature_event(struct wtp_data *wtp, |
| struct hidpp_report *report, |
| struct wtp_event *event) { |
| int i; |
| u8 *buf = &report->rap.params[0]; |
| u8 fingers_this_frame; |
| |
| dbg_hid("%s\n", __func__); |
| |
| if (wtp->feature.event_format == TPRXY_FORMAT_MOUSE_EXTENDED) |
| return -1; |
| |
| for (i = 0; i < TPRXY_SLOTS_PER_FRAME; ++i) { |
| u8 *raw = buf + (2 + i * 7); |
| event->fingers[i].status = get_bit(raw[2], 6); |
| event->fingers[i].abs_x = make_u16(raw[0] & 0x3f, raw[1]); |
| event->fingers[i].abs_y = make_u16(raw[2] & 0x3f, raw[3]); |
| event->fingers[i].pressure = raw[5]; |
| event->fingers[i].id = high_nib(raw[6]); |
| } |
| event->buttons = get_bit(buf[8], 2); |
| event->end_of_frame = get_bit(buf[8], 0); |
| |
| /* For single event frames, the end of frame flag is implied. */ |
| fingers_this_frame = low_nib(buf[15]); |
| if (fingers_this_frame <= TPRXY_SLOTS_PER_FRAME) |
| event->end_of_frame = true; |
| |
| event->has_abs = true; |
| if (wtp->feature.event_format == TPRXY_FORMAT_RAW) |
| event->has_buttons = 0x1; |
| return 0; |
| } |
| |
| /* |
| TouchMouseRawTouchPoints (TMRTP) Feature |
| */ |
| |
| #define TMRTP_FEATURE 0x6110 |
| #define TMRTP_CMD_GET_TOUCHPAD_INFO (0x00 | SOFTWARE_ID) |
| #define TMRTP_CMD_GET_RAW_MODE (0x10 | SOFTWARE_ID) |
| #define TMRTP_CMD_SET_RAW_MODE (0x20 | SOFTWARE_ID) |
| #define TMRTP_EVENT_TOUCH_MOUSE_RAW_TOUCH_POINTS 0x00 |
| #define TMRTP_EVENT_STATUS_CHANGED 0x01 |
| |
| static int tmrtp_init(struct hidpp_device *hidpp_dev) |
| { |
| struct wtp_data *fd = hidpp_dev->driver_data; |
| struct hidpp_report response; |
| int ret; |
| |
| u8 params = 2; /* raw data NOT filtered */ |
| |
| dbg_hid("%s\n", __func__); |
| |
| ret = hidpp_send_fap_command_sync(hidpp_dev, fd->feature.index, |
| TMRTP_CMD_SET_RAW_MODE, ¶ms, 1, &response); |
| |
| return -ret; |
| } |
| |
| static int tmrtp_probe(struct hidpp_device *hidpp_dev, |
| struct wtp_device_info *info) |
| { |
| struct wtp_data *fd = hidpp_dev->driver_data; |
| struct hidpp_report response; |
| int ret; |
| u16 res; |
| u8 *params = (u8 *)response.fap.params; |
| |
| dbg_hid("%s\n", __func__); |
| |
| ret = hidpp_send_fap_command_sync(hidpp_dev, fd->feature.index, |
| TMRTP_CMD_GET_TOUCHPAD_INFO, NULL, 0, &response); |
| |
| if (ret) |
| return -ret; |
| |
| info->abs_max_x = make_u16(params[0], params[1]); |
| info->abs_max_y = make_u16(params[2], params[3]); |
| info->abs_max_pressure = params[8]; |
| info->max_contacts = params[7]; |
| info->origin = params[6]; |
| |
| res = make_u16(params[4], params[5]); |
| info->abs_res_x = res; |
| info->abs_res_y = res; |
| info->has_rel = true; |
| return ret; |
| } |
| |
| static int tmrtp_parse_other_event(struct wtp_data *wtp, |
| struct hidpp_report *report, |
| struct wtp_event *event) { |
| u8 *raw = (u8 *)report; |
| if (report->report_id == GENERIC_EVENT_MOUSE) { |
| generic_parse_mouse_rel(&raw[3], event); |
| generic_parse_mouse_button(raw[1], event); |
| } else if (report->report_id == GENERIC_EVENT_KEYBOARD) { |
| /* In some cases T400 sends keyboard events for middle buttons */ |
| event->has_buttons = BUTTON_MIDDLE_ALT_MASK; |
| event->buttons = raw[1] & BUTTON_MIDDLE_ALT_MASK; |
| } else { |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int tmrtp_parse_feature_event(struct wtp_data *wtp, |
| struct hidpp_report *report, |
| struct wtp_event *event) { |
| int i; |
| u8 *buf = &report->rap.params[0]; |
| |
| dbg_hid("%s\n", __func__); |
| |
| if (high_nib(report->fap.funcindex_clientid) != |
| TMRTP_EVENT_TOUCH_MOUSE_RAW_TOUCH_POINTS) |
| return 0; |
| |
| for (i = 0; i < ARRAYSIZE(event->fingers); ++i) { |
| u8 *raw = buf + i * 4; |
| event->fingers[i].status = (raw[3] != 0xff); |
| event->fingers[i].abs_x = |
| make_u12_8_4(raw[0], low_nib(raw[2])); |
| event->fingers[i].abs_y = |
| make_u12_8_4(raw[1], high_nib(raw[2])); |
| event->fingers[i].pressure = raw[3]; |
| event->fingers[i].id = i + 1; |
| } |
| event->has_abs = true; |
| event->end_of_frame = true; |
| return 0; |
| } |
| |
| |
| /* |
| BTTouchMouseSettings (BTTMS) Feature |
| */ |
| |
| #define BTTMS_FEATURE 0x6120 |
| #define BTTMS_CMD_GET_TOUCHPAD_INFO (0x00 | SOFTWARE_ID) |
| #define BTTMS_CMD_GET_RAW_MODE (0x10 | SOFTWARE_ID) |
| #define BTTMS_CMD_SET_RAW_MODE (0x20 | SOFTWARE_ID) |
| |
| #define BTTMS_FORMAT_01 0x01 |
| #define BTTMS_NUM_SLOTS 6 |
| |
| static int bttms_init(struct hidpp_device *hidpp_dev) |
| { |
| /* We do not use RAW mode in this feature */ |
| return 0; |
| } |
| |
| static int bttms_probe(struct hidpp_device *hidpp_dev, |
| struct wtp_device_info *info) |
| { |
| struct wtp_data *fd = hidpp_dev->driver_data; |
| struct hidpp_report response; |
| int ret; |
| u16 res; |
| u8 *params = (u8 *)response.fap.params; |
| |
| dbg_hid("%s\n", __func__); |
| |
| ret = hidpp_send_fap_command_sync(hidpp_dev, fd->feature.index, |
| BTTMS_CMD_GET_TOUCHPAD_INFO, NULL, 0, &response); |
| |
| if (ret) |
| return -ret; |
| |
| info->abs_max_x = make_u16(params[0], params[1]); |
| info->abs_max_y = make_u16(params[2], params[3]); |
| info->abs_max_pressure = params[5]; |
| info->max_contacts = params[7]; |
| info->origin = params[6]; |
| fd->feature.event_format = params[9]; |
| |
| res = make_u16(params[4], params[5]); |
| info->abs_res_x = res; |
| info->abs_res_y = res; |
| info->has_rel = true; |
| return ret; |
| } |
| |
| static int bttms_parse_other_event(struct wtp_data *wtp, |
| struct hidpp_report *report, |
| struct wtp_event *event) { |
| int id; |
| u8 *raw = (u8 *)report; |
| u8 map = raw[8]; |
| int current_finger = 0; |
| |
| dbg_hid("%s\n", __func__); |
| |
| if (report->report_id != GENERIC_EVENT_MOUSE && |
| wtp->feature.event_format != BTTMS_FORMAT_01) |
| return -1; |
| |
| generic_parse_mouse_rel(&raw[2], event); |
| generic_parse_mouse_button(raw[1], event); |
| |
| for (id = 0; id < BTTMS_NUM_SLOTS; ++id) { |
| if (get_bit(map, id)) { |
| struct wtp_event_finger *finger = |
| &event->fingers[current_finger]; |
| u8 *f_raw = raw + (9 + current_finger * 4); |
| u8 width = low_nib(f_raw[3]); |
| u8 height = high_nib(f_raw[3]); |
| finger->status = CONTACT_STATUS_TOUCH; |
| finger->pressure = (width * width + |
| height * height) / 2; |
| finger->abs_x = make_u12_8_4(f_raw[0], |
| low_nib(f_raw[2])); |
| finger->abs_y = make_u12_8_4(f_raw[1], |
| high_nib(f_raw[2])); |
| finger->id = id + 1; |
| current_finger++; |
| } |
| } |
| |
| event->has_abs = true; |
| event->end_of_frame = !get_bit(raw[7], 7); |
| return 0; |
| } |
| |
| static int bttms_parse_feature_event(struct wtp_data *wtp, |
| struct hidpp_report *report, |
| struct wtp_event *event) { |
| /* We are not using raw events */ |
| return -1; |
| } |
| |
| |
| /* Array enumerating all supported hidpp features */ |
| static struct wtp_feature wtp_supported_features[] = { |
| { |
| TPRXY_FEATURE, |
| 0, 0, |
| &tprxy_init, |
| &tprxy_probe, |
| &tprxy_parse_feature_event, |
| &tprxy_parse_other_event |
| }, |
| { |
| TMRTP_FEATURE, |
| 0, 0, |
| &tmrtp_init, |
| &tmrtp_probe, |
| &tmrtp_parse_feature_event, |
| &tmrtp_parse_other_event |
| }, |
| { |
| BTTMS_FEATURE, |
| 0, 0, |
| &bttms_init, |
| &bttms_probe, |
| &bttms_parse_feature_event, |
| &bttms_parse_other_event |
| }, |
| { } |
| }; |
| |
| /* |
| Common code for all devices/features |
| */ |
| |
| /* Report a single finger as input_events. This method will also assign |
| tracking ids to each new finger. */ |
| static void wtp_process_event_finger(struct wtp_data *fd, |
| struct wtp_event_finger *finger) |
| { |
| int slot = finger->id - 1; |
| |
| bool new_finger = !(fd->prev_slots_used & (1 << slot)); |
| fd->current_slots_used |= 1 << slot; |
| fd->fingers_seen_this_frame++; |
| |
| dbg_hid("Finger %d: (%d,%d,%d) s=%d\n", |
| slot, |
| finger->abs_x, |
| finger->abs_y, |
| finger->pressure, |
| finger->status); |
| |
| input_mt_slot(fd->input, slot); |
| if (new_finger) { |
| input_event(fd->input, EV_ABS, ABS_MT_TRACKING_ID, |
| fd->next_tracking_id++); |
| if (fd->next_tracking_id == 0xffff) |
| fd->next_tracking_id = 1; |
| } |
| input_mt_report_slot_state(fd->input, MT_TOOL_FINGER, 1); |
| input_event(fd->input, EV_ABS, ABS_MT_POSITION_X, |
| ORIGIN_IS_RIGHT(fd->info.origin) ? |
| fd->info.abs_max_x - finger->abs_x : finger->abs_x); |
| input_event(fd->input, EV_ABS, ABS_MT_POSITION_Y, |
| ORIGIN_IS_HIGH(fd->info.origin) ? |
| finger->abs_y : fd->info.abs_max_y - finger->abs_y); |
| input_event(fd->input, EV_ABS, ABS_MT_PRESSURE, finger->pressure); |
| } |
| |
| /* Report an event as input_events */ |
| static int wtp_process_event(struct hidpp_device *hidpp_dev, |
| struct wtp_event *event) |
| { |
| struct wtp_data *fd = (struct wtp_data *)hidpp_dev->driver_data; |
| |
| int i; |
| if (!hidpp_dev->initialized) |
| return -1; |
| |
| /* report buttons */ |
| if (event->has_buttons != 0) { |
| fd->buttons &= ~event->has_buttons; |
| fd->buttons |= event->buttons & event->has_buttons; |
| dbg_hid("Button: 0x%x\n", fd->buttons); |
| input_report_key(fd->input, BTN_LEFT, |
| get_bit(fd->buttons, BUTTON_LEFT)); |
| input_report_key(fd->input, BTN_RIGHT, |
| get_bit(fd->buttons, BUTTON_RIGHT)); |
| input_report_key(fd->input, BTN_MIDDLE, |
| get_bit(fd->buttons, BUTTON_MIDDLE) | |
| get_bit(fd->buttons, BUTTON_MIDDLE_ALT)); |
| } |
| |
| if (event->has_keys) { |
| hid_report_raw_event(hidpp_dev->hid_dev, HID_INPUT_REPORT, |
| event->kbd_report, UNIFYING_KBD_INPUT_REPORT_SIZE, 1); |
| input_sync(fd->input); |
| return 1; |
| } |
| |
| |
| /* report relative mouse movement */ |
| if (event->has_rel) { |
| dbg_hid("REL: (%d, %d)\n", event->rel_x, event->rel_y); |
| input_report_rel(fd->input, REL_X, event->rel_x); |
| input_report_rel(fd->input, REL_Y, event->rel_y); |
| } |
| |
| /* sync now if there is no touch data following */ |
| if (!event->has_abs) { |
| input_sync(fd->input); |
| return 1; /* we successfully consumed the event */ |
| } |
| |
| /* update fingers */ |
| for (i = 0; i < ARRAYSIZE(event->fingers); i++) { |
| if (event->fingers[i].status != CONTACT_STATUS_RELEASED) |
| wtp_process_event_finger(fd, &event->fingers[i]); |
| } |
| |
| /* update released fingers and sync */ |
| if (event->end_of_frame) { |
| for (i = 0; i < SLOT_COUNT; i++) { |
| __u16 slot_mask = 1 << i; |
| bool released = (fd->prev_slots_used & slot_mask) && |
| !(fd->current_slots_used & slot_mask); |
| if (!released) |
| continue; |
| dbg_hid("Finger %d: released\n", i); |
| input_mt_slot(fd->input, i); |
| input_event(fd->input, EV_ABS, ABS_MT_TRACKING_ID, -1); |
| input_mt_report_slot_state(fd->input, MT_TOOL_FINGER, 0); |
| } |
| input_mt_report_pointer_emulation(fd->input, true); |
| |
| input_sync(fd->input); |
| |
| fd->prev_slots_used = fd->current_slots_used; |
| fd->current_slots_used = 0; |
| fd->fingers_seen_this_frame = 0; |
| } |
| return 1; /* we successfully consumed the event */ |
| } |
| |
| /* dispatches events to feature event parsing methods and reports |
| the result as input_events */ |
| static int wtp_hidpp_event_handler(struct hidpp_device *hidpp_device, |
| struct hidpp_report *report) |
| { |
| struct wtp_event event; |
| struct wtp_data *fd = (struct wtp_data *)hidpp_device->driver_data; |
| int ret; |
| |
| memset(&event, 0, sizeof(event)); |
| |
| if (report->report_id == REPORT_ID_HIDPP_LONG && |
| report->rap.sub_id == fd->feature.index) { |
| ret = (*fd->feature.parse_feature_event)(fd, report, &event); |
| } else { |
| ret = (*fd->feature.parse_other_event)(fd, report, &event); |
| } |
| if (ret) |
| return ret; |
| return wtp_process_event(hidpp_device, &event); |
| } |
| |
| /* report device info */ |
| static int wtp_input_mapping(struct hid_device *hdev, struct hid_input *hi, |
| struct hid_field *field, struct hid_usage *usage, |
| unsigned long **bit, int *max) |
| { |
| struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev); |
| struct wtp_data *fd = (struct wtp_data *)hidpp_dev->driver_data; |
| struct input_dev *input = hi->input; |
| int res_x_mm, res_y_mm; |
| |
| dbg_hid("%s:\n", __func__); |
| |
| if ( ((usage->hid & HID_USAGE_PAGE) == HID_UP_KEYBOARD) || |
| ((usage->hid & HID_USAGE_PAGE) == HID_UP_LED) ) |
| return 0; |
| |
| if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON) |
| return -1; |
| |
| fd->input = hi->input; |
| |
| __set_bit(BTN_TOUCH, input->keybit); |
| __set_bit(BTN_TOOL_FINGER, input->keybit); |
| __set_bit(BTN_TOOL_DOUBLETAP, input->keybit); |
| __set_bit(BTN_TOOL_TRIPLETAP, input->keybit); |
| __set_bit(BTN_TOOL_QUADTAP, input->keybit); |
| __set_bit(BTN_TOOL_QUINTTAP, input->keybit); |
| |
| __set_bit(EV_ABS, input->evbit); |
| |
| input_mt_init_slots(input, SLOT_COUNT, 0); |
| |
| input_set_capability(input, EV_KEY, BTN_TOUCH); |
| |
| input_set_abs_params(input, ABS_MT_PRESSURE, 0, 255, 0, 0); |
| input_set_abs_params(input, ABS_MT_POSITION_X, |
| 0, fd->info.abs_max_x, 0, 0); |
| input_set_abs_params(input, ABS_MT_POSITION_Y, |
| 0, fd->info.abs_max_y, 0, 0); |
| input_set_abs_params(input, ABS_X, 0, fd->info.abs_max_x, 0, 0); |
| input_set_abs_params(input, ABS_Y, 0, fd->info.abs_max_y, 0, 0); |
| |
| if (fd->info.has_rel) { |
| input_set_capability(input, EV_REL, REL_X); |
| input_set_capability(input, EV_REL, REL_Y); |
| } |
| |
| res_x_mm = DPI_TO_DPMM(fd->info.abs_res_x); |
| res_y_mm = DPI_TO_DPMM(fd->info.abs_res_y); |
| |
| input_abs_set_res(input, ABS_MT_POSITION_X, res_x_mm); |
| input_abs_set_res(input, ABS_MT_POSITION_Y, res_y_mm); |
| input_abs_set_res(input, ABS_X, res_x_mm); |
| input_abs_set_res(input, ABS_Y, res_y_mm); |
| |
| return 0; |
| } |
| |
| /* probes for all supported features and uses the first available |
| to initialize the device */ |
| static int wtp_init_feature(struct hidpp_device *hidpp_device) |
| { |
| struct wtp_feature *feature = wtp_supported_features; |
| struct wtp_data *fd = (struct wtp_data *)hidpp_device->driver_data; |
| dbg_hid("%s\n", __func__); |
| |
| while (feature->id) { |
| int ret; |
| dbg_hid("Probing feature 0x%x\n", feature->id); |
| ret = hidpp_get_hidpp2_feature_index(hidpp_device, |
| SOFTWARE_ID, |
| feature->id, |
| &feature->index); |
| if (ret) |
| return -ENODEV; |
| |
| if (feature->index != 0) { |
| dbg_hid("Feature found at index: %d\n", feature->index); |
| fd->feature = *feature; |
| ret = (*fd->feature.probe)(hidpp_device, &fd->info); |
| if (ret) |
| return ret; |
| ret = (*fd->feature.init)(hidpp_device); |
| return ret; |
| } else { |
| dbg_hid("unavailable feature 0x%x\n", feature->id); |
| } |
| ++feature; |
| } |
| /* no supported feature found on this device */ |
| return -ENODEV; |
| } |
| |
| static void wtp_connect_change(struct hidpp_device *hidpp_dev, bool connected) |
| { |
| struct wtp_data *fd = (struct wtp_data *)hidpp_dev->driver_data; |
| dbg_hid("%s: connected:%d\n", __func__, connected); |
| if (connected && hidpp_dev->initialized && fd->feature.init) |
| (*fd->feature.init)(hidpp_dev); |
| } |
| |
| static int wtp_probe(struct hid_device *hdev, const struct hid_device_id *id) |
| { |
| struct wtp_data *fd = NULL; |
| struct hidpp_device *hidpp_device = NULL; |
| int ret; |
| struct hidpp_report response; |
| |
| dbg_hid("%s\n", __func__); |
| |
| hidpp_device = kzalloc(sizeof(struct hidpp_device), GFP_KERNEL); |
| if (!hidpp_device) { |
| hid_err(hdev, "cannot allocate hidpp_device\n"); |
| ret = -ENOMEM; |
| goto hidpp_alloc_failed; |
| } |
| |
| fd = kzalloc(sizeof(struct wtp_data), GFP_KERNEL); |
| if (!fd) { |
| hid_err(hdev, "cannot allocate wtp Touch data\n"); |
| ret = -ENOMEM; |
| goto fd_alloc_failed; |
| } |
| fd->next_tracking_id = 1; |
| |
| hidpp_device->driver_data = (void *)fd; |
| hid_set_drvdata(hdev, hidpp_device); |
| |
| hidpp_device->connect_change = wtp_connect_change; |
| |
| ret = hid_parse(hdev); |
| if (ret) { |
| ret = -ENODEV; |
| goto failed; |
| } |
| |
| ret = hidpp_init(hidpp_device, hdev); |
| if (ret) { |
| ret = -ENODEV; |
| goto failed; |
| } |
| |
| hid_device_io_start(hdev); |
| |
| /* Get hid++ version number */ |
| ret = hidpp_send_hidpp2_sync(hidpp_device, REPORT_ID_HIDPP_LONG, |
| 0, 1, |
| SOFTWARE_ID, |
| NULL, 0, &response); |
| if (ret) { |
| dbg_hid("send root cmd returned: %d", ret); |
| ret = -ENODEV; |
| goto failed; |
| } |
| |
| dbg_hid("HID++ version: %d.%d\n", response.rap.params[0], |
| response.rap.params[1]); |
| |
| /* TODO(adlr): Consider requiring a specific/minimum HID++ version. */ |
| |
| ret = wtp_init_feature(hidpp_device); |
| if (ret) { |
| dbg_hid("wtp_init_feature returned: %d", ret); |
| ret = -ENODEV; |
| goto failed; |
| } |
| |
| hid_device_io_stop(hdev); |
| |
| hidpp_device->raw_event = wtp_hidpp_event_handler; |
| |
| hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS; |
| /* Needed because some input reports are split across multiple |
| * incoming packets, and we need the HID system to not call |
| * input_sync() after each incoming packet: */ |
| hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC; |
| ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); |
| if (ret) { |
| ret = -ENODEV; |
| goto failed; |
| } |
| |
| return 0; |
| |
| failed: |
| hid_set_drvdata(hdev, NULL); |
| kfree(fd); |
| fd_alloc_failed: |
| kfree(hidpp_device); |
| hidpp_alloc_failed: |
| return ret; |
| } |
| |
| static void wtp_remove(struct hid_device *hdev) |
| { |
| struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev); |
| struct wtp_data *fd = hidpp_dev->driver_data; |
| dbg_hid("%s\n", __func__); |
| hid_hw_stop(hdev); |
| hidpp_remove(hidpp_dev); |
| kfree(fd); |
| kfree(hidpp_dev); |
| hid_set_drvdata(hdev, NULL); |
| } |
| |
| static struct hid_driver wtp_driver = { |
| .name = "wtp-touch", |
| .id_table = wtp_devices, |
| .probe = wtp_probe, |
| .remove = wtp_remove, |
| .input_mapping = wtp_input_mapping, |
| .raw_event = hidpp_raw_event, |
| }; |
| |
| static int __init wtp_init(void) |
| { |
| return hid_register_driver(&wtp_driver); |
| } |
| |
| static void __exit wtp_exit(void) |
| { |
| hid_unregister_driver(&wtp_driver); |
| } |
| |
| module_init(wtp_init); |
| module_exit(wtp_exit); |