mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-28 00:35:00 -04:00
Merge branch 'for-7.3/logitech' into for-linus
- HID++ 2.0 repogrammable button support (Elliot Douglas) - Bolt receiver support for HID++ devices (Erik Håkansson)
This commit is contained in:
@@ -121,6 +121,7 @@ enum recvr_type {
|
||||
recvr_type_27mhz,
|
||||
recvr_type_bluetooth,
|
||||
recvr_type_dinovo,
|
||||
recvr_type_bolt,
|
||||
};
|
||||
|
||||
struct dj_report {
|
||||
@@ -1156,6 +1157,10 @@ static void logi_hidpp_recv_queue_notif(struct hid_device *hdev,
|
||||
logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem);
|
||||
workitem.reports_supported |= STD_KEYBOARD;
|
||||
break;
|
||||
case 0x10:
|
||||
device_type = "Bolt";
|
||||
logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem);
|
||||
break;
|
||||
}
|
||||
|
||||
/* custom receiver device (eg. powerplay) */
|
||||
@@ -1745,6 +1750,24 @@ static int logi_dj_hidpp_event(struct hid_device *hdev,
|
||||
|
||||
dj_dev = djrcv_dev->paired_dj_devices[device_index];
|
||||
|
||||
/*
|
||||
* Bolt receivers send explicit unpair notifications as HID++ events;
|
||||
* queue device removal when we receive one.
|
||||
*/
|
||||
if (djrcv_dev->type == recvr_type_bolt &&
|
||||
hidpp_report->report_id == REPORT_ID_HIDPP_SHORT &&
|
||||
hidpp_report->sub_id == REPORT_TYPE_NOTIF_DEVICE_UNPAIRED) {
|
||||
struct dj_workitem workitem = {
|
||||
.device_index = device_index,
|
||||
.type = WORKITEM_TYPE_UNPAIRED,
|
||||
};
|
||||
|
||||
kfifo_in(&djrcv_dev->notif_fifo, &workitem, sizeof(workitem));
|
||||
schedule_work(&djrcv_dev->work);
|
||||
spin_unlock_irqrestore(&djrcv_dev->lock, flags);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* With 27 MHz receivers, we do not get an explicit unpair event,
|
||||
* remove the old device if the user has paired a *different* device.
|
||||
@@ -1884,6 +1907,9 @@ static int logi_dj_probe(struct hid_device *hdev,
|
||||
* treat these as logitech-dj interfaces then this causes input events
|
||||
* reported through this extra interface to not be reported correctly.
|
||||
* To avoid this, we treat these as generic-hid devices.
|
||||
*
|
||||
* Bolt receivers only use LOGITECH_DJ_INTERFACE_NUMBER for receiver
|
||||
* reporting. Treat all other Bolt interfaces as generic-hid devices.
|
||||
*/
|
||||
switch (id->driver_data) {
|
||||
case recvr_type_dj: no_dj_interfaces = 3; break;
|
||||
@@ -1897,10 +1923,20 @@ static int logi_dj_probe(struct hid_device *hdev,
|
||||
}
|
||||
if (hid_is_usb(hdev)) {
|
||||
intf = to_usb_interface(hdev->dev.parent);
|
||||
if (intf && intf->altsetting->desc.bInterfaceNumber >=
|
||||
no_dj_interfaces) {
|
||||
hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
|
||||
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (intf) {
|
||||
bool generic_hid_interface;
|
||||
|
||||
if (id->driver_data == recvr_type_bolt)
|
||||
generic_hid_interface =
|
||||
intf->altsetting->desc.bInterfaceNumber !=
|
||||
LOGITECH_DJ_INTERFACE_NUMBER;
|
||||
else
|
||||
generic_hid_interface =
|
||||
intf->altsetting->desc.bInterfaceNumber >= no_dj_interfaces;
|
||||
if (generic_hid_interface) {
|
||||
hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
|
||||
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2103,6 +2139,10 @@ static const struct hid_device_id logi_dj_receivers[] = {
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3),
|
||||
.driver_data = recvr_type_gaming_hidpp_ls_1_3},
|
||||
{ /* Logitech Bolt receiver (0xc548) */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER),
|
||||
.driver_data = recvr_type_bolt},
|
||||
{ /* Logitech lightspeed receiver (0xc54d) */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4),
|
||||
|
||||
@@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
|
||||
#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(28)
|
||||
#define HIDPP_QUIRK_WIRELESS_STATUS BIT(29)
|
||||
#define HIDPP_QUIRK_RESET_HI_RES_SCROLL BIT(30)
|
||||
#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS BIT(31)
|
||||
|
||||
/* These are just aliases for now */
|
||||
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
|
||||
@@ -178,6 +179,8 @@ struct hidpp_scroll_counter {
|
||||
unsigned long long last_time;
|
||||
};
|
||||
|
||||
struct hidpp_reprog_control_mapping;
|
||||
|
||||
struct hidpp_device {
|
||||
struct hid_device *hid_dev;
|
||||
struct input_dev *input;
|
||||
@@ -205,6 +208,8 @@ struct hidpp_device {
|
||||
struct hidpp_scroll_counter vertical_wheel_counter;
|
||||
|
||||
u8 wireless_feature_index;
|
||||
u8 reprog_controls_feature_index;
|
||||
const struct hidpp_reprog_control_mapping *reprog_controls;
|
||||
|
||||
int hires_wheel_multiplier;
|
||||
u8 hires_wheel_feature_index;
|
||||
@@ -983,7 +988,8 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
|
||||
}
|
||||
|
||||
/* the device might not be connected */
|
||||
if (ret == HIDPP_ERROR_RESOURCE_ERROR ||
|
||||
if (ret == HIDPP_ERROR_CONNECT_FAIL ||
|
||||
ret == HIDPP_ERROR_RESOURCE_ERROR ||
|
||||
ret == HIDPP_ERROR_UNKNOWN_DEVICE)
|
||||
return -EIO;
|
||||
|
||||
@@ -3610,6 +3616,211 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* HID++2.0 reprogrammable controls */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#define HIDPP_PAGE_REPROG_CONTROLS_V4 0x1b04
|
||||
|
||||
#define HIDPP_REPROG_CONTROLS_GET_COUNT 0x00
|
||||
#define HIDPP_REPROG_CONTROLS_GET_CID_INFO 0x10
|
||||
#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING 0x30
|
||||
|
||||
#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE BIT(0)
|
||||
#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT BIT(5)
|
||||
|
||||
#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED BIT(0)
|
||||
#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT BIT(1)
|
||||
|
||||
#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED 0x00
|
||||
|
||||
#define HIDPP_REPROG_CONTROL_BACK 0x0053
|
||||
#define HIDPP_REPROG_CONTROL_FORWARD 0x0056
|
||||
|
||||
#define HIDPP_PRODUCT_SIGNATURE_M650 0xb02a
|
||||
|
||||
struct hidpp_reprog_control_mapping {
|
||||
u16 control;
|
||||
u16 code;
|
||||
};
|
||||
|
||||
static const struct hidpp_reprog_control_mapping m650_reprog_control_mappings[] = {
|
||||
{ HIDPP_REPROG_CONTROL_BACK, BTN_BACK },
|
||||
{ HIDPP_REPROG_CONTROL_FORWARD, BTN_FORWARD },
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct hidpp_reprog_control_mapping *
|
||||
hidpp20_reprog_controls_get_mappings(struct hidpp_device *hidpp)
|
||||
{
|
||||
switch (hidpp->hid_dev->product) {
|
||||
case HIDPP_PRODUCT_SIGNATURE_M650:
|
||||
return m650_reprog_control_mappings;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int hidpp20_reprog_controls_get_count(struct hidpp_device *hidpp)
|
||||
{
|
||||
struct hidpp_report response;
|
||||
u8 feature_index = hidpp->reprog_controls_feature_index;
|
||||
u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT;
|
||||
int ret;
|
||||
|
||||
ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, NULL, 0,
|
||||
&response);
|
||||
if (ret > 0)
|
||||
return -EPROTO;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return response.fap.params[0];
|
||||
}
|
||||
|
||||
static int hidpp20_reprog_controls_get_cid_info(struct hidpp_device *hidpp,
|
||||
u8 index, u16 *control,
|
||||
u8 *flags)
|
||||
{
|
||||
struct hidpp_report response;
|
||||
u8 feature_index = hidpp->reprog_controls_feature_index;
|
||||
u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO;
|
||||
int ret;
|
||||
|
||||
ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, &index,
|
||||
sizeof(index), &response);
|
||||
if (ret > 0)
|
||||
return -EPROTO;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*control = get_unaligned_be16(&response.fap.params[0]);
|
||||
*flags = response.fap.params[4];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool hidpp20_reprog_controls_find_control(struct hidpp_device *hidpp,
|
||||
u16 control)
|
||||
{
|
||||
int count, ret;
|
||||
u16 cid;
|
||||
u8 flags;
|
||||
int i;
|
||||
|
||||
count = hidpp20_reprog_controls_get_count(hidpp);
|
||||
if (count < 0)
|
||||
return false;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
ret = hidpp20_reprog_controls_get_cid_info(hidpp, i, &cid,
|
||||
&flags);
|
||||
if (ret)
|
||||
return false;
|
||||
|
||||
if (cid == control)
|
||||
return (flags & HIDPP_REPROG_CONTROLS_FLAG_MOUSE) &&
|
||||
(flags & HIDPP_REPROG_CONTROLS_FLAG_DIVERT);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int hidpp20_reprog_controls_set_control_reporting(struct hidpp_device *hidpp,
|
||||
u16 control, u8 flags)
|
||||
{
|
||||
struct hidpp_report response;
|
||||
u8 params[5];
|
||||
|
||||
put_unaligned_be16(control, ¶ms[0]);
|
||||
params[2] = flags;
|
||||
put_unaligned_be16(control, ¶ms[3]);
|
||||
|
||||
return hidpp_send_fap_command_sync(hidpp,
|
||||
hidpp->reprog_controls_feature_index,
|
||||
HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING,
|
||||
params, sizeof(params), &response);
|
||||
}
|
||||
|
||||
static void hidpp20_reprog_controls_connect(struct hidpp_device *hidpp)
|
||||
{
|
||||
const struct hidpp_reprog_control_mapping *mapping;
|
||||
u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
|
||||
HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
|
||||
|
||||
if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
|
||||
return;
|
||||
|
||||
if (!hidpp->reprog_controls)
|
||||
return;
|
||||
|
||||
if (hidpp_root_get_feature(hidpp, HIDPP_PAGE_REPROG_CONTROLS_V4,
|
||||
&hidpp->reprog_controls_feature_index))
|
||||
return;
|
||||
|
||||
for (mapping = hidpp->reprog_controls; mapping->control; mapping++) {
|
||||
if (!hidpp20_reprog_controls_find_control(hidpp, mapping->control))
|
||||
continue;
|
||||
|
||||
hidpp20_reprog_controls_set_control_reporting(hidpp,
|
||||
mapping->control,
|
||||
flags);
|
||||
}
|
||||
}
|
||||
|
||||
static int hidpp20_reprog_controls_raw_event(struct hidpp_device *hidpp,
|
||||
u8 *data, int size)
|
||||
{
|
||||
const struct hidpp_reprog_control_mapping *mapping;
|
||||
struct hidpp_report *report = (struct hidpp_report *)data;
|
||||
u16 controls[4];
|
||||
bool pressed;
|
||||
unsigned int i, j;
|
||||
|
||||
if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
|
||||
!hidpp->input ||
|
||||
!hidpp->reprog_controls ||
|
||||
hidpp->reprog_controls_feature_index == 0xff)
|
||||
return 0;
|
||||
|
||||
if (size < HIDPP_REPORT_LONG_LENGTH ||
|
||||
report->fap.feature_index != hidpp->reprog_controls_feature_index ||
|
||||
report->fap.funcindex_clientid != HIDPP_REPROG_CONTROLS_EVENT_DIVERTED)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(controls); i++)
|
||||
controls[i] = get_unaligned_be16(&report->fap.params[i * 2]);
|
||||
|
||||
for (mapping = hidpp->reprog_controls; mapping->control; mapping++) {
|
||||
pressed = false;
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(controls); j++) {
|
||||
if (controls[j] == mapping->control) {
|
||||
pressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
input_report_key(hidpp->input, mapping->code, pressed);
|
||||
}
|
||||
|
||||
input_sync(hidpp->input);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void hidpp20_reprog_controls_populate_input(struct hidpp_device *hidpp,
|
||||
struct input_dev *input_dev)
|
||||
{
|
||||
const struct hidpp_reprog_control_mapping *mapping;
|
||||
|
||||
if (!hidpp->reprog_controls)
|
||||
return;
|
||||
|
||||
for (mapping = hidpp->reprog_controls; mapping->control; mapping++)
|
||||
input_set_capability(input_dev, EV_KEY, mapping->code);
|
||||
}
|
||||
|
||||
static void hidpp10_extra_mouse_buttons_populate_input(
|
||||
struct hidpp_device *hidpp, struct input_dev *input_dev)
|
||||
{
|
||||
@@ -3868,6 +4079,9 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
|
||||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS)
|
||||
hidpp10_extra_mouse_buttons_populate_input(hidpp, input);
|
||||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)
|
||||
hidpp20_reprog_controls_populate_input(hidpp, input);
|
||||
}
|
||||
|
||||
static int hidpp_input_configured(struct hid_device *hdev,
|
||||
@@ -3980,6 +4194,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hidpp20_reprog_controls_raw_event(hidpp, data, size);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
|
||||
ret = hidpp10_consumer_keys_raw_event(hidpp, data, size);
|
||||
if (ret != 0)
|
||||
@@ -4170,8 +4388,50 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool hidpp_is_bolt_child(struct hid_device *hdev)
|
||||
{
|
||||
struct device *parent = hdev->dev.parent;
|
||||
struct hid_device *receiver_hdev;
|
||||
|
||||
if (!parent)
|
||||
return false;
|
||||
|
||||
receiver_hdev = to_hid_device(parent);
|
||||
return receiver_hdev->vendor == USB_VENDOR_ID_LOGITECH &&
|
||||
receiver_hdev->product == USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER;
|
||||
}
|
||||
|
||||
static int hidpp_bolt_init(struct hidpp_device *hidpp)
|
||||
{
|
||||
struct hid_device *hdev = hidpp->hid_dev;
|
||||
char *name;
|
||||
int ret;
|
||||
|
||||
ret = hidpp_serial_init(hidpp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
name = hidpp_get_device_name(hidpp);
|
||||
if (!name)
|
||||
return -EIO;
|
||||
|
||||
snprintf(hdev->name, sizeof(hdev->name), "%s", name);
|
||||
dbg_hid("HID++ Bolt: Got name: %s\n", name);
|
||||
|
||||
kfree(name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hidpp_receiver_init(struct hidpp_device *hidpp)
|
||||
{
|
||||
if (hidpp_is_bolt_child(hidpp->hid_dev))
|
||||
return hidpp_bolt_init(hidpp);
|
||||
|
||||
return hidpp_unifying_init(hidpp);
|
||||
}
|
||||
|
||||
/* Get name + serial for USB and Bluetooth HID++ devices */
|
||||
static void hidpp_non_unifying_init(struct hidpp_device *hidpp)
|
||||
static void hidpp_non_receiver_init(struct hidpp_device *hidpp)
|
||||
{
|
||||
struct hid_device *hdev = hidpp->hid_dev;
|
||||
char *name;
|
||||
@@ -4273,6 +4533,8 @@ static void hidpp_connect_event(struct work_struct *work)
|
||||
return;
|
||||
}
|
||||
|
||||
hidpp20_reprog_controls_connect(hidpp);
|
||||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
|
||||
ret = hidpp10_consumer_keys_connect(hidpp);
|
||||
if (ret)
|
||||
@@ -4446,6 +4708,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
hidpp->hid_dev = hdev;
|
||||
hidpp->name = hdev->name;
|
||||
hidpp->quirks = id->driver_data;
|
||||
hidpp->reprog_controls_feature_index = 0xff;
|
||||
hidpp->reprog_controls = hidpp20_reprog_controls_get_mappings(hidpp);
|
||||
hid_set_drvdata(hdev, hidpp);
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
@@ -4519,9 +4783,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
|
||||
/* Get name + serial, store in hdev->name + hdev->uniq */
|
||||
if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE)
|
||||
hidpp_unifying_init(hidpp);
|
||||
hidpp_receiver_init(hidpp);
|
||||
else
|
||||
hidpp_non_unifying_init(hidpp);
|
||||
hidpp_non_receiver_init(hidpp);
|
||||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
|
||||
connect_mask &= ~HID_CONNECT_HIDINPUT;
|
||||
@@ -4716,7 +4980,9 @@ static const struct hid_device_id hidpp_devices[] = {
|
||||
{ /* MX Vertical mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) },
|
||||
{ /* Signature M650 over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
HIDPP_PRODUCT_SIGNATURE_M650),
|
||||
.driver_data = HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS },
|
||||
{ /* MX Master 3 mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
|
||||
{ /* MX Anywhere 3 mouse over Bluetooth */
|
||||
|
||||
Reference in New Issue
Block a user