Skip to content

Commit d56ca98

Browse files
HarryCuttsJiri Kosina
authored andcommitted
HID: logitech: Enable high-resolution scrolling on Logitech mice
There are three features used by various Logitech mice for high-resolution scrolling: the scrolling acceleration bit in HID++ 1.0, and the x2120 and x2121 features in HID++ 2.0 and above. This patch supports all three, and uses the multiplier reported by the mouse for the HID++ 2.0+ features. The full list of product IDs of mice which support high-resolution scrolling was provided by Logitech, but the patch was tested using the following mice (using the Unifying receiver): * HID++ 1.0: Anywhere MX, Performance MX * x2120: M560 * x2121: MX Anywhere 2, MX Master 2S Signed-off-by: Harry Cutts <hcutts@chromium.org> Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
1 parent 051dc9b commit d56ca98

File tree

1 file changed

+245
-4
lines changed

1 file changed

+245
-4
lines changed

drivers/hid/hid-logitech-hidpp.c

Lines changed: 245 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ MODULE_PARM_DESC(disable_tap_to_click,
6464
#define HIDPP_QUIRK_NO_HIDINPUT BIT(23)
6565
#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24)
6666
#define HIDPP_QUIRK_UNIFYING BIT(25)
67+
#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(26)
68+
#define HIDPP_QUIRK_HI_RES_SCROLL_X2120 BIT(27)
69+
#define HIDPP_QUIRK_HI_RES_SCROLL_X2121 BIT(28)
70+
71+
/* Convenience constant to check for any high-res support. */
72+
#define HIDPP_QUIRK_HI_RES_SCROLL (HIDPP_QUIRK_HI_RES_SCROLL_1P0 | \
73+
HIDPP_QUIRK_HI_RES_SCROLL_X2120 | \
74+
HIDPP_QUIRK_HI_RES_SCROLL_X2121)
6775

6876
#define HIDPP_QUIRK_DELAYED_INIT HIDPP_QUIRK_NO_HIDINPUT
6977

@@ -149,6 +157,7 @@ struct hidpp_device {
149157
unsigned long capabilities;
150158

151159
struct hidpp_battery battery;
160+
struct hid_scroll_counter vertical_wheel_counter;
152161
};
153162

154163
/* HID++ 1.0 error codes */
@@ -1157,6 +1166,101 @@ static int hidpp_battery_get_property(struct power_supply *psy,
11571166
return ret;
11581167
}
11591168

