Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1760173AbbKUMhm (ORCPT ); Sat, 21 Nov 2015 07:37:42 -0500 Received: from mail-wm0-f65.google.com ([74.125.82.65]:34857 "EHLO mail-wm0-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751521AbbKUMhe (ORCPT ); Sat, 21 Nov 2015 07:37:34 -0500 From: Edwin Velds To: linux-input@vger.kernel.org Cc: linux-kernel@vger.kernel.org, Simon Wood , Jiri Kosina , =?UTF-8?q?Michal=20Mal=C3=BD?= , elias vanderstuyft , Benjamin Tissoires , Dmitry Torokhov , Edwin Velds Subject: [PATCH 1/2] HID: Create header for Logitech HID++ driver Date: Sat, 21 Nov 2015 13:37:10 +0100 Message-Id: <1448109431-10459-2-git-send-email-e.velds@gmail.com> X-Mailer: git-send-email 2.5.0 In-Reply-To: <1448109431-10459-1-git-send-email-e.velds@gmail.com> References: <1448109431-10459-1-git-send-email-e.velds@gmail.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 107399 Lines: 3865 Renamed the hid-logitech-hidpp.c file and created a header. This prepares for building the driver from multiple sources. Signed-off-by: Edwin Velds --- drivers/hid/Makefile | 2 + drivers/hid/hid-logitech-hidpp-base.c | 1831 +++++++++++++++++++++++++++++++ drivers/hid/hid-logitech-hidpp-base.h | 81 ++ drivers/hid/hid-logitech-hidpp.c | 1900 --------------------------------- 4 files changed, 1914 insertions(+), 1900 deletions(-) create mode 100644 drivers/hid/hid-logitech-hidpp-base.c create mode 100644 drivers/hid/hid-logitech-hidpp-base.h delete mode 100644 drivers/hid/hid-logitech-hidpp.c diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 00011fe..a01c448 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -17,6 +17,8 @@ hid-logitech-$(CONFIG_LOGIRUMBLEPAD2_FF) += hid-lg2ff.o hid-logitech-$(CONFIG_LOGIG940_FF) += hid-lg3ff.o hid-logitech-$(CONFIG_LOGIWHEELS_FF) += hid-lg4ff.o +hid-logitech-hidpp-y := hid-logitech-hidpp-base.o + hid-wiimote-y := hid-wiimote-core.o hid-wiimote-modules.o hid-wiimote-$(CONFIG_DEBUG_FS) += hid-wiimote-debug.o diff --git a/drivers/hid/hid-logitech-hidpp-base.c b/drivers/hid/hid-logitech-hidpp-base.c new file mode 100644 index 0000000..40ab079 --- /dev/null +++ b/drivers/hid/hid-logitech-hidpp-base.c @@ -0,0 +1,1831 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include "hid-ids.h" +#include "hid-logitech-hidpp-base.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Benjamin Tissoires "); +MODULE_AUTHOR("Nestor Lopez Casado "); + +static bool disable_raw_mode; +module_param(disable_raw_mode, bool, 0644); +MODULE_PARM_DESC(disable_raw_mode, + "Disable Raw mode reporting for touchpads and keep firmware gestures."); + +static bool disable_tap_to_click; +module_param(disable_tap_to_click, bool, 0644); +MODULE_PARM_DESC(disable_tap_to_click, + "Disable Tap-To-Click mode reporting for touchpads (only on the K400 currently)."); + +#define REPORT_ID_HIDPP_SHORT 0x10 +#define REPORT_ID_HIDPP_LONG 0x11 +#define REPORT_ID_HIDPP_VERY_LONG 0x12 + +#define HIDPP_QUIRK_CLASS_WTP BIT(0) +#define HIDPP_QUIRK_CLASS_M560 BIT(1) +#define HIDPP_QUIRK_CLASS_K400 BIT(2) +#define HIDPP_QUIRK_CLASS_G920 BIT(3) + +/* bits 2..20 are reserved for classes */ +#define HIDPP_QUIRK_CONNECT_EVENTS BIT(21) +#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) +#define HIDPP_QUIRK_NO_HIDINPUT BIT(23) +#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) + +#define HIDPP_QUIRK_DELAYED_INIT (HIDPP_QUIRK_NO_HIDINPUT | \ + HIDPP_QUIRK_CONNECT_EVENTS) + + +/* HID++ 1.0 error codes */ +#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 +/* HID++ 2.0 error codes */ +#define HIDPP20_ERROR 0xff + +static void hidpp_connect_event(struct hidpp_device *hidpp_dev); + +static int __hidpp_send_report(struct hid_device *hdev, + struct hidpp_report *hidpp_report) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + int fields_count, ret; + + hidpp = hid_get_drvdata(hdev); + + 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; + case REPORT_ID_HIDPP_VERY_LONG: + fields_count = HIDPP_REPORT_VERY_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; + + if (hidpp->quirks & HIDPP_QUIRK_FORCE_OUTPUT_REPORTS) { + ret = hid_hw_output_report(hdev, (u8 *)hidpp_report, fields_count); + } else { + 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->rap.sub_id == HIDPP_ERROR) { + ret = response->rap.params[1]; + dbg_hid("%s:got hidpp error %02X\n", __func__, ret); + goto exit; + } + + if ((response->report_id == REPORT_ID_HIDPP_LONG || + response->report_id == REPORT_ID_HIDPP_VERY_LONG) && + response->fap.feature_index == HIDPP20_ERROR) { + ret = response->fap.params[1]; + dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret); + goto exit; + } + +exit: + mutex_unlock(&hidpp->send_mutex); + return ret; + +} + +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; + + if (param_count > (HIDPP_REPORT_LONG_LENGTH - 4)) + message->report_id = REPORT_ID_HIDPP_VERY_LONG; + else + 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, max_count; + + switch (report_id) { + case REPORT_ID_HIDPP_SHORT: + max_count = HIDPP_REPORT_SHORT_LENGTH - 4; + break; + case REPORT_ID_HIDPP_LONG: + max_count = HIDPP_REPORT_LONG_LENGTH - 4; + break; + case REPORT_ID_HIDPP_VERY_LONG: + max_count = HIDPP_REPORT_VERY_LONG_LENGTH - 4; + break; + default: + return -EINVAL; + } + + if (param_count > max_count) + 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->rap.sub_id == HIDPP_ERROR) || + (answer->fap.feature_index == HIDPP20_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); +} + +/** + * hidpp_prefix_name() prefixes the current given name with "Logitech ". + */ +static void hidpp_prefix_name(char **name, int name_length) +{ +#define PREFIX_LENGTH 9 /* "Logitech " */ + + int new_length; + char *new_name; + + if (name_length > PREFIX_LENGTH && + strncmp(*name, "Logitech ", PREFIX_LENGTH) == 0) + /* The prefix has is already in the name */ + return; + + new_length = PREFIX_LENGTH + name_length; + new_name = kzalloc(new_length, GFP_KERNEL); + if (!new_name) + return; + + snprintf(new_name, new_length, "Logitech %s", *name); + + kfree(*name); + + *name = new_name; +} + +/* -------------------------------------------------------------------------- */ +/* 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); + + /* include the terminating '\0' */ + hidpp_prefix_name(&name, len + 1); + + 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; + + switch (response.report_id) { + case REPORT_ID_HIDPP_VERY_LONG: + count = HIDPP_REPORT_VERY_LONG_LENGTH - 4; + break; + case REPORT_ID_HIDPP_LONG: + count = HIDPP_REPORT_LONG_LENGTH - 4; + break; + case REPORT_ID_HIDPP_SHORT: + count = HIDPP_REPORT_SHORT_LENGTH - 4; + break; + default: + return -EPROTO; + } + + 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; + } + + /* include the terminating '\0' */ + hidpp_prefix_name(&name, __name_length + 1); + + return name; +} + +/* -------------------------------------------------------------------------- */ +/* 0x6010: Touchpad FW items */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_TOUCHPAD_FW_ITEMS 0x6010 + +#define CMD_TOUCHPAD_FW_ITEMS_SET 0x10 + +struct hidpp_touchpad_fw_items { + uint8_t presence; + uint8_t desired_state; + uint8_t state; + uint8_t persistent; +}; + +/** + * send a set state command to the device by reading the current items->state + * field. items is then filled with the current state. + */ +static int hidpp_touchpad_fw_items_set(struct hidpp_device *hidpp, + u8 feature_index, + struct hidpp_touchpad_fw_items *items) +{ + struct hidpp_report response; + int ret; + u8 *params = (u8 *)response.fap.params; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_TOUCHPAD_FW_ITEMS_SET, &items->state, 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; + + items->presence = params[0]; + items->desired_state = params[1]; + items->state = params[2]; + items->persistent = params[3]; + + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* 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) +{ + 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; + + __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 (size < 2) { + hid_err(hdev, "Received HID report of bad size (%d)", + size); + return 1; + } + 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); + return 0; + } else { + if (size < 21) + return 1; + return wtp_mouse_raw_xy_event(hidpp, &data[7]); + } + case REPORT_ID_HIDPP_LONG: + /* size is already checked in hidpp_raw_event. */ + 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 int 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 0; + + if (!wd->x_size) { + ret = wtp_get_config(hidpp); + if (ret) { + hid_err(hdev, "Can not get wtp config: %d\n", ret); + return ret; + } + } + + return hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index, + true, true); +} + +/* ------------------------------------------------------------------------- */ +/* Logitech M560 devices */ +/* ------------------------------------------------------------------------- */ + +/* + * Logitech M560 protocol overview + * + * The Logitech M560 mouse, is designed for windows 8. When the middle and/or + * the sides buttons are pressed, it sends some keyboard keys events + * instead of buttons ones. + * To complicate things further, the middle button keys sequence + * is different from the odd press and the even press. + * + * forward button -> Super_R + * backward button -> Super_L+'d' (press only) + * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only) + * 2nd time: left-click (press only) + * NB: press-only means that when the button is pressed, the + * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated + * together sequentially; instead when the button is released, no event is + * generated ! + * + * With the command + * 100a 3500af03 (where is the mouse id), + * the mouse reacts differently: + * - it never sends a keyboard key event + * - for the three mouse button it sends: + * middle button press 110a 3500af00... + * side 1 button (forward) press 110a 3500b000... + * side 2 button (backward) press 110a 3500ae00... + * middle/side1/side2 button release 110a 35000000... + */ + +static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03}; + +struct m560_private_data { + struct input_dev *input; +}; + +/* how buttons are mapped in the report */ +#define M560_MOUSE_BTN_LEFT 0x01 +#define M560_MOUSE_BTN_RIGHT 0x02 +#define M560_MOUSE_BTN_WHEEL_LEFT 0x08 +#define M560_MOUSE_BTN_WHEEL_RIGHT 0x10 + +#define M560_SUB_ID 0x0a +#define M560_BUTTON_MODE_REGISTER 0x35 + +static int m560_send_config_command(struct hid_device *hdev, bool connected) +{ + struct hidpp_report response; + struct hidpp_device *hidpp_dev; + + hidpp_dev = hid_get_drvdata(hdev); + + if (!connected) + return -ENODEV; + + return hidpp_send_rap_command_sync( + hidpp_dev, + REPORT_ID_HIDPP_SHORT, + M560_SUB_ID, + M560_BUTTON_MODE_REGISTER, + (u8 *)m560_config_parameter, + sizeof(m560_config_parameter), + &response + ); +} + +static int m560_allocate(struct hid_device *hdev) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct m560_private_data *d; + + d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data), + GFP_KERNEL); + if (!d) + return -ENOMEM; + + hidpp->private_data = d; + + return 0; +}; + +static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct m560_private_data *mydata = hidpp->private_data; + + /* sanity check */ + if (!mydata || !mydata->input) { + hid_err(hdev, "error in parameter\n"); + return -EINVAL; + } + + if (size < 7) { + hid_err(hdev, "error in report\n"); + return 0; + } + + if (data[0] == REPORT_ID_HIDPP_LONG && + data[2] == M560_SUB_ID && data[6] == 0x00) { + /* + * m560 mouse report for middle, forward and backward button + * + * data[0] = 0x11 + * data[1] = device-id + * data[2] = 0x0a + * data[5] = 0xaf -> middle + * 0xb0 -> forward + * 0xae -> backward + * 0x00 -> release all + * data[6] = 0x00 + */ + + switch (data[5]) { + case 0xaf: + input_report_key(mydata->input, BTN_MIDDLE, 1); + break; + case 0xb0: + input_report_key(mydata->input, BTN_FORWARD, 1); + break; + case 0xae: + input_report_key(mydata->input, BTN_BACK, 1); + break; + case 0x00: + input_report_key(mydata->input, BTN_BACK, 0); + input_report_key(mydata->input, BTN_FORWARD, 0); + input_report_key(mydata->input, BTN_MIDDLE, 0); + break; + default: + hid_err(hdev, "error in report\n"); + return 0; + } + input_sync(mydata->input); + + } else if (data[0] == 0x02) { + /* + * Logitech M560 mouse report + * + * data[0] = type (0x02) + * data[1..2] = buttons + * data[3..5] = xy + * data[6] = wheel + */ + + int v; + + input_report_key(mydata->input, BTN_LEFT, + !!(data[1] & M560_MOUSE_BTN_LEFT)); + input_report_key(mydata->input, BTN_RIGHT, + !!(data[1] & M560_MOUSE_BTN_RIGHT)); + + if (data[1] & M560_MOUSE_BTN_WHEEL_LEFT) + input_report_rel(mydata->input, REL_HWHEEL, -1); + else if (data[1] & M560_MOUSE_BTN_WHEEL_RIGHT) + input_report_rel(mydata->input, REL_HWHEEL, 1); + + v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12); + input_report_rel(mydata->input, REL_X, v); + + v = hid_snto32(hid_field_extract(hdev, data+3, 12, 12), 12); + input_report_rel(mydata->input, REL_Y, v); + + v = hid_snto32(data[6], 8); + input_report_rel(mydata->input, REL_WHEEL, v); + + input_sync(mydata->input); + } + + return 1; +} + +static void m560_populate_input(struct hidpp_device *hidpp, + struct input_dev *input_dev, bool origin_is_hid_core) +{ + struct m560_private_data *mydata = hidpp->private_data; + + mydata->input = input_dev; + + __set_bit(EV_KEY, mydata->input->evbit); + __set_bit(BTN_MIDDLE, mydata->input->keybit); + __set_bit(BTN_RIGHT, mydata->input->keybit); + __set_bit(BTN_LEFT, mydata->input->keybit); + __set_bit(BTN_BACK, mydata->input->keybit); + __set_bit(BTN_FORWARD, mydata->input->keybit); + + __set_bit(EV_REL, mydata->input->evbit); + __set_bit(REL_X, mydata->input->relbit); + __set_bit(REL_Y, mydata->input->relbit); + __set_bit(REL_WHEEL, mydata->input->relbit); + __set_bit(REL_HWHEEL, mydata->input->relbit); +} + +static int m560_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + return -1; +} + +/* ------------------------------------------------------------------------- */ +/* Logitech K400 devices */ +/* ------------------------------------------------------------------------- */ + +/* + * The Logitech K400 keyboard has an embedded touchpad which is seen + * as a mouse from the OS point of view. There is a hardware shortcut to disable + * tap-to-click but the setting is not remembered accross reset, annoying some + * users. + * + * We can toggle this feature from the host by using the feature 0x6010: + * Touchpad FW items + */ + +struct k400_private_data { + u8 feature_index; +}; + +static int k400_disable_tap_to_click(struct hidpp_device *hidpp) +{ + struct k400_private_data *k400 = hidpp->private_data; + struct hidpp_touchpad_fw_items items = {}; + int ret; + u8 feature_type; + + if (!k400->feature_index) { + ret = hidpp_root_get_feature(hidpp, + HIDPP_PAGE_TOUCHPAD_FW_ITEMS, + &k400->feature_index, &feature_type); + if (ret) + /* means that the device is not powered up */ + return ret; + } + + ret = hidpp_touchpad_fw_items_set(hidpp, k400->feature_index, &items); + if (ret) + return ret; + + return 0; +} + +static int k400_allocate(struct hid_device *hdev) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct k400_private_data *k400; + + k400 = devm_kzalloc(&hdev->dev, sizeof(struct k400_private_data), + GFP_KERNEL); + if (!k400) + return -ENOMEM; + + hidpp->private_data = k400; + + return 0; +}; + +static int k400_connect(struct hid_device *hdev, bool connected) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + + if (!connected) + return 0; + + if (!disable_tap_to_click) + return 0; + + return k400_disable_tap_to_click(hidpp); +} + +/* ------------------------------------------------------------------------- */ +/* Logitech G920 Driving Force Racing Wheel for Xbox One */ +/* ------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_G920_FORCE_FEEDBACK 0x8123 + +/* Using session ID = 1 */ +#define CMD_G920_FORCE_GET_APERTURE 0x51 +#define CMD_G920_FORCE_SET_APERTURE 0x61 + +struct g920_private_data { + u8 force_feature; + u16 range; +}; + +#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev) + +static ssize_t g920_range_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct hidpp_device *hidpp = hid_get_drvdata(hid); + struct g920_private_data *pdata; + + pdata = hidpp->private_data; + if (!pdata) { + hid_err(hid, "Private driver data not found!\n"); + return -EINVAL; + } + + return scnprintf(buf, PAGE_SIZE, "%u\n", pdata->range); +} + +static ssize_t g920_range_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct hidpp_device *hidpp = hid_get_drvdata(hid); + struct g920_private_data *pdata; + struct hidpp_report response; + u8 params[2]; + int ret; + u16 range = simple_strtoul(buf, NULL, 10); + + pdata = hidpp->private_data; + if (!pdata) { + hid_err(hid, "Private driver data not found!\n"); + return -EINVAL; + } + + if (range < 180) + range = 180; + else if (range > 900) + range = 900; + + params[0] = range >> 8; + params[1] = range & 0x00FF; + + ret = hidpp_send_fap_command_sync(hidpp, pdata->force_feature, + CMD_G920_FORCE_SET_APERTURE, params, 2, &response); + if (ret) + return ret; + + pdata->range = range; + return count; +} + +static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, g920_range_show, g920_range_store); + +static int g920_allocate(struct hid_device *hdev) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct g920_private_data *pdata; + + pdata = devm_kzalloc(&hdev->dev, sizeof(struct g920_private_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + hidpp->private_data = pdata; + + return 0; +} + +static int g920_get_config(struct hidpp_device *hidpp) +{ + struct g920_private_data *pdata = hidpp->private_data; + struct hidpp_report response; + u8 feature_type; + u8 feature_index; + int ret; + + pdata = hidpp->private_data; + if (!pdata) { + hid_err(hidpp->hid_dev, "Private driver data not found!\n"); + return -EINVAL; + } + + /* Find feature and store for later use */ + ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK, + &feature_index, &feature_type); + if (ret) + return ret; + + pdata->force_feature = feature_index; + + /* Read current Range */ + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_G920_FORCE_GET_APERTURE, 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; + + pdata->range = get_unaligned_be16(&response.fap.params[0]); + + /* Create sysfs interface */ + ret = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range); + if (ret) + hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d\n", ret); + + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* 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); + else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 && + field->application != HID_GD_MOUSE) + return m560_input_mapping(hdev, hi, field, usage, bit, max); + + return 0; +} + +static int hidpp_input_mapped(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); + + /* Ensure that Logitech G920 is not given a default fuzz/flat value */ + if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { + if (usage->type == EV_ABS && (usage->code == ABS_X || + usage->code == ABS_Y || usage->code == ABS_Z || + usage->code == ABS_RZ)) { + field->application = HID_GD_MULTIAXIS; + } + } + + 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); + else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) + m560_populate_input(hidpp, input, origin_is_hid_core); +} + +static int 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); + + return 0; +} + +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 + * previously 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_CONNECT_EVENTS) && + (schedule_work(&hidpp->work) == 0)) + dbg_hid("%s: connect event already queued\n", __func__); + return 1; + } + + 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); + int ret = 0; + + /* Generic HID++ processing. */ + switch (data[0]) { + case REPORT_ID_HIDPP_VERY_LONG: + if (size != HIDPP_REPORT_VERY_LONG_LENGTH) { + hid_err(hdev, "received hid++ report of bad size (%d)", + size); + return 1; + } + ret = hidpp_raw_hidpp_event(hidpp, data, size); + break; + case REPORT_ID_HIDPP_LONG: + if (size != HIDPP_REPORT_LONG_LENGTH) { + hid_err(hdev, "received hid++ report of bad size (%d)", + size); + return 1; + } + ret = hidpp_raw_hidpp_event(hidpp, data, size); + break; + case REPORT_ID_HIDPP_SHORT: + if (size != HIDPP_REPORT_SHORT_LENGTH) { + hid_err(hdev, "received hid++ report of bad size (%d)", + size); + return 1; + } + ret = hidpp_raw_hidpp_event(hidpp, data, size); + break; + } + + /* If no report is available for further processing, skip calling + * raw_event of subclasses. */ + if (ret != 0) + return ret; + + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) + return wtp_raw_event(hdev, data, size); + else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) + return m560_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 { + dbg_hid("HID++: Got name: %s\n", name); + 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); + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + + 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 = hidpp->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) { + ret = wtp_connect(hdev, connected); + if (ret) + return; + } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) { + ret = m560_send_config_command(hdev, connected); + if (ret) + return; + } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) { + ret = k400_connect(hdev, connected); + if (ret) + return; + } + + if (!connected || hidpp->delayed_input) + return; + + /* the device is already connected, we can ask for its name and + * protocol */ + if (!hidpp->protocol_major) { + ret = !hidpp_is_connected(hidpp); + if (ret) { + hid_err(hdev, "Can not get the protocol version.\n"); + return; + } + hid_info(hdev, "HID++ %u.%u device connected.\n", + hidpp->protocol_major, hidpp->protocol_minor); + } + + if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)) + /* if HID created the input nodes for us, we can stop now */ + return; + + if (!hidpp->name || hidpp->name == hdev->name) { + name = hidpp_get_device_name(hidpp); + if (!name) { + hid_err(hdev, + "unable to retrieve the name of the device"); + return; + } + + devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name); + kfree(name); + if (!devm_name) + return; + + hidpp->name = devm_name; + } + + input = hidpp_allocate_input(hdev); + if (!input) { + hid_err(hdev, "cannot allocate new input device: %d\n", ret); + return; + } + + 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; + hidpp->name = hdev->name; + hid_set_drvdata(hdev, hidpp); + + hidpp->quirks = id->driver_data; + + if (disable_raw_mode) { + hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP; + hidpp->quirks &= ~HIDPP_QUIRK_CONNECT_EVENTS; + hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT; + } + + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { + ret = wtp_allocate(hdev, id); + if (ret) + goto allocate_fail; + } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) { + ret = m560_allocate(hdev); + if (ret) + goto allocate_fail; + } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) { + ret = k400_allocate(hdev); + if (ret) + goto allocate_fail; + } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { + ret = g920_allocate(hdev); + if (ret) + goto 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; + } + + if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) + connect_mask &= ~HID_CONNECT_HIDINPUT; + + if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { + ret = hid_hw_start(hdev, connect_mask); + if (ret) { + hid_err(hdev, "hw start failed\n"); + goto hid_hw_start_fail; + } + ret = hid_hw_open(hdev); + if (ret < 0) { + dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n", + __func__, ret); + hid_hw_stop(hdev); + goto hid_hw_start_fail; + } + } + + + /* Allow incoming packets */ + hid_device_io_start(hdev); + + connected = hidpp_is_connected(hidpp); + if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) { + if (!connected) { + ret = -ENODEV; + hid_err(hdev, "Device not connected"); + goto hid_hw_open_failed; + } + + 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_hw_open_failed; + } else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) { + ret = g920_get_config(hidpp); + if (ret) + goto hid_hw_open_failed; + } + + /* Block incoming packets */ + hid_device_io_stop(hdev); + + if (!(hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) { + 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_CONNECT_EVENTS) { + /* Allow incoming packets */ + hid_device_io_start(hdev); + + hidpp_connect_event(hidpp); + } + + return ret; + +hid_hw_open_failed: + hid_device_io_stop(hdev); + if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { + device_remove_file(&hdev->dev, &dev_attr_range); + hid_hw_close(hdev); + hid_hw_stop(hdev); + } +hid_hw_start_fail: +hid_parse_fail: + cancel_work_sync(&hidpp->work); + mutex_destroy(&hidpp->send_mutex); +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); + + if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { + device_remove_file(&hdev->dev, &dev_attr_range); + hid_hw_close(hdev); + } + hid_hw_stop(hdev); + cancel_work_sync(&hidpp->work); + mutex_destroy(&hidpp->send_mutex); +} + +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 }, + { /* Mouse logitech M560 */ + HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, + USB_VENDOR_ID_LOGITECH, 0x402d), + .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 }, + { /* Keyboard logitech K400 */ + HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, + USB_VENDOR_ID_LOGITECH, 0x4024), + .driver_data = HIDPP_QUIRK_CONNECT_EVENTS | HIDPP_QUIRK_CLASS_K400 }, + + { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, + USB_VENDOR_ID_LOGITECH, HID_ANY_ID)}, + + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL), + .driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS}, + {} +}; + +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, + .input_mapped = hidpp_input_mapped, +}; + +module_hid_driver(hidpp_driver); diff --git a/drivers/hid/hid-logitech-hidpp-base.h b/drivers/hid/hid-logitech-hidpp-base.h new file mode 100644 index 0000000..4d73055 --- /dev/null +++ b/drivers/hid/hid-logitech-hidpp-base.h @@ -0,0 +1,81 @@ +#ifndef __HID_LOGITECH_HIDPP_H +#define __HID_LOGITECH_HIDPP_H + +#include +#include + + +/* + * 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. + */ + +#define HIDPP_REPORT_SHORT_LENGTH 7 +#define HIDPP_REPORT_LONG_LENGTH 20 +#define HIDPP_REPORT_VERY_LONG_LENGTH 64 + +struct fap { + u8 feature_index; + u8 funcindex_clientid; + u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U]; +}; + +struct rap { + u8 sub_id; + u8 reg_address; + u8 params[HIDPP_REPORT_VERY_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; + char *name; /* will never be NULL and should not be freed */ + 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; +}; + + +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); + +#endif diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c deleted file mode 100644 index f2a4811..0000000 --- a/drivers/hid/hid-logitech-hidpp.c +++ /dev/null @@ -1,1900 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include "hid-ids.h" - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Benjamin Tissoires "); -MODULE_AUTHOR("Nestor Lopez Casado "); - -static bool disable_raw_mode; -module_param(disable_raw_mode, bool, 0644); -MODULE_PARM_DESC(disable_raw_mode, - "Disable Raw mode reporting for touchpads and keep firmware gestures."); - -static bool disable_tap_to_click; -module_param(disable_tap_to_click, bool, 0644); -MODULE_PARM_DESC(disable_tap_to_click, - "Disable Tap-To-Click mode reporting for touchpads (only on the K400 currently)."); - -#define REPORT_ID_HIDPP_SHORT 0x10 -#define REPORT_ID_HIDPP_LONG 0x11 -#define REPORT_ID_HIDPP_VERY_LONG 0x12 - -#define HIDPP_REPORT_SHORT_LENGTH 7 -#define HIDPP_REPORT_LONG_LENGTH 20 -#define HIDPP_REPORT_VERY_LONG_LENGTH 64 - -#define HIDPP_QUIRK_CLASS_WTP BIT(0) -#define HIDPP_QUIRK_CLASS_M560 BIT(1) -#define HIDPP_QUIRK_CLASS_K400 BIT(2) -#define HIDPP_QUIRK_CLASS_G920 BIT(3) - -/* bits 2..20 are reserved for classes */ -#define HIDPP_QUIRK_CONNECT_EVENTS BIT(21) -#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) -#define HIDPP_QUIRK_NO_HIDINPUT BIT(23) -#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) - -#define HIDPP_QUIRK_DELAYED_INIT (HIDPP_QUIRK_NO_HIDINPUT | \ - HIDPP_QUIRK_CONNECT_EVENTS) - -/* - * 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_VERY_LONG_LENGTH - 4U]; -}; - -struct rap { - u8 sub_id; - u8 reg_address; - u8 params[HIDPP_REPORT_VERY_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; - char *name; /* will never be NULL and should not be freed */ - 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; -}; - - -/* HID++ 1.0 error codes */ -#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 -/* HID++ 2.0 error codes */ -#define HIDPP20_ERROR 0xff - -static void hidpp_connect_event(struct hidpp_device *hidpp_dev); - -static int __hidpp_send_report(struct hid_device *hdev, - struct hidpp_report *hidpp_report) -{ - struct hidpp_device *hidpp = hid_get_drvdata(hdev); - int fields_count, ret; - - hidpp = hid_get_drvdata(hdev); - - 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; - case REPORT_ID_HIDPP_VERY_LONG: - fields_count = HIDPP_REPORT_VERY_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; - - if (hidpp->quirks & HIDPP_QUIRK_FORCE_OUTPUT_REPORTS) { - ret = hid_hw_output_report(hdev, (u8 *)hidpp_report, fields_count); - } else { - 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->rap.sub_id == HIDPP_ERROR) { - ret = response->rap.params[1]; - dbg_hid("%s:got hidpp error %02X\n", __func__, ret); - goto exit; - } - - if ((response->report_id == REPORT_ID_HIDPP_LONG || - response->report_id == REPORT_ID_HIDPP_VERY_LONG) && - response->fap.feature_index == HIDPP20_ERROR) { - ret = response->fap.params[1]; - dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, 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; - - if (param_count > (HIDPP_REPORT_LONG_LENGTH - 4)) - message->report_id = REPORT_ID_HIDPP_VERY_LONG; - else - 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, max_count; - - switch (report_id) { - case REPORT_ID_HIDPP_SHORT: - max_count = HIDPP_REPORT_SHORT_LENGTH - 4; - break; - case REPORT_ID_HIDPP_LONG: - max_count = HIDPP_REPORT_LONG_LENGTH - 4; - break; - case REPORT_ID_HIDPP_VERY_LONG: - max_count = HIDPP_REPORT_VERY_LONG_LENGTH - 4; - break; - default: - return -EINVAL; - } - - if (param_count > max_count) - 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->rap.sub_id == HIDPP_ERROR) || - (answer->fap.feature_index == HIDPP20_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); -} - -/** - * hidpp_prefix_name() prefixes the current given name with "Logitech ". - */ -static void hidpp_prefix_name(char **name, int name_length) -{ -#define PREFIX_LENGTH 9 /* "Logitech " */ - - int new_length; - char *new_name; - - if (name_length > PREFIX_LENGTH && - strncmp(*name, "Logitech ", PREFIX_LENGTH) == 0) - /* The prefix has is already in the name */ - return; - - new_length = PREFIX_LENGTH + name_length; - new_name = kzalloc(new_length, GFP_KERNEL); - if (!new_name) - return; - - snprintf(new_name, new_length, "Logitech %s", *name); - - kfree(*name); - - *name = new_name; -} - -/* -------------------------------------------------------------------------- */ -/* 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); - - /* include the terminating '\0' */ - hidpp_prefix_name(&name, len + 1); - - 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; - - switch (response.report_id) { - case REPORT_ID_HIDPP_VERY_LONG: - count = HIDPP_REPORT_VERY_LONG_LENGTH - 4; - break; - case REPORT_ID_HIDPP_LONG: - count = HIDPP_REPORT_LONG_LENGTH - 4; - break; - case REPORT_ID_HIDPP_SHORT: - count = HIDPP_REPORT_SHORT_LENGTH - 4; - break; - default: - return -EPROTO; - } - - 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; - } - - /* include the terminating '\0' */ - hidpp_prefix_name(&name, __name_length + 1); - - return name; -} - -/* -------------------------------------------------------------------------- */ -/* 0x6010: Touchpad FW items */ -/* -------------------------------------------------------------------------- */ - -#define HIDPP_PAGE_TOUCHPAD_FW_ITEMS 0x6010 - -#define CMD_TOUCHPAD_FW_ITEMS_SET 0x10 - -struct hidpp_touchpad_fw_items { - uint8_t presence; - uint8_t desired_state; - uint8_t state; - uint8_t persistent; -}; - -/** - * send a set state command to the device by reading the current items->state - * field. items is then filled with the current state. - */ -static int hidpp_touchpad_fw_items_set(struct hidpp_device *hidpp, - u8 feature_index, - struct hidpp_touchpad_fw_items *items) -{ - struct hidpp_report response; - int ret; - u8 *params = (u8 *)response.fap.params; - - ret = hidpp_send_fap_command_sync(hidpp, feature_index, - CMD_TOUCHPAD_FW_ITEMS_SET, &items->state, 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; - - items->presence = params[0]; - items->desired_state = params[1]; - items->state = params[2]; - items->persistent = params[3]; - - return 0; -} - -/* -------------------------------------------------------------------------- */ -/* 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) -{ - 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; - - __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 (size < 2) { - hid_err(hdev, "Received HID report of bad size (%d)", - size); - return 1; - } - 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); - return 0; - } else { - if (size < 21) - return 1; - return wtp_mouse_raw_xy_event(hidpp, &data[7]); - } - case REPORT_ID_HIDPP_LONG: - /* size is already checked in hidpp_raw_event. */ - 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 int 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 0; - - if (!wd->x_size) { - ret = wtp_get_config(hidpp); - if (ret) { - hid_err(hdev, "Can not get wtp config: %d\n", ret); - return ret; - } - } - - return hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index, - true, true); -} - -/* ------------------------------------------------------------------------- */ -/* Logitech M560 devices */ -/* ------------------------------------------------------------------------- */ - -/* - * Logitech M560 protocol overview - * - * The Logitech M560 mouse, is designed for windows 8. When the middle and/or - * the sides buttons are pressed, it sends some keyboard keys events - * instead of buttons ones. - * To complicate things further, the middle button keys sequence - * is different from the odd press and the even press. - * - * forward button -> Super_R - * backward button -> Super_L+'d' (press only) - * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only) - * 2nd time: left-click (press only) - * NB: press-only means that when the button is pressed, the - * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated - * together sequentially; instead when the button is released, no event is - * generated ! - * - * With the command - * 100a 3500af03 (where is the mouse id), - * the mouse reacts differently: - * - it never sends a keyboard key event - * - for the three mouse button it sends: - * middle button press 110a 3500af00... - * side 1 button (forward) press 110a 3500b000... - * side 2 button (backward) press 110a 3500ae00... - * middle/side1/side2 button release 110a 35000000... - */ - -static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03}; - -struct m560_private_data { - struct input_dev *input; -}; - -/* how buttons are mapped in the report */ -#define M560_MOUSE_BTN_LEFT 0x01 -#define M560_MOUSE_BTN_RIGHT 0x02 -#define M560_MOUSE_BTN_WHEEL_LEFT 0x08 -#define M560_MOUSE_BTN_WHEEL_RIGHT 0x10 - -#define M560_SUB_ID 0x0a -#define M560_BUTTON_MODE_REGISTER 0x35 - -static int m560_send_config_command(struct hid_device *hdev, bool connected) -{ - struct hidpp_report response; - struct hidpp_device *hidpp_dev; - - hidpp_dev = hid_get_drvdata(hdev); - - if (!connected) - return -ENODEV; - - return hidpp_send_rap_command_sync( - hidpp_dev, - REPORT_ID_HIDPP_SHORT, - M560_SUB_ID, - M560_BUTTON_MODE_REGISTER, - (u8 *)m560_config_parameter, - sizeof(m560_config_parameter), - &response - ); -} - -static int m560_allocate(struct hid_device *hdev) -{ - struct hidpp_device *hidpp = hid_get_drvdata(hdev); - struct m560_private_data *d; - - d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data), - GFP_KERNEL); - if (!d) - return -ENOMEM; - - hidpp->private_data = d; - - return 0; -}; - -static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) -{ - struct hidpp_device *hidpp = hid_get_drvdata(hdev); - struct m560_private_data *mydata = hidpp->private_data; - - /* sanity check */ - if (!mydata || !mydata->input) { - hid_err(hdev, "error in parameter\n"); - return -EINVAL; - } - - if (size < 7) { - hid_err(hdev, "error in report\n"); - return 0; - } - - if (data[0] == REPORT_ID_HIDPP_LONG && - data[2] == M560_SUB_ID && data[6] == 0x00) { - /* - * m560 mouse report for middle, forward and backward button - * - * data[0] = 0x11 - * data[1] = device-id - * data[2] = 0x0a - * data[5] = 0xaf -> middle - * 0xb0 -> forward - * 0xae -> backward - * 0x00 -> release all - * data[6] = 0x00 - */ - - switch (data[5]) { - case 0xaf: - input_report_key(mydata->input, BTN_MIDDLE, 1); - break; - case 0xb0: - input_report_key(mydata->input, BTN_FORWARD, 1); - break; - case 0xae: - input_report_key(mydata->input, BTN_BACK, 1); - break; - case 0x00: - input_report_key(mydata->input, BTN_BACK, 0); - input_report_key(mydata->input, BTN_FORWARD, 0); - input_report_key(mydata->input, BTN_MIDDLE, 0); - break; - default: - hid_err(hdev, "error in report\n"); - return 0; - } - input_sync(mydata->input); - - } else if (data[0] == 0x02) { - /* - * Logitech M560 mouse report - * - * data[0] = type (0x02) - * data[1..2] = buttons - * data[3..5] = xy - * data[6] = wheel - */ - - int v; - - input_report_key(mydata->input, BTN_LEFT, - !!(data[1] & M560_MOUSE_BTN_LEFT)); - input_report_key(mydata->input, BTN_RIGHT, - !!(data[1] & M560_MOUSE_BTN_RIGHT)); - - if (data[1] & M560_MOUSE_BTN_WHEEL_LEFT) - input_report_rel(mydata->input, REL_HWHEEL, -1); - else if (data[1] & M560_MOUSE_BTN_WHEEL_RIGHT) - input_report_rel(mydata->input, REL_HWHEEL, 1); - - v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12); - input_report_rel(mydata->input, REL_X, v); - - v = hid_snto32(hid_field_extract(hdev, data+3, 12, 12), 12); - input_report_rel(mydata->input, REL_Y, v); - - v = hid_snto32(data[6], 8); - input_report_rel(mydata->input, REL_WHEEL, v); - - input_sync(mydata->input); - } - - return 1; -} - -static void m560_populate_input(struct hidpp_device *hidpp, - struct input_dev *input_dev, bool origin_is_hid_core) -{ - struct m560_private_data *mydata = hidpp->private_data; - - mydata->input = input_dev; - - __set_bit(EV_KEY, mydata->input->evbit); - __set_bit(BTN_MIDDLE, mydata->input->keybit); - __set_bit(BTN_RIGHT, mydata->input->keybit); - __set_bit(BTN_LEFT, mydata->input->keybit); - __set_bit(BTN_BACK, mydata->input->keybit); - __set_bit(BTN_FORWARD, mydata->input->keybit); - - __set_bit(EV_REL, mydata->input->evbit); - __set_bit(REL_X, mydata->input->relbit); - __set_bit(REL_Y, mydata->input->relbit); - __set_bit(REL_WHEEL, mydata->input->relbit); - __set_bit(REL_HWHEEL, mydata->input->relbit); -} - -static int m560_input_mapping(struct hid_device *hdev, struct hid_input *hi, - struct hid_field *field, struct hid_usage *usage, - unsigned long **bit, int *max) -{ - return -1; -} - -/* ------------------------------------------------------------------------- */ -/* Logitech K400 devices */ -/* ------------------------------------------------------------------------- */ - -/* - * The Logitech K400 keyboard has an embedded touchpad which is seen - * as a mouse from the OS point of view. There is a hardware shortcut to disable - * tap-to-click but the setting is not remembered accross reset, annoying some - * users. - * - * We can toggle this feature from the host by using the feature 0x6010: - * Touchpad FW items - */ - -struct k400_private_data { - u8 feature_index; -}; - -static int k400_disable_tap_to_click(struct hidpp_device *hidpp) -{ - struct k400_private_data *k400 = hidpp->private_data; - struct hidpp_touchpad_fw_items items = {}; - int ret; - u8 feature_type; - - if (!k400->feature_index) { - ret = hidpp_root_get_feature(hidpp, - HIDPP_PAGE_TOUCHPAD_FW_ITEMS, - &k400->feature_index, &feature_type); - if (ret) - /* means that the device is not powered up */ - return ret; - } - - ret = hidpp_touchpad_fw_items_set(hidpp, k400->feature_index, &items); - if (ret) - return ret; - - return 0; -} - -static int k400_allocate(struct hid_device *hdev) -{ - struct hidpp_device *hidpp = hid_get_drvdata(hdev); - struct k400_private_data *k400; - - k400 = devm_kzalloc(&hdev->dev, sizeof(struct k400_private_data), - GFP_KERNEL); - if (!k400) - return -ENOMEM; - - hidpp->private_data = k400; - - return 0; -}; - -static int k400_connect(struct hid_device *hdev, bool connected) -{ - struct hidpp_device *hidpp = hid_get_drvdata(hdev); - - if (!connected) - return 0; - - if (!disable_tap_to_click) - return 0; - - return k400_disable_tap_to_click(hidpp); -} - -/* ------------------------------------------------------------------------- */ -/* Logitech G920 Driving Force Racing Wheel for Xbox One */ -/* ------------------------------------------------------------------------- */ - -#define HIDPP_PAGE_G920_FORCE_FEEDBACK 0x8123 - -/* Using session ID = 1 */ -#define CMD_G920_FORCE_GET_APERTURE 0x51 -#define CMD_G920_FORCE_SET_APERTURE 0x61 - -struct g920_private_data { - u8 force_feature; - u16 range; -}; - -#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev) - -static ssize_t g920_range_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct hid_device *hid = to_hid_device(dev); - struct hidpp_device *hidpp = hid_get_drvdata(hid); - struct g920_private_data *pdata; - - pdata = hidpp->private_data; - if (!pdata) { - hid_err(hid, "Private driver data not found!\n"); - return -EINVAL; - } - - return scnprintf(buf, PAGE_SIZE, "%u\n", pdata->range); -} - -static ssize_t g920_range_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct hid_device *hid = to_hid_device(dev); - struct hidpp_device *hidpp = hid_get_drvdata(hid); - struct g920_private_data *pdata; - struct hidpp_report response; - u8 params[2]; - int ret; - u16 range = simple_strtoul(buf, NULL, 10); - - pdata = hidpp->private_data; - if (!pdata) { - hid_err(hid, "Private driver data not found!\n"); - return -EINVAL; - } - - if (range < 180) - range = 180; - else if (range > 900) - range = 900; - - params[0] = range >> 8; - params[1] = range & 0x00FF; - - ret = hidpp_send_fap_command_sync(hidpp, pdata->force_feature, - CMD_G920_FORCE_SET_APERTURE, params, 2, &response); - if (ret) - return ret; - - pdata->range = range; - return count; -} - -static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, g920_range_show, g920_range_store); - -static int g920_allocate(struct hid_device *hdev) -{ - struct hidpp_device *hidpp = hid_get_drvdata(hdev); - struct g920_private_data *pdata; - - pdata = devm_kzalloc(&hdev->dev, sizeof(struct g920_private_data), - GFP_KERNEL); - if (!pdata) - return -ENOMEM; - - hidpp->private_data = pdata; - - return 0; -} - -static int g920_get_config(struct hidpp_device *hidpp) -{ - struct g920_private_data *pdata = hidpp->private_data; - struct hidpp_report response; - u8 feature_type; - u8 feature_index; - int ret; - - pdata = hidpp->private_data; - if (!pdata) { - hid_err(hidpp->hid_dev, "Private driver data not found!\n"); - return -EINVAL; - } - - /* Find feature and store for later use */ - ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK, - &feature_index, &feature_type); - if (ret) - return ret; - - pdata->force_feature = feature_index; - - /* Read current Range */ - ret = hidpp_send_fap_command_sync(hidpp, feature_index, - CMD_G920_FORCE_GET_APERTURE, 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; - - pdata->range = get_unaligned_be16(&response.fap.params[0]); - - /* Create sysfs interface */ - ret = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range); - if (ret) - hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d\n", ret); - - return 0; -} - -/* -------------------------------------------------------------------------- */ -/* 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); - else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 && - field->application != HID_GD_MOUSE) - return m560_input_mapping(hdev, hi, field, usage, bit, max); - - return 0; -} - -static int hidpp_input_mapped(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); - - /* Ensure that Logitech G920 is not given a default fuzz/flat value */ - if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { - if (usage->type == EV_ABS && (usage->code == ABS_X || - usage->code == ABS_Y || usage->code == ABS_Z || - usage->code == ABS_RZ)) { - field->application = HID_GD_MULTIAXIS; - } - } - - 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); - else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) - m560_populate_input(hidpp, input, origin_is_hid_core); -} - -static int 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); - - return 0; -} - -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 - * previously 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_CONNECT_EVENTS) && - (schedule_work(&hidpp->work) == 0)) - dbg_hid("%s: connect event already queued\n", __func__); - return 1; - } - - 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); - int ret = 0; - - /* Generic HID++ processing. */ - switch (data[0]) { - case REPORT_ID_HIDPP_VERY_LONG: - if (size != HIDPP_REPORT_VERY_LONG_LENGTH) { - hid_err(hdev, "received hid++ report of bad size (%d)", - size); - return 1; - } - ret = hidpp_raw_hidpp_event(hidpp, data, size); - break; - case REPORT_ID_HIDPP_LONG: - if (size != HIDPP_REPORT_LONG_LENGTH) { - hid_err(hdev, "received hid++ report of bad size (%d)", - size); - return 1; - } - ret = hidpp_raw_hidpp_event(hidpp, data, size); - break; - case REPORT_ID_HIDPP_SHORT: - if (size != HIDPP_REPORT_SHORT_LENGTH) { - hid_err(hdev, "received hid++ report of bad size (%d)", - size); - return 1; - } - ret = hidpp_raw_hidpp_event(hidpp, data, size); - break; - } - - /* If no report is available for further processing, skip calling - * raw_event of subclasses. */ - if (ret != 0) - return ret; - - if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) - return wtp_raw_event(hdev, data, size); - else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) - return m560_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 { - dbg_hid("HID++: Got name: %s\n", name); - 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); - struct hidpp_device *hidpp = hid_get_drvdata(hdev); - - 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 = hidpp->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) { - ret = wtp_connect(hdev, connected); - if (ret) - return; - } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) { - ret = m560_send_config_command(hdev, connected); - if (ret) - return; - } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) { - ret = k400_connect(hdev, connected); - if (ret) - return; - } - - if (!connected || hidpp->delayed_input) - return; - - /* the device is already connected, we can ask for its name and - * protocol */ - if (!hidpp->protocol_major) { - ret = !hidpp_is_connected(hidpp); - if (ret) { - hid_err(hdev, "Can not get the protocol version.\n"); - return; - } - hid_info(hdev, "HID++ %u.%u device connected.\n", - hidpp->protocol_major, hidpp->protocol_minor); - } - - if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)) - /* if HID created the input nodes for us, we can stop now */ - return; - - if (!hidpp->name || hidpp->name == hdev->name) { - name = hidpp_get_device_name(hidpp); - if (!name) { - hid_err(hdev, - "unable to retrieve the name of the device"); - return; - } - - devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name); - kfree(name); - if (!devm_name) - return; - - hidpp->name = devm_name; - } - - input = hidpp_allocate_input(hdev); - if (!input) { - hid_err(hdev, "cannot allocate new input device: %d\n", ret); - return; - } - - 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; - hidpp->name = hdev->name; - hid_set_drvdata(hdev, hidpp); - - hidpp->quirks = id->driver_data; - - if (disable_raw_mode) { - hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP; - hidpp->quirks &= ~HIDPP_QUIRK_CONNECT_EVENTS; - hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT; - } - - if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { - ret = wtp_allocate(hdev, id); - if (ret) - goto allocate_fail; - } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) { - ret = m560_allocate(hdev); - if (ret) - goto allocate_fail; - } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) { - ret = k400_allocate(hdev); - if (ret) - goto allocate_fail; - } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { - ret = g920_allocate(hdev); - if (ret) - goto 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; - } - - if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) - connect_mask &= ~HID_CONNECT_HIDINPUT; - - if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { - ret = hid_hw_start(hdev, connect_mask); - if (ret) { - hid_err(hdev, "hw start failed\n"); - goto hid_hw_start_fail; - } - ret = hid_hw_open(hdev); - if (ret < 0) { - dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n", - __func__, ret); - hid_hw_stop(hdev); - goto hid_hw_start_fail; - } - } - - - /* Allow incoming packets */ - hid_device_io_start(hdev); - - connected = hidpp_is_connected(hidpp); - if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) { - if (!connected) { - ret = -ENODEV; - hid_err(hdev, "Device not connected"); - goto hid_hw_open_failed; - } - - 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_hw_open_failed; - } else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) { - ret = g920_get_config(hidpp); - if (ret) - goto hid_hw_open_failed; - } - - /* Block incoming packets */ - hid_device_io_stop(hdev); - - if (!(hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) { - 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_CONNECT_EVENTS) { - /* Allow incoming packets */ - hid_device_io_start(hdev); - - hidpp_connect_event(hidpp); - } - - return ret; - -hid_hw_open_failed: - hid_device_io_stop(hdev); - if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { - device_remove_file(&hdev->dev, &dev_attr_range); - hid_hw_close(hdev); - hid_hw_stop(hdev); - } -hid_hw_start_fail: -hid_parse_fail: - cancel_work_sync(&hidpp->work); - mutex_destroy(&hidpp->send_mutex); -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); - - if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { - device_remove_file(&hdev->dev, &dev_attr_range); - hid_hw_close(hdev); - } - hid_hw_stop(hdev); - cancel_work_sync(&hidpp->work); - mutex_destroy(&hidpp->send_mutex); -} - -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 }, - { /* Mouse logitech M560 */ - HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, - USB_VENDOR_ID_LOGITECH, 0x402d), - .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 }, - { /* Keyboard logitech K400 */ - HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, - USB_VENDOR_ID_LOGITECH, 0x4024), - .driver_data = HIDPP_QUIRK_CONNECT_EVENTS | HIDPP_QUIRK_CLASS_K400 }, - - { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, - USB_VENDOR_ID_LOGITECH, HID_ANY_ID)}, - - { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL), - .driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS}, - {} -}; - -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, - .input_mapped = hidpp_input_mapped, -}; - -module_hid_driver(hidpp_driver); -- 2.5.0 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/