mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-23 20:53:53 +08:00
c39e3d5fc9
Now that the receiver forwards the connect/disconnect events, we can know when the device is available to communicate with us. When it is ready, we can for instance retrieve its full name, which guarantee that we always have the same name for the DJ device (the DJ name is somewhat shorter than the HID++ name). This mechanism is mandatory for the touchpads line, which has the min/max information stored in the device. This information can only be retrieved when the device is connected. So we can not populate the input device until we are sure that the device is connected. This patch creates a new input device for such devices. However, this input is not bound to hid directly, so the various drivers which wants to use it are required to process completely the incoming reports in .raw_event(). Note that the patch in itself just adds the bits for the next ones, and this feature is disabled by default. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Tested-by: Andrew de los Reyes <adlr@chromium.org> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
1059 lines
27 KiB
C
1059 lines
27 KiB
C
/*
|
|
* HIDPP protocol for Logitech Unifying receivers
|
|
*
|
|
* Copyright (c) 2011 Logitech (c)
|
|
* Copyright (c) 2012-2013 Google (c)
|
|
* Copyright (c) 2013-2014 Red Hat Inc.
|
|
*/
|
|
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; version 2 of the License.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/hid.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/kfifo.h>
|
|
#include <linux/input/mt.h>
|
|
#include <asm/unaligned.h>
|
|
#include "hid-ids.h"
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
|
|
MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>");
|
|
|
|
#define REPORT_ID_HIDPP_SHORT 0x10
|
|
#define REPORT_ID_HIDPP_LONG 0x11
|
|
|
|
#define HIDPP_REPORT_SHORT_LENGTH 7
|
|
#define HIDPP_REPORT_LONG_LENGTH 20
|
|
|
|
#define HIDPP_QUIRK_CLASS_WTP BIT(0)
|
|
|
|
/* bits 1..20 are reserved for classes */
|
|
#define HIDPP_QUIRK_DELAYED_INIT BIT(21)
|
|
|
|
/*
|
|
* There are two hidpp protocols in use, the first version hidpp10 is known
|
|
* as register access protocol or RAP, the second version hidpp20 is known as
|
|
* feature access protocol or FAP
|
|
*
|
|
* Most older devices (including the Unifying usb receiver) use the RAP protocol
|
|
* where as most newer devices use the FAP protocol. Both protocols are
|
|
* compatible with the underlying transport, which could be usb, Unifiying, or
|
|
* bluetooth. The message lengths are defined by the hid vendor specific report
|
|
* descriptor for the HIDPP_SHORT report type (total message lenth 7 bytes) and
|
|
* the HIDPP_LONG report type (total message length 20 bytes)
|
|
*
|
|
* The RAP protocol uses both report types, whereas the FAP only uses HIDPP_LONG
|
|
* messages. The Unifying receiver itself responds to RAP messages (device index
|
|
* is 0xFF for the receiver), and all messages (short or long) with a device
|
|
* index between 1 and 6 are passed untouched to the corresponding paired
|
|
* Unifying device.
|
|
*
|
|
* The paired device can be RAP or FAP, it will receive the message untouched
|
|
* from the Unifiying receiver.
|
|
*/
|
|
|
|
struct fap {
|
|
u8 feature_index;
|
|
u8 funcindex_clientid;
|
|
u8 params[HIDPP_REPORT_LONG_LENGTH - 4U];
|
|
};
|
|
|
|
struct rap {
|
|
u8 sub_id;
|
|
u8 reg_address;
|
|
u8 params[HIDPP_REPORT_LONG_LENGTH - 4U];
|
|
};
|
|
|
|
struct hidpp_report {
|
|
u8 report_id;
|
|
u8 device_index;
|
|
union {
|
|
struct fap fap;
|
|
struct rap rap;
|
|
u8 rawbytes[sizeof(struct fap)];
|
|
};
|
|
} __packed;
|
|
|
|
struct hidpp_device {
|
|
struct hid_device *hid_dev;
|
|
struct mutex send_mutex;
|
|
void *send_receive_buf;
|
|
wait_queue_head_t wait;
|
|
bool answer_available;
|
|
u8 protocol_major;
|
|
u8 protocol_minor;
|
|
|
|
void *private_data;
|
|
|
|
struct work_struct work;
|
|
struct kfifo delayed_work_fifo;
|
|
atomic_t connected;
|
|
struct input_dev *delayed_input;
|
|
|
|
unsigned long quirks;
|
|
};
|
|
|
|
|
|
#define HIDPP_ERROR 0x8f
|
|
#define HIDPP_ERROR_SUCCESS 0x00
|
|
#define HIDPP_ERROR_INVALID_SUBID 0x01
|
|
#define HIDPP_ERROR_INVALID_ADRESS 0x02
|
|
#define HIDPP_ERROR_INVALID_VALUE 0x03
|
|
#define HIDPP_ERROR_CONNECT_FAIL 0x04
|
|
#define HIDPP_ERROR_TOO_MANY_DEVICES 0x05
|
|
#define HIDPP_ERROR_ALREADY_EXISTS 0x06
|
|
#define HIDPP_ERROR_BUSY 0x07
|
|
#define HIDPP_ERROR_UNKNOWN_DEVICE 0x08
|
|
#define HIDPP_ERROR_RESOURCE_ERROR 0x09
|
|
#define HIDPP_ERROR_REQUEST_UNAVAILABLE 0x0a
|
|
#define HIDPP_ERROR_INVALID_PARAM_VALUE 0x0b
|
|
#define HIDPP_ERROR_WRONG_PIN_CODE 0x0c
|
|
|
|
static void hidpp_connect_event(struct hidpp_device *hidpp_dev);
|
|
|
|
static int __hidpp_send_report(struct hid_device *hdev,
|
|
struct hidpp_report *hidpp_report)
|
|
{
|
|
int fields_count, ret;
|
|
|
|
switch (hidpp_report->report_id) {
|
|
case REPORT_ID_HIDPP_SHORT:
|
|
fields_count = HIDPP_REPORT_SHORT_LENGTH;
|
|
break;
|
|
case REPORT_ID_HIDPP_LONG:
|
|
fields_count = HIDPP_REPORT_LONG_LENGTH;
|
|
break;
|
|
default:
|
|
return -ENODEV;
|
|
}
|
|
|
|
/*
|
|
* set the device_index as the receiver, it will be overwritten by
|
|
* hid_hw_request if needed
|
|
*/
|
|
hidpp_report->device_index = 0xff;
|
|
|
|
ret = hid_hw_raw_request(hdev, hidpp_report->report_id,
|
|
(u8 *)hidpp_report, fields_count, HID_OUTPUT_REPORT,
|
|
HID_REQ_SET_REPORT);
|
|
|
|
return ret == fields_count ? 0 : -1;
|
|
}
|
|
|
|
static int hidpp_send_message_sync(struct hidpp_device *hidpp,
|
|
struct hidpp_report *message,
|
|
struct hidpp_report *response)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&hidpp->send_mutex);
|
|
|
|
hidpp->send_receive_buf = response;
|
|
hidpp->answer_available = false;
|
|
|
|
/*
|
|
* So that we can later validate the answer when it arrives
|
|
* in hidpp_raw_event
|
|
*/
|
|
*response = *message;
|
|
|
|
ret = __hidpp_send_report(hidpp->hid_dev, message);
|
|
|
|
if (ret) {
|
|
dbg_hid("__hidpp_send_report returned err: %d\n", ret);
|
|
memset(response, 0, sizeof(struct hidpp_report));
|
|
goto exit;
|
|
}
|
|
|
|
if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
|
|
5*HZ)) {
|
|
dbg_hid("%s:timeout waiting for response\n", __func__);
|
|
memset(response, 0, sizeof(struct hidpp_report));
|
|
ret = -ETIMEDOUT;
|
|
}
|
|
|
|
if (response->report_id == REPORT_ID_HIDPP_SHORT &&
|
|
response->fap.feature_index == HIDPP_ERROR) {
|
|
ret = response->fap.params[1];
|
|
dbg_hid("__hidpp_send_report got hidpp error %02X\n", ret);
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
mutex_unlock(&hidpp->send_mutex);
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
|
|
u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
|
|
struct hidpp_report *response)
|
|
{
|
|
struct hidpp_report *message = kzalloc(sizeof(struct hidpp_report),
|
|
GFP_KERNEL);
|
|
int ret;
|
|
|
|
if (param_count > sizeof(message->fap.params))
|
|
return -EINVAL;
|
|
|
|
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 = kzalloc(sizeof(struct hidpp_report),
|
|
GFP_KERNEL);
|
|
int ret;
|
|
|
|
if ((report_id != REPORT_ID_HIDPP_SHORT) &&
|
|
(report_id != REPORT_ID_HIDPP_LONG))
|
|
return -EINVAL;
|
|
|
|
if (param_count > sizeof(message->rap.params))
|
|
return -EINVAL;
|
|
|
|
message->report_id = report_id;
|
|
message->rap.sub_id = sub_id;
|
|
message->rap.reg_address = reg_address;
|
|
memcpy(&message->rap.params, params, param_count);
|
|
|
|
ret = hidpp_send_message_sync(hidpp_dev, message, response);
|
|
kfree(message);
|
|
return ret;
|
|
}
|
|
|
|
static void delayed_work_cb(struct work_struct *work)
|
|
{
|
|
struct hidpp_device *hidpp = container_of(work, struct hidpp_device,
|
|
work);
|
|
hidpp_connect_event(hidpp);
|
|
}
|
|
|
|
static inline bool hidpp_match_answer(struct hidpp_report *question,
|
|
struct hidpp_report *answer)
|
|
{
|
|
return (answer->fap.feature_index == question->fap.feature_index) &&
|
|
(answer->fap.funcindex_clientid == question->fap.funcindex_clientid);
|
|
}
|
|
|
|
static inline bool hidpp_match_error(struct hidpp_report *question,
|
|
struct hidpp_report *answer)
|
|
{
|
|
return (answer->fap.feature_index == HIDPP_ERROR) &&
|
|
(answer->fap.funcindex_clientid == question->fap.feature_index) &&
|
|
(answer->fap.params[0] == question->fap.funcindex_clientid);
|
|
}
|
|
|
|
static inline bool hidpp_report_is_connect_event(struct hidpp_report *report)
|
|
{
|
|
return (report->report_id == REPORT_ID_HIDPP_SHORT) &&
|
|
(report->rap.sub_id == 0x41);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* HIDP++ 1.0 commands */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
#define HIDPP_SET_REGISTER 0x80
|
|
#define HIDPP_GET_REGISTER 0x81
|
|
#define HIDPP_SET_LONG_REGISTER 0x82
|
|
#define HIDPP_GET_LONG_REGISTER 0x83
|
|
|
|
#define HIDPP_REG_PAIRING_INFORMATION 0xB5
|
|
#define DEVICE_NAME 0x40
|
|
|
|
static char *hidpp_get_unifying_name(struct hidpp_device *hidpp_dev)
|
|
{
|
|
struct hidpp_report response;
|
|
int ret;
|
|
/* hid-logitech-dj is in charge of setting the right device index */
|
|
u8 params[1] = { DEVICE_NAME };
|
|
char *name;
|
|
int len;
|
|
|
|
ret = hidpp_send_rap_command_sync(hidpp_dev,
|
|
REPORT_ID_HIDPP_SHORT,
|
|
HIDPP_GET_LONG_REGISTER,
|
|
HIDPP_REG_PAIRING_INFORMATION,
|
|
params, 1, &response);
|
|
if (ret)
|
|
return NULL;
|
|
|
|
len = response.rap.params[1];
|
|
|
|
name = kzalloc(len + 1, GFP_KERNEL);
|
|
if (!name)
|
|
return NULL;
|
|
|
|
memcpy(name, &response.rap.params[2], len);
|
|
return name;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* 0x0000: Root */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
#define HIDPP_PAGE_ROOT 0x0000
|
|
#define HIDPP_PAGE_ROOT_IDX 0x00
|
|
|
|
#define CMD_ROOT_GET_FEATURE 0x01
|
|
#define CMD_ROOT_GET_PROTOCOL_VERSION 0x11
|
|
|
|
static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
|
|
u8 *feature_index, u8 *feature_type)
|
|
{
|
|
struct hidpp_report response;
|
|
int ret;
|
|
u8 params[2] = { feature >> 8, feature & 0x00FF };
|
|
|
|
ret = hidpp_send_fap_command_sync(hidpp,
|
|
HIDPP_PAGE_ROOT_IDX,
|
|
CMD_ROOT_GET_FEATURE,
|
|
params, 2, &response);
|
|
if (ret)
|
|
return ret;
|
|
|
|
*feature_index = response.fap.params[0];
|
|
*feature_type = response.fap.params[1];
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
|
|
{
|
|
struct hidpp_report response;
|
|
int ret;
|
|
|
|
ret = hidpp_send_fap_command_sync(hidpp,
|
|
HIDPP_PAGE_ROOT_IDX,
|
|
CMD_ROOT_GET_PROTOCOL_VERSION,
|
|
NULL, 0, &response);
|
|
|
|
if (ret == 1) {
|
|
hidpp->protocol_major = 1;
|
|
hidpp->protocol_minor = 0;
|
|
return 0;
|
|
}
|
|
|
|
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)
|
|
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)
|
|
return -ret;
|
|
|
|
if (response.report_id == REPORT_ID_HIDPP_LONG)
|
|
count = HIDPP_REPORT_LONG_LENGTH - 4;
|
|
else
|
|
count = HIDPP_REPORT_SHORT_LENGTH - 4;
|
|
|
|
if (len_buf < count)
|
|
count = len_buf;
|
|
|
|
for (i = 0; i < count; i++)
|
|
device_name[i] = response.fap.params[i];
|
|
|
|
return count;
|
|
}
|
|
|
|
static char *hidpp_get_device_name(struct hidpp_device *hidpp, u8 *name_length)
|
|
{
|
|
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)
|
|
goto out_err;
|
|
|
|
ret = hidpp_devicenametype_get_count(hidpp, feature_index,
|
|
&__name_length);
|
|
if (ret)
|
|
goto out_err;
|
|
|
|
name = kzalloc(__name_length + 1, GFP_KERNEL);
|
|
if (!name)
|
|
goto out_err;
|
|
|
|
*name_length = __name_length + 1;
|
|
while (index < __name_length)
|
|
index += hidpp_devicenametype_get_device_name(hidpp,
|
|
feature_index, index, name + index,
|
|
__name_length - index);
|
|
|
|
return name;
|
|
|
|
out_err:
|
|
*name_length = 0;
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* 0x6100: TouchPadRawXY */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
#define HIDPP_PAGE_TOUCHPAD_RAW_XY 0x6100
|
|
|
|
#define CMD_TOUCHPAD_GET_RAW_INFO 0x01
|
|
|
|
#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)
|
|
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;
|
|
}
|
|
|
|
/* ************************************************************************** */
|
|
/* */
|
|
/* Device Support */
|
|
/* */
|
|
/* ************************************************************************** */
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Touchpad HID++ devices */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
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);
|
|
|
|
__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)
|
|
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;
|
|
|
|
if (!wd || !wd->input || (data[0] != 0x02) || size < 21)
|
|
return 1;
|
|
|
|
return wtp_mouse_raw_xy_event(hidpp, &data[7]);
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
};
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Generic HID++ devices */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|
struct hid_field *field, struct hid_usage *usage,
|
|
unsigned long **bit, int *max)
|
|
{
|
|
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
|
|
|
|
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
|
|
return wtp_input_mapping(hdev, hi, field, usage, bit, max);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void hidpp_populate_input(struct hidpp_device *hidpp,
|
|
struct input_dev *input, bool origin_is_hid_core)
|
|
{
|
|
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
|
|
wtp_populate_input(hidpp, input, origin_is_hid_core);
|
|
}
|
|
|
|
static void hidpp_input_configured(struct hid_device *hdev,
|
|
struct hid_input *hidinput)
|
|
{
|
|
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
|
|
struct input_dev *input = hidinput->input;
|
|
|
|
hidpp_populate_input(hidpp, input, true);
|
|
}
|
|
|
|
static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
|
|
int size)
|
|
{
|
|
struct hidpp_report *question = hidpp->send_receive_buf;
|
|
struct hidpp_report *answer = hidpp->send_receive_buf;
|
|
struct hidpp_report *report = (struct hidpp_report *)data;
|
|
|
|
/*
|
|
* If the mutex is locked then we have a pending answer from a
|
|
* previoulsly sent command
|
|
*/
|
|
if (unlikely(mutex_is_locked(&hidpp->send_mutex))) {
|
|
/*
|
|
* Check for a correct hidpp20 answer or the corresponding
|
|
* error
|
|
*/
|
|
if (hidpp_match_answer(question, report) ||
|
|
hidpp_match_error(question, report)) {
|
|
*answer = *report;
|
|
hidpp->answer_available = true;
|
|
wake_up(&hidpp->wait);
|
|
/*
|
|
* This was an answer to a command that this driver sent
|
|
* We return 1 to hid-core to avoid forwarding the
|
|
* command upstream as it has been treated by the driver
|
|
*/
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (unlikely(hidpp_report_is_connect_event(report))) {
|
|
atomic_set(&hidpp->connected,
|
|
!(report->rap.params[0] & (1 << 6)));
|
|
if ((hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) &&
|
|
(schedule_work(&hidpp->work) == 0))
|
|
dbg_hid("%s: connect event already queued\n", __func__);
|
|
return 1;
|
|
}
|
|
|
|
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
|
|
return wtp_raw_event(hidpp->hid_dev, data, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
|
|
u8 *data, int size)
|
|
{
|
|
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
|
|
|
|
switch (data[0]) {
|
|
case REPORT_ID_HIDPP_LONG:
|
|
if (size != HIDPP_REPORT_LONG_LENGTH) {
|
|
hid_err(hdev, "received hid++ report of bad size (%d)",
|
|
size);
|
|
return 1;
|
|
}
|
|
return hidpp_raw_hidpp_event(hidpp, data, size);
|
|
case REPORT_ID_HIDPP_SHORT:
|
|
if (size != HIDPP_REPORT_SHORT_LENGTH) {
|
|
hid_err(hdev, "received hid++ report of bad size (%d)",
|
|
size);
|
|
return 1;
|
|
}
|
|
return hidpp_raw_hidpp_event(hidpp, data, size);
|
|
}
|
|
|
|
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
|
|
return wtp_raw_event(hdev, data, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying)
|
|
{
|
|
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
|
|
char *name;
|
|
u8 name_length;
|
|
|
|
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, &name_length);
|
|
|
|
if (!name)
|
|
hid_err(hdev, "unable to retrieve the name of the device");
|
|
else
|
|
snprintf(hdev->name, sizeof(hdev->name), "%s", name);
|
|
|
|
kfree(name);
|
|
}
|
|
|
|
static int hidpp_input_open(struct input_dev *dev)
|
|
{
|
|
struct hid_device *hid = input_get_drvdata(dev);
|
|
|
|
return hid_hw_open(hid);
|
|
}
|
|
|
|
static void hidpp_input_close(struct input_dev *dev)
|
|
{
|
|
struct hid_device *hid = input_get_drvdata(dev);
|
|
|
|
hid_hw_close(hid);
|
|
}
|
|
|
|
static struct input_dev *hidpp_allocate_input(struct hid_device *hdev)
|
|
{
|
|
struct input_dev *input_dev = devm_input_allocate_device(&hdev->dev);
|
|
|
|
if (!input_dev)
|
|
return NULL;
|
|
|
|
input_set_drvdata(input_dev, hdev);
|
|
input_dev->open = hidpp_input_open;
|
|
input_dev->close = hidpp_input_close;
|
|
|
|
input_dev->name = hdev->name;
|
|
input_dev->phys = hdev->phys;
|
|
input_dev->uniq = hdev->uniq;
|
|
input_dev->id.bustype = hdev->bus;
|
|
input_dev->id.vendor = hdev->vendor;
|
|
input_dev->id.product = hdev->product;
|
|
input_dev->id.version = hdev->version;
|
|
input_dev->dev.parent = &hdev->dev;
|
|
|
|
return input_dev;
|
|
}
|
|
|
|
static void hidpp_connect_event(struct hidpp_device *hidpp)
|
|
{
|
|
struct hid_device *hdev = hidpp->hid_dev;
|
|
int ret = 0;
|
|
bool connected = atomic_read(&hidpp->connected);
|
|
struct input_dev *input;
|
|
char *name, *devm_name;
|
|
u8 name_length;
|
|
|
|
if (!connected || hidpp->delayed_input)
|
|
return;
|
|
|
|
if (!hidpp->protocol_major) {
|
|
ret = !hidpp_is_connected(hidpp);
|
|
if (ret) {
|
|
hid_err(hdev, "Can not get the protocol version.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* the device is already connected, we can ask for its name and
|
|
* protocol */
|
|
hid_info(hdev, "HID++ %u.%u device connected.\n",
|
|
hidpp->protocol_major, hidpp->protocol_minor);
|
|
|
|
input = hidpp_allocate_input(hdev);
|
|
if (!input) {
|
|
hid_err(hdev, "cannot allocate new input device: %d\n", ret);
|
|
return;
|
|
}
|
|
|
|
name = hidpp_get_device_name(hidpp, &name_length);
|
|
if (!name) {
|
|
hid_err(hdev, "unable to retrieve the name of the device");
|
|
} else {
|
|
devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name);
|
|
if (devm_name)
|
|
input->name = devm_name;
|
|
kfree(name);
|
|
}
|
|
|
|
hidpp_populate_input(hidpp, input, false);
|
|
|
|
ret = input_register_device(input);
|
|
if (ret)
|
|
input_free_device(input);
|
|
|
|
hidpp->delayed_input = input;
|
|
}
|
|
|
|
static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|
{
|
|
struct hidpp_device *hidpp;
|
|
int ret;
|
|
bool connected;
|
|
unsigned int connect_mask = HID_CONNECT_DEFAULT;
|
|
|
|
hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device),
|
|
GFP_KERNEL);
|
|
if (!hidpp)
|
|
return -ENOMEM;
|
|
|
|
hidpp->hid_dev = hdev;
|
|
hid_set_drvdata(hdev, hidpp);
|
|
|
|
hidpp->quirks = id->driver_data;
|
|
|
|
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
|
|
ret = wtp_allocate(hdev, id);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
INIT_WORK(&hidpp->work, delayed_work_cb);
|
|
mutex_init(&hidpp->send_mutex);
|
|
init_waitqueue_head(&hidpp->wait);
|
|
|
|
ret = hid_parse(hdev);
|
|
if (ret) {
|
|
hid_err(hdev, "%s:parse failed\n", __func__);
|
|
goto hid_parse_fail;
|
|
}
|
|
|
|
/* Allow incoming packets */
|
|
hid_device_io_start(hdev);
|
|
|
|
connected = hidpp_is_connected(hidpp);
|
|
if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) {
|
|
if (!connected) {
|
|
hid_err(hdev, "Device not connected");
|
|
goto hid_parse_fail;
|
|
}
|
|
|
|
hid_info(hdev, "HID++ %u.%u device connected.\n",
|
|
hidpp->protocol_major, hidpp->protocol_minor);
|
|
}
|
|
|
|
hidpp_overwrite_name(hdev, id->group == HID_GROUP_LOGITECH_DJ_DEVICE);
|
|
atomic_set(&hidpp->connected, connected);
|
|
|
|
if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
|
|
ret = wtp_get_config(hidpp);
|
|
if (ret)
|
|
goto hid_parse_fail;
|
|
}
|
|
|
|
/* Block incoming packets */
|
|
hid_device_io_stop(hdev);
|
|
|
|
if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
|
|
connect_mask &= ~HID_CONNECT_HIDINPUT;
|
|
|
|
ret = hid_hw_start(hdev, connect_mask);
|
|
if (ret) {
|
|
hid_err(hdev, "%s:hid_hw_start returned error\n", __func__);
|
|
goto hid_hw_start_fail;
|
|
}
|
|
|
|
if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) {
|
|
/* Allow incoming packets */
|
|
hid_device_io_start(hdev);
|
|
|
|
hidpp_connect_event(hidpp);
|
|
}
|
|
|
|
return ret;
|
|
|
|
hid_hw_start_fail:
|
|
hid_parse_fail:
|
|
cancel_work_sync(&hidpp->work);
|
|
mutex_destroy(&hidpp->send_mutex);
|
|
hid_set_drvdata(hdev, NULL);
|
|
return ret;
|
|
}
|
|
|
|
static void hidpp_remove(struct hid_device *hdev)
|
|
{
|
|
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
|
|
|
|
cancel_work_sync(&hidpp->work);
|
|
mutex_destroy(&hidpp->send_mutex);
|
|
hid_hw_stop(hdev);
|
|
}
|
|
|
|
static const struct hid_device_id hidpp_devices[] = {
|
|
{ /* wireless touchpad T651 */
|
|
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
|
|
USB_DEVICE_ID_LOGITECH_T651),
|
|
.driver_data = HIDPP_QUIRK_CLASS_WTP },
|
|
|
|
{ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
|
|
USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},
|
|
{}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(hid, hidpp_devices);
|
|
|
|
static struct hid_driver hidpp_driver = {
|
|
.name = "logitech-hidpp-device",
|
|
.id_table = hidpp_devices,
|
|
.probe = hidpp_probe,
|
|
.remove = hidpp_remove,
|
|
.raw_event = hidpp_raw_event,
|
|
.input_configured = hidpp_input_configured,
|
|
.input_mapping = hidpp_input_mapping,
|
|
};
|
|
|
|
module_hid_driver(hidpp_driver);
|