1169+
/* -------------------------------------------------------------------------- */
1170+
/* 0x2120: Hi-resolution scrolling */
1171+
/* -------------------------------------------------------------------------- */
1172+
1173+
#define HIDPP_PAGE_HI_RESOLUTION_SCROLLING 0x2120
1174+
1175+
#define CMD_HI_RESOLUTION_SCROLLING_SET_HIGHRES_SCROLLING_MODE 0x10
1176+
1177+
static int hidpp_hrs_set_highres_scrolling_mode(struct hidpp_device *hidpp,
1178+
bool enabled, u8 *multiplier)
1179+
{
1180+
u8 feature_index;
1181+
u8 feature_type;
1182+
int ret;
1183+
u8 params[1];
1184+
struct hidpp_report response;
1185+
1186+
ret = hidpp_root_get_feature(hidpp,
1187+
HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
1188+
&feature_index,
1189+
&feature_type);
1190+
if (ret)
1191+
return ret;
1192+
1193+
params[0] = enabled ? BIT(0) : 0;
1194+
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
1195+
CMD_HI_RESOLUTION_SCROLLING_SET_HIGHRES_SCROLLING_MODE,
1196+
params, sizeof(params), &response);
1197+
if (ret)
1198+
return ret;
1199+
*multiplier = response.fap.params[1];
1200+
return 0;
1201+
}
1202+
1203+
/* -------------------------------------------------------------------------- */
1204+
/* 0x2121: HiRes Wheel */
1205+
/* -------------------------------------------------------------------------- */
1206+
1207+
#define HIDPP_PAGE_HIRES_WHEEL 0x2121
1208+
1209+
#define CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY 0x00
1210+
#define CMD_HIRES_WHEEL_SET_WHEEL_MODE 0x20
1211+
1212+
static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp,
1213+
u8 *multiplier)
1214+
{
1215+
u8 feature_index;
1216+
u8 feature_type;
1217+
int ret;
1218+
struct hidpp_report response;
1219+
1220+
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
1221+
&feature_index, &feature_type);
1222+
if (ret)
1223+
goto return_default;
1224+
1225+
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
1226+
CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY,
1227+
NULL, 0, &response);
1228+
if (ret)
1229+
goto return_default;
1230+
1231+
*multiplier = response.fap.params[0];
1232+
return 0;
1233+
return_default:
1234+
*multiplier = 8;
1235+
hid_warn(hidpp->hid_dev,
1236+
"Couldn't get wheel multiplier (error %d), assuming %d.\n",
1237+
ret, *multiplier);
1238+
return ret;
1239+
}
1240+
1241+
static int hidpp_hrw_set_wheel_mode(struct hidpp_device *hidpp, bool invert,
1242+
bool high_resolution, bool use_hidpp)
1243+
{
1244+
u8 feature_index;
1245+
u8 feature_type;
1246+
int ret;
1247+
u8 params[1];
1248+
struct hidpp_report response;
1249+
1250+
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
1251+
&feature_index, &feature_type);
1252+
if (ret)
1253+
return ret;
1254+
1255+
params[0] = (invert ? BIT(2) : 0) |
1256+
(high_resolution ? BIT(1) : 0) |
1257+
(use_hidpp ? BIT(0) : 0);
1258+
1259+
return hidpp_send_fap_command_sync(hidpp, feature_index,
1260+
CMD_HIRES_WHEEL_SET_WHEEL_MODE,
1261+
params, sizeof(params), &response);
1262+
}
1263+
11601264
/* -------------------------------------------------------------------------- */
11611265
/* 0x4301: Solar Keyboard */
11621266
/* -------------------------------------------------------------------------- */
@@ -2420,7 +2524,8 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
24202524
input_report_rel(mydata->input, REL_Y, v);
24212525

24222526
v = hid_snto32(data[6], 8);
2423-
input_report_rel(mydata->input, REL_WHEEL, v);
2527+
hid_scroll_counter_handle_scroll(
2528+
&hidpp->vertical_wheel_counter, v);
24242529

24252530
input_sync(mydata->input);
24262531
}
@@ -2548,6 +2653,73 @@ static int g920_get_config(struct hidpp_device *hidpp)
25482653
return 0;
25492654
}
25502655

2656+
/* -------------------------------------------------------------------------- */
2657+
/* High-resolution scroll wheels */
2658+
/* -------------------------------------------------------------------------- */
2659+
2660+
/**
2661+
* struct hi_res_scroll_info - Stores info on a device's high-res scroll wheel.
2662+
* @product_id: the HID product ID of the device being described.
2663+
* @microns_per_hi_res_unit: the distance moved by the user's finger for each
2664+
* high-resolution unit reported by the device, in
2665+
* 256ths of a millimetre.
2666+
*/
2667+
struct hi_res_scroll_info {
2668+
__u32 product_id;
2669+
int microns_per_hi_res_unit;
2670+
};
2671+
2672+
static struct hi_res_scroll_info hi_res_scroll_devices[] = {
2673+
{ /* Anywhere MX */
2674+
.product_id = 0x1017, .microns_per_hi_res_unit = 445 },
2675+
{ /* Performance MX */
2676+
.product_id = 0x101a, .microns_per_hi_res_unit = 406 },
2677+
{ /* M560 */
2678+
.product_id = 0x402d, .microns_per_hi_res_unit = 435 },
2679+
{ /* MX Master 2S */
2680+
.product_id = 0x4069, .microns_per_hi_res_unit = 406 },
2681+
};
2682+
2683+
static int hi_res_scroll_look_up_microns(__u32 product_id)
2684+
{
2685+
int i;
2686+
int num_devices = sizeof(hi_res_scroll_devices)
2687+
/ sizeof(hi_res_scroll_devices[0]);
2688+
for (i = 0; i < num_devices; i++) {
2689+
if (hi_res_scroll_devices[i].product_id == product_id)
2690+
return hi_res_scroll_devices[i].microns_per_hi_res_unit;
2691+
}
2692+
/* We don't have a value for this device, so use a sensible default. */
2693+
return 406;
2694+
}
2695+
2696+
static int hi_res_scroll_enable(struct hidpp_device *hidpp)
2697+
{
2698+
int ret;
2699+
u8 multiplier;
2700+
2701+
if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_X2121) {
2702+
ret = hidpp_hrw_set_wheel_mode(hidpp, false, true, false);
2703+
hidpp_hrw_get_wheel_capability(hidpp, &multiplier);
2704+
} else if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_X2120) {
2705+
ret = hidpp_hrs_set_highres_scrolling_mode(hidpp, true,
2706+
&multiplier);
2707+
} else /* if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_1P0) */ {
2708+
ret = hidpp10_enable_scrolling_acceleration(hidpp);
2709+
multiplier = 8;
2710+
}
2711+
if (ret)
2712+
return ret;
2713+
2714+
hidpp->vertical_wheel_counter.resolution_multiplier = multiplier;
2715+
hidpp->vertical_wheel_counter.microns_per_hi_res_unit =
2716+
hi_res_scroll_look_up_microns(hidpp->hid_dev->product);
2717+
hid_info(hidpp->hid_dev, "multiplier = %d, microns = %d\n",
2718+
multiplier,
2719+
hidpp->vertical_wheel_counter.microns_per_hi_res_unit);
2720+
return 0;
2721+
}
2722+
25512723
/* -------------------------------------------------------------------------- */
25522724
/* Generic HID++ devices */
25532725
/* -------------------------------------------------------------------------- */
@@ -2593,6 +2765,11 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
25932765
wtp_populate_input(hidpp, input, origin_is_hid_core);
25942766
else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
25952767
m560_populate_input(hidpp, input, origin_is_hid_core);
2768+
2769+
if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) {
2770+
input_set_capability(input, EV_REL, REL_WHEEL_HI_RES);
2771+
hidpp->vertical_wheel_counter.dev = input;
2772+
}
25962773
}
25972774

25982775
static int hidpp_input_configured(struct hid_device *hdev,
@@ -2711,6 +2888,27 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
27112888
return 0;
27122889
}
27132890

2891+
static int hidpp_event(struct hid_device *hdev, struct hid_field *field,
2892+
struct hid_usage *usage, __s32 value)
2893+
{
2894+
/* This function will only be called for scroll events, due to the
2895+
* restriction imposed in hidpp_usages.
2896+
*/
2897+
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
2898+
struct hid_scroll_counter *counter = &hidpp->vertical_wheel_counter;
2899+
/* A scroll event may occur before the multiplier has been retrieved or
2900+
* the input device set, or high-res scroll enabling may fail. In such
2901+
* cases we must return early (falling back to default behaviour) to
2902+
* avoid a crash in hid_scroll_counter_handle_scroll.
2903+
*/
2904+
if (!(hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) || value == 0
2905+
|| counter->dev == NULL || counter->resolution_multiplier == 0)
2906+
return 0;
2907+
2908+
hid_scroll_counter_handle_scroll(counter, value);
2909+
return 1;
2910+
}
2911+
27142912
static int hidpp_initialize_battery(struct hidpp_device *hidpp)
27152913
{
27162914
static atomic_t battery_no = ATOMIC_INIT(0);
@@ -2922,6 +3120,9 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
29223120
if (hidpp->battery.ps)
29233121
power_supply_changed(hidpp->battery.ps);
29243122

3123+
if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL)
3124+
hi_res_scroll_enable(hidpp);
3125+
29253126
if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) || hidpp->delayed_input)
29263127
/* if the input nodes are already created, we can stop now */
29273128
return;
@@ -3107,6 +3308,10 @@ static void hidpp_remove(struct hid_device *hdev)
31073308
mutex_destroy(&hidpp->send_mutex);
31083309
}
31093310

