* [PATCH v2 0/4] HID: steelseries: Add support for Arctis headset lineup
@ 2026-01-12 4:19 Sriman Achanta
2026-01-12 4:19 ` [PATCH v2 1/4] HID: hid-ids: Add SteelSeries Arctis headset device IDs Sriman Achanta
` (5 more replies)
0 siblings, 6 replies; 15+ messages in thread
From: Sriman Achanta @ 2026-01-12 4:19 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel; +Cc: Sriman Achanta
This patch series adds comprehensive support for the SteelSeries Arctis
wireless gaming headset lineup to the hid-steelseries driver.
The current driver provides only basic battery monitoring for Arctis 1
and Arctis 9. This series extends support to 25+ Arctis models with
full feature control including sidetone, auto-sleep, microphone
controls, volume limiting, and Bluetooth settings.
The driver restructure uses a capability-based device info system to
cleanly handle the varying feature sets across different Arctis
generations while maintaining support for the legacy SRW-S1 racing
wheel.
Patch 1: Add 27 new device IDs to hid-ids.h
Patch 2: Add HID quirks for proper device initialization
Patch 3: Update ABI documentation for new sysfs attributes
Patch 4: Complete driver implementation with all features
Tested on Arctis Nova 7 (0x2202). All other implementation details are
based on the reverse engineering done in the HeadsetControl library
(abe3ac8).
V2:
- Fix Documentation formatting issues
Sriman Achanta (4):
HID: hid-ids: Add SteelSeries Arctis headset device IDs
HID: quirks: Add INPUT_CONFIGURED quirk for SteelSeries Arctis
headsets
Documentation: ABI: Document SteelSeries headset sysfs attributes
HID: steelseries: Add support for Arctis headset lineup
.../ABI/testing/sysfs-driver-hid-srws1 | 21 -
.../ABI/testing/sysfs-driver-hid-steelseries | 131 ++
drivers/hid/hid-ids.h | 33 +-
drivers/hid/hid-quirks.c | 25 +
drivers/hid/hid-steelseries.c | 2061 ++++++++++++++---
5 files changed, 1925 insertions(+), 346 deletions(-)
delete mode 100644 Documentation/ABI/testing/sysfs-driver-hid-srws1
create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-steelseries
--
2.52.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 1/4] HID: hid-ids: Add SteelSeries Arctis headset device IDs
2026-01-12 4:19 [PATCH v2 0/4] HID: steelseries: Add support for Arctis headset lineup Sriman Achanta
@ 2026-01-12 4:19 ` Sriman Achanta
2026-01-12 13:08 ` Bastien Nocera
2026-01-12 4:19 ` [PATCH v2 2/4] HID: quirks: Add INPUT_CONFIGURED quirk for SteelSeries Arctis headsets Sriman Achanta
` (4 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: Sriman Achanta @ 2026-01-12 4:19 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel; +Cc: Sriman Achanta
Add USB device IDs for the complete SteelSeries Arctis headset lineup,
including:
- Arctis 1, 1 Wireless, 7, 7P, 7X variants
- Arctis 7+ series (PS5, Xbox, Destiny editions)
- Arctis 9 Wireless
- Arctis Pro Wireless
- Arctis Nova 3, 3P, 3X
- Arctis Nova 5, 5X
- Arctis Nova 7 series (multiple variants and special editions)
- Arctis Nova Pro Wireless and Pro X
This also fixes the existing ARCTIS_1 ID to use the correct product ID
(0x12b3 instead of 0x12b6, which is actually the Arctis 1 Xbox variant).
These IDs will be used by the updated hid-steelseries driver to provide
battery monitoring, sidetone control, and other device-specific features
for these wireless gaming headsets.
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
drivers/hid/hid-ids.h | 33 +++++++++++++++++++++++++++++----
1 file changed, 29 insertions(+), 4 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index d31711f1aaec..f4f91fb4c2b9 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1303,10 +1303,35 @@
#define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142
#define USB_DEVICE_ID_STEAM_DECK 0x1205
-#define USB_VENDOR_ID_STEELSERIES 0x1038
-#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
-#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
-#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
+#define USB_VENDOR_ID_STEELSERIES 0x1038
+#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b3
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7 0x1260
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P 0x12d5
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X 0x12d7
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2 0x12ad
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS 0x220e
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P 0x2212
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X 0x2216
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY 0x2236
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_PRO 0x1290
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3 0x12ec
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P 0x2269
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X 0x226d
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5 0x2232
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X 0x2253
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 0x2202
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X 0x2206
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P 0x220a
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_REV2 0x2258
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO 0x223a
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW 0x227a
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2 0x227e
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2 0x229e
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO 0x12e0
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X 0x12e5
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
--
2.52.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 2/4] HID: quirks: Add INPUT_CONFIGURED quirk for SteelSeries Arctis headsets
2026-01-12 4:19 [PATCH v2 0/4] HID: steelseries: Add support for Arctis headset lineup Sriman Achanta
2026-01-12 4:19 ` [PATCH v2 1/4] HID: hid-ids: Add SteelSeries Arctis headset device IDs Sriman Achanta
@ 2026-01-12 4:19 ` Sriman Achanta
2026-01-12 13:08 ` Bastien Nocera
2026-01-12 4:19 ` [PATCH v2 3/4] Documentation: ABI: Document SteelSeries headset sysfs attributes Sriman Achanta
` (3 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: Sriman Achanta @ 2026-01-12 4:19 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel; +Cc: Sriman Achanta
Add HID_QUIRK_INPUT_CONFIGURED for all SteelSeries Arctis headsets that
require the hid-steelseries driver. This quirk ensures proper device
initialization and prevents conflicts with generic HID drivers.
The quirk is necessary because these devices expose multiple HID
interfaces, and the hid-steelseries driver needs to bind to specific
interfaces based on the device capabilities. Without this quirk, the
generic HID driver may interfere with device-specific functionality like
battery monitoring and feature controls.
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
drivers/hid/hid-quirks.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index c89a015686c0..8a7c3f433040 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -699,7 +699,32 @@ static const struct hid_device_id hid_have_special_driver[] = {
#if IS_ENABLED(CONFIG_HID_STEELSERIES)
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_PRO) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_REV2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X) },
#endif
#if IS_ENABLED(CONFIG_HID_SUNPLUS)
{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
--
2.52.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 3/4] Documentation: ABI: Document SteelSeries headset sysfs attributes
2026-01-12 4:19 [PATCH v2 0/4] HID: steelseries: Add support for Arctis headset lineup Sriman Achanta
2026-01-12 4:19 ` [PATCH v2 1/4] HID: hid-ids: Add SteelSeries Arctis headset device IDs Sriman Achanta
2026-01-12 4:19 ` [PATCH v2 2/4] HID: quirks: Add INPUT_CONFIGURED quirk for SteelSeries Arctis headsets Sriman Achanta
@ 2026-01-12 4:19 ` Sriman Achanta
2026-01-12 13:08 ` Bastien Nocera
2026-01-12 4:19 ` [PATCH v2 4/4] HID: steelseries: Add support for Arctis headset lineup Sriman Achanta
` (2 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: Sriman Achanta @ 2026-01-12 4:19 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel; +Cc: Sriman Achanta
Replace the SRW-S1 specific documentation with comprehensive
documentation for the hid-steelseries driver covering both the legacy
SRW-S1 wheel and the modern Arctis headset lineup.
New sysfs attributes documented:
- sidetone_level: Control microphone monitoring volume
- inactive_time: Auto-sleep timeout configuration
- chatmix_level: Game/Chat audio balance (read-only)
- mic_mute_led_brightness: Microphone mute LED brightness control
- mic_volume: Internal microphone gain control
- volume_limiter: EU hearing protection volume limiter
- bluetooth_on_power: Bluetooth auto-enable on power-on
- bluetooth_call_vol: Bluetooth call audio attenuation settings
The SRW-S1 LED documentation is preserved and moved into the new unified
documentation file.
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
.../ABI/testing/sysfs-driver-hid-srws1 | 21 ---
.../ABI/testing/sysfs-driver-hid-steelseries | 131 ++++++++++++++++++
2 files changed, 131 insertions(+), 21 deletions(-)
delete mode 100644 Documentation/ABI/testing/sysfs-driver-hid-srws1
create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-steelseries
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-srws1 b/Documentation/ABI/testing/sysfs-driver-hid-srws1
deleted file mode 100644
index d0eba70c7d40..000000000000
--- a/Documentation/ABI/testing/sysfs-driver-hid-srws1
+++ /dev/null
@@ -1,21 +0,0 @@
-What: /sys/class/leds/SRWS1::<serial>::RPM1
-What: /sys/class/leds/SRWS1::<serial>::RPM2
-What: /sys/class/leds/SRWS1::<serial>::RPM3
-What: /sys/class/leds/SRWS1::<serial>::RPM4
-What: /sys/class/leds/SRWS1::<serial>::RPM5
-What: /sys/class/leds/SRWS1::<serial>::RPM6
-What: /sys/class/leds/SRWS1::<serial>::RPM7
-What: /sys/class/leds/SRWS1::<serial>::RPM8
-What: /sys/class/leds/SRWS1::<serial>::RPM9
-What: /sys/class/leds/SRWS1::<serial>::RPM10
-What: /sys/class/leds/SRWS1::<serial>::RPM11
-What: /sys/class/leds/SRWS1::<serial>::RPM12
-What: /sys/class/leds/SRWS1::<serial>::RPM13
-What: /sys/class/leds/SRWS1::<serial>::RPM14
-What: /sys/class/leds/SRWS1::<serial>::RPM15
-What: /sys/class/leds/SRWS1::<serial>::RPMALL
-Date: Jan 2013
-KernelVersion: 3.9
-Contact: Simon Wood <simon@mungewell.org>
-Description: Provides a control for turning on/off the LEDs which form
- an RPM meter on the front of the controller
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-steelseries b/Documentation/ABI/testing/sysfs-driver-hid-steelseries
new file mode 100644
index 000000000000..751cf01ceda3
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-hid-steelseries
@@ -0,0 +1,131 @@
+What: /sys/class/leds/SRWS1::<serial>::RPM1
+What: /sys/class/leds/SRWS1::<serial>::RPM2
+What: /sys/class/leds/SRWS1::<serial>::RPM3
+What: /sys/class/leds/SRWS1::<serial>::RPM4
+What: /sys/class/leds/SRWS1::<serial>::RPM5
+What: /sys/class/leds/SRWS1::<serial>::RPM6
+What: /sys/class/leds/SRWS1::<serial>::RPM7
+What: /sys/class/leds/SRWS1::<serial>::RPM8
+What: /sys/class/leds/SRWS1::<serial>::RPM9
+What: /sys/class/leds/SRWS1::<serial>::RPM10
+What: /sys/class/leds/SRWS1::<serial>::RPM11
+What: /sys/class/leds/SRWS1::<serial>::RPM12
+What: /sys/class/leds/SRWS1::<serial>::RPM13
+What: /sys/class/leds/SRWS1::<serial>::RPM14
+What: /sys/class/leds/SRWS1::<serial>::RPM15
+What: /sys/class/leds/SRWS1::<serial>::RPMALL
+Date: Jan 2013
+KernelVersion: 3.9
+Contact: Simon Wood <simon@mungewell.org>
+Description: Provides a control for turning on/off the LEDs which form
+ an RPM meter on the front of the controller
+
+What: /sys/class/hid/drivers/steelseries/<dev>/sidetone_level
+Date: January 2025
+KernelVersion: 6.19
+Contact: Sriman Achanta <srimanachanta@gmail.com>
+Description:
+ Controls the sidetone (microphone monitoring) volume level.
+ This determines how much of the microphone input is fed back into
+ the headset speakers.
+
+ Range: 0-128 (mapped internally to device-specific values).
+
+ Access: Write
+
+What: /sys/class/hid/drivers/steelseries/<dev>/inactive_time
+Date: January 2025
+KernelVersion: 6.19
+Contact: Sriman Achanta <srimanachanta@gmail.com>
+Description:
+ Sets the time in minutes before the headset automatically enters
+ standby/sleep mode when no audio is playing.
+
+ Range: 0-90 (minutes).
+ Some devices (e.g., Arctis 1/7X) map this to specific presets.
+
+ Access: Write
+
+What: /sys/class/hid/drivers/steelseries/<dev>/chatmix_level
+Date: January 2025
+KernelVersion: 6.19
+Contact: Sriman Achanta <srimanachanta@gmail.com>
+Description:
+ Reports the current balance between Game and Chat audio channels
+ (ChatMix). This value changes when the physical ChatMix dial
+ on the headset is adjusted.
+
+ Range: 0-128
+ 0 = 100% Chat / 0% Game
+ 64 = 50% Chat / 50% Game (Balanced)
+ 128 = 0% Chat / 100% Game
+
+ Access: Read
+
+What: /sys/class/hid/drivers/steelseries/<dev>/mic_mute_led_brightness
+Date: January 2025
+KernelVersion: 6.19
+Contact: Sriman Achanta <srimanachanta@gmail.com>
+Description:
+ Controls the brightness of the LED on the microphone boom that
+ indicates when the microphone is muted.
+
+ Range: 0-3 (off, low, medium, high) for most devices.
+ 0-10 for newer Nova series devices.
+
+ Access: Write
+
+What: /sys/class/hid/drivers/steelseries/<dev>/mic_volume
+Date: January 2025
+KernelVersion: 6.19
+Contact: Sriman Achanta <srimanachanta@gmail.com>
+Description:
+ Controls the internal microphone gain/volume of the headset.
+ This is distinct from the OS input volume.
+
+ Range: 0-128 (mapped internally to device-specific values).
+
+ Access: Write
+
+What: /sys/class/hid/drivers/steelseries/<dev>/volume_limiter
+Date: January 2025
+KernelVersion: 6.19
+Contact: Sriman Achanta <srimanachanta@gmail.com>
+Description:
+ Enables or disables the EU volume limiter (hearing protection).
+ When enabled, the maximum output volume is capped.
+
+ Values:
+ 0 = Disabled
+ 1 = Enabled
+
+ Access: Write
+
+What: /sys/class/hid/drivers/steelseries/<dev>/bluetooth_on_power
+Date: January 2025
+KernelVersion: 6.19
+Contact: Sriman Achanta <srimanachanta@gmail.com>
+Description:
+ Configures whether the Bluetooth radio automatically turns on
+ when the headset is powered on.
+
+ Values:
+ 0 = Bluetooth must be turned on manually
+ 1 = Bluetooth turns on automatically with headset
+
+ Access: Write
+
+What: /sys/class/hid/drivers/steelseries/<dev>/bluetooth_call_vol
+Date: January 2025
+KernelVersion: 6.19
+Contact: Sriman Achanta <srimanachanta@gmail.com>
+Description:
+ Configures how the 2.4GHz Game/Chat audio is attenuated when
+ a Bluetooth call is active.
+
+ Values:
+ 0 = No attenuation (mix both equally)
+ 1 = Attenuate Game audio by -12dB
+ 2 = Mute Game audio completely
+
+ Access: Write
--
2.52.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 4/4] HID: steelseries: Add support for Arctis headset lineup
2026-01-12 4:19 [PATCH v2 0/4] HID: steelseries: Add support for Arctis headset lineup Sriman Achanta
` (2 preceding siblings ...)
2026-01-12 4:19 ` [PATCH v2 3/4] Documentation: ABI: Document SteelSeries headset sysfs attributes Sriman Achanta
@ 2026-01-12 4:19 ` Sriman Achanta
2026-01-12 13:09 ` Bastien Nocera
2026-01-20 19:01 ` [PATCH v2 0/4] " Benjamin Wheeler
2026-01-20 20:01 ` [PATCH 0/3] HID: steelseries: clean up functions, move battery request data to structs Benjamin Wheeler
5 siblings, 1 reply; 15+ messages in thread
From: Sriman Achanta @ 2026-01-12 4:19 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel; +Cc: Sriman Achanta
Add full support for the SteelSeries Arctis wireless gaming headset
lineup, extending the driver from basic support for 2 models (Arctis 1
and 9) to comprehensive support for 25+ models across all Arctis
generations.
This is a major restructure of the hid-steelseries driver that replaces
the previous minimal implementation with a unified, capability-based
architecture.
Architecture changes:
- Introduce steelseries_device_info structure to define per-device
capabilities, interface bindings, and metadata
- Implement capability system (SS_CAP_*) for feature detection: battery,
sidetone, chatmix, microphone controls, volume limiting, and Bluetooth
settings
- Add interface binding logic to correctly bind to HID control
interfaces on multi-interface USB devices using two modes:
* Mode 0: Bind to first enumerated interface (for Arctis 9, Pro)
* Mode 1: Bind to specific interface via bitmask (for other models)
- Create device info tables for all supported Arctis models with their
specific capabilities and interface requirements
Features added:
- Battery monitoring: Implement power_supply integration with periodic
polling and device-specific battery request protocols for all model
families. Supports battery capacity reporting, charging status, and
wireless connection tracking.
- Sidetone control: Sysfs attribute to adjust microphone monitoring
volume (0-128) with device-specific mapping to hardware ranges
- Auto-sleep timeout: Configure inactivity timeout (0-90 minutes) before
headset enters standby mode
- ChatMix reporting: Read-only sysfs attribute reporting game/chat audio
balance from physical dial on supported models
- Microphone controls:
* Mute LED brightness (0-3 or 0-10 depending on model)
* Internal microphone gain/volume (0-128)
- Volume limiter: Enable/disable EU hearing protection (max volume cap)
- Bluetooth controls (Nova 7 series):
* Auto-enable Bluetooth on power-on
* Configure game audio attenuation during BT calls
Implementation details:
- Device-specific raw_event parsing for battery updates across different
HID report formats (8-byte, 12-byte, 64-byte, 128-byte)
- Helper functions for HID feature reports and output reports to handle
different communication methods across device families
- Attribute visibility system to expose only relevant controls for each
device based on capability flags
- Save-state commands after configuration changes to persist settings
across power cycles
The legacy SRW-S1 racing wheel controller support is preserved
unchanged.
Tested on Arctis Nova 7 (0x2202). All other implementation details are
based on the reverse engineering done in the HeadsetControl library
(abe3ac8).
Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
---
drivers/hid/hid-steelseries.c | 2061 ++++++++++++++++++++++++++++-----
1 file changed, 1740 insertions(+), 321 deletions(-)
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631aa1..a0046fbc830b 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -4,9 +4,7 @@
*
* Copyright (c) 2013 Simon Wood
* Copyright (c) 2023 Bastien Nocera
- */
-
-/*
+ * Copyright (c) 2025 Sriman Achanta
*/
#include <linux/device.h>
@@ -14,124 +12,144 @@
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/power_supply.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
#include "hid-ids.h"
-#define STEELSERIES_SRWS1 BIT(0)
-#define STEELSERIES_ARCTIS_1 BIT(1)
-#define STEELSERIES_ARCTIS_9 BIT(2)
+#define SS_CAP_SIDETONE BIT(0)
+#define SS_CAP_BATTERY BIT(1)
+#define SS_CAP_INACTIVE_TIME BIT(2)
+#define SS_CAP_CHATMIX BIT(3)
+#define SS_CAP_MIC_MUTE_LED BIT(4)
+#define SS_CAP_MIC_VOLUME BIT(5)
+#define SS_CAP_VOLUME_LIMITER BIT(6)
+#define SS_CAP_BT_POWER_ON BIT(7)
+#define SS_CAP_BT_CALL_VOL BIT(8)
+
+/* Legacy quirk flag for SRW-S1 */
+#define STEELSERIES_SRWS1 BIT(0)
+
+struct steelseries_device_info {
+ u16 product_id;
+ const char *name;
+ u8 interface_binding_mode; /* 0 = first enumerated, 1 = specific interface(s) */
+ u16 valid_interfaces; /* Bitmask when mode = 1, ignored when mode = 0 */
+ unsigned long capabilities;
+};
struct steelseries_device {
struct hid_device *hdev;
- unsigned long quirks;
-
- struct delayed_work battery_work;
- spinlock_t lock;
- bool removed;
+ const struct steelseries_device_info *info;
+ /* Battery subsystem */
struct power_supply_desc battery_desc;
struct power_supply *battery;
- uint8_t battery_capacity;
+ struct delayed_work battery_work;
+ u8 battery_capacity;
bool headset_connected;
bool battery_charging;
+
+ /* Synchronization */
+ spinlock_t lock;
+ bool removed;
+
+ /* Cached chatmix value (read-only from status) */
+ int chatmix_level;
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
- (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
+ (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
#define SRWS1_NUMBER_LEDS 15
struct steelseries_srws1_data {
__u16 led_state;
- /* the last element is used for setting all leds simultaneously */
struct led_classdev *led[SRWS1_NUMBER_LEDS + 1];
};
#endif
-/* Fixed report descriptor for Steelseries SRW-S1 wheel controller
- *
- * The original descriptor hides the sensitivity and assists dials
- * a custom vendor usage page. This inserts a patch to make them
- * appear in the 'Generic Desktop' usage.
- */
-
+/* Fixed report descriptor for Steelseries SRW-S1 wheel controller */
static const __u8 steelseries_srws1_rdesc_fixed[] = {
-0x05, 0x01, /* Usage Page (Desktop) */
-0x09, 0x08, /* Usage (MultiAxis), Changed */
-0xA1, 0x01, /* Collection (Application), */
-0xA1, 0x02, /* Collection (Logical), */
-0x95, 0x01, /* Report Count (1), */
-0x05, 0x01, /* Changed Usage Page (Desktop), */
-0x09, 0x30, /* Changed Usage (X), */
-0x16, 0xF8, 0xF8, /* Logical Minimum (-1800), */
-0x26, 0x08, 0x07, /* Logical Maximum (1800), */
-0x65, 0x14, /* Unit (Degrees), */
-0x55, 0x0F, /* Unit Exponent (15), */
-0x75, 0x10, /* Report Size (16), */
-0x81, 0x02, /* Input (Variable), */
-0x09, 0x31, /* Changed Usage (Y), */
-0x15, 0x00, /* Logical Minimum (0), */
-0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
-0x75, 0x0C, /* Report Size (12), */
-0x81, 0x02, /* Input (Variable), */
-0x09, 0x32, /* Changed Usage (Z), */
-0x15, 0x00, /* Logical Minimum (0), */
-0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
-0x75, 0x0C, /* Report Size (12), */
-0x81, 0x02, /* Input (Variable), */
-0x05, 0x01, /* Usage Page (Desktop), */
-0x09, 0x39, /* Usage (Hat Switch), */
-0x25, 0x07, /* Logical Maximum (7), */
-0x35, 0x00, /* Physical Minimum (0), */
-0x46, 0x3B, 0x01, /* Physical Maximum (315), */
-0x65, 0x14, /* Unit (Degrees), */
-0x75, 0x04, /* Report Size (4), */
-0x95, 0x01, /* Report Count (1), */
-0x81, 0x02, /* Input (Variable), */
-0x25, 0x01, /* Logical Maximum (1), */
-0x45, 0x01, /* Physical Maximum (1), */
-0x65, 0x00, /* Unit, */
-0x75, 0x01, /* Report Size (1), */
-0x95, 0x03, /* Report Count (3), */
-0x81, 0x01, /* Input (Constant), */
-0x05, 0x09, /* Usage Page (Button), */
-0x19, 0x01, /* Usage Minimum (01h), */
-0x29, 0x11, /* Usage Maximum (11h), */
-0x95, 0x11, /* Report Count (17), */
-0x81, 0x02, /* Input (Variable), */
- /* ---- Dial patch starts here ---- */
-0x05, 0x01, /* Usage Page (Desktop), */
-0x09, 0x33, /* Usage (RX), */
-0x75, 0x04, /* Report Size (4), */
-0x95, 0x02, /* Report Count (2), */
-0x15, 0x00, /* Logical Minimum (0), */
-0x25, 0x0b, /* Logical Maximum (b), */
-0x81, 0x02, /* Input (Variable), */
-0x09, 0x35, /* Usage (RZ), */
-0x75, 0x04, /* Report Size (4), */
-0x95, 0x01, /* Report Count (1), */
-0x25, 0x03, /* Logical Maximum (3), */
-0x81, 0x02, /* Input (Variable), */
- /* ---- Dial patch ends here ---- */
-0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
-0x09, 0x01, /* Usage (01h), */
-0x75, 0x04, /* Changed Report Size (4), */
-0x95, 0x0D, /* Changed Report Count (13), */
-0x81, 0x02, /* Input (Variable), */
-0xC0, /* End Collection, */
-0xA1, 0x02, /* Collection (Logical), */
-0x09, 0x02, /* Usage (02h), */
-0x75, 0x08, /* Report Size (8), */
-0x95, 0x10, /* Report Count (16), */
-0x91, 0x02, /* Output (Variable), */
-0xC0, /* End Collection, */
-0xC0 /* End Collection */
+ 0x05, 0x01, /* Usage Page (Desktop) */
+ 0x09, 0x08, /* Usage (MultiAxis), Changed */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0xA1, 0x02, /* Collection (Logical), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x05, 0x01, /* Changed Usage Page (Desktop), */
+ 0x09, 0x30, /* Changed Usage (X), */
+ 0x16, 0xF8, 0xF8, /* Logical Minimum (-1800), */
+ 0x26, 0x08, 0x07, /* Logical Maximum (1800), */
+ 0x65, 0x14, /* Unit (Degrees), */
+ 0x55, 0x0F, /* Unit Exponent (15), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x31, /* Changed Usage (Y), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
+ 0x75, 0x0C, /* Report Size (12), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x32, /* Changed Usage (Z), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
+ 0x75, 0x0C, /* Report Size (12), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x39, /* Usage (Hat Switch), */
+ 0x25, 0x07, /* Logical Maximum (7), */
+ 0x35, 0x00, /* Physical Minimum (0), */
+ 0x46, 0x3B, 0x01, /* Physical Maximum (315), */
+ 0x65, 0x14, /* Unit (Degrees), */
+ 0x75, 0x04, /* Report Size (4), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x45, 0x01, /* Physical Maximum (1), */
+ 0x65, 0x00, /* Unit, */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x05, 0x09, /* Usage Page (Button), */
+ 0x19, 0x01, /* Usage Minimum (01h), */
+ 0x29, 0x11, /* Usage Maximum (11h), */
+ 0x95, 0x11, /* Report Count (17), */
+ 0x81, 0x02, /* Input (Variable), */
+ /* ---- Dial patch starts here ---- */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x33, /* Usage (RX), */
+ 0x75, 0x04, /* Report Size (4), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x25, 0x0b, /* Logical Maximum (b), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x35, /* Usage (RZ), */
+ 0x75, 0x04, /* Report Size (4), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x25, 0x03, /* Logical Maximum (3), */
+ 0x81, 0x02, /* Input (Variable), */
+ /* ---- Dial patch ends here ---- */
+ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
+ 0x09, 0x01, /* Usage (01h), */
+ 0x75, 0x04, /* Changed Report Size (4), */
+ 0x95, 0x0D, /* Changed Report Count (13), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xC0, /* End Collection, */
+ 0xA1, 0x02, /* Collection (Logical), */
+ 0x09, 0x02, /* Usage (02h), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x10, /* Report Count (16), */
+ 0x91, 0x02, /* Output (Variable), */
+ 0xC0, /* End Collection, */
+ 0xC0 /* End Collection */
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
- (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
+ (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds)
{
- struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
- struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+ struct list_head *report_list =
+ &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
+ struct hid_report *report =
+ list_entry(report_list->next, struct hid_report, list);
__s32 *value = report->field[0]->value;
value[0] = 0x40;
@@ -152,12 +170,11 @@ static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds)
value[15] = 0x00;
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
-
- /* Note: LED change does not show on device until the device is read/polled */
}
-static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev,
- enum led_brightness value)
+static void
+steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev,
+ enum led_brightness value)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hid = to_hid_device(dev);
@@ -176,7 +193,8 @@ static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cd
steelseries_srws1_set_leds(hid, drv_data->led_state);
}
-static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev)
+static enum led_brightness
+steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hid = to_hid_device(dev);
@@ -193,7 +211,7 @@ static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_c
}
static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev,
- enum led_brightness value)
+ enum led_brightness value)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hid = to_hid_device(dev);
@@ -221,7 +239,8 @@ static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev,
}
}
-static enum led_brightness steelseries_srws1_led_get_brightness(struct led_classdev *led_cdev)
+static enum led_brightness
+steelseries_srws1_led_get_brightness(struct led_classdev *led_cdev)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hid = to_hid_device(dev);
@@ -245,7 +264,7 @@ static enum led_brightness steelseries_srws1_led_get_brightness(struct led_class
}
static int steelseries_srws1_probe(struct hid_device *hdev,
- const struct hid_device_id *id)
+ const struct hid_device_id *id)
{
int ret, i;
struct led_classdev *led;
@@ -288,7 +307,8 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
name_sz = strlen(hdev->uniq) + 16;
/* 'ALL', for setting all LEDs simultaneously */
- led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
+ led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev) + name_sz,
+ GFP_KERNEL);
if (!led) {
hid_err(hdev, "can't allocate memory for LED ALL\n");
goto out;
@@ -305,20 +325,23 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
drv_data->led[SRWS1_NUMBER_LEDS] = led;
ret = devm_led_classdev_register(&hdev->dev, led);
if (ret) {
- hid_err(hdev, "failed to register LED %d. Aborting.\n", SRWS1_NUMBER_LEDS);
- goto out; /* let the driver continue without LEDs */
+ hid_err(hdev, "failed to register LED %d. Aborting.\n",
+ SRWS1_NUMBER_LEDS);
+ goto out;
}
/* Each individual LED */
for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
- led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
+ led = devm_kzalloc(&hdev->dev,
+ sizeof(struct led_classdev) + name_sz,
+ GFP_KERNEL);
if (!led) {
hid_err(hdev, "can't allocate memory for LED %d\n", i);
break;
}
name = (void *)(&led[1]);
- snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i+1);
+ snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i + 1);
led->name = name;
led->brightness = 0;
led->max_brightness = 1;
@@ -329,8 +352,9 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
ret = devm_led_classdev_register(&hdev->dev, led);
if (ret) {
- hid_err(hdev, "failed to register LED %d. Aborting.\n", i);
- break; /* but let the driver continue without LEDs */
+ hid_err(hdev, "failed to register LED %d. Aborting.\n",
+ i);
+ break;
}
}
out:
@@ -340,51 +364,277 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
}
#endif
-#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
+static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
+ __u8 *rdesc,
+ unsigned int *rsize)
+{
+ if (hdev->vendor != USB_VENDOR_ID_STEELSERIES ||
+ hdev->product != USB_DEVICE_ID_STEELSERIES_SRWS1)
+ return rdesc;
+
+ if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8 &&
+ rdesc[29] == 0xbb && rdesc[40] == 0xc5) {
+ hid_info(hdev,
+ "Fixing up Steelseries SRW-S1 report descriptor\n");
+ *rsize = sizeof(steelseries_srws1_rdesc_fixed);
+ return steelseries_srws1_rdesc_fixed;
+ }
+ return rdesc;
+}
+
+static const struct steelseries_device_info arctis_1_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_1,
+ .name = "Arctis 1 Wireless",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+};
-#define ARCTIS_1_BATTERY_RESPONSE_LEN 8
-#define ARCTIS_9_BATTERY_RESPONSE_LEN 64
-static const char arctis_1_battery_request[] = { 0x06, 0x12 };
-static const char arctis_9_battery_request[] = { 0x00, 0x20 };
+static const struct steelseries_device_info arctis_1_x_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X,
+ .name = "Arctis 1 Wireless for Xbox",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+};
-static int steelseries_headset_request_battery(struct hid_device *hdev,
- const char *request, size_t len)
-{
- u8 *write_buf;
- int ret;
+static const struct steelseries_device_info arctis_7_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_7,
+ .name = "Arctis 7",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(5),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+};
- /* Request battery information */
- write_buf = kmemdup(request, len, GFP_KERNEL);
- if (!write_buf)
- return -ENOMEM;
+static const struct steelseries_device_info arctis_7_p_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P,
+ .name = "Arctis 7P",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+};
- hid_dbg(hdev, "Sending battery request report");
- ret = hid_hw_raw_request(hdev, request[0], write_buf, len,
- HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
- if (ret < (int)len) {
- hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
- ret = -ENODATA;
- }
+static const struct steelseries_device_info arctis_7_x_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X,
+ .name = "Arctis 7X",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+};
- kfree(write_buf);
- return ret;
-}
+static const struct steelseries_device_info arctis_7_gen2_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2,
+ .name = "Arctis 7 (2019 Edition)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(5),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+};
-static void steelseries_headset_fetch_battery(struct hid_device *hdev)
-{
- int ret = 0;
+static const struct steelseries_device_info arctis_7_plus_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS,
+ .name = "Arctis 7+",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
+ SS_CAP_INACTIVE_TIME | SS_CAP_CHATMIX,
+};
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1)
- ret = steelseries_headset_request_battery(hdev,
- arctis_1_battery_request, sizeof(arctis_1_battery_request));
- else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
- ret = steelseries_headset_request_battery(hdev,
- arctis_9_battery_request, sizeof(arctis_9_battery_request));
+static const struct steelseries_device_info arctis_7_plus_p_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P,
+ .name = "Arctis 7+ (PlayStation)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
+ SS_CAP_INACTIVE_TIME | SS_CAP_CHATMIX,
+};
- if (ret < 0)
- hid_dbg(hdev,
- "Battery query failed (err: %d)\n", ret);
-}
+static const struct steelseries_device_info arctis_7_plus_x_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X,
+ .name = "Arctis 7+ (Xbox)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
+ SS_CAP_INACTIVE_TIME | SS_CAP_CHATMIX,
+};
+
+static const struct steelseries_device_info arctis_7_plus_destiny_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY,
+ .name = "Arctis 7+ (Destiny Edition)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
+ SS_CAP_INACTIVE_TIME | SS_CAP_CHATMIX,
+};
+
+static const struct steelseries_device_info arctis_9_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_9,
+ .name = "Arctis 9",
+ .interface_binding_mode = 0,
+ .valid_interfaces = 0,
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
+ SS_CAP_INACTIVE_TIME | SS_CAP_CHATMIX,
+};
+
+static const struct steelseries_device_info arctis_pro_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_PRO,
+ .name = "Arctis Pro Wireless",
+ .interface_binding_mode = 0,
+ .valid_interfaces = 0,
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+};
+
+static const struct steelseries_device_info arctis_nova_3_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3,
+ .name = "Arctis Nova 3",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(4),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_MIC_MUTE_LED |
+ SS_CAP_MIC_VOLUME,
+};
+
+static const struct steelseries_device_info arctis_nova_3_p_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P,
+ .name = "Arctis Nova 3 (PlayStation)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(0),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_VOLUME,
+};
+
+static const struct steelseries_device_info arctis_nova_3_x_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X,
+ .name = "Arctis Nova 3 (Xbox)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(0),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_VOLUME,
+};
+
+static const struct steelseries_device_info arctis_nova_5_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5,
+ .name = "Arctis Nova 5",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
+ SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER,
+};
+
+static const struct steelseries_device_info arctis_nova_5_x_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X,
+ .name = "Arctis Nova 5X",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
+ SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER,
+};
+
+static const struct steelseries_device_info arctis_nova_7_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7,
+ .name = "Arctis Nova 7",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
+ SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
+ SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+};
+
+static const struct steelseries_device_info arctis_nova_7_x_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X,
+ .name = "Arctis Nova 7X",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
+ SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
+ SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+};
+
+static const struct steelseries_device_info arctis_nova_7_p_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P,
+ .name = "Arctis Nova 7P",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
+ SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
+ SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+};
+
+static const struct steelseries_device_info arctis_nova_7_x_rev2_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_REV2,
+ .name = "Arctis Nova 7X (Rev 2)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
+ SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
+ SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+};
+
+static const struct steelseries_device_info arctis_nova_7_diablo_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO,
+ .name = "Arctis Nova 7 (Diablo IV Edition)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
+ SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
+ SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+};
+
+static const struct steelseries_device_info arctis_nova_7_wow_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW,
+ .name = "Arctis Nova 7 (World of Warcraft Edition)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
+ SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
+ SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+};
+
+static const struct steelseries_device_info arctis_nova_7_gen2_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2,
+ .name = "Arctis Nova 7 (Gen 2)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
+ SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
+ SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+};
+
+static const struct steelseries_device_info arctis_nova_7_x_gen2_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2,
+ .name = "Arctis Nova 7X (Gen 2)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(3),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
+ SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
+ SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
+ SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+};
+
+static const struct steelseries_device_info arctis_nova_pro_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO,
+ .name = "Arctis Nova Pro Wireless",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(4),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+};
+
+static const struct steelseries_device_info arctis_nova_pro_x_info = {
+ .product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X,
+ .name = "Arctis Nova Pro Wireless (Xbox)",
+ .interface_binding_mode = 1,
+ .valid_interfaces = BIT(4),
+ .capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+};
+
+#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
static int battery_capacity_to_level(int capacity)
{
@@ -395,29 +645,45 @@ static int battery_capacity_to_level(int capacity)
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
}
-static void steelseries_headset_battery_timer_tick(struct work_struct *work)
+static u8 steelseries_map_battery(u8 capacity, u8 min_in, u8 max_in)
+{
+ if (capacity >= max_in)
+ return 100;
+ if (capacity <= min_in)
+ return 0;
+ return (capacity - min_in) * 100 / (max_in - min_in);
+}
+
+static void steelseries_headset_set_wireless_status(struct hid_device *hdev,
+ bool connected)
{
- struct steelseries_device *sd = container_of(work,
- struct steelseries_device, battery_work.work);
- struct hid_device *hdev = sd->hdev;
+ struct usb_interface *intf;
- steelseries_headset_fetch_battery(hdev);
+ if (!hid_is_usb(hdev))
+ return;
+
+ intf = to_usb_interface(hdev->dev.parent);
+ usb_set_wireless_status(intf, connected ?
+ USB_WIRELESS_STATUS_CONNECTED :
+ USB_WIRELESS_STATUS_DISCONNECTED);
}
#define STEELSERIES_PREFIX "SteelSeries "
#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX)
-static int steelseries_headset_battery_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
+static int steelseries_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
{
struct steelseries_device *sd = power_supply_get_drvdata(psy);
+ unsigned long flags;
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = sd->hdev->name;
- while (!strncmp(val->strval, STEELSERIES_PREFIX, STEELSERIES_PREFIX_LEN))
+ while (!strncmp(val->strval, STEELSERIES_PREFIX,
+ STEELSERIES_PREFIX_LEN))
val->strval += STEELSERIES_PREFIX_LEN;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
@@ -427,21 +693,28 @@ static int steelseries_headset_battery_get_property(struct power_supply *psy,
val->intval = 1;
break;
case POWER_SUPPLY_PROP_STATUS:
+ spin_lock_irqsave(&sd->lock, flags);
if (sd->headset_connected) {
val->intval = sd->battery_charging ?
- POWER_SUPPLY_STATUS_CHARGING :
- POWER_SUPPLY_STATUS_DISCHARGING;
- } else
+ POWER_SUPPLY_STATUS_CHARGING :
+ POWER_SUPPLY_STATUS_DISCHARGING;
+ } else {
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+ spin_unlock_irqrestore(&sd->lock, flags);
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
break;
case POWER_SUPPLY_PROP_CAPACITY:
+ spin_lock_irqsave(&sd->lock, flags);
val->intval = sd->battery_capacity;
+ spin_unlock_irqrestore(&sd->lock, flags);
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ spin_lock_irqsave(&sd->lock, flags);
val->intval = battery_capacity_to_level(sd->battery_capacity);
+ spin_unlock_irqrestore(&sd->lock, flags);
break;
default:
ret = -EINVAL;
@@ -450,289 +723,1434 @@ static int steelseries_headset_battery_get_property(struct power_supply *psy,
return ret;
}
-static void
-steelseries_headset_set_wireless_status(struct hid_device *hdev,
- bool connected)
+static enum power_supply_property steelseries_battery_props[] = {
+ POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+};
+
+/* Forward declarations for battery request functions */
+static int steelseries_arctis_1_request_battery(struct hid_device *hdev);
+static int steelseries_arctis_7_plus_request_battery(struct hid_device *hdev);
+static int steelseries_arctis_9_request_battery(struct hid_device *hdev);
+static int steelseries_arctis_nova_request_battery(struct hid_device *hdev);
+static int steelseries_arctis_nova_3p_request_battery(struct hid_device *hdev);
+static int
+steelseries_arctis_pro_wireless_request_battery(struct hid_device *hdev);
+
+static int steelseries_request_battery(struct hid_device *hdev)
{
- struct usb_interface *intf;
+ u16 product = hdev->product;
- if (!hid_is_usb(hdev))
- return;
+ /* Route to device-specific battery request handler */
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X)
+ return steelseries_arctis_1_request_battery(hdev);
- intf = to_usb_interface(hdev->dev.parent);
- usb_set_wireless_status(intf, connected ?
- USB_WIRELESS_STATUS_CONNECTED :
- USB_WIRELESS_STATUS_DISCONNECTED);
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY)
+ return steelseries_arctis_7_plus_request_battery(hdev);
+
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
+ return steelseries_arctis_9_request_battery(hdev);
+
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_PRO)
+ return steelseries_arctis_pro_wireless_request_battery(hdev);
+
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X)
+ return steelseries_arctis_nova_3p_request_battery(hdev);
+
+ /* All other Nova series use the same battery request */
+ return steelseries_arctis_nova_request_battery(hdev);
}
-static enum power_supply_property steelseries_headset_battery_props[] = {
- POWER_SUPPLY_PROP_MODEL_NAME,
- POWER_SUPPLY_PROP_MANUFACTURER,
- POWER_SUPPLY_PROP_PRESENT,
- POWER_SUPPLY_PROP_STATUS,
- POWER_SUPPLY_PROP_SCOPE,
- POWER_SUPPLY_PROP_CAPACITY,
- POWER_SUPPLY_PROP_CAPACITY_LEVEL,
-};
+static void steelseries_battery_timer_tick(struct work_struct *work)
+{
+ struct steelseries_device *sd = container_of(
+ work, struct steelseries_device, battery_work.work);
+
+ steelseries_request_battery(sd->hdev);
+}
-static int steelseries_headset_battery_register(struct steelseries_device *sd)
+static int steelseries_battery_register(struct steelseries_device *sd)
{
static atomic_t battery_no = ATOMIC_INIT(0);
- struct power_supply_config battery_cfg = { .drv_data = sd, };
+ struct power_supply_config battery_cfg = {
+ .drv_data = sd,
+ };
unsigned long n;
int ret;
sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
- sd->battery_desc.properties = steelseries_headset_battery_props;
- sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_headset_battery_props);
- sd->battery_desc.get_property = steelseries_headset_battery_get_property;
+ sd->battery_desc.properties = steelseries_battery_props;
+ sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_battery_props);
+ sd->battery_desc.get_property = steelseries_battery_get_property;
sd->battery_desc.use_for_apm = 0;
n = atomic_inc_return(&battery_no) - 1;
- sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
- "steelseries_headset_battery_%ld", n);
+ sd->battery_desc.name =
+ devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
+ "steelseries_headset_battery_%ld", n);
if (!sd->battery_desc.name)
return -ENOMEM;
- /* avoid the warning of 0% battery while waiting for the first info */
steelseries_headset_set_wireless_status(sd->hdev, false);
- sd->battery_capacity = 100;
+ sd->battery_capacity =
+ 100; /* Start with full to avoid low battery warnings */
sd->battery_charging = false;
+ sd->headset_connected = false;
+ sd->chatmix_level = 64;
- sd->battery = devm_power_supply_register(&sd->hdev->dev,
- &sd->battery_desc, &battery_cfg);
+ sd->battery = devm_power_supply_register(
+ &sd->hdev->dev, &sd->battery_desc, &battery_cfg);
if (IS_ERR(sd->battery)) {
ret = PTR_ERR(sd->battery);
- hid_err(sd->hdev,
- "%s:power_supply_register failed with error %d\n",
- __func__, ret);
+ hid_err(sd->hdev, "Failed to register battery: %d\n", ret);
return ret;
}
power_supply_powers(sd->battery, &sd->hdev->dev);
- INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
- steelseries_headset_fetch_battery(sd->hdev);
+ INIT_DELAYED_WORK(&sd->battery_work, steelseries_battery_timer_tick);
+ steelseries_request_battery(sd->hdev);
- if (sd->quirks & STEELSERIES_ARCTIS_9) {
- /* The first fetch_battery request can remain unanswered in some cases */
- schedule_delayed_work(&sd->battery_work,
- msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
+ /* Arctis 9 may need a retry */
+ if (sd->hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
+ schedule_delayed_work(
+ &sd->battery_work,
+ msecs_to_jiffies(
+ STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
}
return 0;
}
-static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t usage_page)
+/* Helper function to send feature reports */
+static int steelseries_send_feature_report(struct hid_device *hdev,
+ const u8 *data, size_t len)
{
- return hdev->rdesc[0] == 0x06 &&
- hdev->rdesc[1] == usage_page &&
- hdev->rdesc[2] == 0xff;
+ u8 *buf;
+ int ret;
+
+ buf = kmemdup(data, len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = hid_hw_raw_request(hdev, data[0], buf, len, HID_FEATURE_REPORT,
+ HID_REQ_SET_REPORT);
+ kfree(buf);
+
+ if (ret < 0)
+ return ret;
+ if (ret < len)
+ return -EIO;
+
+ return 0;
}
-static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
+/* Helper function to send output reports */
+static int steelseries_send_output_report(struct hid_device *hdev,
+ const u8 *data, size_t len)
{
- struct steelseries_device *sd;
+ u8 *buf;
int ret;
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
-#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
- (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
- return steelseries_srws1_probe(hdev, id);
-#else
- return -ENODEV;
-#endif
- }
-
- sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
- if (!sd)
+ buf = kmemdup(data, len, GFP_KERNEL);
+ if (!buf)
return -ENOMEM;
- hid_set_drvdata(hdev, sd);
- sd->hdev = hdev;
- sd->quirks = id->driver_data;
- ret = hid_parse(hdev);
- if (ret)
+ /* Use raw_request with OUTPUT_REPORT type for devices without Interrupt OUT */
+ ret = hid_hw_raw_request(hdev, data[0], buf, len, HID_OUTPUT_REPORT,
+ HID_REQ_SET_REPORT);
+ kfree(buf);
+
+ if (ret < 0)
return ret;
+ if (ret < len)
+ return -EIO;
- if (sd->quirks & STEELSERIES_ARCTIS_9 &&
- !steelseries_is_vendor_usage_page(hdev, 0xc0))
- return -ENODEV;
+ return 0;
+}
- spin_lock_init(&sd->lock);
+/* Sidetone level attribute */
+static ssize_t sidetone_level_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ /* Sidetone is write-only, no way to read current value */
+ return sysfs_emit(buf, "Write-only attribute (0-128)\n");
+}
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
- if (ret)
- return ret;
+static ssize_t sidetone_level_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ u16 product = hdev->product;
+ unsigned int value;
+ u8 data[64] = { 0 };
+ int ret;
- ret = hid_hw_open(hdev);
- if (ret)
- return ret;
+ if (kstrtouint(buf, 10, &value))
+ return -EINVAL;
+ if (value > 128)
+ return -EINVAL;
+
+ /* Device-specific sidetone mappings */
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2) {
+ /* Map 0-128 to 0x00-0x12 (18) */
+ u8 level = (value * 0x12) / 128;
+
+ if (level == 0) {
+ data[0] = 0x06;
+ data[1] = 0x35;
+ data[2] = 0x00;
+ ret = steelseries_send_feature_report(hdev, data, 31);
+ } else {
+ data[0] = 0x06;
+ data[1] = 0x35;
+ data[2] = 0x01;
+ data[3] = 0x00;
+ data[4] = level;
+ ret = steelseries_send_feature_report(hdev, data, 31);
+ }
+ if (ret >= 0) {
+ /* Save state */
+ data[0] = 0x06;
+ data[1] = 0x09;
+ steelseries_send_feature_report(hdev, data, 31);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY) {
+ /* Map to 0-3 levels */
+ u8 level;
+
+ if (value < 26)
+ level = 0x0;
+ else if (value < 51)
+ level = 0x1;
+ else if (value < 76)
+ level = 0x2;
+ else
+ level = 0x3;
+
+ data[0] = 0x00;
+ data[1] = 0x39;
+ data[2] = level;
+ ret = steelseries_send_feature_report(hdev, data, 64);
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
+ /* Arctis 9: exponential mapping to 0xc0-0xfd */
+ u8 level;
+
+ if (value == 0)
+ level = 0xc0;
+ else
+ level = 0xc0 + ((value * (0xfd - 0xc0)) / 128);
+
+ data[0] = 0x06;
+ data[1] = 0x00;
+ data[2] = level;
+ ret = steelseries_send_feature_report(hdev, data, 31);
+ if (ret >= 0) {
+ data[0] = 0x90;
+ data[1] = 0x00;
+ steelseries_send_feature_report(hdev, data, 31);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_PRO) {
+ /* Arctis Pro Wireless: 0x00-0x09 */
+ u8 level = (value * 0x09) / 128;
+
+ data[0] = 0x39;
+ data[1] = 0xAA;
+ data[2] = level;
+ ret = steelseries_send_feature_report(hdev, data, 31);
+ if (ret >= 0) {
+ data[0] = 0x90;
+ data[1] = 0xAA;
+ steelseries_send_feature_report(hdev, data, 31);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3) {
+ /* Nova 3: 0-3 levels */
+ u8 level;
+
+ if (value < 26)
+ level = 0x0;
+ else if (value < 51)
+ level = 0x1;
+ else if (value < 76)
+ level = 0x2;
+ else
+ level = 0x3;
+
+ data[0] = 0x06;
+ data[1] = 0x39;
+ data[2] = level;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ data[0] = 0x06;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X) {
+ /* Nova 3P/3X: Map to 0-10 */
+ u8 level = (value * 0x0a) / 128;
+
+ data[0] = 0x39;
+ data[1] = level;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ data[0] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) {
+ /* Nova 5: Map to 0-10 */
+ u8 level = (value * 0x0a) / 128;
+
+ data[0] = 0x00;
+ data[1] = 0x39;
+ data[2] = level;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ data[0] = 0x00;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ data[0] = 0x00;
+ data[1] = 0x35;
+ data[2] = 0x01;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X) {
+ /* Nova Pro: 0-3 only */
+ if (value > 3)
+ return -EINVAL;
+ data[0] = 0x06;
+ data[1] = 0x39;
+ data[2] = value;
+ ret = steelseries_send_output_report(hdev, data, 31);
+ if (ret >= 0) {
+ data[0] = 0x06;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 31);
+ }
+ } else {
+ /* Nova 7 series: 0-3 levels */
+ u8 level;
+
+ if (value < 26)
+ level = 0x0;
+ else if (value < 51)
+ level = 0x1;
+ else if (value < 76)
+ level = 0x2;
+ else
+ level = 0x3;
+
+ data[0] = 0x00;
+ data[1] = 0x39;
+ data[2] = level;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ memset(data, 0, sizeof(data));
+ data[0] = 0x00;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ }
- if (steelseries_headset_battery_register(sd) < 0)
- hid_err(sd->hdev,
- "Failed to register battery for headset\n");
+ return (ret < 0) ? ret : count;
+}
+static DEVICE_ATTR_RW(sidetone_level);
- return ret;
+/* Inactive time attribute */
+static ssize_t inactive_time_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "Write-only attribute (0-90 minutes)\n");
}
-static void steelseries_remove(struct hid_device *hdev)
+static ssize_t inactive_time_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
{
- struct steelseries_device *sd;
- unsigned long flags;
+ struct hid_device *hdev = to_hid_device(dev);
+ u16 product = hdev->product;
+ unsigned int value;
+ u8 data[64] = { 0 };
+ int ret;
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
-#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
- (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
- hid_hw_stop(hdev);
-#endif
- return;
+ if (kstrtouint(buf, 10, &value))
+ return -EINVAL;
+ if (value > 90)
+ return -EINVAL;
+
+ /* Device-specific mappings */
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X) {
+ data[0] = 0x06;
+ data[1] = 0x53;
+ data[2] = value;
+ ret = steelseries_send_feature_report(hdev, data, 31);
+ if (ret >= 0) {
+ data[0] = 0x06;
+ data[1] = 0x09;
+ steelseries_send_feature_report(hdev, data, 31);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2) {
+ data[0] = 0x06;
+ data[1] = 0x51;
+ data[2] = value;
+ ret = steelseries_send_feature_report(hdev, data, 31);
+ if (ret >= 0) {
+ data[0] = 0x06;
+ data[1] = 0x09;
+ steelseries_send_feature_report(hdev, data, 31);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY) {
+ data[0] = 0x00;
+ data[1] = 0xa3;
+ data[2] = value;
+ ret = steelseries_send_feature_report(hdev, data, 64);
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
+ /* Arctis 9 uses seconds */
+ u32 seconds = value * 60;
+
+ data[0] = 0x04;
+ data[1] = 0x00;
+ data[2] = (seconds >> 8) & 0xff;
+ data[3] = seconds & 0xff;
+ ret = steelseries_send_feature_report(hdev, data, 31);
+ if (ret >= 0) {
+ data[0] = 0x90;
+ data[1] = 0x00;
+ steelseries_send_feature_report(hdev, data, 31);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_PRO) {
+ /* Pro Wireless uses 10-minute increments */
+ u8 increments = value / 10;
+
+ data[0] = 0x3c;
+ data[1] = 0xAA;
+ data[2] = increments;
+ ret = steelseries_send_feature_report(hdev, data, 31);
+ if (ret >= 0) {
+ data[0] = 0x90;
+ data[1] = 0xAA;
+ steelseries_send_feature_report(hdev, data, 31);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X) {
+ /* Map to specific values */
+ u8 mapped;
+
+ if (value >= 90)
+ mapped = 90;
+ else if (value >= 75)
+ mapped = 75;
+ else if (value >= 60)
+ mapped = 60;
+ else if (value >= 45)
+ mapped = 45;
+ else if (value >= 30)
+ mapped = 30;
+ else if (value >= 15)
+ mapped = 15;
+ else if (value >= 10)
+ mapped = 10;
+ else if (value >= 5)
+ mapped = 5;
+ else if (value >= 1)
+ mapped = 1;
+ else
+ mapped = 0;
+
+ data[0] = 0xa3;
+ data[1] = mapped;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ data[0] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X) {
+ /* Map to enum values */
+ u8 mapped;
+
+ if (value >= 45)
+ mapped = 6;
+ else if (value >= 23)
+ mapped = 5;
+ else if (value >= 13)
+ mapped = 4;
+ else if (value >= 8)
+ mapped = 3;
+ else if (value >= 3)
+ mapped = 2;
+ else if (value > 0)
+ mapped = 1;
+ else
+ mapped = 0;
+
+ data[0] = 0x06;
+ data[1] = 0xc1;
+ data[2] = mapped;
+ ret = steelseries_send_output_report(hdev, data, 31);
+ if (ret >= 0) {
+ data[0] = 0x06;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 31);
+ }
+ } else {
+ /* Nova 5/7 series */
+ data[0] = 0x00;
+ data[1] = 0xa3;
+ data[2] = value;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ memset(data, 0, sizeof(data));
+ data[0] = 0x00;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
}
- sd = hid_get_drvdata(hdev);
+ return (ret < 0) ? ret : count;
+}
+static DEVICE_ATTR_RW(inactive_time);
- spin_lock_irqsave(&sd->lock, flags);
- sd->removed = true;
- spin_unlock_irqrestore(&sd->lock, flags);
+/* ChatMix level attribute (read-only) */
+static ssize_t chatmix_level_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct steelseries_device *sd = hid_get_drvdata(hdev);
- cancel_delayed_work_sync(&sd->battery_work);
+ return sysfs_emit(buf, "%d\n", sd->chatmix_level);
+}
+static DEVICE_ATTR_RO(chatmix_level);
- hid_hw_close(hdev);
- hid_hw_stop(hdev);
+/* Microphone mute LED brightness */
+static ssize_t mic_mute_led_brightness_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf,
+ "Write-only (0-3 or 0-10 depending on device)\n");
}
-static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
- __u8 *rdesc, unsigned int *rsize)
+static ssize_t mic_mute_led_brightness_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
{
- if (hdev->vendor != USB_VENDOR_ID_STEELSERIES ||
- hdev->product != USB_DEVICE_ID_STEELSERIES_SRWS1)
- return rdesc;
+ struct hid_device *hdev = to_hid_device(dev);
+ u16 product = hdev->product;
+ unsigned int value;
+ u8 data[64] = { 0 };
+ int ret;
- if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8
- && rdesc[29] == 0xbb && rdesc[40] == 0xc5) {
- hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n");
- *rsize = sizeof(steelseries_srws1_rdesc_fixed);
- return steelseries_srws1_rdesc_fixed;
+ if (kstrtouint(buf, 10, &value))
+ return -EINVAL;
+
+ /* Device-specific validation */
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3) {
+ if (value > 3)
+ return -EINVAL;
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) {
+ if (value > 10)
+ return -EINVAL;
+ /* Map special values */
+ if (value == 2)
+ value = 0x04;
+ else if (value == 3)
+ value = 0x0a;
+ } else {
+ if (value > 3)
+ return -EINVAL;
}
- return rdesc;
+
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3) {
+ data[0] = 0x06;
+ data[1] = 0xae;
+ data[2] = value;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ data[0] = 0x06;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) {
+ data[0] = 0x00;
+ data[1] = 0xae;
+ data[2] = value;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ data[0] = 0x00;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ data[0] = 0x00;
+ data[1] = 0x35;
+ data[2] = 0x01;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ } else {
+ /* Nova 7 series */
+ data[0] = 0x00;
+ data[1] = 0xae;
+ data[2] = value;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ memset(data, 0, sizeof(data));
+ data[0] = 0x00;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ }
+
+ return (ret < 0) ? ret : count;
}
+static DEVICE_ATTR_RW(mic_mute_led_brightness);
-static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in, uint8_t max_in)
+/* Microphone volume */
+static ssize_t mic_volume_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
- if (capacity >= max_in)
- return 100;
- if (capacity <= min_in)
- return 0;
- return (capacity - min_in) * 100 / (max_in - min_in);
+ return sysfs_emit(buf, "Write-only (0-128)\n");
+}
+
+static ssize_t mic_volume_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ u16 product = hdev->product;
+ unsigned int value;
+ u8 data[64] = { 0 };
+ u8 mapped;
+ int ret;
+
+ if (kstrtouint(buf, 10, &value))
+ return -EINVAL;
+ if (value > 128)
+ return -EINVAL;
+
+ /* Map 0-128 to device-specific range */
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3) {
+ /* Map to 0-10 */
+ if (value < 13)
+ mapped = 0x00;
+ else if (value < 25)
+ mapped = 0x01;
+ else if (value < 37)
+ mapped = 0x02;
+ else if (value < 49)
+ mapped = 0x03;
+ else if (value < 61)
+ mapped = 0x04;
+ else if (value < 73)
+ mapped = 0x05;
+ else if (value < 85)
+ mapped = 0x06;
+ else if (value < 97)
+ mapped = 0x07;
+ else if (value < 109)
+ mapped = 0x08;
+ else if (value < 121)
+ mapped = 0x09;
+ else
+ mapped = 0x0a;
+
+ data[0] = 0x06;
+ data[1] = 0x37;
+ data[2] = mapped;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ data[0] = 0x06;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X) {
+ /* Map to 0-14 */
+ mapped = (value * 0x0e) / 128;
+ data[0] = 0x37;
+ data[1] = mapped;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ data[0] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ } else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) {
+ /* Map to 0-15 */
+ mapped = value / 8;
+ if (mapped == 16)
+ mapped = 15;
+
+ data[0] = 0x00;
+ data[1] = 0x37;
+ data[2] = mapped;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ data[0] = 0x00;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ data[0] = 0x00;
+ data[1] = 0x35;
+ data[2] = 0x01;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ } else {
+ /* Nova 7: map to 0-7 */
+ mapped = value / 16;
+ if (mapped == 8)
+ mapped = 7;
+
+ data[0] = 0x00;
+ data[1] = 0x37;
+ data[2] = mapped;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ memset(data, 0, sizeof(data));
+ data[0] = 0x00;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ }
+
+ return (ret < 0) ? ret : count;
+}
+static DEVICE_ATTR_RW(mic_volume);
+
+/* Volume limiter */
+static ssize_t volume_limiter_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "Write-only (0=off, 1=on)\n");
+}
+
+static ssize_t volume_limiter_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ u16 product = hdev->product;
+ unsigned int value;
+ u8 data[64] = { 0 };
+ int ret;
+
+ if (kstrtouint(buf, 10, &value))
+ return -EINVAL;
+ if (value > 1)
+ return -EINVAL;
+
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) {
+ data[0] = 0x00;
+ data[1] = 0x27;
+ data[2] = value;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ data[0] = 0x00;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ data[0] = 0x00;
+ data[1] = 0x35;
+ data[2] = 0x01;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ } else {
+ /* Nova 7 series */
+ data[0] = 0x00;
+ data[1] = 0x3a;
+ data[2] = value;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ memset(data, 0, sizeof(data));
+ data[0] = 0x00;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+ }
+
+ return (ret < 0) ? ret : count;
}
+static DEVICE_ATTR_RW(volume_limiter);
-static int steelseries_headset_raw_event(struct hid_device *hdev,
- struct hid_report *report, u8 *read_buf,
- int size)
+/* Bluetooth when powered on */
+static ssize_t bluetooth_on_power_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
+ return sysfs_emit(buf, "Write-only (0=off, 1=on)\n");
+}
+
+static ssize_t bluetooth_on_power_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ unsigned int value;
+ u8 data[64] = { 0 };
+ int ret;
+
+ if (kstrtouint(buf, 10, &value))
+ return -EINVAL;
+ if (value > 1)
+ return -EINVAL;
+
+ data[0] = 0x00;
+ data[1] = 0xb2;
+ data[2] = value;
+ ret = steelseries_send_output_report(hdev, data, 64);
+ if (ret >= 0) {
+ /* Send save state command as output report */
+ memset(data, 0, sizeof(data));
+ data[0] = 0x00;
+ data[1] = 0x09;
+ steelseries_send_output_report(hdev, data, 64);
+ }
+
+ return (ret < 0) ? ret : count;
+}
+static DEVICE_ATTR_RW(bluetooth_on_power);
+
+/* Bluetooth call volume */
+static ssize_t bluetooth_call_vol_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf,
+ "Write-only (0=nothing, 1=-12dB, 2=mute game)\n");
+}
+
+static ssize_t bluetooth_call_vol_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ unsigned int value;
+ u8 data[64] = { 0 };
+ int ret;
+
+ if (kstrtouint(buf, 10, &value))
+ return -EINVAL;
+ if (value > 2)
+ return -EINVAL;
+
+ data[0] = 0x00;
+ data[1] = 0xb3;
+ data[2] = value;
+ ret = steelseries_send_output_report(hdev, data, 64);
+
+ return (ret < 0) ? ret : count;
+}
+static DEVICE_ATTR_RW(bluetooth_call_vol);
+
+/* Attribute group setup based on capabilities */
+static struct attribute *steelseries_attrs[] = {
+ &dev_attr_sidetone_level.attr,
+ &dev_attr_inactive_time.attr,
+ &dev_attr_chatmix_level.attr,
+ &dev_attr_mic_mute_led_brightness.attr,
+ &dev_attr_mic_volume.attr,
+ &dev_attr_volume_limiter.attr,
+ &dev_attr_bluetooth_on_power.attr,
+ &dev_attr_bluetooth_call_vol.attr,
+ NULL
+};
+
+static umode_t steelseries_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct hid_device *hdev = to_hid_device(dev);
struct steelseries_device *sd = hid_get_drvdata(hdev);
+ unsigned long caps = sd->info->capabilities;
+
+ if (attr == &dev_attr_sidetone_level.attr)
+ return (caps & SS_CAP_SIDETONE) ? attr->mode : 0;
+ if (attr == &dev_attr_inactive_time.attr)
+ return (caps & SS_CAP_INACTIVE_TIME) ? attr->mode : 0;
+ if (attr == &dev_attr_chatmix_level.attr)
+ return (caps & SS_CAP_CHATMIX) ? attr->mode : 0;
+ if (attr == &dev_attr_mic_mute_led_brightness.attr)
+ return (caps & SS_CAP_MIC_MUTE_LED) ? attr->mode : 0;
+ if (attr == &dev_attr_mic_volume.attr)
+ return (caps & SS_CAP_MIC_VOLUME) ? attr->mode : 0;
+ if (attr == &dev_attr_volume_limiter.attr)
+ return (caps & SS_CAP_VOLUME_LIMITER) ? attr->mode : 0;
+ if (attr == &dev_attr_bluetooth_on_power.attr)
+ return (caps & SS_CAP_BT_POWER_ON) ? attr->mode : 0;
+ if (attr == &dev_attr_bluetooth_call_vol.attr)
+ return (caps & SS_CAP_BT_CALL_VOL) ? attr->mode : 0;
+
+ return 0;
+}
+
+static const struct attribute_group steelseries_attr_group = {
+ .attrs = steelseries_attrs,
+ .is_visible = steelseries_attr_is_visible,
+};
+
+static int steelseries_arctis_1_request_battery(struct hid_device *hdev)
+{
+ const u8 data[] = { 0x06, 0x12 };
+
+ return steelseries_send_feature_report(hdev, data, sizeof(data));
+}
+
+static int steelseries_arctis_7_plus_request_battery(struct hid_device *hdev)
+{
+ const u8 data[] = { 0x00, 0xb0 };
+
+ return steelseries_send_output_report(hdev, data, sizeof(data));
+}
+
+static int steelseries_arctis_9_request_battery(struct hid_device *hdev)
+{
+ const u8 data[] = { 0x00, 0x20 };
+
+ return steelseries_send_feature_report(hdev, data, sizeof(data));
+}
+
+static int steelseries_arctis_nova_request_battery(struct hid_device *hdev)
+{
+ const u8 data[] = { 0x00, 0xb0 };
+
+ return steelseries_send_output_report(hdev, data, sizeof(data));
+}
+
+static int steelseries_arctis_nova_3p_request_battery(struct hid_device *hdev)
+{
+ const u8 data[] = { 0xb0 };
+
+ return steelseries_send_output_report(hdev, data, sizeof(data));
+}
+
+static int
+steelseries_arctis_pro_wireless_request_battery(struct hid_device *hdev)
+{
+ /* Request battery - response will arrive asynchronously via raw_event */
+ const u8 data[] = { 0x40, 0xAA };
+
+ return steelseries_send_output_report(hdev, data, sizeof(data));
+}
+
+static int steelseries_raw_event(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ struct steelseries_device *sd = hid_get_drvdata(hdev);
+ u16 product = hdev->product;
int capacity = sd->battery_capacity;
bool connected = sd->headset_connected;
bool charging = sd->battery_charging;
- unsigned long flags;
+ int chatmix = sd->chatmix_level;
+ unsigned long flags = 0;
- /* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ /* Skip SRW-S1 */
+ if (product == USB_DEVICE_ID_STEELSERIES_SRWS1)
return 0;
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
- hid_dbg(sd->hdev,
- "Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
- if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
- memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request))) {
- if (!delayed_work_pending(&sd->battery_work))
- goto request_battery;
- return 0;
+ /* Arctis 1 family (Arctis 1, 1X, 7P, 7X) */
+ if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X) {
+ if (size < 8)
+ goto schedule_work;
+
+ if (data[2] == 0x01) {
+ connected = false;
+ capacity = 100;
+ } else {
+ connected = true;
+ capacity = data[3];
+ if (capacity > 100)
+ capacity = 100;
}
- if (read_buf[2] == 0x01) {
+ }
+
+ /* Arctis 7 (original and 2019) */
+ else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2) {
+ /* Battery response is 8 bytes for Arctis 7 */
+ if (size < 8)
+ goto schedule_work;
+
+ connected = true;
+ charging = false;
+
+ /* Battery level is in data[2] */
+ capacity = data[2];
+ if (capacity > 100)
+ capacity = 100;
+ }
+
+ /* Arctis 7+ family */
+ else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY) {
+ if (size < 6)
+ goto schedule_work;
+
+ /* data[1] == 0x01 means HEADSET_OFFLINE */
+ if (data[1] == 0x01) {
connected = false;
capacity = 100;
} else {
connected = true;
- capacity = read_buf[3];
+ /* data[3] == 0x01 means charging */
+ charging = (data[3] == 0x01);
+ /* data[2] contains battery level (0x00-0x04 range) */
+ capacity = steelseries_map_battery(data[2], 0x00, 0x04);
+
+ /* ChatMix available */
+ if (size >= 6 &&
+ (sd->info->capabilities & SS_CAP_CHATMIX)) {
+ /* data[4] is game (0-100), data[5] is chat (0-100) */
+ int game = (data[4] * 64) / 100;
+ int chat = (data[5] * -64) / 100;
+
+ chatmix = 64 - (chat + game);
+ }
}
}
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
- hid_dbg(sd->hdev,
- "Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf);
- if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) {
- if (!delayed_work_pending(&sd->battery_work))
- goto request_battery;
- return 0;
+ /* Arctis 9 */
+ else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
+ if (size < 12)
+ goto schedule_work;
+
+ connected = true;
+
+ charging = (data[4] == 0x01);
+
+ capacity = steelseries_map_battery(data[3], 0x64, 0x9A);
+
+ /* ChatMix: data[9] is game (0-19), data[10] is chat (0-19) */
+ if (size >= 11 && (sd->info->capabilities & SS_CAP_CHATMIX)) {
+ int game = (data[9] * 64) / 19;
+ int chat = (data[10] * -64) / 19;
+
+ chatmix = 64 - (chat + game);
}
+ }
- if (read_buf[0] == 0xaa && read_buf[1] == 0x01) {
+ /* Arctis Pro Wireless */
+ else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_PRO) {
+ if (size >= 2 && (data[0] == 0x02 || data[0] == 0x04)) {
+ /* This is a connection status response */
+ /* HEADSET_OFFLINE */
+ if (data[0] == 0x02) {
+ connected = false;
+ capacity = 100;
+ charging = false;
+ }
+ /* HEADSET_ONLINE (0x04) */
+ else {
+ connected = true;
+ charging = false;
+ }
+ } else if (size >= 1 && sd->headset_connected) {
+ /* This is a battery level response (only valid if headset connected) */
+ /* Battery range is 0x00-0x04 */
+ capacity = steelseries_map_battery(data[0], 0x00, 0x04);
+ }
+ }
+
+ /* Arctis Nova 3 */
+ else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3) {
+ /* No battery monitoring for wired headset */
+ goto schedule_work;
+ }
+
+ /* Arctis Nova 3P/3X Wireless */
+ else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X) {
+ if (size < 4)
+ goto schedule_work;
+
+ /* data[1] == 0x02 means HEADSET_OFFLINE */
+ if (data[1] == 0x02) {
+ connected = false;
+ capacity = 100;
+ } else {
connected = true;
- charging = read_buf[4] == 0x01;
+ charging = false;
+ /* data[3] contains battery level (0x00-0x64 range, 0-100) */
+ capacity = steelseries_map_battery(data[3], 0x00, 0x64);
+ }
+ }
+
+ /* Arctis Nova 5/5X */
+ else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) {
+ if (size < 16)
+ goto schedule_work;
+
+ /* data[1] == 0x02 means HEADSET_OFFLINE */
+ if (data[1] == 0x02) {
+ connected = false;
+ capacity = 100;
+ } else {
+ connected = true;
+ /* data[4] == 0x01 means charging */
+ charging = (data[4] == 0x01);
+ /* data[3] contains battery level (0-100) */
+ capacity = data[3];
+ if (capacity > 100)
+ capacity = 100;
+
+ /* ChatMix available */
+ if (size >= 7 &&
+ (sd->info->capabilities & SS_CAP_CHATMIX)) {
+ /* data[5] is game (0-100), data[6] is chat (0-100) */
+ int game = (data[5] * 64) / 100;
+ int chat = (data[6] * -64) / 100;
+
+ chatmix = 64 - (chat + game);
+ }
+ }
+ }
- /*
- * Found no official documentation about min and max.
- * Values defined by testing.
- */
- capacity = steelseries_headset_map_capacity(read_buf[3], 0x68, 0x9d);
+ /* Arctis Nova 7 family */
+ else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_REV2 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2 ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2) {
+ if (size < 8)
+ goto schedule_work;
+
+ /* data[3] == 0x00 means HEADSET_OFFLINE */
+ if (data[3] == 0x00) {
+ connected = false;
+ capacity = 100;
} else {
- /*
- * Device is off and sends the last known status read_buf[1] == 0x03 or
- * there is no known status of the device read_buf[0] == 0x55
- */
+ connected = true;
+ /* data[3] == 0x01 means charging */
+ charging = (data[3] == 0x01);
+ /* data[2] contains battery level (0x00-0x04 range) */
+ capacity = steelseries_map_battery(data[2], 0x00, 0x04);
+
+ /* ChatMix available */
+ if (size >= 6 &&
+ (sd->info->capabilities & SS_CAP_CHATMIX)) {
+ /* data[4] is game (0-100), data[5] is chat (0-100) */
+ int game = (data[4] * 64) / 100;
+ int chat = (data[5] * -64) / 100;
+
+ chatmix = 64 - (chat + game);
+ }
+ }
+ }
+
+ /* Arctis Nova Pro Wireless */
+ else if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO ||
+ product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X) {
+ if (size < 16)
+ goto schedule_work;
+
+ /* data[15] contains headset status */
+ if (data[15] == 0x01) { /* HEADSET_OFFLINE */
connected = false;
+ capacity = 100;
+ } else if (data[15] == 0x02) { /* HEADSET_CABLE_CHARGING */
+ connected = true;
+ charging = true;
+ /* data[6] contains battery level (0x00-0x08 range) */
+ capacity = steelseries_map_battery(data[6], 0x00, 0x08);
+ } else if (data[15] == 0x08) { /* HEADSET_ONLINE */
+ connected = true;
charging = false;
+ /* data[6] contains battery level (0x00-0x08 range) */
+ capacity = steelseries_map_battery(data[6], 0x00, 0x08);
+ } else {
+ /* Unknown status */
+ goto schedule_work;
}
}
+ /* Update state if changed */
+ spin_lock_irqsave(&sd->lock, flags);
+
if (connected != sd->headset_connected) {
- hid_dbg(sd->hdev,
+ hid_dbg(hdev,
"Connected status changed from %sconnected to %sconnected\n",
sd->headset_connected ? "" : "not ",
connected ? "" : "not ");
sd->headset_connected = connected;
+ spin_unlock_irqrestore(&sd->lock, flags);
steelseries_headset_set_wireless_status(hdev, connected);
+ spin_lock_irqsave(&sd->lock, flags);
}
if (capacity != sd->battery_capacity) {
- hid_dbg(sd->hdev,
- "Battery capacity changed from %d%% to %d%%\n",
+ hid_dbg(hdev, "Battery capacity changed from %d%% to %d%%\n",
sd->battery_capacity, capacity);
sd->battery_capacity = capacity;
+ spin_unlock_irqrestore(&sd->lock, flags);
power_supply_changed(sd->battery);
+ spin_lock_irqsave(&sd->lock, flags);
}
if (charging != sd->battery_charging) {
- hid_dbg(sd->hdev,
+ hid_dbg(hdev,
"Battery charging status changed from %scharging to %scharging\n",
sd->battery_charging ? "" : "not ",
charging ? "" : "not ");
sd->battery_charging = charging;
+ spin_unlock_irqrestore(&sd->lock, flags);
power_supply_changed(sd->battery);
+ spin_lock_irqsave(&sd->lock, flags);
}
-request_battery:
- spin_lock_irqsave(&sd->lock, flags);
+ if (chatmix != sd->chatmix_level)
+ sd->chatmix_level = chatmix;
+
+schedule_work:
if (!sd->removed)
- schedule_delayed_work(&sd->battery_work,
- msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
+ schedule_delayed_work(
+ &sd->battery_work,
+ msecs_to_jiffies(
+ STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
spin_unlock_irqrestore(&sd->lock, flags);
return 0;
}
-static const struct hid_device_id steelseries_devices[] = {
- { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1),
- .driver_data = STEELSERIES_SRWS1 },
+static bool steelseries_is_vendor_usage_page(struct hid_device *hdev,
+ u8 usage_page)
+{
+ return hdev->rdesc[0] == 0x06 && hdev->rdesc[1] == usage_page &&
+ hdev->rdesc[2] == 0xff;
+}
+
+static int steelseries_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ struct steelseries_device_info *info =
+ (struct steelseries_device_info *)id->driver_data;
+ struct steelseries_device *sd;
+ struct usb_interface *intf;
+ u8 interface_num;
+ int ret;
+
+ /* Legacy SRW-S1 handling */
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
+#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
+ return steelseries_srws1_probe(hdev, id);
+#else
+ return -ENODEV;
+#endif
+ }
+
+ /* Get interface number for binding check */
+ if (hid_is_usb(hdev)) {
+ intf = to_usb_interface(hdev->dev.parent);
+ interface_num = intf->cur_altsetting->desc.bInterfaceNumber;
+ } else {
+ /* Non-USB devices not supported for modern Arctis */
+ return -ENODEV;
+ }
+
+ /* Interface binding logic */
+ if (info->interface_binding_mode == 0) {
+ /* Mode 0: First enumerated (interface 0) */
+ if (interface_num != 0)
+ return -ENODEV;
+ } else {
+ /* Mode 1: Check bitmask */
+ if (!(info->valid_interfaces & BIT(interface_num)))
+ return -ENODEV;
+ }
+
+ sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
+ if (!sd)
+ return -ENOMEM;
+
+ sd->hdev = hdev;
+ sd->info = info;
+ hid_set_drvdata(hdev, sd);
+
+ ret = hid_parse(hdev);
+ if (ret)
+ return ret;
+
+ /* Arctis 9 requires vendor usage page check */
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9 &&
+ !steelseries_is_vendor_usage_page(hdev, 0xc0))
+ return -ENODEV;
+
+ spin_lock_init(&sd->lock);
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = hid_hw_open(hdev);
+ if (ret)
+ goto err_stop;
+
+ /* Register battery if supported */
+ if (info->capabilities & SS_CAP_BATTERY) {
+ ret = steelseries_battery_register(sd);
+ if (ret < 0)
+ hid_warn(hdev, "Failed to register battery: %d\n", ret);
+ }
+
+ /* Create sysfs attributes */
+ ret = sysfs_create_group(&hdev->dev.kobj, &steelseries_attr_group);
+ if (ret)
+ hid_warn(hdev, "Failed to create sysfs attributes: %d\n", ret);
+
+ hid_info(hdev, "SteelSeries %s initialized\n", info->name);
- { /* SteelSeries Arctis 1 Wireless for XBox */
- HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1),
- .driver_data = STEELSERIES_ARCTIS_1 },
+ return 0;
- { /* SteelSeries Arctis 9 Wireless for XBox */
- HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
- .driver_data = STEELSERIES_ARCTIS_9 },
+err_stop:
+ hid_hw_stop(hdev);
+ return ret;
+}
- { }
+static void steelseries_remove(struct hid_device *hdev)
+{
+ struct steelseries_device *sd;
+ unsigned long flags;
+
+ /* Legacy SRW-S1 */
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
+#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
+ hid_hw_stop(hdev);
+#endif
+ return;
+ }
+
+ sd = hid_get_drvdata(hdev);
+
+ sysfs_remove_group(&hdev->dev.kobj, &steelseries_attr_group);
+
+ spin_lock_irqsave(&sd->lock, flags);
+ sd->removed = true;
+ spin_unlock_irqrestore(&sd->lock, flags);
+
+ cancel_delayed_work_sync(&sd->battery_work);
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id steelseries_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_SRWS1),
+ .driver_data = STEELSERIES_SRWS1 },
+
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_1),
+ .driver_data = (unsigned long)&arctis_1_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
+ .driver_data = (unsigned long)&arctis_1_x_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_7),
+ .driver_data = (unsigned long)&arctis_7_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P),
+ .driver_data = (unsigned long)&arctis_7_p_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X),
+ .driver_data = (unsigned long)&arctis_7_x_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2),
+ .driver_data = (unsigned long)&arctis_7_gen2_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS),
+ .driver_data = (unsigned long)&arctis_7_plus_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P),
+ .driver_data = (unsigned long)&arctis_7_plus_p_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X),
+ .driver_data = (unsigned long)&arctis_7_plus_x_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY),
+ .driver_data = (unsigned long)&arctis_7_plus_destiny_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
+ .driver_data = (unsigned long)&arctis_9_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_PRO),
+ .driver_data = (unsigned long)&arctis_pro_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3),
+ .driver_data = (unsigned long)&arctis_nova_3_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P),
+ .driver_data = (unsigned long)&arctis_nova_3_p_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X),
+ .driver_data = (unsigned long)&arctis_nova_3_x_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5),
+ .driver_data = (unsigned long)&arctis_nova_5_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X),
+ .driver_data = (unsigned long)&arctis_nova_5_x_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7),
+ .driver_data = (unsigned long)&arctis_nova_7_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X),
+ .driver_data = (unsigned long)&arctis_nova_7_x_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P),
+ .driver_data = (unsigned long)&arctis_nova_7_p_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_REV2),
+ .driver_data = (unsigned long)&arctis_nova_7_x_rev2_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO),
+ .driver_data = (unsigned long)&arctis_nova_7_diablo_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW),
+ .driver_data = (unsigned long)&arctis_nova_7_wow_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2),
+ .driver_data = (unsigned long)&arctis_nova_7_gen2_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2),
+ .driver_data = (unsigned long)&arctis_nova_7_x_gen2_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO),
+ .driver_data = (unsigned long)&arctis_nova_pro_info },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
+ USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X),
+ .driver_data = (unsigned long)&arctis_nova_pro_x_info },
+ {}
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
@@ -742,12 +2160,13 @@ static struct hid_driver steelseries_driver = {
.probe = steelseries_probe,
.remove = steelseries_remove,
.report_fixup = steelseries_srws1_report_fixup,
- .raw_event = steelseries_headset_raw_event,
+ .raw_event = steelseries_raw_event,
};
-
module_hid_driver(steelseries_driver);
-MODULE_DESCRIPTION("HID driver for Steelseries devices");
+
+MODULE_DESCRIPTION("HID driver for SteelSeries devices");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
MODULE_AUTHOR("Simon Wood <simon@mungewell.org>");
MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");
+MODULE_AUTHOR("Sriman Achanta <srimanachanta@gmail.com>");
--
2.52.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/4] HID: hid-ids: Add SteelSeries Arctis headset device IDs
2026-01-12 4:19 ` [PATCH v2 1/4] HID: hid-ids: Add SteelSeries Arctis headset device IDs Sriman Achanta
@ 2026-01-12 13:08 ` Bastien Nocera
0 siblings, 0 replies; 15+ messages in thread
From: Bastien Nocera @ 2026-01-12 13:08 UTC (permalink / raw)
To: Sriman Achanta, Jiri Kosina, Benjamin Tissoires, linux-input,
linux-kernel
On Sun, 2026-01-11 at 23:19 -0500, Sriman Achanta wrote:
> Add USB device IDs for the complete SteelSeries Arctis headset
> lineup,
> including:
> - Arctis 1, 1 Wireless, 7, 7P, 7X variants
> - Arctis 7+ series (PS5, Xbox, Destiny editions)
> - Arctis 9 Wireless
> - Arctis Pro Wireless
> - Arctis Nova 3, 3P, 3X
> - Arctis Nova 5, 5X
> - Arctis Nova 7 series (multiple variants and special editions)
> - Arctis Nova Pro Wireless and Pro X
>
> This also fixes the existing ARCTIS_1 ID to use the correct product
> ID
> (0x12b3 instead of 0x12b6, which is actually the Arctis 1 Xbox
> variant).
"This also fixes" usually is a good way to tell you that this should
have been a separate patch.
It would be useful if you could change the indentation in a first
patch, "fix" the USB ID in a second patch, and add new IDs in a third
patch. Note that in your second patch, you'll need to change the source
code to use that new identifier otherwise you'll be breaking my headset
:)
Please make sure to CC: me on future patchsets you send.
>
> These IDs will be used by the updated hid-steelseries driver to
> provide
> battery monitoring, sidetone control, and other device-specific
> features
> for these wireless gaming headsets.
>
> Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
> ---
> drivers/hid/hid-ids.h | 33 +++++++++++++++++++++++++++++----
> 1 file changed, 29 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index d31711f1aaec..f4f91fb4c2b9 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -1303,10 +1303,35 @@
> #define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142
> #define USB_DEVICE_ID_STEAM_DECK 0x1205
>
> -#define USB_VENDOR_ID_STEELSERIES 0x1038
> -#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
> -#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
> -#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
> +#define USB_VENDOR_ID_STEELSERIES 0x1038
> +#define
> USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b3
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7 0x1260
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P 0x12d5
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X 0x12d7
> +#define
> USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2 0x12ad
> +#define
> USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS 0x220e
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P 0x2212
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X 0x2216
> +#define
> USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY 0x2236
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_PRO 0x1290
> +#define
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3 0x12ec
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P 0x2269
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X 0x226d
> +#define
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5 0x2232
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X 0x2253
> +#define
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 0x2202
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X 0x2206
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P 0x220a
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_REV2 0x2258
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO 0x223a
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW 0x227a
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2 0x227e
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2 0x229e
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO 0x12e0
> +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X 0x12e5
>
> #define USB_VENDOR_ID_SUN 0x0430
> #define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/4] HID: quirks: Add INPUT_CONFIGURED quirk for SteelSeries Arctis headsets
2026-01-12 4:19 ` [PATCH v2 2/4] HID: quirks: Add INPUT_CONFIGURED quirk for SteelSeries Arctis headsets Sriman Achanta
@ 2026-01-12 13:08 ` Bastien Nocera
0 siblings, 0 replies; 15+ messages in thread
From: Bastien Nocera @ 2026-01-12 13:08 UTC (permalink / raw)
To: Sriman Achanta, Jiri Kosina, Benjamin Tissoires, linux-input,
linux-kernel
On Sun, 2026-01-11 at 23:19 -0500, Sriman Achanta wrote:
> Add HID_QUIRK_INPUT_CONFIGURED for all SteelSeries Arctis headsets
> that
> require the hid-steelseries driver. This quirk ensures proper device
> initialization and prevents conflicts with generic HID drivers.
>
> The quirk is necessary because these devices expose multiple HID
> interfaces, and the hid-steelseries driver needs to bind to specific
> interfaces based on the device capabilities. Without this quirk, the
> generic HID driver may interfere with device-specific functionality
> like
> battery monitoring and feature controls.
Only devices that need it should be added to this list.
USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X (which used to be called
USB_DEVICE_ID_STEELSERIES_ARCTIS_1 before patch 1 in the list) only has
one HID interface, so doesn't need this patch.
>
> Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
> ---
> drivers/hid/hid-quirks.c | 25 +++++++++++++++++++++++++
> 1 file changed, 25 insertions(+)
>
> diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
> index c89a015686c0..8a7c3f433040 100644
> --- a/drivers/hid/hid-quirks.c
> +++ b/drivers/hid/hid-quirks.c
> @@ -699,7 +699,32 @@ static const struct hid_device_id
> hid_have_special_driver[] = {
> #if IS_ENABLED(CONFIG_HID_STEELSERIES)
> { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_SRWS1) },
> { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_1) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_7) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_7_GEN2) },
> { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_9) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_PRO) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_REV2) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO) },
> + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
> USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_PRO_X) },
> #endif
> #if IS_ENABLED(CONFIG_HID_SUNPLUS)
> { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS,
> USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 3/4] Documentation: ABI: Document SteelSeries headset sysfs attributes
2026-01-12 4:19 ` [PATCH v2 3/4] Documentation: ABI: Document SteelSeries headset sysfs attributes Sriman Achanta
@ 2026-01-12 13:08 ` Bastien Nocera
0 siblings, 0 replies; 15+ messages in thread
From: Bastien Nocera @ 2026-01-12 13:08 UTC (permalink / raw)
To: Sriman Achanta, Jiri Kosina, Benjamin Tissoires, linux-input,
linux-kernel
On Sun, 2026-01-11 at 23:19 -0500, Sriman Achanta wrote:
> Replace the SRW-S1 specific documentation with comprehensive
> documentation for the hid-steelseries driver covering both the legacy
> SRW-S1 wheel and the modern Arctis headset lineup.
Renaming the sysfs-driver-hid-srws1 should be done separately from
adding new contents.
This patch should also come after the feature gets added.
>
> New sysfs attributes documented:
> - sidetone_level: Control microphone monitoring volume
This should be an ALSA mixer, not a sysfs file.
> - inactive_time: Auto-sleep timeout configuration
> - chatmix_level: Game/Chat audio balance (read-only)
Ditto for an ALSA mixer.
> - mic_mute_led_brightness: Microphone mute LED brightness control
This probably needs to be a standard LED device.
> - mic_volume: Internal microphone gain control
Ditto for an ALSA mixer.
> - volume_limiter: EU hearing protection volume limiter
Ditto for an ALSA switch.
> - bluetooth_on_power: Bluetooth auto-enable on power-on
> - bluetooth_call_vol: Bluetooth call audio attenuation settings
Ditto for an ALSA mixer.
>
> The SRW-S1 LED documentation is preserved and moved into the new
> unified
> documentation file.
>
> Signed-off-by: Sriman Achanta <srimanachanta@gmail.com>
> ---
> .../ABI/testing/sysfs-driver-hid-srws1 | 21 ---
> .../ABI/testing/sysfs-driver-hid-steelseries | 131
> ++++++++++++++++++
> 2 files changed, 131 insertions(+), 21 deletions(-)
> delete mode 100644 Documentation/ABI/testing/sysfs-driver-hid-srws1
> create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-
> steelseries
>
> diff --git a/Documentation/ABI/testing/sysfs-driver-hid-srws1
> b/Documentation/ABI/testing/sysfs-driver-hid-srws1
> deleted file mode 100644
> index d0eba70c7d40..000000000000
> --- a/Documentation/ABI/testing/sysfs-driver-hid-srws1
> +++ /dev/null
> @@ -1,21 +0,0 @@
> -What: /sys/class/leds/SRWS1::<serial>::RPM1
> -What: /sys/class/leds/SRWS1::<serial>::RPM2
> -What: /sys/class/leds/SRWS1::<serial>::RPM3
> -What: /sys/class/leds/SRWS1::<serial>::RPM4
> -What: /sys/class/leds/SRWS1::<serial>::RPM5
> -What: /sys/class/leds/SRWS1::<serial>::RPM6
> -What: /sys/class/leds/SRWS1::<serial>::RPM7
> -What: /sys/class/leds/SRWS1::<serial>::RPM8
> -What: /sys/class/leds/SRWS1::<serial>::RPM9
> -What: /sys/class/leds/SRWS1::<serial>::RPM10
> -What: /sys/class/leds/SRWS1::<serial>::RPM11
> -What: /sys/class/leds/SRWS1::<serial>::RPM12
> -What: /sys/class/leds/SRWS1::<serial>::RPM13
> -What: /sys/class/leds/SRWS1::<serial>::RPM14
> -What: /sys/class/leds/SRWS1::<serial>::RPM15
> -What: /sys/class/leds/SRWS1::<serial>::RPMALL
> -Date: Jan 2013
> -KernelVersion: 3.9
> -Contact: Simon Wood <simon@mungewell.org>
> -Description: Provides a control for turning on/off the LEDs which
> form
> - an RPM meter on the front of the controller
> diff --git a/Documentation/ABI/testing/sysfs-driver-hid-steelseries
> b/Documentation/ABI/testing/sysfs-driver-hid-steelseries
> new file mode 100644
> index 000000000000..751cf01ceda3
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-hid-steelseries
> @@ -0,0 +1,131 @@
> +What: /sys/class/leds/SRWS1::<serial>::RPM1
> +What: /sys/class/leds/SRWS1::<serial>::RPM2
> +What: /sys/class/leds/SRWS1::<serial>::RPM3
> +What: /sys/class/leds/SRWS1::<serial>::RPM4
> +What: /sys/class/leds/SRWS1::<serial>::RPM5
> +What: /sys/class/leds/SRWS1::<serial>::RPM6
> +What: /sys/class/leds/SRWS1::<serial>::RPM7
> +What: /sys/class/leds/SRWS1::<serial>::RPM8
> +What: /sys/class/leds/SRWS1::<serial>::RPM9
> +What: /sys/class/leds/SRWS1::<serial>::RPM10
> +What: /sys/class/leds/SRWS1::<serial>::RPM11
> +What: /sys/class/leds/SRWS1::<serial>::RPM12
> +What: /sys/class/leds/SRWS1::<serial>::RPM13
> +What: /sys/class/leds/SRWS1::<serial>::RPM14
> +What: /sys/class/leds/SRWS1::<serial>::RPM15
> +What: /sys/class/leds/SRWS1::<serial>::RPMALL
> +Date: Jan 2013
> +KernelVersion: 3.9
> +Contact: Simon Wood <simon@mungewell.org>
> +Description: Provides a control for turning on/off the LEDs which
> form
> + an RPM meter on the front of the controller
> +
> +What: /sys/class/hid/drivers/steelseries/<dev>/sidetone_le
> vel
> +Date: January 2025
> +KernelVersion: 6.19
> +Contact: Sriman Achanta <srimanachanta@gmail.com>
> +Description:
> + Controls the sidetone (microphone monitoring) volume
> level.
> + This determines how much of the microphone input is
> fed back into
> + the headset speakers.
> +
> + Range: 0-128 (mapped internally to device-specific
> values).
> +
> + Access: Write
> +
> +What: /sys/class/hid/drivers/steelseries/<dev>/inactive_ti
> me
> +Date: January 2025
> +KernelVersion: 6.19
> +Contact: Sriman Achanta <srimanachanta@gmail.com>
> +Description:
> + Sets the time in minutes before the headset
> automatically enters
> + standby/sleep mode when no audio is playing.
> +
> + Range: 0-90 (minutes).
> + Some devices (e.g., Arctis 1/7X) map this to
> specific presets.
> +
> + Access: Write
> +
> +What: /sys/class/hid/drivers/steelseries/<dev>/chatmix_lev
> el
> +Date: January 2025
> +KernelVersion: 6.19
> +Contact: Sriman Achanta <srimanachanta@gmail.com>
> +Description:
> + Reports the current balance between Game and Chat
> audio channels
> + (ChatMix). This value changes when the physical
> ChatMix dial
> + on the headset is adjusted.
> +
> + Range: 0-128
> + 0 = 100% Chat / 0% Game
> + 64 = 50% Chat / 50% Game (Balanced)
> + 128 = 0% Chat / 100% Game
> +
> + Access: Read
> +
> +What: /sys/class/hid/drivers/steelseries/<dev>/mic_mute_le
> d_brightness
> +Date: January 2025
> +KernelVersion: 6.19
> +Contact: Sriman Achanta <srimanachanta@gmail.com>
> +Description:
> + Controls the brightness of the LED on the microphone
> boom that
> + indicates when the microphone is muted.
> +
> + Range: 0-3 (off, low, medium, high) for most
> devices.
> + 0-10 for newer Nova series devices.
> +
> + Access: Write
> +
> +What: /sys/class/hid/drivers/steelseries/<dev>/mic_volume
> +Date: January 2025
> +KernelVersion: 6.19
> +Contact: Sriman Achanta <srimanachanta@gmail.com>
> +Description:
> + Controls the internal microphone gain/volume of the
> headset.
> + This is distinct from the OS input volume.
> +
> + Range: 0-128 (mapped internally to device-specific
> values).
> +
> + Access: Write
> +
> +What: /sys/class/hid/drivers/steelseries/<dev>/volume_limi
> ter
> +Date: January 2025
> +KernelVersion: 6.19
> +Contact: Sriman Achanta <srimanachanta@gmail.com>
> +Description:
> + Enables or disables the EU volume limiter (hearing
> protection).
> + When enabled, the maximum output volume is capped.
> +
> + Values:
> + 0 = Disabled
> + 1 = Enabled
> +
> + Access: Write
> +
> +What: /sys/class/hid/drivers/steelseries/<dev>/bluetooth_o
> n_power
> +Date: January 2025
> +KernelVersion: 6.19
> +Contact: Sriman Achanta <srimanachanta@gmail.com>
> +Description:
> + Configures whether the Bluetooth radio automatically
> turns on
> + when the headset is powered on.
> +
> + Values:
> + 0 = Bluetooth must be turned on manually
> + 1 = Bluetooth turns on automatically with headset
> +
> + Access: Write
> +
> +What: /sys/class/hid/drivers/steelseries/<dev>/bluetooth_c
> all_vol
> +Date: January 2025
> +KernelVersion: 6.19
> +Contact: Sriman Achanta <srimanachanta@gmail.com>
> +Description:
> + Configures how the 2.4GHz Game/Chat audio is
> attenuated when
> + a Bluetooth call is active.
> +
> + Values:
> + 0 = No attenuation (mix both equally)
> + 1 = Attenuate Game audio by -12dB
> + 2 = Mute Game audio completely
> +
> + Access: Write
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 4/4] HID: steelseries: Add support for Arctis headset lineup
2026-01-12 4:19 ` [PATCH v2 4/4] HID: steelseries: Add support for Arctis headset lineup Sriman Achanta
@ 2026-01-12 13:09 ` Bastien Nocera
[not found] ` <CABMjph80yxmXnZjLgUrhFN7cKf2P=VjWF0rfWxG0rYOa42f5eQ@mail.gmail.com>
0 siblings, 1 reply; 15+ messages in thread
From: Bastien Nocera @ 2026-01-12 13:09 UTC (permalink / raw)
To: Sriman Achanta, Jiri Kosina, Benjamin Tissoires, linux-input,
linux-kernel
On Sun, 2026-01-11 at 23:19 -0500, Sriman Achanta wrote:
> Add full support for the SteelSeries Arctis wireless gaming headset
> lineup, extending the driver from basic support for 2 models (Arctis
> 1
> and 9) to comprehensive support for 25+ models across all Arctis
> generations.
>
> This is a major restructure of the hid-steelseries driver that
> replaces
> the previous minimal implementation with a unified, capability-based
> architecture.
This patch needs to be split up, at the very least it needs new
features to be split up from any other refactoring that might be needed
to support features with each new feature getting its own commit.
As mentioned in the earlier patch, sidetone control, chatmix level, mic
level, volume limiter and bluetooth call volume all should be
implemented as ALSA mixers/switches so they can be toggled with stock
tools and presented in a uniform way up the stack (Pipewire/Pulseaudio
and desktop environments).
An additional comments inline.
> <snip>
> -/* Fixed report descriptor for Steelseries SRW-S1 wheel controller
> - *
> - * The original descriptor hides the sensitivity and assists dials
> - * a custom vendor usage page. This inserts a patch to make them
> - * appear in the 'Generic Desktop' usage.
> - */
> -
> +/* Fixed report descriptor for Steelseries SRW-S1 wheel controller
> */
There's really no need to reindent this array.
> static const __u8 steelseries_srws1_rdesc_fixed[] = {
> -0x05, 0x01, /* Usage Page (Desktop) */
> -0x09, 0x08, /* Usage (MultiAxis), Changed */
> -0xA1, 0x01, /* Collection (Application), */
> -0xA1, 0x02, /* Collection (Logical), */
> -0x95, 0x01, /* Report Count (1), */
> -0x05, 0x01, /* Changed Usage Page (Desktop), */
> -0x09, 0x30, /* Changed Usage (X), */
> -0x16, 0xF8, 0xF8, /* Logical Minimum (-1800), */
> -0x26, 0x08, 0x07, /* Logical Maximum (1800), */
> -0x65, 0x14, /* Unit (Degrees), */
> -0x55, 0x0F, /* Unit Exponent (15), */
> -0x75, 0x10, /* Report Size (16), */
> -0x81, 0x02, /* Input (Variable), */
> -0x09, 0x31, /* Changed Usage (Y), */
> -0x15, 0x00, /* Logical Minimum (0), */
> -0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
> -0x75, 0x0C, /* Report Size (12), */
> -0x81, 0x02, /* Input (Variable), */
> -0x09, 0x32, /* Changed Usage (Z), */
> -0x15, 0x00, /* Logical Minimum (0), */
> -0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
> -0x75, 0x0C, /* Report Size (12), */
> -0x81, 0x02, /* Input (Variable), */
> -0x05, 0x01, /* Usage Page (Desktop), */
> -0x09, 0x39, /* Usage (Hat Switch), */
> -0x25, 0x07, /* Logical Maximum (7), */
> -0x35, 0x00, /* Physical Minimum (0), */
> -0x46, 0x3B, 0x01, /* Physical Maximum (315), */
> -0x65, 0x14, /* Unit (Degrees), */
> -0x75, 0x04, /* Report Size (4), */
> -0x95, 0x01, /* Report Count (1), */
> -0x81, 0x02, /* Input (Variable), */
> -0x25, 0x01, /* Logical Maximum (1), */
> -0x45, 0x01, /* Physical Maximum (1), */
> -0x65, 0x00, /* Unit, */
> -0x75, 0x01, /* Report Size (1), */
> -0x95, 0x03, /* Report Count (3), */
> -0x81, 0x01, /* Input (Constant), */
> -0x05, 0x09, /* Usage Page (Button), */
> -0x19, 0x01, /* Usage Minimum (01h), */
> -0x29, 0x11, /* Usage Maximum (11h), */
> -0x95, 0x11, /* Report Count (17), */
> -0x81, 0x02, /* Input (Variable), */
> - /* ---- Dial patch starts here ---- */
> -0x05, 0x01, /* Usage Page (Desktop), */
> -0x09, 0x33, /* Usage (RX), */
> -0x75, 0x04, /* Report Size (4), */
> -0x95, 0x02, /* Report Count (2), */
> -0x15, 0x00, /* Logical Minimum (0), */
> -0x25, 0x0b, /* Logical Maximum (b), */
> -0x81, 0x02, /* Input (Variable), */
> -0x09, 0x35, /* Usage (RZ), */
> -0x75, 0x04, /* Report Size (4), */
> -0x95, 0x01, /* Report Count (1), */
> -0x25, 0x03, /* Logical Maximum (3), */
> -0x81, 0x02, /* Input (Variable), */
> - /* ---- Dial patch ends here ---- */
> -0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
> -0x09, 0x01, /* Usage (01h), */
> -0x75, 0x04, /* Changed Report Size (4), */
> -0x95, 0x0D, /* Changed Report Count (13), */
> -0x81, 0x02, /* Input (Variable), */
> -0xC0, /* End Collection, */
> -0xA1, 0x02, /* Collection (Logical), */
> -0x09, 0x02, /* Usage (02h), */
> -0x75, 0x08, /* Report Size (8), */
> -0x95, 0x10, /* Report Count (16), */
> -0x91, 0x02, /* Output (Variable), */
> -0xC0, /* End Collection, */
> -0xC0 /* End Collection */
> + 0x05, 0x01, /* Usage Page (Desktop) */
> + 0x09, 0x08, /* Usage (MultiAxis), Changed */
> + 0xA1, 0x01, /* Collection (Application), */
> + 0xA1, 0x02, /* Collection (Logical), */
> + 0x95, 0x01, /* Report Count (1), */
> + 0x05, 0x01, /* Changed Usage Page (Desktop), */
> + 0x09, 0x30, /* Changed Usage (X), */
> + 0x16, 0xF8, 0xF8, /* Logical Minimum (-1800), */
> + 0x26, 0x08, 0x07, /* Logical Maximum (1800), */
> + 0x65, 0x14, /* Unit (Degrees), */
> + 0x55, 0x0F, /* Unit Exponent (15), */
> + 0x75, 0x10, /* Report Size (16), */
> + 0x81, 0x02, /* Input (Variable), */
> + 0x09, 0x31, /* Changed Usage (Y), */
> + 0x15, 0x00, /* Logical Minimum (0), */
> + 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
> + 0x75, 0x0C, /* Report Size (12), */
> + 0x81, 0x02, /* Input (Variable), */
> + 0x09, 0x32, /* Changed Usage (Z), */
> + 0x15, 0x00, /* Logical Minimum (0), */
> + 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */
> + 0x75, 0x0C, /* Report Size (12), */
> + 0x81, 0x02, /* Input (Variable), */
> + 0x05, 0x01, /* Usage Page (Desktop), */
> + 0x09, 0x39, /* Usage (Hat Switch), */
> + 0x25, 0x07, /* Logical Maximum (7), */
> + 0x35, 0x00, /* Physical Minimum (0), */
> + 0x46, 0x3B, 0x01, /* Physical Maximum (315), */
> + 0x65, 0x14, /* Unit (Degrees), */
> + 0x75, 0x04, /* Report Size (4), */
> + 0x95, 0x01, /* Report Count (1), */
> + 0x81, 0x02, /* Input (Variable), */
> + 0x25, 0x01, /* Logical Maximum (1), */
> + 0x45, 0x01, /* Physical Maximum (1), */
> + 0x65, 0x00, /* Unit, */
> + 0x75, 0x01, /* Report Size (1), */
> + 0x95, 0x03, /* Report Count (3), */
> + 0x81, 0x01, /* Input (Constant), */
> + 0x05, 0x09, /* Usage Page (Button), */
> + 0x19, 0x01, /* Usage Minimum (01h), */
> + 0x29, 0x11, /* Usage Maximum (11h), */
> + 0x95, 0x11, /* Report Count (17), */
> + 0x81, 0x02, /* Input (Variable), */
> + /* ---- Dial patch starts here ---- */
> + 0x05, 0x01, /* Usage Page (Desktop), */
> + 0x09, 0x33, /* Usage (RX), */
> + 0x75, 0x04, /* Report Size (4), */
> + 0x95, 0x02, /* Report Count (2), */
> + 0x15, 0x00, /* Logical Minimum (0), */
> + 0x25, 0x0b, /* Logical Maximum (b), */
> + 0x81, 0x02, /* Input (Variable), */
> + 0x09, 0x35, /* Usage (RZ), */
> + 0x75, 0x04, /* Report Size (4), */
> + 0x95, 0x01, /* Report Count (1), */
> + 0x25, 0x03, /* Logical Maximum (3), */
> + 0x81, 0x02, /* Input (Variable), */
> + /* ---- Dial patch ends here ---- */
> + 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
> + 0x09, 0x01, /* Usage (01h), */
> + 0x75, 0x04, /* Changed Report Size (4), */
> + 0x95, 0x0D, /* Changed Report Count (13), */
> + 0x81, 0x02, /* Input (Variable), */
> + 0xC0, /* End Collection, */
> + 0xA1, 0x02, /* Collection (Logical), */
> + 0x09, 0x02, /* Usage (02h), */
> + 0x75, 0x08, /* Report Size (8), */
> + 0x95, 0x10, /* Report Count (16), */
> + 0x91, 0x02, /* Output (Variable), */
> + 0xC0, /* End Collection, */
> + 0xC0 /* End Collection */
> };
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 0/4] HID: steelseries: Add support for Arctis headset lineup
2026-01-12 4:19 [PATCH v2 0/4] HID: steelseries: Add support for Arctis headset lineup Sriman Achanta
` (3 preceding siblings ...)
2026-01-12 4:19 ` [PATCH v2 4/4] HID: steelseries: Add support for Arctis headset lineup Sriman Achanta
@ 2026-01-20 19:01 ` Benjamin Wheeler
2026-01-20 20:01 ` [PATCH 0/3] HID: steelseries: clean up functions, move battery request data to structs Benjamin Wheeler
5 siblings, 0 replies; 15+ messages in thread
From: Benjamin Wheeler @ 2026-01-20 19:01 UTC (permalink / raw)
To: Sriman Achanta, Jiri Kosina, Benjamin Tissoires, linux-input,
linux-kernel
Hello,
I'm Benjamin, and I coincidentally have also been looking into this
driver. Sriman, if you would like, I'd be happy to work together on this
driver with you! In reviewing your patches, I have found and fixed a bug
in my local tree regarding battery requests.
The Steelseries Arctis 7 and Arctis 7 (2019 ed.) are not implemented in
the battery request logic, and simply report 100% (the default value
during battery registration). I discovered this bug while testing your
driver with my Arctis 7 :-)
Would you like me to send a formal patch for this? I also have some
patches I can send that clean up the battery logic in general that make
the code less complex.
I am also happy to look into creating ALSA mixers/switches as well.
Please let me know how I can help! Excellent work so far, and smart
choice in using HeadsetControl, they've done most of the heavy lifting
for us.
Sincerely,
Benjamin Wheeler
On 1/11/26 11:19 PM, Sriman Achanta wrote:
> This patch series adds comprehensive support for the SteelSeries Arctis
> wireless gaming headset lineup to the hid-steelseries driver.
>
> The current driver provides only basic battery monitoring for Arctis 1
> and Arctis 9. This series extends support to 25+ Arctis models with
> full feature control including sidetone, auto-sleep, microphone
> controls, volume limiting, and Bluetooth settings.
>
> The driver restructure uses a capability-based device info system to
> cleanly handle the varying feature sets across different Arctis
> generations while maintaining support for the legacy SRW-S1 racing
> wheel.
>
> Patch 1: Add 27 new device IDs to hid-ids.h
> Patch 2: Add HID quirks for proper device initialization
> Patch 3: Update ABI documentation for new sysfs attributes
> Patch 4: Complete driver implementation with all features
>
> Tested on Arctis Nova 7 (0x2202). All other implementation details are
> based on the reverse engineering done in the HeadsetControl library
> (abe3ac8).
>
> V2:
> - Fix Documentation formatting issues
>
> Sriman Achanta (4):
> HID: hid-ids: Add SteelSeries Arctis headset device IDs
> HID: quirks: Add INPUT_CONFIGURED quirk for SteelSeries Arctis
> headsets
> Documentation: ABI: Document SteelSeries headset sysfs attributes
> HID: steelseries: Add support for Arctis headset lineup
>
> .../ABI/testing/sysfs-driver-hid-srws1 | 21 -
> .../ABI/testing/sysfs-driver-hid-steelseries | 131 ++
> drivers/hid/hid-ids.h | 33 +-
> drivers/hid/hid-quirks.c | 25 +
> drivers/hid/hid-steelseries.c | 2061 ++++++++++++++---
> 5 files changed, 1925 insertions(+), 346 deletions(-)
> delete mode 100644 Documentation/ABI/testing/sysfs-driver-hid-srws1
> create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-steelseries
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 0/3] HID: steelseries: clean up functions, move battery request data to structs
2026-01-12 4:19 [PATCH v2 0/4] HID: steelseries: Add support for Arctis headset lineup Sriman Achanta
` (4 preceding siblings ...)
2026-01-20 19:01 ` [PATCH v2 0/4] " Benjamin Wheeler
@ 2026-01-20 20:01 ` Benjamin Wheeler
2026-01-20 20:02 ` [PATCH 1/3] HID: steelseries: Clean up hid send_report functions Benjamin Wheeler
` (2 more replies)
5 siblings, 3 replies; 15+ messages in thread
From: Benjamin Wheeler @ 2026-01-20 20:01 UTC (permalink / raw)
To: srimanachanta; +Cc: bentiss, jikos, linux-input, linux-kernel
Hi Sriman,
As requested, here are my patches for your v2 of the new driver.
They fix an issue where the Arctis 7 incorrectly shows 100% battery due
to submitting an invalid report. They also include some cleanups of a
couple functions.
I've tested these by loading the module in a VM, and seeing that the
Arctis 7 reads the correct battery % through the Ubuntu desktop.
If these don't fit nicely into your v3, feel free to take what you need
out of them, or, once you submit v3, I can re-do my patches and
re-submit.
Thanks!
Benjamin Wheeler (3):
HID: steelseries: Clean up hid send_report functions
HID: steelseries: Add battery request info (byte flags) to device
info.
HID: steelseries: Use device data for battery requests
drivers/hid/hid-steelseries.c | 217 +++++++++++++++++-----------------
1 file changed, 111 insertions(+), 106 deletions(-)
--
2.52.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 1/3] HID: steelseries: Clean up hid send_report functions
2026-01-20 20:01 ` [PATCH 0/3] HID: steelseries: clean up functions, move battery request data to structs Benjamin Wheeler
@ 2026-01-20 20:02 ` Benjamin Wheeler
2026-01-20 20:02 ` [PATCH 2/3] HID: steelseries: Add battery request info (byte flags) to device info Benjamin Wheeler
2026-01-20 20:02 ` [PATCH 3/3] HID: steelseries: Use device data for battery requests Benjamin Wheeler
2 siblings, 0 replies; 15+ messages in thread
From: Benjamin Wheeler @ 2026-01-20 20:02 UTC (permalink / raw)
To: srimanachanta; +Cc: bentiss, jikos, linux-input, linux-kernel
These functions do the same thing, so combine them into one with a
parameter.
Use helper functions to keep callsites the same.
Signed-off-by: Benjamin Wheeler <benjaminwheeler0510@gmail.com>
---
drivers/hid/hid-steelseries.c | 45 ++++++++++++++++-------------------
1 file changed, 21 insertions(+), 24 deletions(-)
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index a0046fbc830b..63b086fa6df0 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -829,9 +829,13 @@ static int steelseries_battery_register(struct steelseries_device *sd)
return 0;
}
-/* Helper function to send feature reports */
-static int steelseries_send_feature_report(struct hid_device *hdev,
- const u8 *data, size_t len)
+/**
+ * Send an hid report to the device.
+ * Supported types are output reports and feature reports.
+ */
+static int __steelseries_send_report(struct hid_device *const hdev,
+ const u8 *const data, const size_t len,
+ const enum hid_report_type type)
{
u8 *buf;
int ret;
@@ -840,8 +844,9 @@ static int steelseries_send_feature_report(struct hid_device *hdev,
if (!buf)
return -ENOMEM;
- ret = hid_hw_raw_request(hdev, data[0], buf, len, HID_FEATURE_REPORT,
+ ret = hid_hw_raw_request(hdev, data[0], buf, len, type,
HID_REQ_SET_REPORT);
+
kfree(buf);
if (ret < 0)
@@ -852,28 +857,19 @@ static int steelseries_send_feature_report(struct hid_device *hdev,
return 0;
}
-/* Helper function to send output reports */
-static int steelseries_send_output_report(struct hid_device *hdev,
- const u8 *data, size_t len)
+static inline int steelseries_send_feature_report(struct hid_device *const hdev,
+ const u8 *const data,
+ const size_t len)
{
- u8 *buf;
- int ret;
-
- buf = kmemdup(data, len, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- /* Use raw_request with OUTPUT_REPORT type for devices without Interrupt OUT */
- ret = hid_hw_raw_request(hdev, data[0], buf, len, HID_OUTPUT_REPORT,
- HID_REQ_SET_REPORT);
- kfree(buf);
-
- if (ret < 0)
- return ret;
- if (ret < len)
- return -EIO;
+ return __steelseries_send_report(hdev, data, len, HID_FEATURE_REPORT);
+}
- return 0;
+static inline int steelseries_send_output_report(struct hid_device *const hdev,
+ const u8 *const data,
+ const size_t len)
+{
+ // NOTE: Output report (HID_OUTPUT_REPORT) is for devices without Interrupt OUT
+ return __steelseries_send_report(hdev, data, len, HID_OUTPUT_REPORT);
}
/* Sidetone level attribute */
@@ -2170,3 +2166,4 @@ MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
MODULE_AUTHOR("Simon Wood <simon@mungewell.org>");
MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");
MODULE_AUTHOR("Sriman Achanta <srimanachanta@gmail.com>");
+MODULE_AUTHOR("Benjamin Wheeler <benjaminwheeler0510@gmail.com>");
--
2.52.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/3] HID: steelseries: Add battery request info (byte flags) to device info.
2026-01-20 20:01 ` [PATCH 0/3] HID: steelseries: clean up functions, move battery request data to structs Benjamin Wheeler
2026-01-20 20:02 ` [PATCH 1/3] HID: steelseries: Clean up hid send_report functions Benjamin Wheeler
@ 2026-01-20 20:02 ` Benjamin Wheeler
2026-01-20 20:02 ` [PATCH 3/3] HID: steelseries: Use device data for battery requests Benjamin Wheeler
2 siblings, 0 replies; 15+ messages in thread
From: Benjamin Wheeler @ 2026-01-20 20:02 UTC (permalink / raw)
To: srimanachanta; +Cc: bentiss, jikos, linux-input, linux-kernel
This way, the device info is decoupled from the functions that call it.
This will allow for simplification of calling these functions.
Signed-off-by: Benjamin Wheeler <benjaminwheeler0510@gmail.com>
---
drivers/hid/hid-steelseries.c | 79 +++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index 63b086fa6df0..dabc4763f072 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -31,12 +31,22 @@
/* Legacy quirk flag for SRW-S1 */
#define STEELSERIES_SRWS1 BIT(0)
+/**
+ * Stores info about how to send a report to this device.
+ */
+struct steelseries_report_data {
+ const u8 data[2];
+ const u8 len; // Only ever 1 or 2, no need for size_t.
+ const enum hid_report_type type;
+};
+
struct steelseries_device_info {
u16 product_id;
const char *name;
u8 interface_binding_mode; /* 0 = first enumerated, 1 = specific interface(s) */
u16 valid_interfaces; /* Bitmask when mode = 1, ignored when mode = 0 */
unsigned long capabilities;
+ const struct steelseries_report_data *const report_data;
};
struct steelseries_device {
@@ -382,12 +392,55 @@ static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
return rdesc;
}
+// Report data for: arctis 1, 1x, 7p, 7x
+static const struct steelseries_report_data report_data__1_1x_7p_7x = {
+ .data = { 0x06, 0x12 },
+ .len = 2,
+ .type = HID_FEATURE_REPORT
+};
+
+// Report data for: arctis 7, 7 v2 (2019 ed.)
+static const struct steelseries_report_data report_data__7_7v2 = {
+ .data = { 0x06, 0x18 },
+ .len = 2,
+ .type = HID_OUTPUT_REPORT
+};
+
+// Report data for: 7 plus, 7 plus P, 7 plus X, 7 plus Destiny, all novas besides nova 3p, nova 3x
+static const struct steelseries_report_data report_data__7plus_nova = {
+ .data = { 0x00, 0xb0 },
+ .len = 2,
+ .type = HID_OUTPUT_REPORT
+};
+
+// Report data for: arctis 9
+static const struct steelseries_report_data report_data__9 = {
+ .data = { 0x00, 0x20 },
+ .len = 2,
+ .type = HID_FEATURE_REPORT
+};
+
+// Report data for: Pro
+static const struct steelseries_report_data report_data__pro = {
+ .data = { 0x40, 0xAA },
+ .len = 2,
+ .type = HID_OUTPUT_REPORT
+};
+
+// Report data for: Nova 3p, 3x
+static const struct steelseries_report_data report_data__nova_3p_3x = {
+ .data = { 0xb0 },
+ .len = 1,
+ .type = HID_OUTPUT_REPORT
+};
+
static const struct steelseries_device_info arctis_1_info = {
.product_id = USB_DEVICE_ID_STEELSERIES_ARCTIS_1,
.name = "Arctis 1 Wireless",
.interface_binding_mode = 1,
.valid_interfaces = BIT(3),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+ .report_data = &report_data__1_1x_7p_7x,
};
static const struct steelseries_device_info arctis_1_x_info = {
@@ -396,6 +449,7 @@ static const struct steelseries_device_info arctis_1_x_info = {
.interface_binding_mode = 1,
.valid_interfaces = BIT(3),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+ .report_data = &report_data__1_1x_7p_7x,
};
static const struct steelseries_device_info arctis_7_info = {
@@ -404,6 +458,7 @@ static const struct steelseries_device_info arctis_7_info = {
.interface_binding_mode = 1,
.valid_interfaces = BIT(5),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+ .report_data = &report_data__7_7v2,
};
static const struct steelseries_device_info arctis_7_p_info = {
@@ -412,6 +467,7 @@ static const struct steelseries_device_info arctis_7_p_info = {
.interface_binding_mode = 1,
.valid_interfaces = BIT(3),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+ .report_data = &report_data__1_1x_7p_7x,
};
static const struct steelseries_device_info arctis_7_x_info = {
@@ -420,6 +476,7 @@ static const struct steelseries_device_info arctis_7_x_info = {
.interface_binding_mode = 1,
.valid_interfaces = BIT(3),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+ .report_data = &report_data__1_1x_7p_7x,
};
static const struct steelseries_device_info arctis_7_gen2_info = {
@@ -428,6 +485,7 @@ static const struct steelseries_device_info arctis_7_gen2_info = {
.interface_binding_mode = 1,
.valid_interfaces = BIT(5),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+ .report_data = &report_data__7_7v2,
};
static const struct steelseries_device_info arctis_7_plus_info = {
@@ -437,6 +495,7 @@ static const struct steelseries_device_info arctis_7_plus_info = {
.valid_interfaces = BIT(3),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
SS_CAP_INACTIVE_TIME | SS_CAP_CHATMIX,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_7_plus_p_info = {
@@ -446,6 +505,7 @@ static const struct steelseries_device_info arctis_7_plus_p_info = {
.valid_interfaces = BIT(3),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
SS_CAP_INACTIVE_TIME | SS_CAP_CHATMIX,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_7_plus_x_info = {
@@ -455,6 +515,7 @@ static const struct steelseries_device_info arctis_7_plus_x_info = {
.valid_interfaces = BIT(3),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
SS_CAP_INACTIVE_TIME | SS_CAP_CHATMIX,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_7_plus_destiny_info = {
@@ -464,6 +525,7 @@ static const struct steelseries_device_info arctis_7_plus_destiny_info = {
.valid_interfaces = BIT(3),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
SS_CAP_INACTIVE_TIME | SS_CAP_CHATMIX,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_9_info = {
@@ -473,6 +535,7 @@ static const struct steelseries_device_info arctis_9_info = {
.valid_interfaces = 0,
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
SS_CAP_INACTIVE_TIME | SS_CAP_CHATMIX,
+ .report_data = &report_data__9,
};
static const struct steelseries_device_info arctis_pro_info = {
@@ -481,6 +544,7 @@ static const struct steelseries_device_info arctis_pro_info = {
.interface_binding_mode = 0,
.valid_interfaces = 0,
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+ .report_data = &report_data__pro,
};
static const struct steelseries_device_info arctis_nova_3_info = {
@@ -490,6 +554,7 @@ static const struct steelseries_device_info arctis_nova_3_info = {
.valid_interfaces = BIT(4),
.capabilities = SS_CAP_SIDETONE | SS_CAP_MIC_MUTE_LED |
SS_CAP_MIC_VOLUME,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_3_p_info = {
@@ -499,6 +564,7 @@ static const struct steelseries_device_info arctis_nova_3_p_info = {
.valid_interfaces = BIT(0),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_VOLUME,
+ .report_data = &report_data__nova_3p_3x,
};
static const struct steelseries_device_info arctis_nova_3_x_info = {
@@ -508,6 +574,7 @@ static const struct steelseries_device_info arctis_nova_3_x_info = {
.valid_interfaces = BIT(0),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY |
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_VOLUME,
+ .report_data = &report_data__nova_3p_3x,
};
static const struct steelseries_device_info arctis_nova_5_info = {
@@ -518,6 +585,7 @@ static const struct steelseries_device_info arctis_nova_5_info = {
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_5_x_info = {
@@ -528,6 +596,7 @@ static const struct steelseries_device_info arctis_nova_5_x_info = {
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_CHATMIX |
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_7_info = {
@@ -539,6 +608,7 @@ static const struct steelseries_device_info arctis_nova_7_info = {
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_7_x_info = {
@@ -550,6 +620,7 @@ static const struct steelseries_device_info arctis_nova_7_x_info = {
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_7_p_info = {
@@ -561,6 +632,7 @@ static const struct steelseries_device_info arctis_nova_7_p_info = {
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_7_x_rev2_info = {
@@ -572,6 +644,7 @@ static const struct steelseries_device_info arctis_nova_7_x_rev2_info = {
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_7_diablo_info = {
@@ -583,6 +656,7 @@ static const struct steelseries_device_info arctis_nova_7_diablo_info = {
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_7_wow_info = {
@@ -594,6 +668,7 @@ static const struct steelseries_device_info arctis_nova_7_wow_info = {
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_7_gen2_info = {
@@ -605,6 +680,7 @@ static const struct steelseries_device_info arctis_nova_7_gen2_info = {
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_7_x_gen2_info = {
@@ -616,6 +692,7 @@ static const struct steelseries_device_info arctis_nova_7_x_gen2_info = {
SS_CAP_INACTIVE_TIME | SS_CAP_MIC_MUTE_LED |
SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER |
SS_CAP_BT_POWER_ON | SS_CAP_BT_CALL_VOL,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_pro_info = {
@@ -624,6 +701,7 @@ static const struct steelseries_device_info arctis_nova_pro_info = {
.interface_binding_mode = 1,
.valid_interfaces = BIT(4),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+ .report_data = &report_data__7plus_nova,
};
static const struct steelseries_device_info arctis_nova_pro_x_info = {
@@ -632,6 +710,7 @@ static const struct steelseries_device_info arctis_nova_pro_x_info = {
.interface_binding_mode = 1,
.valid_interfaces = BIT(4),
.capabilities = SS_CAP_SIDETONE | SS_CAP_BATTERY | SS_CAP_INACTIVE_TIME,
+ .report_data = &report_data__7plus_nova,
};
#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
--
2.52.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 3/3] HID: steelseries: Use device data for battery requests
2026-01-20 20:01 ` [PATCH 0/3] HID: steelseries: clean up functions, move battery request data to structs Benjamin Wheeler
2026-01-20 20:02 ` [PATCH 1/3] HID: steelseries: Clean up hid send_report functions Benjamin Wheeler
2026-01-20 20:02 ` [PATCH 2/3] HID: steelseries: Add battery request info (byte flags) to device info Benjamin Wheeler
@ 2026-01-20 20:02 ` Benjamin Wheeler
2 siblings, 0 replies; 15+ messages in thread
From: Benjamin Wheeler @ 2026-01-20 20:02 UTC (permalink / raw)
To: srimanachanta; +Cc: bentiss, jikos, linux-input, linux-kernel
This eliminates several functions that are no longer needed.
When making a battery request, the required data to do so is simply
pulled from the device struct.
Signed-off-by: Benjamin Wheeler <benjaminwheeler0510@gmail.com>
---
drivers/hid/hid-steelseries.c | 93 +++++------------------------------
1 file changed, 11 insertions(+), 82 deletions(-)
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index dabc4763f072..1200213bc8f3 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -809,44 +809,17 @@ static enum power_supply_property steelseries_battery_props[] = {
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
};
-/* Forward declarations for battery request functions */
-static int steelseries_arctis_1_request_battery(struct hid_device *hdev);
-static int steelseries_arctis_7_plus_request_battery(struct hid_device *hdev);
-static int steelseries_arctis_9_request_battery(struct hid_device *hdev);
-static int steelseries_arctis_nova_request_battery(struct hid_device *hdev);
-static int steelseries_arctis_nova_3p_request_battery(struct hid_device *hdev);
-static int
-steelseries_arctis_pro_wireless_request_battery(struct hid_device *hdev);
+static int __steelseries_send_report(struct hid_device *const hdev,
+ const u8 *const data, const size_t len,
+ const enum hid_report_type type);
-static int steelseries_request_battery(struct hid_device *hdev)
+static int
+steelseries_request_battery(const struct steelseries_device *const sd)
{
- u16 product = hdev->product;
-
- /* Route to device-specific battery request handler */
- if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
- product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X ||
- product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_P ||
- product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_X)
- return steelseries_arctis_1_request_battery(hdev);
-
- if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS ||
- product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_P ||
- product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_X ||
- product == USB_DEVICE_ID_STEELSERIES_ARCTIS_7_PLUS_DESTINY)
- return steelseries_arctis_7_plus_request_battery(hdev);
-
- if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
- return steelseries_arctis_9_request_battery(hdev);
-
- if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_PRO)
- return steelseries_arctis_pro_wireless_request_battery(hdev);
-
- if (product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_P ||
- product == USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_3_X)
- return steelseries_arctis_nova_3p_request_battery(hdev);
-
- /* All other Nova series use the same battery request */
- return steelseries_arctis_nova_request_battery(hdev);
+ const struct steelseries_report_data *const report_data =
+ sd->info->report_data; // Alias
+ return __steelseries_send_report(sd->hdev, report_data->data,
+ report_data->len, report_data->type);
}
static void steelseries_battery_timer_tick(struct work_struct *work)
@@ -854,7 +827,7 @@ static void steelseries_battery_timer_tick(struct work_struct *work)
struct steelseries_device *sd = container_of(
work, struct steelseries_device, battery_work.work);
- steelseries_request_battery(sd->hdev);
+ steelseries_request_battery(sd);
}
static int steelseries_battery_register(struct steelseries_device *sd)
@@ -895,7 +868,7 @@ static int steelseries_battery_register(struct steelseries_device *sd)
power_supply_powers(sd->battery, &sd->hdev->dev);
INIT_DELAYED_WORK(&sd->battery_work, steelseries_battery_timer_tick);
- steelseries_request_battery(sd->hdev);
+ steelseries_request_battery(sd);
/* Arctis 9 may need a retry */
if (sd->hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
@@ -1685,50 +1658,6 @@ static const struct attribute_group steelseries_attr_group = {
.is_visible = steelseries_attr_is_visible,
};
-static int steelseries_arctis_1_request_battery(struct hid_device *hdev)
-{
- const u8 data[] = { 0x06, 0x12 };
-
- return steelseries_send_feature_report(hdev, data, sizeof(data));
-}
-
-static int steelseries_arctis_7_plus_request_battery(struct hid_device *hdev)
-{
- const u8 data[] = { 0x00, 0xb0 };
-
- return steelseries_send_output_report(hdev, data, sizeof(data));
-}
-
-static int steelseries_arctis_9_request_battery(struct hid_device *hdev)
-{
- const u8 data[] = { 0x00, 0x20 };
-
- return steelseries_send_feature_report(hdev, data, sizeof(data));
-}
-
-static int steelseries_arctis_nova_request_battery(struct hid_device *hdev)
-{
- const u8 data[] = { 0x00, 0xb0 };
-
- return steelseries_send_output_report(hdev, data, sizeof(data));
-}
-
-static int steelseries_arctis_nova_3p_request_battery(struct hid_device *hdev)
-{
- const u8 data[] = { 0xb0 };
-
- return steelseries_send_output_report(hdev, data, sizeof(data));
-}
-
-static int
-steelseries_arctis_pro_wireless_request_battery(struct hid_device *hdev)
-{
- /* Request battery - response will arrive asynchronously via raw_event */
- const u8 data[] = { 0x40, 0xAA };
-
- return steelseries_send_output_report(hdev, data, sizeof(data));
-}
-
static int steelseries_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
--
2.52.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v2 4/4] HID: steelseries: Add support for Arctis headset lineup
[not found] ` <CABMjph9TzmCfks0OEtCx4gFFe0pKOccBwVutq6dVH2DhzSC5vA@mail.gmail.com>
@ 2026-02-26 10:01 ` Bastien Nocera
0 siblings, 0 replies; 15+ messages in thread
From: Bastien Nocera @ 2026-02-26 10:01 UTC (permalink / raw)
To: Sriman Achanta; +Cc: linux-kernel, linux-input
Hey,
On Thu, 2026-02-26 at 04:33 -0500, Sriman Achanta wrote:
> I agree these should be proper ALSA controls rather than sysfs files.
> I've been investigating the best way to accomplish this, since the
> challenge is that these values (chatmix, sidetone, mic volume, etc.)
> are reported/controlled via the HID vendor interface, while the USB
> audio card is owned by snd-usb-audio.
>
> There are a few approaches I've found precedent for: (1) adding
> vendor quirks in sound/usb/mixer_quirks.c that bridge back to the HID
> interface, similar to the DualSense jack detection code which uses an
> input_handler to receive events from hid-playstation; (2) creating a
> separate snd_card from the HID driver itself with custom mixer
> controls, similar to how hid-prodikeys creates its own ALSA card; or
> (3) having the HID driver find the sibling USB audio card and add
> controls to it directly (though this has no existing precedent and
> raises lifetime/ordering concerns).
>
> My inclination is toward option 2 — a dedicated snd_card owned by the
> HID driver — since the chatmix dial and sidetone are logically
> properties of the HID device rather than the audio stream, and it
> avoids fragile cross-driver coupling. However, this does mean
> userspace sees a second ALSA card for what is physically one device.
> I'd appreciate guidance on which approach the maintainers would
> prefer before I implement it.
Option 2 is definitely the way to go. I would expect widely deployed
front-ends to ALSA like Pipewire and PulseAudio to be able to make it
look like the 2 sets of mixing controls come from the same physical
device.
Cheers
>
> Best,
> Sriman Achanta
>
> On Wed, Jan 14, 2026 at 4:51 AM Bastien Nocera <hadess@hadess.net>
> wrote:
> > On Tue, 2026-01-13 at 21:57 -0500, Sriman Achanta wrote:
> > > > As mentioned in the earlier patch, sidetone control, chatmix
> > > > level, mic
> > > > level, volume limiter and bluetooth call volume all should be
> > > > implemented as ALSA mixers/switches so they can be toggled with
> > > > stock
> > > > tools and presented in a uniform way up the stack
> > > > (Pipewire/Pulseaudio
> > > > and desktop environments).
> > >
> > > My only concern with this is that there is no ability to read the
> > > setting state from the headset so the only way to implement ALSA
> > > mixers would be to return a guessed value / default and then
> > > write-
> > > through caching for subsequently set values, creating a desync
> > > between the device and the reported state of the mixer.
> >
> > How is that different from a sysfs value that you can't read when
> > the
> > headset is off?
> >
> > > Additionally, inactive_timeout and bt_power_on should remain as
> > > Sysfs handles as they modify the power system, not audio.
> >
> > They should however be separate patches, so the maintainers have a
> > choice to take them or not, especially since adding new sysfs files
> > is
> > frowned upon, and maintainers will likely want a more generic way
> > to
> > implement that functionality for other devices as well.
> >
> > Note that you emailed me in private, you should definitely CC: the
> > mailing-list when you answer, otherwise nobody but me will see your
> > response.
> >
> > Cheers
> >
> > >
> > > On Mon, Jan 12, 2026 at 8:09 AM Bastien Nocera
> > > <hadess@hadess.net>
> > > wrote:
> > > > On Sun, 2026-01-11 at 23:19 -0500, Sriman Achanta wrote:
> > > > > Add full support for the SteelSeries Arctis wireless gaming
> > > > > headset
> > > > > lineup, extending the driver from basic support for 2 models
> > > > > (Arctis
> > > > > 1
> > > > > and 9) to comprehensive support for 25+ models across all
> > > > > Arctis
> > > > > generations.
> > > > >
> > > > > This is a major restructure of the hid-steelseries driver
> > > > > that
> > > > > replaces
> > > > > the previous minimal implementation with a unified,
> > > > > capability-
> > > > > based
> > > > > architecture.
> > > >
> > > > This patch needs to be split up, at the very least it needs new
> > > > features to be split up from any other refactoring that might
> > > > be
> > > > needed
> > > > to support features with each new feature getting its own
> > > > commit.
> > > >
> > > > As mentioned in the earlier patch, sidetone control, chatmix
> > > > level,
> > > > mic
> > > > level, volume limiter and bluetooth call volume all should be
> > > > implemented as ALSA mixers/switches so they can be toggled with
> > > > stock
> > > > tools and presented in a uniform way up the stack
> > > > (Pipewire/Pulseaudio
> > > > and desktop environments).
> > > >
> > > > An additional comments inline.
> > > >
> > > > > <snip>
> > > > > -/* Fixed report descriptor for Steelseries SRW-S1 wheel
> > > > > controller
> > > > > - *
> > > > > - * The original descriptor hides the sensitivity and assists
> > > > > dials
> > > > > - * a custom vendor usage page. This inserts a patch to make
> > > > > them
> > > > > - * appear in the 'Generic Desktop' usage.
> > > > > - */
> > > > > -
> > > > > +/* Fixed report descriptor for Steelseries SRW-S1 wheel
> > > > > controller
> > > > > */
> > > >
> > > > There's really no need to reindent this array.
> > > >
> > > > > static const __u8 steelseries_srws1_rdesc_fixed[] = {
> > > > > -0x05, 0x01, /* Usage Page (Desktop)
> > > > > */
> > > > > -0x09, 0x08, /* Usage (MultiAxis), Changed
> > > > > */
> > > > > -0xA1, 0x01, /* Collection (Application),
> > > > > */
> > > > > -0xA1, 0x02, /* Collection (Logical),
> > > > > */
> > > > > -0x95, 0x01, /* Report Count (1),
> > > > > */
> > > > > -0x05, 0x01, /* Changed Usage Page (Desktop),
> > > > > */
> > > > > -0x09, 0x30, /* Changed Usage (X),
> > > > > */
> > > > > -0x16, 0xF8, 0xF8, /* Logical Minimum (-1800),
> > > > > */
> > > > > -0x26, 0x08, 0x07, /* Logical Maximum (1800),
> > > > > */
> > > > > -0x65, 0x14, /* Unit (Degrees),
> > > > > */
> > > > > -0x55, 0x0F, /* Unit Exponent (15),
> > > > > */
> > > > > -0x75, 0x10, /* Report Size (16),
> > > > > */
> > > > > -0x81, 0x02, /* Input (Variable),
> > > > > */
> > > > > -0x09, 0x31, /* Changed Usage (Y),
> > > > > */
> > > > > -0x15, 0x00, /* Logical Minimum (0),
> > > > > */
> > > > > -0x26, 0xFF, 0x03, /* Logical Maximum (1023),
> > > > > */
> > > > > -0x75, 0x0C, /* Report Size (12),
> > > > > */
> > > > > -0x81, 0x02, /* Input (Variable),
> > > > > */
> > > > > -0x09, 0x32, /* Changed Usage (Z),
> > > > > */
> > > > > -0x15, 0x00, /* Logical Minimum (0),
> > > > > */
> > > > > -0x26, 0xFF, 0x03, /* Logical Maximum (1023),
> > > > > */
> > > > > -0x75, 0x0C, /* Report Size (12),
> > > > > */
> > > > > -0x81, 0x02, /* Input (Variable),
> > > > > */
> > > > > -0x05, 0x01, /* Usage Page (Desktop),
> > > > > */
> > > > > -0x09, 0x39, /* Usage (Hat Switch),
> > > > > */
> > > > > -0x25, 0x07, /* Logical Maximum (7),
> > > > > */
> > > > > -0x35, 0x00, /* Physical Minimum (0),
> > > > > */
> > > > > -0x46, 0x3B, 0x01, /* Physical Maximum (315),
> > > > > */
> > > > > -0x65, 0x14, /* Unit (Degrees),
> > > > > */
> > > > > -0x75, 0x04, /* Report Size (4),
> > > > > */
> > > > > -0x95, 0x01, /* Report Count (1),
> > > > > */
> > > > > -0x81, 0x02, /* Input (Variable),
> > > > > */
> > > > > -0x25, 0x01, /* Logical Maximum (1),
> > > > > */
> > > > > -0x45, 0x01, /* Physical Maximum (1),
> > > > > */
> > > > > -0x65, 0x00, /* Unit,
> > > > > */
> > > > > -0x75, 0x01, /* Report Size (1),
> > > > > */
> > > > > -0x95, 0x03, /* Report Count (3),
> > > > > */
> > > > > -0x81, 0x01, /* Input (Constant),
> > > > > */
> > > > > -0x05, 0x09, /* Usage Page (Button),
> > > > > */
> > > > > -0x19, 0x01, /* Usage Minimum (01h),
> > > > > */
> > > > > -0x29, 0x11, /* Usage Maximum (11h),
> > > > > */
> > > > > -0x95, 0x11, /* Report Count (17),
> > > > > */
> > > > > -0x81, 0x02, /* Input (Variable),
> > > > > */
> > > > > - /* ---- Dial patch starts here ----
> > > > > */
> > > > > -0x05, 0x01, /* Usage Page (Desktop),
> > > > > */
> > > > > -0x09, 0x33, /* Usage (RX),
> > > > > */
> > > > > -0x75, 0x04, /* Report Size (4),
> > > > > */
> > > > > -0x95, 0x02, /* Report Count (2),
> > > > > */
> > > > > -0x15, 0x00, /* Logical Minimum (0),
> > > > > */
> > > > > -0x25, 0x0b, /* Logical Maximum (b),
> > > > > */
> > > > > -0x81, 0x02, /* Input (Variable),
> > > > > */
> > > > > -0x09, 0x35, /* Usage (RZ),
> > > > > */
> > > > > -0x75, 0x04, /* Report Size (4),
> > > > > */
> > > > > -0x95, 0x01, /* Report Count (1),
> > > > > */
> > > > > -0x25, 0x03, /* Logical Maximum (3),
> > > > > */
> > > > > -0x81, 0x02, /* Input (Variable),
> > > > > */
> > > > > - /* ---- Dial patch ends here ----
> > > > > */
> > > > > -0x06, 0x00, 0xFF, /* Usage Page (FF00h),
> > > > > */
> > > > > -0x09, 0x01, /* Usage (01h),
> > > > > */
> > > > > -0x75, 0x04, /* Changed Report Size (4),
> > > > > */
> > > > > -0x95, 0x0D, /* Changed Report Count (13),
> > > > > */
> > > > > -0x81, 0x02, /* Input (Variable),
> > > > > */
> > > > > -0xC0, /* End Collection,
> > > > > */
> > > > > -0xA1, 0x02, /* Collection (Logical),
> > > > > */
> > > > > -0x09, 0x02, /* Usage (02h),
> > > > > */
> > > > > -0x75, 0x08, /* Report Size (8),
> > > > > */
> > > > > -0x95, 0x10, /* Report Count (16),
> > > > > */
> > > > > -0x91, 0x02, /* Output (Variable),
> > > > > */
> > > > > -0xC0, /* End Collection,
> > > > > */
> > > > > -0xC0 /* End Collection
> > > > > */
> > > > > + 0x05, 0x01, /* Usage Page (Desktop) */
> > > > > + 0x09, 0x08, /* Usage (MultiAxis), Changed */
> > > > > + 0xA1, 0x01, /* Collection (Application), */
> > > > > + 0xA1, 0x02, /* Collection (Logical), */
> > > > > + 0x95, 0x01, /* Report Count (1), */
> > > > > + 0x05, 0x01, /* Changed Usage Page (Desktop), */
> > > > > + 0x09, 0x30, /* Changed Usage (X), */
> > > > > + 0x16, 0xF8, 0xF8, /* Logical Minimum (-
> > > > > 1800),
> > > > > */
> > > > > + 0x26, 0x08, 0x07, /* Logical Maximum
> > > > > (1800),
> > > > > */
> > > > > + 0x65, 0x14, /* Unit (Degrees), */
> > > > > + 0x55, 0x0F, /* Unit Exponent (15), */
> > > > > + 0x75, 0x10, /* Report Size (16), */
> > > > > + 0x81, 0x02, /* Input (Variable), */
> > > > > + 0x09, 0x31, /* Changed Usage (Y), */
> > > > > + 0x15, 0x00, /* Logical Minimum (0), */
> > > > > + 0x26, 0xFF, 0x03, /* Logical Maximum
> > > > > (1023),
> > > > > */
> > > > > + 0x75, 0x0C, /* Report Size (12), */
> > > > > + 0x81, 0x02, /* Input (Variable), */
> > > > > + 0x09, 0x32, /* Changed Usage (Z), */
> > > > > + 0x15, 0x00, /* Logical Minimum (0), */
> > > > > + 0x26, 0xFF, 0x03, /* Logical Maximum
> > > > > (1023),
> > > > > */
> > > > > + 0x75, 0x0C, /* Report Size (12), */
> > > > > + 0x81, 0x02, /* Input (Variable), */
> > > > > + 0x05, 0x01, /* Usage Page (Desktop), */
> > > > > + 0x09, 0x39, /* Usage (Hat Switch), */
> > > > > + 0x25, 0x07, /* Logical Maximum (7), */
> > > > > + 0x35, 0x00, /* Physical Minimum (0), */
> > > > > + 0x46, 0x3B, 0x01, /* Physical Maximum
> > > > > (315),
> > > > > */
> > > > > + 0x65, 0x14, /* Unit (Degrees), */
> > > > > + 0x75, 0x04, /* Report Size (4), */
> > > > > + 0x95, 0x01, /* Report Count (1), */
> > > > > + 0x81, 0x02, /* Input (Variable), */
> > > > > + 0x25, 0x01, /* Logical Maximum (1), */
> > > > > + 0x45, 0x01, /* Physical Maximum (1), */
> > > > > + 0x65, 0x00, /* Unit, */
> > > > > + 0x75, 0x01, /* Report Size (1), */
> > > > > + 0x95, 0x03, /* Report Count (3), */
> > > > > + 0x81, 0x01, /* Input (Constant), */
> > > > > + 0x05, 0x09, /* Usage Page (Button), */
> > > > > + 0x19, 0x01, /* Usage Minimum (01h), */
> > > > > + 0x29, 0x11, /* Usage Maximum (11h), */
> > > > > + 0x95, 0x11, /* Report Count (17), */
> > > > > + 0x81, 0x02, /* Input (Variable), */
> > > > > + /* ---- Dial patch starts here ---- */
> > > > > + 0x05, 0x01, /* Usage Page (Desktop), */
> > > > > + 0x09, 0x33, /* Usage (RX), */
> > > > > + 0x75, 0x04, /* Report Size (4), */
> > > > > + 0x95, 0x02, /* Report Count (2), */
> > > > > + 0x15, 0x00, /* Logical Minimum (0), */
> > > > > + 0x25, 0x0b, /* Logical Maximum (b), */
> > > > > + 0x81, 0x02, /* Input (Variable), */
> > > > > + 0x09, 0x35, /* Usage (RZ), */
> > > > > + 0x75, 0x04, /* Report Size (4), */
> > > > > + 0x95, 0x01, /* Report Count (1), */
> > > > > + 0x25, 0x03, /* Logical Maximum (3), */
> > > > > + 0x81, 0x02, /* Input (Variable), */
> > > > > + /* ---- Dial patch ends here ---- */
> > > > > + 0x06, 0x00, 0xFF, /* Usage Page
> > > > > (FF00h),
> > > > > */
> > > > > + 0x09, 0x01, /* Usage (01h), */
> > > > > + 0x75, 0x04, /* Changed Report Size (4), */
> > > > > + 0x95, 0x0D, /* Changed Report Count (13), */
> > > > > + 0x81, 0x02, /* Input (Variable), */
> > > > > + 0xC0, /* End Collection, */
> > > > > + 0xA1, 0x02, /* Collection (Logical), */
> > > > > + 0x09, 0x02, /* Usage (02h), */
> > > > > + 0x75, 0x08, /* Report Size (8), */
> > > > > + 0x95, 0x10, /* Report Count (16), */
> > > > > + 0x91, 0x02, /* Output (Variable), */
> > > > > + 0xC0, /* End Collection, */
> > > > > + 0xC0 /* End Collection */
> > > > > };
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-02-26 10:02 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-12 4:19 [PATCH v2 0/4] HID: steelseries: Add support for Arctis headset lineup Sriman Achanta
2026-01-12 4:19 ` [PATCH v2 1/4] HID: hid-ids: Add SteelSeries Arctis headset device IDs Sriman Achanta
2026-01-12 13:08 ` Bastien Nocera
2026-01-12 4:19 ` [PATCH v2 2/4] HID: quirks: Add INPUT_CONFIGURED quirk for SteelSeries Arctis headsets Sriman Achanta
2026-01-12 13:08 ` Bastien Nocera
2026-01-12 4:19 ` [PATCH v2 3/4] Documentation: ABI: Document SteelSeries headset sysfs attributes Sriman Achanta
2026-01-12 13:08 ` Bastien Nocera
2026-01-12 4:19 ` [PATCH v2 4/4] HID: steelseries: Add support for Arctis headset lineup Sriman Achanta
2026-01-12 13:09 ` Bastien Nocera
[not found] ` <CABMjph80yxmXnZjLgUrhFN7cKf2P=VjWF0rfWxG0rYOa42f5eQ@mail.gmail.com>
[not found] ` <e5b256fa8da7fe079f3e3b10a1b0b6a0faf8e913.camel@hadess.net>
[not found] ` <CABMjph9TzmCfks0OEtCx4gFFe0pKOccBwVutq6dVH2DhzSC5vA@mail.gmail.com>
2026-02-26 10:01 ` Bastien Nocera
2026-01-20 19:01 ` [PATCH v2 0/4] " Benjamin Wheeler
2026-01-20 20:01 ` [PATCH 0/3] HID: steelseries: clean up functions, move battery request data to structs Benjamin Wheeler
2026-01-20 20:02 ` [PATCH 1/3] HID: steelseries: Clean up hid send_report functions Benjamin Wheeler
2026-01-20 20:02 ` [PATCH 2/3] HID: steelseries: Add battery request info (byte flags) to device info Benjamin Wheeler
2026-01-20 20:02 ` [PATCH 3/3] HID: steelseries: Use device data for battery requests Benjamin Wheeler
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox