| /* |
| * HIDPP protocol for Logitech Unifying receivers |
| * |
| * Copyright (c) 2011 Logitech (c) |
| * Copyright (c) 2012-2013 Google (c) |
| * Copyright (c) 2013-2014 Red Hat Inc. |
| */ |
| |
| /* |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the Free |
| * Software Foundation; version 2 of the License. |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/device.h> |
| #include <linux/hid.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/sched.h> |
| #include <linux/kfifo.h> |
| #include <linux/input/mt.h> |
| #include <asm/unaligned.h> |
| #include "hid-ids.h" |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); |
| MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>"); |
| |
| #define REPORT_ID_HIDPP_SHORT 0x10 |
| #define REPORT_ID_HIDPP_LONG 0x11 |
| |
| #define HIDPP_REPORT_SHORT_LENGTH 7 |
| #define HIDPP_REPORT_LONG_LENGTH 20 |
| |
| #define HIDPP_QUIRK_CLASS_WTP BIT(0) |
| |
| /* bits 1..20 are reserved for classes */ |
| #define HIDPP_QUIRK_DELAYED_INIT BIT(21) |
| #define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) |
| #define HIDPP_QUIRK_MULTI_INPUT BIT(23) |
| |
| /* |
| * There are two hidpp protocols in use, the first version hidpp10 is known |
| * as register access protocol or RAP, the second version hidpp20 is known as |
| * feature access protocol or FAP |
| * |
| * Most older devices (including the Unifying usb receiver) use the RAP protocol |
| * where as most newer devices use the FAP protocol. Both protocols are |
| * compatible with the underlying transport, which could be usb, Unifiying, or |
| * bluetooth. The message lengths are defined by the hid vendor specific report |
| * descriptor for the HIDPP_SHORT report type (total message lenth 7 bytes) and |
| * the HIDPP_LONG report type (total message length 20 bytes) |
| * |
| * The RAP protocol uses both report types, whereas the FAP only uses HIDPP_LONG |
| * messages. The Unifying receiver itself responds to RAP messages (device index |
| * is 0xFF for the receiver), and all messages (short or long) with a device |
| * index between 1 and 6 are passed untouched to the corresponding paired |
| * Unifying device. |
| * |
| * The paired device can be RAP or FAP, it will receive the message untouched |
| * from the Unifiying receiver. |
| */ |
| |
| struct fap { |
| u8 feature_index; |
| u8 funcindex_clientid; |
| u8 params[HIDPP_REPORT_LONG_LENGTH - 4U]; |
| }; |
| |
| struct rap { |
| u8 sub_id; |
| u8 reg_address; |
| u8 params[HIDPP_REPORT_LONG_LENGTH - 4U]; |
| }; |
| |
| struct hidpp_report { |
| u8 report_id; |
| u8 device_index; |
| union { |
| struct fap fap; |
| struct rap rap; |
| u8 rawbytes[sizeof(struct fap)]; |
| }; |
| } __packed; |
| |
| struct hidpp_device { |
| struct hid_device *hid_dev; |
| struct mutex send_mutex; |
| void *send_receive_buf; |
| wait_queue_head_t wait; |
| bool answer_available; |
| u8 protocol_major; |
| u8 protocol_minor; |
| |
| void *private_data; |
| |
| struct work_struct work; |
| struct kfifo delayed_work_fifo; |
| atomic_t connected; |
| struct input_dev *delayed_input; |
| |
| unsigned long quirks; |
| }; |
| |
| |
| #define HIDPP_ERROR 0x8f |
| #define HIDPP_ERROR_SUCCESS 0x00 |
| #define HIDPP_ERROR_INVALID_SUBID 0x01 |
| #define HIDPP_ERROR_INVALID_ADRESS 0x02 |
| #define HIDPP_ERROR_INVALID_VALUE 0x03 |
| #define HIDPP_ERROR_CONNECT_FAIL 0x04 |
| #define HIDPP_ERROR_TOO_MANY_DEVICES 0x05 |
| #define HIDPP_ERROR_ALREADY_EXISTS 0x06 |
| #define HIDPP_ERROR_BUSY 0x07 |
| #define HIDPP_ERROR_UNKNOWN_DEVICE 0x08 |
| #define HIDPP_ERROR_RESOURCE_ERROR 0x09 |
| #define HIDPP_ERROR_REQUEST_UNAVAILABLE 0x0a |
| #define HIDPP_ERROR_INVALID_PARAM_VALUE 0x0b |
| #define HIDPP_ERROR_WRONG_PIN_CODE 0x0c |
| |
| static void hidpp_connect_event(struct hidpp_device *hidpp_dev); |
| |
| static int __hidpp_send_report(struct hid_device *hdev, |
| struct hidpp_report *hidpp_report) |
| { |
| int fields_count, ret; |
| |
| switch (hidpp_report->report_id) { |
| case REPORT_ID_HIDPP_SHORT: |
| fields_count = HIDPP_REPORT_SHORT_LENGTH; |
| break; |
| case REPORT_ID_HIDPP_LONG: |
| fields_count = HIDPP_REPORT_LONG_LENGTH; |
| break; |
| default: |
| return -ENODEV; |
| } |
| |
| /* |
| * set the device_index as the receiver, it will be overwritten by |
| * hid_hw_request if needed |
| */ |
| hidpp_report->device_index = 0xff; |
| |
| ret = hid_hw_raw_request(hdev, hidpp_report->report_id, |
| (u8 *)hidpp_report, fields_count, HID_OUTPUT_REPORT, |
| HID_REQ_SET_REPORT); |
| |
| 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 |
| * by the protocol itself. |
| * - If ' something else' is negative, that means that we had a classic error |
| * (-ENOMEM, -EPIPE, etc...) |
| */ |
| static int hidpp_send_message_sync(struct hidpp_device *hidpp, |
| struct hidpp_report *message, |
| struct hidpp_report *response) |
| { |
| int ret; |
| |
| mutex_lock(&hidpp->send_mutex); |
| |
| hidpp->send_receive_buf = response; |
| hidpp->answer_available = false; |
| |
| /* |
| * So that we can later validate the answer when it arrives |
| * in hidpp_raw_event |
| */ |
| *response = *message; |
| |
| ret = __hidpp_send_report(hidpp->hid_dev, message); |
| |
| if (ret) { |
| dbg_hid("__hidpp_send_report returned err: %d\n", ret); |
| memset(response, 0, sizeof(struct hidpp_report)); |
| goto exit; |
| } |
| |
| if (!wait_event_timeout(hidpp->wait, hidpp->answer_available, |
| 5*HZ)) { |
| dbg_hid("%s:timeout waiting for response\n", __func__); |
| memset(response, 0, sizeof(struct hidpp_report)); |
| ret = -ETIMEDOUT; |
| } |
| |
| if (response->report_id == REPORT_ID_HIDPP_SHORT && |
| response->fap.feature_index == HIDPP_ERROR) { |
| ret = response->fap.params[1]; |
| dbg_hid("__hidpp_send_report got hidpp error %02X\n", ret); |
| goto exit; |
| } |
| |
| exit: |
| mutex_unlock(&hidpp->send_mutex); |
| return ret; |
| |
| } |
| |
| static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, |
| u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count, |
| struct hidpp_report *response) |
| { |
| struct hidpp_report *message; |
| int ret; |
| |
| if (param_count > sizeof(message->fap.params)) |
| return -EINVAL; |
| |
| message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL); |
| if (!message) |
| return -ENOMEM; |
| message->report_id = REPORT_ID_HIDPP_LONG; |
| message->fap.feature_index = feat_index; |
| message->fap.funcindex_clientid = funcindex_clientid; |
| memcpy(&message->fap.params, params, param_count); |
| |
| ret = hidpp_send_message_sync(hidpp, message, response); |
| kfree(message); |
| return ret; |
| } |
| |
| static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, |
| u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count, |
| struct hidpp_report *response) |
| { |
| struct hidpp_report *message; |
| int ret; |
| |
| if ((report_id != REPORT_ID_HIDPP_SHORT) && |
| (report_id != REPORT_ID_HIDPP_LONG)) |
| return -EINVAL; |
| |
| if (param_count > sizeof(message->rap.params)) |
| return -EINVAL; |
| |
| message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL); |
| if (!message) |
| return -ENOMEM; |
| message->report_id = report_id; |
| message->rap.sub_id = sub_id; |
| message->rap.reg_address = reg_address; |
| memcpy(&message->rap.params, params, param_count); |
| |
| ret = hidpp_send_message_sync(hidpp_dev, message, response); |
| kfree(message); |
| return ret; |
| } |
| |
| static void delayed_work_cb(struct work_struct *work) |
| { |
| struct hidpp_device *hidpp = container_of(work, struct hidpp_device, |
| work); |
| hidpp_connect_event(hidpp); |
| } |
| |
| static inline bool hidpp_match_answer(struct hidpp_report *question, |
| struct hidpp_report *answer) |
| { |
| return (answer->fap.feature_index == question->fap.feature_index) && |
| (answer->fap.funcindex_clientid == question->fap.funcindex_clientid); |
| } |
| |
| static inline bool hidpp_match_error(struct hidpp_report *question, |
| struct hidpp_report *answer) |
| { |
| return (answer->fap.feature_index == HIDPP_ERROR) && |
| (answer->fap.funcindex_clientid == question->fap.feature_index) && |
| (answer->fap.params[0] == question->fap.funcindex_clientid); |
| } |
| |
| static inline bool hidpp_report_is_connect_event(struct hidpp_report *report) |
| { |
| return (report->report_id == REPORT_ID_HIDPP_SHORT) && |
| (report->rap.sub_id == 0x41); |
| } |
| |
| /* -------------------------------------------------------------------------- */ |
| /* HIDP++ 1.0 commands */ |
| /* -------------------------------------------------------------------------- */ |
| |
| #define HIDPP_SET_REGISTER 0x80 |
| #define HIDPP_GET_REGISTER 0x81 |
| #define HIDPP_SET_LONG_REGISTER 0x82 |
| #define HIDPP_GET_LONG_REGISTER 0x83 |
| |
| #define HIDPP_REG_PAIRING_INFORMATION 0xB5 |
| #define DEVICE_NAME 0x40 |
| |
| static char *hidpp_get_unifying_name(struct hidpp_device *hidpp_dev) |
| { |
| struct hidpp_report response; |
| int ret; |
| /* hid-logitech-dj is in charge of setting the right device index */ |
| u8 params[1] = { DEVICE_NAME }; |
| char *name; |
| int len; |
| |
| ret = hidpp_send_rap_command_sync(hidpp_dev, |
| REPORT_ID_HIDPP_SHORT, |
| HIDPP_GET_LONG_REGISTER, |
| HIDPP_REG_PAIRING_INFORMATION, |
| params, 1, &response); |
| if (ret) |
| return NULL; |
| |
| len = response.rap.params[1]; |
| |
| if (2 + len > sizeof(response.rap.params)) |
| return NULL; |
| |
| name = kzalloc(len + 1, GFP_KERNEL); |
| if (!name) |
| return NULL; |
| |
| memcpy(name, &response.rap.params[2], len); |
| return name; |
| } |
| |
| /* -------------------------------------------------------------------------- */ |
| /* 0x0000: Root */ |
| /* -------------------------------------------------------------------------- */ |
| |
| #define HIDPP_PAGE_ROOT 0x0000 |
| #define HIDPP_PAGE_ROOT_IDX 0x00 |
| |
| #define CMD_ROOT_GET_FEATURE 0x01 |
| #define CMD_ROOT_GET_PROTOCOL_VERSION 0x11 |
| |
| static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature, |
| u8 *feature_index, u8 *feature_type) |
| { |
| struct hidpp_report response; |
| int ret; |
| u8 params[2] = { feature >> 8, feature & 0x00FF }; |
| |
| ret = hidpp_send_fap_command_sync(hidpp, |
| HIDPP_PAGE_ROOT_IDX, |
| CMD_ROOT_GET_FEATURE, |
| params, 2, &response); |
| if (ret) |
| return ret; |
| |
| *feature_index = response.fap.params[0]; |
| *feature_type = response.fap.params[1]; |
| |
| return ret; |
| } |
| |
| static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) |
| { |
| struct hidpp_report response; |
| int ret; |
| |
| ret = hidpp_send_fap_command_sync(hidpp, |
| HIDPP_PAGE_ROOT_IDX, |
| CMD_ROOT_GET_PROTOCOL_VERSION, |
| NULL, 0, &response); |
| |
| if (ret == HIDPP_ERROR_INVALID_SUBID) { |
| hidpp->protocol_major = 1; |
| hidpp->protocol_minor = 0; |
| return 0; |
| } |
| |
| /* the device might not be connected */ |
| if (ret == HIDPP_ERROR_RESOURCE_ERROR) |
| return -EIO; |
| |
| if (ret > 0) { |
| hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", |
| __func__, ret); |
| return -EPROTO; |
| } |
| if (ret) |
| return ret; |
| |
| hidpp->protocol_major = response.fap.params[0]; |
| hidpp->protocol_minor = response.fap.params[1]; |
| |
| return ret; |
| } |
| |
| static bool hidpp_is_connected(struct hidpp_device *hidpp) |
| { |
| int ret; |
| |
| ret = hidpp_root_get_protocol_version(hidpp); |
| if (!ret) |
| hid_dbg(hidpp->hid_dev, "HID++ %u.%u device connected.\n", |
| hidpp->protocol_major, hidpp->protocol_minor); |
| return ret == 0; |
| } |
| |
| /* -------------------------------------------------------------------------- */ |
| /* 0x0005: GetDeviceNameType */ |
| /* -------------------------------------------------------------------------- */ |
| |
| #define HIDPP_PAGE_GET_DEVICE_NAME_TYPE 0x0005 |
| |
| #define CMD_GET_DEVICE_NAME_TYPE_GET_COUNT 0x01 |
| #define CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME 0x11 |
| #define CMD_GET_DEVICE_NAME_TYPE_GET_TYPE 0x21 |
| |
| static int hidpp_devicenametype_get_count(struct hidpp_device *hidpp, |
| u8 feature_index, u8 *nameLength) |
| { |
| struct hidpp_report response; |
| int ret; |
| |
| ret = hidpp_send_fap_command_sync(hidpp, feature_index, |
| CMD_GET_DEVICE_NAME_TYPE_GET_COUNT, NULL, 0, &response); |
| |
| if (ret > 0) { |
| hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", |
| __func__, ret); |
| return -EPROTO; |
| } |
| if (ret) |
| return ret; |
| |
| *nameLength = response.fap.params[0]; |
| |
| return ret; |
| } |
| |
| static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp, |
| u8 feature_index, u8 char_index, char *device_name, int len_buf) |
| { |
| struct hidpp_report response; |
| int ret, i; |
| int count; |
| |
| ret = hidpp_send_fap_command_sync(hidpp, feature_index, |
| CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME, &char_index, 1, |
| &response); |
| |
| if (ret > 0) { |
| hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", |
| __func__, ret); |
| return -EPROTO; |
| } |
| if (ret) |
| return ret; |
| |
| if (response.report_id == REPORT_ID_HIDPP_LONG) |
| count = HIDPP_REPORT_LONG_LENGTH - 4; |
| else |
| count = HIDPP_REPORT_SHORT_LENGTH - 4; |
| |
| if (len_buf < count) |
| count = len_buf; |
| |
| for (i = 0; i < count; i++) |
| device_name[i] = response.fap.params[i]; |
| |
| return count; |
| } |
| |
| static char *hidpp_get_device_name(struct hidpp_device *hidpp) |
| { |
| u8 feature_type; |
| u8 feature_index; |
| u8 __name_length; |
| char *name; |
| unsigned index = 0; |
| int ret; |
| |
| ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE, |
| &feature_index, &feature_type); |
| if (ret) |
| return NULL; |
| |
| ret = hidpp_devicenametype_get_count(hidpp, feature_index, |
| &__name_length); |
| if (ret) |
| return NULL; |
| |
| name = kzalloc(__name_length + 1, GFP_KERNEL); |
| if (!name) |
| return NULL; |
| |
| while (index < __name_length) { |
| ret = hidpp_devicenametype_get_device_name(hidpp, |
| feature_index, index, name + index, |
| __name_length - index); |
| if (ret <= 0) { |
| kfree(name); |
| return NULL; |
| } |
| index += ret; |
| } |
| |
| return name; |
| } |
| |
| /* -------------------------------------------------------------------------- */ |
| /* 0x6100: TouchPadRawXY */ |
| /* -------------------------------------------------------------------------- */ |
| |
| #define HIDPP_PAGE_TOUCHPAD_RAW_XY 0x6100 |
| |
| #define CMD_TOUCHPAD_GET_RAW_INFO 0x01 |
| #define CMD_TOUCHPAD_SET_RAW_REPORT_STATE 0x21 |
| |
| #define EVENT_TOUCHPAD_RAW_XY 0x00 |
| |
| #define TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT 0x01 |
| #define TOUCHPAD_RAW_XY_ORIGIN_UPPER_LEFT 0x03 |
| |
| struct hidpp_touchpad_raw_info { |
| u16 x_size; |
| u16 y_size; |
| u8 z_range; |
| u8 area_range; |
| u8 timestamp_unit; |
| u8 maxcontacts; |
| u8 origin; |
| u16 res; |
| }; |
| |
| struct hidpp_touchpad_raw_xy_finger { |
| u8 contact_type; |
| u8 contact_status; |
| u16 x; |
| u16 y; |
| u8 z; |
| u8 area; |
| u8 finger_id; |
| }; |
| |
| struct hidpp_touchpad_raw_xy { |
| u16 timestamp; |
| struct hidpp_touchpad_raw_xy_finger fingers[2]; |
| u8 spurious_flag; |
| u8 end_of_frame; |
| u8 finger_count; |
| u8 button; |
| }; |
| |
| static int hidpp_touchpad_get_raw_info(struct hidpp_device *hidpp, |
| u8 feature_index, struct hidpp_touchpad_raw_info *raw_info) |
| { |
| struct hidpp_report response; |
| int ret; |
| u8 *params = (u8 *)response.fap.params; |
| |
| ret = hidpp_send_fap_command_sync(hidpp, feature_index, |
| CMD_TOUCHPAD_GET_RAW_INFO, NULL, 0, &response); |
| |
| if (ret > 0) { |
| hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", |
| __func__, ret); |
| return -EPROTO; |
| } |
| if (ret) |
| return ret; |
| |
| raw_info->x_size = get_unaligned_be16(¶ms[0]); |
| raw_info->y_size = get_unaligned_be16(¶ms[2]); |
| raw_info->z_range = params[4]; |
| raw_info->area_range = params[5]; |
| raw_info->maxcontacts = params[7]; |
| raw_info->origin = params[8]; |
| /* res is given in unit per inch */ |
| raw_info->res = get_unaligned_be16(¶ms[13]) * 2 / 51; |
| |
| return ret; |
| } |
| |
| static int hidpp_touchpad_set_raw_report_state(struct hidpp_device *hidpp_dev, |
| u8 feature_index, bool send_raw_reports, |
| bool sensor_enhanced_settings) |
| { |
| struct hidpp_report response; |
| |
| /* |
| * Params: |
| * bit 0 - enable raw |
| * bit 1 - 16bit Z, no area |
| * bit 2 - enhanced sensitivity |
| * bit 3 - width, height (4 bits each) instead of area |
| * bit 4 - send raw + gestures (degrades smoothness) |
| * remaining bits - reserved |
| */ |
| u8 params = send_raw_reports | (sensor_enhanced_settings << 2); |
| |
| return hidpp_send_fap_command_sync(hidpp_dev, feature_index, |
| CMD_TOUCHPAD_SET_RAW_REPORT_STATE, ¶ms, 1, &response); |
| } |
| |
| static void hidpp_touchpad_touch_event(u8 *data, |
| struct hidpp_touchpad_raw_xy_finger *finger) |
| { |
| u8 x_m = data[0] << 2; |
| u8 y_m = data[2] << 2; |
| |
| finger->x = x_m << 6 | data[1]; |
| finger->y = y_m << 6 | data[3]; |
| |
| finger->contact_type = data[0] >> 6; |
| finger->contact_status = data[2] >> 6; |
| |
| finger->z = data[4]; |
| finger->area = data[5]; |
| finger->finger_id = data[6] >> 4; |
| } |
| |
| static void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev, |
| u8 *data, struct hidpp_touchpad_raw_xy *raw_xy) |
| { |
| memset(raw_xy, 0, sizeof(struct hidpp_touchpad_raw_xy)); |
| raw_xy->end_of_frame = data[8] & 0x01; |
| raw_xy->spurious_flag = (data[8] >> 1) & 0x01; |
| raw_xy->finger_count = data[15] & 0x0f; |
| raw_xy->button = (data[8] >> 2) & 0x01; |
| |
| if (raw_xy->finger_count) { |
| hidpp_touchpad_touch_event(&data[2], &raw_xy->fingers[0]); |
| hidpp_touchpad_touch_event(&data[9], &raw_xy->fingers[1]); |
| } |
| } |
| |
| /* ************************************************************************** */ |
| /* */ |
| /* Device Support */ |
| /* */ |
| /* ************************************************************************** */ |
| |
| /* -------------------------------------------------------------------------- */ |
| /* Touchpad HID++ devices */ |
| /* -------------------------------------------------------------------------- */ |
| |
| #define WTP_MANUAL_RESOLUTION 39 |
| |
| struct wtp_data { |
| struct input_dev *input; |
| u16 x_size, y_size; |
| u8 finger_count; |
| u8 mt_feature_index; |
| u8 button_feature_index; |
| u8 maxcontacts; |
| bool flip_y; |
| unsigned int resolution; |
| }; |
| |
| 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 = hid_get_drvdata(hdev); |
| |
| if ((hidpp->quirks & HIDPP_QUIRK_MULTI_INPUT) && |
| (field->application == HID_GD_KEYBOARD)) |
| return 0; |
| |
| return -1; |
| } |
| |
| static void wtp_populate_input(struct hidpp_device *hidpp, |
| struct input_dev *input_dev, bool origin_is_hid_core) |
| { |
| struct wtp_data *wd = hidpp->private_data; |
| |
| if ((hidpp->quirks & HIDPP_QUIRK_MULTI_INPUT) && origin_is_hid_core) |
| /* this is the generic hid-input call */ |
| return; |
| |
| __set_bit(EV_ABS, input_dev->evbit); |
| __set_bit(EV_KEY, input_dev->evbit); |
| __clear_bit(EV_REL, input_dev->evbit); |
| __clear_bit(EV_LED, input_dev->evbit); |
| |
| input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, wd->x_size, 0, 0); |
| input_abs_set_res(input_dev, ABS_MT_POSITION_X, wd->resolution); |
| input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, wd->y_size, 0, 0); |
| input_abs_set_res(input_dev, ABS_MT_POSITION_Y, wd->resolution); |
| |
| /* Max pressure is not given by the devices, pick one */ |
| input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 50, 0, 0); |
| |
| input_set_capability(input_dev, EV_KEY, BTN_LEFT); |
| |
| if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS) |
| input_set_capability(input_dev, EV_KEY, BTN_RIGHT); |
| else |
| __set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit); |
| |
| input_mt_init_slots(input_dev, wd->maxcontacts, INPUT_MT_POINTER | |
| INPUT_MT_DROP_UNUSED); |
| |
| wd->input = input_dev; |
| } |
| |
| static void wtp_touch_event(struct wtp_data *wd, |
| struct hidpp_touchpad_raw_xy_finger *touch_report) |
| { |
| int slot; |
| |
| if (!touch_report->finger_id || touch_report->contact_type) |
| /* no actual data */ |
| return; |
| |
| slot = input_mt_get_slot_by_key(wd->input, touch_report->finger_id); |
| |
| input_mt_slot(wd->input, slot); |
| input_mt_report_slot_state(wd->input, MT_TOOL_FINGER, |
| touch_report->contact_status); |
| if (touch_report->contact_status) { |
| input_event(wd->input, EV_ABS, ABS_MT_POSITION_X, |
| touch_report->x); |
| input_event(wd->input, EV_ABS, ABS_MT_POSITION_Y, |
| wd->flip_y ? wd->y_size - touch_report->y : |
| touch_report->y); |
| input_event(wd->input, EV_ABS, ABS_MT_PRESSURE, |
| touch_report->area); |
| } |
| } |
| |
| static void wtp_send_raw_xy_event(struct hidpp_device *hidpp, |
| struct hidpp_touchpad_raw_xy *raw) |
| { |
| struct wtp_data *wd = hidpp->private_data; |
| int i; |
| |
| for (i = 0; i < 2; i++) |
| wtp_touch_event(wd, &(raw->fingers[i])); |
| |
| if (raw->end_of_frame && |
| !(hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS)) |
| input_event(wd->input, EV_KEY, BTN_LEFT, raw->button); |
| |
| if (raw->end_of_frame || raw->finger_count <= 2) { |
| input_mt_sync_frame(wd->input); |
| input_sync(wd->input); |
| } |
| } |
| |
| static int wtp_mouse_raw_xy_event(struct hidpp_device *hidpp, u8 *data) |
| { |
| struct wtp_data *wd = hidpp->private_data; |
| u8 c1_area = ((data[7] & 0xf) * (data[7] & 0xf) + |
| (data[7] >> 4) * (data[7] >> 4)) / 2; |
| u8 c2_area = ((data[13] & 0xf) * (data[13] & 0xf) + |
| (data[13] >> 4) * (data[13] >> 4)) / 2; |
| struct hidpp_touchpad_raw_xy raw = { |
| .timestamp = data[1], |
| .fingers = { |
| { |
| .contact_type = 0, |
| .contact_status = !!data[7], |
| .x = get_unaligned_le16(&data[3]), |
| .y = get_unaligned_le16(&data[5]), |
| .z = c1_area, |
| .area = c1_area, |
| .finger_id = data[2], |
| }, { |
| .contact_type = 0, |
| .contact_status = !!data[13], |
| .x = get_unaligned_le16(&data[9]), |
| .y = get_unaligned_le16(&data[11]), |
| .z = c2_area, |
| .area = c2_area, |
| .finger_id = data[8], |
| } |
| }, |
| .finger_count = wd->maxcontacts, |
| .spurious_flag = 0, |
| .end_of_frame = (data[0] >> 7) == 0, |
| .button = data[0] & 0x01, |
| }; |
| |
| wtp_send_raw_xy_event(hidpp, &raw); |
| |
| return 1; |
| } |
| |
| static int wtp_raw_event(struct hid_device *hdev, u8 *data, int size) |
| { |
| struct hidpp_device *hidpp = hid_get_drvdata(hdev); |
| struct wtp_data *wd = hidpp->private_data; |
| struct hidpp_report *report = (struct hidpp_report *)data; |
| struct hidpp_touchpad_raw_xy raw; |
| |
| if (!wd || !wd->input) |
| return 1; |
| |
| switch (data[0]) { |
| case 0x02: |
| if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS) { |
| input_event(wd->input, EV_KEY, BTN_LEFT, |
| !!(data[1] & 0x01)); |
| input_event(wd->input, EV_KEY, BTN_RIGHT, |
| !!(data[1] & 0x02)); |
| input_sync(wd->input); |
| } else { |
| if (size < 21) |
| return 1; |
| return wtp_mouse_raw_xy_event(hidpp, &data[7]); |
| } |
| case REPORT_ID_HIDPP_LONG: |
| if ((report->fap.feature_index != wd->mt_feature_index) || |
| (report->fap.funcindex_clientid != EVENT_TOUCHPAD_RAW_XY)) |
| return 1; |
| hidpp_touchpad_raw_xy_event(hidpp, data + 4, &raw); |
| |
| wtp_send_raw_xy_event(hidpp, &raw); |
| return 0; |
| } |
| |
| return 0; |
| } |
| |
| static int wtp_get_config(struct hidpp_device *hidpp) |
| { |
| struct wtp_data *wd = hidpp->private_data; |
| struct hidpp_touchpad_raw_info raw_info = {0}; |
| u8 feature_type; |
| int ret; |
| |
| ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY, |
| &wd->mt_feature_index, &feature_type); |
| if (ret) |
| /* means that the device is not powered up */ |
| return ret; |
| |
| ret = hidpp_touchpad_get_raw_info(hidpp, wd->mt_feature_index, |
| &raw_info); |
| if (ret) |
| return ret; |
| |
| wd->x_size = raw_info.x_size; |
| wd->y_size = raw_info.y_size; |
| wd->maxcontacts = raw_info.maxcontacts; |
| wd->flip_y = raw_info.origin == TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT; |
| wd->resolution = raw_info.res; |
| if (!wd->resolution) |
| wd->resolution = WTP_MANUAL_RESOLUTION; |
| |
| return 0; |
| } |
| |
| static int wtp_allocate(struct hid_device *hdev, const struct hid_device_id *id) |
| { |
| struct hidpp_device *hidpp = hid_get_drvdata(hdev); |
| struct wtp_data *wd; |
| |
| wd = devm_kzalloc(&hdev->dev, sizeof(struct wtp_data), |
| GFP_KERNEL); |
| if (!wd) |
| return -ENOMEM; |
| |
| hidpp->private_data = wd; |
| |
| return 0; |
| }; |
| |
| static void wtp_connect(struct hid_device *hdev, bool connected) |
| { |
| struct hidpp_device *hidpp = hid_get_drvdata(hdev); |
| struct wtp_data *wd = hidpp->private_data; |
| int ret; |
| |
| if (!connected) |
| return; |
| |
| if (!wd->x_size) { |
| ret = wtp_get_config(hidpp); |
| if (ret) { |
| hid_err(hdev, "Can not get wtp config: %d\n", ret); |
| return; |
| } |
| } |
| |
| hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index, |
| true, true); |
| } |
| |
| /* -------------------------------------------------------------------------- */ |
| /* Generic HID++ devices */ |
| /* -------------------------------------------------------------------------- */ |
| |
| static int hidpp_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 = hid_get_drvdata(hdev); |
| |
| if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) |
| return wtp_input_mapping(hdev, hi, field, usage, bit, max); |
| |
| return 0; |
| } |
| |
| static void hidpp_populate_input(struct hidpp_device *hidpp, |
| struct input_dev *input, bool origin_is_hid_core) |
| { |
| if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) |
| wtp_populate_input(hidpp, input, origin_is_hid_core); |
| } |
| |
| static void hidpp_input_configured(struct hid_device *hdev, |
| struct hid_input *hidinput) |
| { |
| struct hidpp_device *hidpp = hid_get_drvdata(hdev); |
| struct input_dev *input = hidinput->input; |
| |
| hidpp_populate_input(hidpp, input, true); |
| } |
| |
| static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, |
| int size) |
| { |
| struct hidpp_report *question = hidpp->send_receive_buf; |
| struct hidpp_report *answer = hidpp->send_receive_buf; |
| struct hidpp_report *report = (struct hidpp_report *)data; |
| |
| /* |
| * If the mutex is locked then we have a pending answer from a |
| * previoulsly sent command |
| */ |
| if (unlikely(mutex_is_locked(&hidpp->send_mutex))) { |
| /* |
| * Check for a correct hidpp20 answer or the corresponding |
| * error |
| */ |
| if (hidpp_match_answer(question, report) || |
| hidpp_match_error(question, report)) { |
| *answer = *report; |
| hidpp->answer_available = true; |
| wake_up(&hidpp->wait); |
| /* |
| * This was an answer to a command that this driver sent |
| * We return 1 to hid-core to avoid forwarding the |
| * command upstream as it has been treated by the driver |
| */ |
| |
| return 1; |
| } |
| } |
| |
| if (unlikely(hidpp_report_is_connect_event(report))) { |
| atomic_set(&hidpp->connected, |
| !(report->rap.params[0] & (1 << 6))); |
| if ((hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) && |
| (schedule_work(&hidpp->work) == 0)) |
| dbg_hid("%s: connect event already queued\n", __func__); |
| return 1; |
| } |
| |
| if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) |
| return wtp_raw_event(hidpp->hid_dev, data, size); |
| |
| return 0; |
| } |
| |
| static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, |
| u8 *data, int size) |
| { |
| struct hidpp_device *hidpp = hid_get_drvdata(hdev); |
| |
| switch (data[0]) { |
| case REPORT_ID_HIDPP_LONG: |
| if (size != HIDPP_REPORT_LONG_LENGTH) { |
| hid_err(hdev, "received hid++ report of bad size (%d)", |
| size); |
| return 1; |
| } |
| return hidpp_raw_hidpp_event(hidpp, data, size); |
| case REPORT_ID_HIDPP_SHORT: |
| if (size != HIDPP_REPORT_SHORT_LENGTH) { |
| hid_err(hdev, "received hid++ report of bad size (%d)", |
| size); |
| return 1; |
| } |
| return hidpp_raw_hidpp_event(hidpp, data, size); |
| } |
| |
| if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) |
| return wtp_raw_event(hdev, data, size); |
| |
| return 0; |
| } |
| |
| static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying) |
| { |
| struct hidpp_device *hidpp = hid_get_drvdata(hdev); |
| char *name; |
| |
| if (use_unifying) |
| /* |
| * the device is connected through an Unifying receiver, and |
| * might not be already connected. |
| * Ask the receiver for its name. |
| */ |
| name = hidpp_get_unifying_name(hidpp); |
| else |
| name = hidpp_get_device_name(hidpp); |
| |
| if (!name) |
| hid_err(hdev, "unable to retrieve the name of the device"); |
| else |
| snprintf(hdev->name, sizeof(hdev->name), "%s", name); |
| |
| kfree(name); |
| } |
| |
| static int hidpp_input_open(struct input_dev *dev) |
| { |
| struct hid_device *hid = input_get_drvdata(dev); |
| |
| return hid_hw_open(hid); |
| } |
| |
| static void hidpp_input_close(struct input_dev *dev) |
| { |
| struct hid_device *hid = input_get_drvdata(dev); |
| |
| hid_hw_close(hid); |
| } |
| |
| static struct input_dev *hidpp_allocate_input(struct hid_device *hdev) |
| { |
| struct input_dev *input_dev = devm_input_allocate_device(&hdev->dev); |
| |
| if (!input_dev) |
| return NULL; |
| |
| input_set_drvdata(input_dev, hdev); |
| input_dev->open = hidpp_input_open; |
| input_dev->close = hidpp_input_close; |
| |
| input_dev->name = hdev->name; |
| input_dev->phys = hdev->phys; |
| input_dev->uniq = hdev->uniq; |
| input_dev->id.bustype = hdev->bus; |
| input_dev->id.vendor = hdev->vendor; |
| input_dev->id.product = hdev->product; |
| input_dev->id.version = hdev->version; |
| input_dev->dev.parent = &hdev->dev; |
| |
| return input_dev; |
| } |
| |
| static void hidpp_connect_event(struct hidpp_device *hidpp) |
| { |
| struct hid_device *hdev = hidpp->hid_dev; |
| int ret = 0; |
| bool connected = atomic_read(&hidpp->connected); |
| struct input_dev *input; |
| char *name, *devm_name; |
| |
| if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) |
| wtp_connect(hdev, connected); |
| |
| if (!connected || hidpp->delayed_input) |
| return; |
| |
| if (!hidpp->protocol_major) { |
| ret = !hidpp_is_connected(hidpp); |
| if (ret) { |
| hid_err(hdev, "Can not get the protocol version.\n"); |
| return; |
| } |
| } |
| |
| /* the device is already connected, we can ask for its name and |
| * protocol */ |
| hid_info(hdev, "HID++ %u.%u device connected.\n", |
| hidpp->protocol_major, hidpp->protocol_minor); |
| |
| input = hidpp_allocate_input(hdev); |
| if (!input) { |
| hid_err(hdev, "cannot allocate new input device: %d\n", ret); |
| return; |
| } |
| |
| name = hidpp_get_device_name(hidpp); |
| if (!name) { |
| hid_err(hdev, "unable to retrieve the name of the device"); |
| } else { |
| devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name); |
| if (devm_name) |
| input->name = devm_name; |
| kfree(name); |
| } |
| |
| hidpp_populate_input(hidpp, input, false); |
| |
| ret = input_register_device(input); |
| if (ret) |
| input_free_device(input); |
| |
| hidpp->delayed_input = input; |
| } |
| |
| static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) |
| { |
| struct hidpp_device *hidpp; |
| int ret; |
| bool connected; |
| unsigned int connect_mask = HID_CONNECT_DEFAULT; |
| |
| hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device), |
| GFP_KERNEL); |
| if (!hidpp) |
| return -ENOMEM; |
| |
| hidpp->hid_dev = hdev; |
| hid_set_drvdata(hdev, hidpp); |
| |
| hidpp->quirks = id->driver_data; |
| |
| if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { |
| ret = wtp_allocate(hdev, id); |
| if (ret) |
| goto wtp_allocate_fail; |
| } |
| |
| INIT_WORK(&hidpp->work, delayed_work_cb); |
| mutex_init(&hidpp->send_mutex); |
| init_waitqueue_head(&hidpp->wait); |
| |
| ret = hid_parse(hdev); |
| if (ret) { |
| hid_err(hdev, "%s:parse failed\n", __func__); |
| goto hid_parse_fail; |
| } |
| |
| /* Allow incoming packets */ |
| hid_device_io_start(hdev); |
| |
| connected = hidpp_is_connected(hidpp); |
| if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) { |
| if (!connected) { |
| hid_err(hdev, "Device not connected"); |
| hid_device_io_stop(hdev); |
| goto hid_parse_fail; |
| } |
| |
| hid_info(hdev, "HID++ %u.%u device connected.\n", |
| hidpp->protocol_major, hidpp->protocol_minor); |
| } |
| |
| hidpp_overwrite_name(hdev, id->group == HID_GROUP_LOGITECH_DJ_DEVICE); |
| atomic_set(&hidpp->connected, connected); |
| |
| if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) { |
| ret = wtp_get_config(hidpp); |
| if (ret) |
| goto hid_parse_fail; |
| } |
| |
| /* Block incoming packets */ |
| hid_device_io_stop(hdev); |
| |
| if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) |
| connect_mask &= ~HID_CONNECT_HIDINPUT; |
| |
| /* Re-enable hidinput for multi-input devices */ |
| if (hidpp->quirks & HIDPP_QUIRK_MULTI_INPUT) |
| connect_mask |= HID_CONNECT_HIDINPUT; |
| |
| ret = hid_hw_start(hdev, connect_mask); |
| if (ret) { |
| hid_err(hdev, "%s:hid_hw_start returned error\n", __func__); |
| goto hid_hw_start_fail; |
| } |
| |
| if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) { |
| /* Allow incoming packets */ |
| hid_device_io_start(hdev); |
| |
| hidpp_connect_event(hidpp); |
| } |
| |
| return ret; |
| |
| hid_hw_start_fail: |
| hid_parse_fail: |
| cancel_work_sync(&hidpp->work); |
| mutex_destroy(&hidpp->send_mutex); |
| wtp_allocate_fail: |
| hid_set_drvdata(hdev, NULL); |
| return ret; |
| } |
| |
| static void hidpp_remove(struct hid_device *hdev) |
| { |
| struct hidpp_device *hidpp = hid_get_drvdata(hdev); |
| |
| cancel_work_sync(&hidpp->work); |
| mutex_destroy(&hidpp->send_mutex); |
| hid_hw_stop(hdev); |
| } |
| |
| static const struct hid_device_id hidpp_devices[] = { |
| { /* wireless touchpad */ |
| HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, |
| USB_VENDOR_ID_LOGITECH, 0x4011), |
| .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT | |
| HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS }, |
| { /* wireless touchpad T650 */ |
| HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, |
| USB_VENDOR_ID_LOGITECH, 0x4101), |
| .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT }, |
| { /* wireless touchpad T651 */ |
| HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, |
| USB_DEVICE_ID_LOGITECH_T651), |
| .driver_data = HIDPP_QUIRK_CLASS_WTP }, |
| { /* Keyboard TK820 */ |
| HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, |
| USB_VENDOR_ID_LOGITECH, 0x4102), |
| .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_MULTI_INPUT | |
| HIDPP_QUIRK_CLASS_WTP }, |
| |
| { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, |
| USB_VENDOR_ID_LOGITECH, HID_ANY_ID)}, |
| {} |
| }; |
| |
| MODULE_DEVICE_TABLE(hid, hidpp_devices); |
| |
| static struct hid_driver hidpp_driver = { |
| .name = "logitech-hidpp-device", |
| .id_table = hidpp_devices, |
| .probe = hidpp_probe, |
| .remove = hidpp_remove, |
| .raw_event = hidpp_raw_event, |
| .input_configured = hidpp_input_configured, |
| .input_mapping = hidpp_input_mapping, |
| }; |
| |
| module_hid_driver(hidpp_driver); |