3311+
#define LDJ_DEVICE(product) \
3312+
HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, \
3313+
USB_VENDOR_ID_LOGITECH, (product))
3314+
31103315
static const struct hid_device_id hidpp_devices[] = {
31113316
{ /* wireless touchpad */
31123317
HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
@@ -3121,10 +3326,39 @@ static const struct hid_device_id hidpp_devices[] = {
31213326
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
31223327
USB_DEVICE_ID_LOGITECH_T651),
31233328
.driver_data = HIDPP_QUIRK_CLASS_WTP },
3329+
{ /* Mouse Logitech Anywhere MX */
3330+
LDJ_DEVICE(0x1017), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
3331+
{ /* Mouse Logitech Cube */
3332+
LDJ_DEVICE(0x4010), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2120 },
3333+
{ /* Mouse Logitech M335 */
3334+
LDJ_DEVICE(0x4050), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3335+
{ /* Mouse Logitech M515 */
3336+
LDJ_DEVICE(0x4007), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2120 },
31243337
{ /* Mouse logitech M560 */
3125-
HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
3126-
USB_VENDOR_ID_LOGITECH, 0x402d),
3127-
.driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 },
3338+
LDJ_DEVICE(0x402d),
3339+
.driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560
3340+
| HIDPP_QUIRK_HI_RES_SCROLL_X2120 },
3341+
{ /* Mouse Logitech M705 (firmware RQM17) */
3342+
LDJ_DEVICE(0x101b), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
3343+
{ /* Mouse Logitech M705 (firmware RQM67) */
3344+
LDJ_DEVICE(0x406d), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3345+
{ /* Mouse Logitech M720 */
3346+
LDJ_DEVICE(0x405e), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3347+
{ /* Mouse Logitech MX Anywhere 2 */
3348+
LDJ_DEVICE(0x404a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3349+
{ LDJ_DEVICE(0xb013), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3350+
{ LDJ_DEVICE(0xb018), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3351+
{ LDJ_DEVICE(0xb01f), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3352+
{ /* Mouse Logitech MX Anywhere 2S */
3353+
LDJ_DEVICE(0x406a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3354+
{ /* Mouse Logitech MX Master */
3355+
LDJ_DEVICE(0x4041), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3356+
{ LDJ_DEVICE(0x4060), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3357+
{ LDJ_DEVICE(0x4071), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3358+
{ /* Mouse Logitech MX Master 2S */
3359+
LDJ_DEVICE(0x4069), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 },
3360+
{ /* Mouse Logitech Performance MX */
3361+
LDJ_DEVICE(0x101a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
31283362
{ /* Keyboard logitech K400 */
31293363
HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
31303364
USB_VENDOR_ID_LOGITECH, 0x4024),
@@ -3144,12 +3378,19 @@ static const struct hid_device_id hidpp_devices[] = {
31443378

31453379
MODULE_DEVICE_TABLE(hid, hidpp_devices);
31463380

3381+
static const struct hid_usage_id hidpp_usages[] = {
3382+
{ HID_GD_WHEEL, EV_REL, REL_WHEEL },
3383+
{ HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1}
3384+
};
3385+
31473386
static struct hid_driver hidpp_driver = {
31483387
.name = "logitech-hidpp-device",
31493388
.id_table = hidpp_devices,
31503389
.probe = hidpp_probe,
31513390
.remove = hidpp_remove,
31523391
.raw_event = hidpp_raw_event,
3392+
.usage_table = hidpp_usages,
3393+
.event = hidpp_event,
31533394
.input_configured = hidpp_input_configured,
31543395
.input_mapping = hidpp_input_mapping,
31553396
.input_mapped = hidpp_input_mapped,

0 commit comments

Comments
 (0)