mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 11:41:29 -04:00
HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support
Some Logitech HID++ 2.0 mice can report diverted reprogrammable controls through HID++ feature 0x1b04, SpecialKeysMseButtons / REPROG_CONTROLS_V4, instead of the normal HID mouse report. Add a quirk-gated event path for those controls. The handler temporarily diverts verified per-product controls, parses divertedButtonsEvent as the current pressed-control list, and reports the corresponding evdev key state for every mapped control. Keep the control mappings in per-product arrays so adding support for another mouse does not change the evdev capabilities advertised by already-supported devices. Documentation for feature 0x1b04 describes divertedButtonsEvent as a list of currently pressed diverted buttons, which is the event format handled here. Link: https://lekensteyn.nl/files/logitech/x1b04_specialkeysmsebuttons.html Signed-off-by: Elliot Douglas <edouglas7358@gmail.com> Signed-off-by: Jiri Kosina <jkosina@suse.com>
This commit is contained in:
committed by
Jiri Kosina
parent
6bad2e38fe
commit
5e21032ac9
@@ -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;
|
||||
@@ -3601,6 +3606,195 @@ 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
|
||||
|
||||
struct hidpp_reprog_control_mapping {
|
||||
u16 control;
|
||||
u16 code;
|
||||
};
|
||||
|
||||
static const struct hidpp_reprog_control_mapping *
|
||||
hidpp20_reprog_controls_get_mappings(struct hidpp_device *hidpp)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -3859,6 +4053,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,
|
||||
@@ -3971,6 +4168,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)
|
||||
@@ -4264,6 +4465,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)
|
||||
@@ -4437,6 +4640,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);
|
||||
|
||||
Reference in New Issue
Block a user