From: "Erik Håkansson" <erikhakan@gmail.com>
To: Jiri Kosina <jikos@kernel.org>, Benjamin Tissoires <bentiss@kernel.org>
Cc: "Filipe Laíns" <lains@riseup.net>,
"Bastien Nocera" <hadess@hadess.net>,
"Rafael Passos" <rafael@rcpassos.me>,
"Grégoire Stein" <greyxor@protonmail.com>,
"Alexey Zagorodnikov" <xglooom@gmail.com>,
"Oleksandr Natalenko" <oleksandr@natalenko.name>,
"Roman Stingler" <roman.stingler@gmail.com>,
"Lovekesh Solanki" <lovekeshsolanki00@gmail.com>,
"Kateřina Medvědová" <k8ie@mcld.eu>,
linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
"Erik Håkansson" <erikhakan@gmail.com>
Subject: [PATCH 2/2] HID: logitech-hidpp: fix Bolt wheel mode handling
Date: Tue, 22 Sep 2026 19:40:28 +0200 [thread overview]
Message-ID: <20260922-feature-bolt-fix-v1-2-63b0fa8da0d3@gmail.com> (raw)
In-Reply-To: <20260922-feature-bolt-fix-v1-0-63b0fa8da0d3@gmail.com>
Preserve the device's user-selected resolution and inversion settings
while enabling HID++ wheel notifications for Bolt devices. Track diverted
wheel events and apply the device multiplier to both high- and
low-resolution events.
Signed-off-by: Erik Håkansson <erikhakan@gmail.com>
---
drivers/hid/hid-logitech-hidpp.c | 112 +++++++++++++++++++++++++++++++++------
1 file changed, 97 insertions(+), 15 deletions(-)
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 166980be57b7..55e9ac5e5ab7 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -213,6 +213,7 @@ struct hidpp_device {
int hires_wheel_multiplier;
u8 hires_wheel_feature_index;
+ bool hires_wheel_diverted; /* whether wheel events use HID++ notifications */
bool connected_once;
};
@@ -2043,8 +2044,14 @@ static int hidpp_hrs_set_highres_scrolling_mode(struct hidpp_device *hidpp,
#define HIDPP_PAGE_HIRES_WHEEL 0x2121
+#define EVENT_HIRES_WHEEL_MOVEMENT 0x00
#define CMD_HIRES_WHEEL_GET_WHEEL_CAPABILITY 0x00
+#define CMD_HIRES_WHEEL_GET_WHEEL_MODE 0x10
#define CMD_HIRES_WHEEL_SET_WHEEL_MODE 0x20
+#define HIRES_WHEEL_MODE_USE_HIDPP BIT(0)
+#define HIRES_WHEEL_MODE_HIGH_RESOLUTION BIT(1)
+#define HIRES_WHEEL_MODE_INVERT BIT(2)
+#define HIRES_WHEEL_EVENT_HIGH_RESOLUTION BIT(4)
static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp,
u8 *multiplier)
@@ -2072,6 +2079,26 @@ static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp,
return ret;
}
+static int hidpp_hrw_get_wheel_mode(struct hidpp_device *hidpp, u8 *mode)
+{
+ u8 feature_index;
+ struct hidpp_report response;
+
+ int ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
+ &feature_index);
+ if (ret)
+ return ret;
+
+ ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+ CMD_HIRES_WHEEL_GET_WHEEL_MODE,
+ NULL, 0, &response);
+ if (ret)
+ return ret;
+
+ *mode = response.fap.params[0];
+ return 0;
+}
+
static int hidpp_hrw_set_wheel_mode(struct hidpp_device *hidpp, bool invert,
bool high_resolution, bool use_hidpp)
{
@@ -2085,9 +2112,9 @@ static int hidpp_hrw_set_wheel_mode(struct hidpp_device *hidpp, bool invert,
if (ret)
return ret;
- params[0] = (invert ? BIT(2) : 0) |
- (high_resolution ? BIT(1) : 0) |
- (use_hidpp ? BIT(0) : 0);
+ params[0] = (invert ? HIRES_WHEEL_MODE_INVERT : 0) |
+ (high_resolution ? HIRES_WHEEL_MODE_HIGH_RESOLUTION : 0) |
+ (use_hidpp ? HIRES_WHEEL_MODE_USE_HIDPP : 0);
return hidpp_send_fap_command_sync(hidpp, feature_index,
CMD_HIRES_WHEEL_SET_WHEEL_MODE,
@@ -3912,16 +3939,43 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp)
{
int ret;
u8 multiplier = 1;
+ bool high_resolution = true;
+ const bool is_bolt = hidpp_is_bolt_child(hidpp->hid_dev);
+
+ if (is_bolt)
+ hidpp->hires_wheel_diverted = false;
if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL) {
- bool use_hidpp = hidpp_is_bolt_child(hidpp->hid_dev);
+ u8 mode;
+
+ /* Preserve the persistent mode, except for Bolt event routing. */
+ ret = hidpp_hrw_get_wheel_mode(hidpp, &mode);
+ if (ret == 0) {
+ bool invert = mode & HIRES_WHEEL_MODE_INVERT;
+ bool use_hidpp = mode & HIRES_WHEEL_MODE_USE_HIDPP;
+
+ high_resolution = mode & HIRES_WHEEL_MODE_HIGH_RESOLUTION;
+
+ /* Preserve user-set resolution and inversion; enable HID++ for Bolt. */
+ if (is_bolt && high_resolution && !use_hidpp) {
+ ret = hidpp_hrw_set_wheel_mode(hidpp, invert,
+ high_resolution, true);
+ if (ret == 0)
+ use_hidpp = true;
+ }
- ret = hidpp_hrw_set_wheel_mode(hidpp, false, true, use_hidpp);
- if (ret == 0)
- ret = hidpp_hrw_get_wheel_capability(hidpp, &multiplier);
+ if (ret == 0)
+ ret = hidpp_hrw_get_wheel_capability(hidpp, &multiplier);
+ if (ret == 0)
+ hidpp->hires_wheel_diverted = use_hidpp;
+ }
} else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL) {
- ret = hidpp_hrs_set_highres_scrolling_mode(hidpp, true,
+ /* Bolt reports for this feature also bypass hid-logitech-hidpp. */
+ high_resolution = !is_bolt;
+ ret = hidpp_hrs_set_highres_scrolling_mode(hidpp, !is_bolt,
&multiplier);
+ if (!ret && is_bolt)
+ multiplier = 1;
} else /* if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL) */ {
ret = hidpp10_enable_scrolling_acceleration(hidpp);
multiplier = 8;
@@ -3938,8 +3992,12 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp)
multiplier = 1;
}
+ /* Keep the device-reported multiplier for HID++ event conversion. */
hidpp->hires_wheel_multiplier = multiplier;
- hidpp->vertical_wheel_counter.wheel_multiplier = multiplier;
+ /* Apply the multiplier only while high-resolution mode or HID++ diversion is active. */
+ hidpp->vertical_wheel_counter.wheel_multiplier =
+ (high_resolution || hidpp->hires_wheel_diverted) ?
+ multiplier : 1;
hid_dbg(hidpp->hid_dev, "wheel multiplier = %d\n", multiplier);
return 0;
}
@@ -3997,17 +4055,41 @@ static int hidpp20_hires_wheel_raw_event(struct hidpp_device *hidpp,
if ((data[3] & 0xf0) == CMD_HIRES_WHEEL_SET_WHEEL_MODE) {
u8 mode = data[4];
- bool hires = (mode & 0x02) != 0;
- int new_multiplier = (hires && hidpp->hires_wheel_multiplier > 0)
+ bool hires = mode & HIRES_WHEEL_MODE_HIGH_RESOLUTION;
+ bool use_hidpp = mode & HIRES_WHEEL_MODE_USE_HIDPP;
+ int new_multiplier = ((hires || use_hidpp) &&
+ hidpp->hires_wheel_multiplier > 0)
? hidpp->hires_wheel_multiplier : 1;
+
+ hidpp->hires_wheel_diverted = use_hidpp;
hidpp->vertical_wheel_counter.wheel_multiplier = new_multiplier;
return 1;
}
- /* wheel movement event: 16-bit signed delta in HID++ ticks */
- if ((data[3] & 0xf0) == 0x00 && size >= 7 && hidpp->input &&
- hidpp->vertical_wheel_counter.wheel_multiplier) {
- s16 delta = get_unaligned_be16(&data[5]);
+ /*
+ * HID++ 0x2121 wheelMovement event:
+ * data[3] = event/function index in the upper nibble (0x0),
+ * client ID in the lower nibble
+ * data[4] = event flags; bit 4 indicates high-resolution units
+ * data[5:6] = signed 16-bit big-endian vertical wheel delta
+ *
+ * The outer HID++ path normally validates long reports as 20 bytes, but
+ * keep the size check below before reading data[5:6] from the buffer.
+ * Low-resolution deltas are converted using the device's multiplier
+ * before being passed to the scroll counter.
+ */
+ if (data[3] == EVENT_HIRES_WHEEL_MOVEMENT && hidpp->hires_wheel_diverted) {
+ int delta;
+
+ if (size < 7 || !hidpp->input ||
+ !hidpp->vertical_wheel_counter.wheel_multiplier ||
+ !hidpp->hires_wheel_multiplier)
+ return 1;
+
+ delta = (s16)get_unaligned_be16(&data[5]);
+ if (!(data[4] & HIRES_WHEEL_EVENT_HIGH_RESOLUTION))
+ /* low-resolution delta: convert to hi-res units */
+ delta *= hidpp->hires_wheel_multiplier;
if (delta) {
hidpp_scroll_counter_handle_scroll(hidpp->input,
--
2.55.0
next prev parent reply other threads:[~2026-09-22 17:40 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-22 17:40 [PATCH 0/2] HID: logitech-hidpp: fix Bolt hi-res wheel handling Erik Håkansson
2026-09-22 17:40 ` [PATCH 1/2] HID: logitech-hidpp: fix hi-res scroll for Bolt-connected MX Master Erik Håkansson
2026-09-22 17:51 ` sashiko-bot
2026-09-22 17:40 ` Erik Håkansson [this message]
2026-09-22 17:53 ` [PATCH 2/2] HID: logitech-hidpp: fix Bolt wheel mode handling sashiko-bot
2026-09-23 6:39 ` [PATCH 0/2] HID: logitech-hidpp: fix Bolt hi-res wheel handling Oleksandr Natalenko
2026-09-23 7:54 ` Benjamin Tissoires
2026-09-23 16:09 ` erikhakan
2026-09-24 6:51 ` Oleksandr Natalenko
2026-09-24 21:41 ` Erik Håkansson
2026-09-24 21:48 ` erikhakan
2026-09-24 21:57 ` Oleksandr Natalenko
2026-09-26 11:13 ` Erik Håkansson
2026-09-26 11:39 ` Oleksandr Natalenko
2026-09-26 12:12 ` Erik Håkansson
2026-09-26 12:24 ` Oleksandr Natalenko
2026-09-26 16:47 ` Erik Håkansson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260922-feature-bolt-fix-v1-2-63b0fa8da0d3@gmail.com \
--to=erikhakan@gmail.com \
--cc=bentiss@kernel.org \
--cc=greyxor@protonmail.com \
--cc=hadess@hadess.net \
--cc=jikos@kernel.org \
--cc=k8ie@mcld.eu \
--cc=lains@riseup.net \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lovekeshsolanki00@gmail.com \
--cc=oleksandr@natalenko.name \
--cc=rafael@rcpassos.me \
--cc=roman.stingler@gmail.com \
--cc=xglooom@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.