* Re: [git pull] Input updates for v7.0-rc6
From: pr-tracker-bot @ 2026-04-04 15:34 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Linus Torvalds, linux-kernel, linux-input
In-Reply-To: <adCpWQOyfvE6E6k0@google.com>
The pull request you sent on Fri, 3 Apr 2026 23:02:12 -0700:
> git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git tags/input-for-v7.0-rc6
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/3aae9383f42f687221c011d7ee87529398e826b3
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply
* [PATCH] HID: winwing: add support for URSA MINOR combat joysticks
From: René Onier @ 2026-04-04 17:26 UTC (permalink / raw)
To: linux-input; +Cc: jikos, bentiss, ivan.gorinov, rene.onier
Add device IDs for the Winwing URSA MINOR Combat Joystick L (0xbc29)
and R (0xbc2a). These joysticks declare 128 buttons in their HID
report descriptor, causing the generic HID driver to reject buttons
above KEY_MAX (767) with "Invalid code" errors.
The URSA MINOR joysticks use buttons in the 33-64 range for grip
controls including the index trigger, so map_more_buttons is set
to 1 to enable the extended KEY_MACRO mapping.
Tested with both left and right URSA MINOR joysticks on kernel 6.19.
Signed-off-by: René Onier <rene.onier@gmail.com>
---
hid-winwing.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/hid-winwing.c b/hid-winwing.c
index ab65dc1..f3e6ea0 100644
--- a/hid-winwing.c
+++ b/hid-winwing.c
@@ -250,6 +250,8 @@ static const struct hid_device_id winwing_devices[] = {
{ HID_USB_DEVICE(0x4098, 0xbd64), .driver_data = 1 }, /* TGRIP-15EX */
{ HID_USB_DEVICE(0x4098, 0xbe68), .driver_data = 0 }, /* TGRIP-16EX */
{ HID_USB_DEVICE(0x4098, 0xbe62), .driver_data = 0 }, /* TGRIP-18 */
+ { HID_USB_DEVICE(0x4098, 0xbc29), .driver_data = 1 }, /* URSA MINOR L */
+ { HID_USB_DEVICE(0x4098, 0xbc2a), .driver_data = 1 }, /* URSA MINOR R */
{}
};
--
2.43.0
^ permalink raw reply related
* [dtor-input:for-linus] BUILD SUCCESS 0d9363a764d9d601a05591f9695cea8b429e9be3
From: kernel test robot @ 2026-04-04 18:08 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git for-linus
branch HEAD: 0d9363a764d9d601a05591f9695cea8b429e9be3 Input: xpad - add support for BETOP BTP-KP50B/C controller's wireless mode
elapsed time: 735m
configs tested: 195
configs skipped: 2
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc defconfig gcc-15.2.0
arc randconfig-001-20260404 gcc-15.2.0
arc randconfig-002-20260404 gcc-15.2.0
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm defconfig gcc-15.2.0
arm randconfig-001-20260404 gcc-15.2.0
arm randconfig-002-20260404 gcc-15.2.0
arm randconfig-003-20260404 gcc-15.2.0
arm randconfig-004-20260404 gcc-15.2.0
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260404 gcc-15.2.0
arm64 randconfig-001-20260404 gcc-8.5.0
arm64 randconfig-002-20260404 gcc-15.2.0
arm64 randconfig-002-20260404 gcc-8.5.0
arm64 randconfig-003-20260404 clang-23
arm64 randconfig-003-20260404 gcc-15.2.0
arm64 randconfig-004-20260404 gcc-15.2.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260404 gcc-15.2.0
csky randconfig-001-20260404 gcc-9.5.0
csky randconfig-002-20260404 gcc-15.2.0
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260404 gcc-15.2.0
hexagon randconfig-002-20260404 gcc-15.2.0
i386 allmodconfig clang-20
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001-20260404 clang-20
i386 buildonly-randconfig-001-20260404 gcc-14
i386 buildonly-randconfig-002-20260404 clang-20
i386 buildonly-randconfig-003-20260404 clang-20
i386 buildonly-randconfig-003-20260404 gcc-14
i386 buildonly-randconfig-004-20260404 clang-20
i386 buildonly-randconfig-005-20260404 clang-20
i386 buildonly-randconfig-006-20260404 clang-20
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260404 clang-20
i386 randconfig-002-20260404 clang-20
i386 randconfig-003-20260404 clang-20
i386 randconfig-004-20260404 clang-20
i386 randconfig-005-20260404 clang-20
i386 randconfig-006-20260404 clang-20
i386 randconfig-007-20260404 clang-20
i386 randconfig-011-20260404 clang-20
i386 randconfig-012-20260404 clang-20
i386 randconfig-013-20260404 clang-20
i386 randconfig-014-20260404 clang-20
i386 randconfig-015-20260404 clang-20
i386 randconfig-016-20260404 clang-20
i386 randconfig-017-20260404 clang-20
loongarch allmodconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260404 gcc-15.2.0
loongarch randconfig-002-20260404 gcc-15.2.0
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips omega2p_defconfig clang-23
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-19
nios2 randconfig-001-20260404 gcc-15.2.0
nios2 randconfig-002-20260404 gcc-15.2.0
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc allnoconfig gcc-15.2.0
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allnoconfig gcc-15.2.0
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260404 gcc-13.4.0
parisc randconfig-002-20260404 gcc-15.2.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc allnoconfig gcc-15.2.0
powerpc ksi8560_defconfig gcc-15.2.0
powerpc mpc834x_itx_defconfig clang-16
powerpc randconfig-001-20260404 gcc-8.5.0
powerpc randconfig-002-20260404 clang-23
powerpc64 randconfig-001-20260404 clang-23
powerpc64 randconfig-002-20260404 gcc-10.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260404 clang-19
riscv randconfig-001-20260404 clang-20
riscv randconfig-002-20260404 clang-20
riscv randconfig-002-20260404 clang-23
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260404 clang-20
s390 randconfig-002-20260404 clang-20
s390 randconfig-002-20260404 clang-23
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allnoconfig gcc-15.2.0
sh allyesconfig clang-19
sh defconfig gcc-14
sh randconfig-001-20260404 clang-20
sh randconfig-001-20260404 gcc-15.2.0
sh randconfig-002-20260404 clang-20
sh randconfig-002-20260404 gcc-9.5.0
sparc allnoconfig clang-23
sparc allnoconfig gcc-15.2.0
sparc defconfig gcc-15.2.0
sparc randconfig-001-20260404 clang-20
sparc randconfig-002-20260404 clang-20
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260404 clang-20
sparc64 randconfig-002-20260404 clang-20
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260404 clang-20
um randconfig-002-20260404 clang-20
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260404 gcc-13
x86_64 buildonly-randconfig-002-20260404 gcc-13
x86_64 buildonly-randconfig-003-20260404 gcc-13
x86_64 buildonly-randconfig-004-20260404 gcc-13
x86_64 buildonly-randconfig-005-20260404 gcc-13
x86_64 buildonly-randconfig-006-20260404 gcc-13
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260404 gcc-14
x86_64 randconfig-002-20260404 gcc-14
x86_64 randconfig-003-20260404 gcc-14
x86_64 randconfig-004-20260404 gcc-14
x86_64 randconfig-005-20260404 gcc-14
x86_64 randconfig-006-20260404 gcc-14
x86_64 randconfig-011-20260404 clang-20
x86_64 randconfig-011-20260404 gcc-14
x86_64 randconfig-012-20260404 gcc-14
x86_64 randconfig-013-20260404 clang-20
x86_64 randconfig-013-20260404 gcc-14
x86_64 randconfig-014-20260404 clang-20
x86_64 randconfig-014-20260404 gcc-14
x86_64 randconfig-015-20260404 clang-20
x86_64 randconfig-015-20260404 gcc-14
x86_64 randconfig-016-20260404 clang-20
x86_64 randconfig-016-20260404 gcc-14
x86_64 randconfig-071-20260404 gcc-14
x86_64 randconfig-072-20260404 gcc-14
x86_64 randconfig-073-20260404 gcc-14
x86_64 randconfig-074-20260404 gcc-14
x86_64 randconfig-075-20260404 gcc-14
x86_64 randconfig-076-20260404 gcc-14
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig clang-23
xtensa randconfig-001-20260404 clang-20
xtensa randconfig-002-20260404 clang-20
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [dtor-input:next] BUILD SUCCESS 84e7a17d1813394b48b0641fce8217fc0bba1960
From: kernel test robot @ 2026-04-04 18:08 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
branch HEAD: 84e7a17d1813394b48b0641fce8217fc0bba1960 Input: xpad - add RedOctane Games vendor id
elapsed time: 735m
configs tested: 235
configs skipped: 3
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allmodconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc allyesconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arc randconfig-001-20260404 gcc-15.2.0
arc randconfig-001-20260404 gcc-8.5.0
arc randconfig-002-20260404 gcc-15.2.0
arc randconfig-002-20260404 gcc-8.5.0
arm allnoconfig clang-23
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm allyesconfig gcc-15.2.0
arm defconfig gcc-15.2.0
arm randconfig-001-20260404 gcc-14.3.0
arm randconfig-001-20260404 gcc-15.2.0
arm randconfig-002-20260404 gcc-15.2.0
arm randconfig-003-20260404 gcc-10.5.0
arm randconfig-003-20260404 gcc-15.2.0
arm randconfig-004-20260404 gcc-12.5.0
arm randconfig-004-20260404 gcc-15.2.0
arm64 allmodconfig clang-19
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260404 gcc-15.2.0
arm64 randconfig-001-20260404 gcc-8.5.0
arm64 randconfig-002-20260404 gcc-15.2.0
arm64 randconfig-002-20260404 gcc-8.5.0
arm64 randconfig-003-20260404 clang-23
arm64 randconfig-003-20260404 gcc-15.2.0
arm64 randconfig-004-20260404 gcc-15.2.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260404 gcc-15.2.0
csky randconfig-001-20260404 gcc-9.5.0
csky randconfig-002-20260404 gcc-15.2.0
hexagon allmodconfig clang-17
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig clang-23
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260404 gcc-15.2.0
hexagon randconfig-002-20260404 gcc-15.2.0
i386 allmodconfig clang-20
i386 allmodconfig gcc-14
i386 allnoconfig gcc-14
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 allyesconfig gcc-14
i386 buildonly-randconfig-001-20260404 clang-20
i386 buildonly-randconfig-001-20260404 gcc-14
i386 buildonly-randconfig-002-20260404 clang-20
i386 buildonly-randconfig-003-20260404 clang-20
i386 buildonly-randconfig-003-20260404 gcc-14
i386 buildonly-randconfig-004-20260404 clang-20
i386 buildonly-randconfig-005-20260404 clang-20
i386 buildonly-randconfig-006-20260404 clang-20
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260404 clang-20
i386 randconfig-002-20260404 clang-20
i386 randconfig-003-20260404 clang-20
i386 randconfig-004-20260404 clang-20
i386 randconfig-005-20260404 clang-20
i386 randconfig-005-20260404 gcc-14
i386 randconfig-006-20260404 clang-20
i386 randconfig-007-20260404 clang-20
i386 randconfig-011-20260404 clang-20
i386 randconfig-012-20260404 clang-20
i386 randconfig-013-20260404 clang-20
i386 randconfig-014-20260404 clang-20
i386 randconfig-015-20260404 clang-20
i386 randconfig-015-20260404 gcc-14
i386 randconfig-016-20260404 clang-20
i386 randconfig-017-20260404 clang-20
i386 randconfig-017-20260404 gcc-12
loongarch allmodconfig clang-19
loongarch allmodconfig clang-23
loongarch allnoconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260404 gcc-15.2.0
loongarch randconfig-002-20260404 gcc-15.2.0
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k allyesconfig gcc-15.2.0
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips omega2p_defconfig clang-23
nios2 allmodconfig clang-23
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig clang-23
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-19
nios2 randconfig-001-20260404 gcc-15.2.0
nios2 randconfig-002-20260404 gcc-15.2.0
openrisc allmodconfig clang-23
openrisc allmodconfig gcc-15.2.0
openrisc allnoconfig clang-23
openrisc allnoconfig gcc-15.2.0
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allnoconfig gcc-15.2.0
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260404 gcc-13.4.0
parisc randconfig-002-20260404 gcc-15.2.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc allnoconfig gcc-15.2.0
powerpc bamboo_defconfig clang-23
powerpc ep88xc_defconfig gcc-15.2.0
powerpc ksi8560_defconfig gcc-15.2.0
powerpc mpc834x_itx_defconfig clang-16
powerpc randconfig-001-20260404 gcc-8.5.0
powerpc randconfig-002-20260404 clang-23
powerpc64 randconfig-001-20260404 clang-23
powerpc64 randconfig-002-20260404 gcc-10.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
riscv defconfig clang-23
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260404 clang-19
riscv randconfig-001-20260404 clang-20
riscv randconfig-002-20260404 clang-20
riscv randconfig-002-20260404 clang-23
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig clang-23
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260404 clang-20
s390 randconfig-002-20260404 clang-20
s390 randconfig-002-20260404 clang-23
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allnoconfig gcc-15.2.0
sh allyesconfig clang-19
sh defconfig gcc-14
sh defconfig gcc-15.2.0
sh randconfig-001-20260404 clang-20
sh randconfig-001-20260404 gcc-15.2.0
sh randconfig-002-20260404 clang-20
sh randconfig-002-20260404 gcc-9.5.0
sparc allnoconfig clang-23
sparc allnoconfig gcc-15.2.0
sparc defconfig gcc-15.2.0
sparc randconfig-001-20260404 clang-20
sparc randconfig-001-20260404 gcc-15.2.0
sparc randconfig-002-20260404 clang-20
sparc randconfig-002-20260404 gcc-8.5.0
sparc64 allmodconfig clang-23
sparc64 defconfig clang-20
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260404 clang-20
sparc64 randconfig-001-20260404 gcc-14.3.0
sparc64 randconfig-002-20260404 clang-20
sparc64 randconfig-002-20260404 clang-23
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-14
um allyesconfig gcc-15.2.0
um defconfig clang-23
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260404 clang-20
um randconfig-002-20260404 clang-20
um randconfig-002-20260404 gcc-14
um x86_64_defconfig clang-23
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260404 gcc-13
x86_64 buildonly-randconfig-002-20260404 gcc-13
x86_64 buildonly-randconfig-003-20260404 gcc-13
x86_64 buildonly-randconfig-004-20260404 gcc-13
x86_64 buildonly-randconfig-005-20260404 gcc-13
x86_64 buildonly-randconfig-006-20260404 gcc-13
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260404 gcc-14
x86_64 randconfig-002-20260404 gcc-14
x86_64 randconfig-003-20260404 gcc-14
x86_64 randconfig-004-20260404 gcc-14
x86_64 randconfig-005-20260404 gcc-14
x86_64 randconfig-006-20260404 gcc-14
x86_64 randconfig-011-20260404 clang-20
x86_64 randconfig-011-20260404 gcc-14
x86_64 randconfig-012-20260404 gcc-14
x86_64 randconfig-013-20260404 clang-20
x86_64 randconfig-013-20260404 gcc-14
x86_64 randconfig-014-20260404 clang-20
x86_64 randconfig-014-20260404 gcc-14
x86_64 randconfig-015-20260404 clang-20
x86_64 randconfig-015-20260404 gcc-14
x86_64 randconfig-016-20260404 clang-20
x86_64 randconfig-016-20260404 gcc-14
x86_64 randconfig-071-20260404 gcc-14
x86_64 randconfig-072-20260404 gcc-14
x86_64 randconfig-073-20260404 gcc-14
x86_64 randconfig-074-20260404 gcc-14
x86_64 randconfig-075-20260404 gcc-14
x86_64 randconfig-076-20260404 gcc-14
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig clang-23
xtensa allyesconfig gcc-15.2.0
xtensa randconfig-001-20260404 clang-20
xtensa randconfig-001-20260404 gcc-11.5.0
xtensa randconfig-002-20260404 clang-20
xtensa randconfig-002-20260404 gcc-13.4.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v2 0/1] HID: add malicious HID device detection driver
From: Greg KH @ 2026-04-05 5:31 UTC (permalink / raw)
To: Zubeyr Almaho
Cc: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel,
security
In-Reply-To: <20260404133746.80914-1-zybo1000@gmail.com>
On Sat, Apr 04, 2026 at 04:37:44PM +0300, Zubeyr Almaho wrote:
> Hi Jiri, Benjamin,
>
> This series introduces hid-omg-detect, a passive HID monitor that scores
> potentially malicious keyboard-like USB devices (BadUSB / O.MG style)
> using:
>
> - keystroke timing entropy,
> - plug-and-type latency,
> - USB descriptor fingerprinting.
>
> When the configurable threshold is crossed, the module emits a warning
> with a userspace mitigation hint (usbguard).
>
> The driver does not block, delay, or modify HID input events.
That's cute, but no need to get security@kernel.org involved as this is
a new feature, not a bug triage.
Also, why not just do this as an ebpf program instead as you have full
access to the hid data stream there?
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH RESEND] Input: atkbd - skip cleanup when used as a wakeup source
From: Henry Barnor @ 2026-04-05 5:36 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: hbarnor, linux-input, linux-kernel
In-Reply-To: <acrwy_Y6QMapp5fw@google.com>
Hi Dmitry,
> We unfortunately need to support devices much older than that, so we can
> not be sure that this change is safe. Historically there we a lot of
> instances where systems were unhappy if we attempted to enter shutdown
> or suspend paths with devices in state other than freshly reset.
Thanks for the feedback and the context on legacy hardware risks. That makes sense.
I will explore handling this within the Android system layer instead.
Thanks.
^ permalink raw reply
* [PATCH] HID: sony: fix style issues
From: Rosalie Wanders @ 2026-04-06 2:47 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Henrik Rydberg
Cc: Rosalie Wanders, linux-input, linux-kernel
This commit fixes inconsistent quirk names and also fixes all the
checkpatch.pl issues alongside inconsistent code, it also adds static
asserts to assert struct sizes at compile time.
Signed-off-by: Rosalie Wanders <rosalie@mailbox.org>
---
drivers/hid/hid-sony.c | 79 +++++++++++++++++++-----------------------
1 file changed, 35 insertions(+), 44 deletions(-)
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index bd4b4a470869..695ca085b410 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -67,8 +67,8 @@
#define RB4_GUITAR_PS4_USB BIT(18)
#define RB4_GUITAR_PS4_BT BIT(19)
#define RB4_GUITAR_PS5 BIT(20)
-#define RB3_PS3_PRO_INSTRUMENT BIT(21)
-#define PS3_DJH_TURNTABLE BIT(22)
+#define RB3_PRO_INSTRUMENT BIT(21)
+#define DJH_TURNTABLE BIT(22)
#define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
#define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT)
@@ -110,13 +110,6 @@ static const char ghl_ps4_magic_data[] = {
0x30, 0x02, 0x08, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00
};
-/* Rock Band 3 PS3 Pro Instruments require sending a report
- * once an instrument is connected to its dongle.
- * We need to retry sending these reports,
- * but to avoid doing this too often we delay the retries
- */
-#define RB3_PRO_INSTRUMENT_POKE_RETRY_INTERVAL 8 /* In seconds */
-
/* PS/3 Motion controller */
static const u8 motion_rdesc[] = {
0x05, 0x01, /* Usage Page (Desktop), */
@@ -477,6 +470,7 @@ struct sixaxis_led {
u8 duty_off; /* % of duty_length the led is off (0xff means 100%) */
u8 duty_on; /* % of duty_length the led is on (0xff mean 100%) */
} __packed;
+static_assert(sizeof(struct sixaxis_led) == 5);
struct sixaxis_rumble {
u8 padding;
@@ -485,6 +479,7 @@ struct sixaxis_rumble {
u8 left_duration; /* Left motor duration (0xff means forever) */
u8 left_motor_force; /* left (large) motor, supports force values from 0 to 255 */
} __packed;
+static_assert(sizeof(struct sixaxis_rumble) == 5);
struct sixaxis_output_report {
u8 report_id;
@@ -494,11 +489,13 @@ struct sixaxis_output_report {
struct sixaxis_led led[4]; /* LEDx at (4 - x) */
struct sixaxis_led _reserved; /* LED5, not actually soldered */
} __packed;
+static_assert(sizeof(struct sixaxis_output_report) == 36);
union sixaxis_output_report_01 {
struct sixaxis_output_report data;
u8 buf[36];
};
+static_assert(sizeof(union sixaxis_output_report_01) == 36);
struct motion_output_report_02 {
u8 type, zero;
@@ -506,6 +503,7 @@ struct motion_output_report_02 {
u8 zero2;
u8 rumble;
};
+static_assert(sizeof(struct motion_output_report_02) == 7);
#define SIXAXIS_REPORT_0xF2_SIZE 17
#define SIXAXIS_REPORT_0xF5_SIZE 8
@@ -536,7 +534,7 @@ struct sony_sc {
struct led_classdev *leds[MAX_LEDS];
unsigned long quirks;
struct work_struct state_worker;
- void (*send_output_report)(struct sony_sc *);
+ void (*send_output_report)(struct sony_sc *sc);
struct power_supply *battery;
struct power_supply_desc battery_desc;
int device_id;
@@ -613,11 +611,11 @@ static int ghl_init_urb(struct sony_sc *sc, struct usb_device *usbdev,
pipe = usb_sndctrlpipe(usbdev, 0);
cr = devm_kzalloc(&sc->hdev->dev, sizeof(*cr), GFP_ATOMIC);
- if (cr == NULL)
+ if (!cr)
return -ENOMEM;
databuf = devm_kzalloc(&sc->hdev->dev, poke_size, GFP_ATOMIC);
- if (databuf == NULL)
+ if (!databuf)
return -ENOMEM;
cr->bRequestType =
@@ -952,6 +950,7 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
static const u8 sixaxis_battery_capacity[] = { 0, 1, 25, 50, 75, 100 };
unsigned long flags;
int offset;
+ u8 index;
u8 battery_capacity;
int battery_status;
@@ -967,7 +966,7 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
battery_capacity = 100;
battery_status = (rd[offset] & 0x01) ? POWER_SUPPLY_STATUS_FULL : POWER_SUPPLY_STATUS_CHARGING;
} else {
- u8 index = rd[offset] <= 5 ? rd[offset] : 5;
+ index = rd[offset] <= 5 ? rd[offset] : 5;
battery_capacity = sixaxis_battery_capacity[index];
battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
}
@@ -1005,7 +1004,7 @@ static void nsg_mrxu_parse_report(struct sony_sc *sc, u8 *rd, int size)
* the touch-related data starts at offset 2.
* For the first byte, bit 0 is set when touchpad button is pressed.
* Bit 2 is set when a touch is active and the drag (Fn) key is pressed.
- * This drag key is mapped to BTN_LEFT. It is operational only when a
+ * This drag key is mapped to BTN_LEFT. It is operational only when a
* touch point is active.
* Bit 4 is set when only the first touch point is active.
* Bit 6 is set when only the second touch point is active.
@@ -1152,11 +1151,10 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
/* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're
* sending full reports, and 0x02 when only sending navigation.
*/
- if ((sc->quirks & RB3_PS3_PRO_INSTRUMENT) && rd[24] == 0x02) {
- /* Only attempt to enable report every 8 seconds */
+ if ((sc->quirks & RB3_PRO_INSTRUMENT) && rd[24] == 0x02) {
+ /* Only attempt to enable full report every 8 seconds */
if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) {
- sc->rb3_pro_poke_jiffies = jiffies +
- (RB3_PRO_INSTRUMENT_POKE_RETRY_INTERVAL * HZ);
+ sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8);
rb3_pro_instrument_enable_full_report(sc);
}
}
@@ -1218,7 +1216,7 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
if (sc->quirks & GH_GUITAR_TILT)
return gh_guitar_mapping(hdev, hi, field, usage, bit, max);
- if (sc->quirks & PS3_DJH_TURNTABLE)
+ if (sc->quirks & DJH_TURNTABLE)
return djh_turntable_mapping(hdev, hi, field, usage, bit, max);
if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT))
@@ -1273,19 +1271,18 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
input_set_abs_params(sc->touchpad, ABS_MT_POSITION_Y, 0, h, 0, 0);
if (touch_major > 0) {
- input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MAJOR,
+ input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MAJOR,
0, touch_major, 0, 0);
if (touch_minor > 0)
- input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MINOR,
+ input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MINOR,
0, touch_minor, 0, 0);
if (orientation > 0)
- input_set_abs_params(sc->touchpad, ABS_MT_ORIENTATION,
+ input_set_abs_params(sc->touchpad, ABS_MT_ORIENTATION,
0, orientation, 0, 0);
}
- if (sc->quirks & NSG_MRXU_REMOTE) {
+ if (sc->quirks & NSG_MRXU_REMOTE)
__set_bit(EV_REL, sc->touchpad->evbit);
- }
ret = input_mt_init_slots(sc->touchpad, touch_count, INPUT_MT_POINTER);
if (ret < 0)
@@ -1440,7 +1437,7 @@ static void sixaxis_set_leds_from_id(struct sony_sc *sc)
int id = sc->device_id;
- BUILD_BUG_ON(MAX_LEDS < ARRAY_SIZE(sixaxis_leds[0]));
+ BUILD_BUG_ON(ARRAY_SIZE(sixaxis_leds[0]) > MAX_LEDS);
if (id < 0)
return;
@@ -1458,7 +1455,7 @@ static void buzz_set_leds(struct sony_sc *sc)
struct hid_report, list);
s32 *value = report->field[0]->value;
- BUILD_BUG_ON(MAX_LEDS < 4);
+ BUILD_BUG_ON(4 > MAX_LEDS);
value[0] = 0x00;
value[1] = sc->led_state[0] ? 0xff : 0x00;
@@ -1655,15 +1652,12 @@ static int sony_leds_init(struct sony_sc *sc)
name_sz = strlen(dev_name(&hdev->dev)) + strlen(color_name_str[n]) + 2;
led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev) + name_sz, GFP_KERNEL);
- if (!led) {
- hid_err(hdev, "Couldn't allocate memory for LED %d\n", n);
+ if (!led)
return -ENOMEM;
- }
name = (void *)(&led[1]);
if (use_color_names)
- snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev),
- color_name_str[n]);
+ snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), color_name_str[n]);
else
snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), n + 1);
led->name = name;
@@ -2180,7 +2174,7 @@ static int sony_input_configured(struct hid_device *hdev,
}
sony_init_output_report(sc, sixaxis_send_output_report);
- } else if (sc->quirks & RB3_PS3_PRO_INSTRUMENT) {
+ } else if (sc->quirks & RB3_PRO_INSTRUMENT) {
/*
* Rock Band 3 PS3 Pro Instruments also do not handle HID Output
* Reports on the interrupt EP like they should, so we need to force
@@ -2309,10 +2303,8 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
quirks |= SHANWAN_GAMEPAD;
sc = devm_kzalloc(&hdev->dev, sizeof(*sc), GFP_KERNEL);
- if (sc == NULL) {
- hid_err(hdev, "can't alloc sony descriptor\n");
+ if (!sc)
return -ENOMEM;
- }
spin_lock_init(&sc->lock);
@@ -2360,9 +2352,8 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto err;
}
- if (sc->quirks & RB3_PS3_PRO_INSTRUMENT) {
+ if (sc->quirks & RB3_PRO_INSTRUMENT)
sc->rb3_pro_poke_jiffies = 0;
- }
if (sc->quirks & (GHL_GUITAR_PS3WIIU | GHL_GUITAR_PS4)) {
if (!hid_is_usb(hdev)) {
@@ -2514,7 +2505,7 @@ static const struct hid_device_id sony_devices[] = {
.driver_data = INSTRUMENT },
/* DJ Hero PS3 Guitar Dongle */
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_DJH_TURNTABLE),
- .driver_data = PS3_DJH_TURNTABLE | INSTRUMENT },
+ .driver_data = DJH_TURNTABLE | INSTRUMENT },
/* Guitar Hero Live PS4 guitar dongles */
{ HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE),
.driver_data = GHL_GUITAR_PS4 | GH_GUITAR_TILT | INSTRUMENT },
@@ -2552,17 +2543,17 @@ static const struct hid_device_id sony_devices[] = {
.driver_data = INSTRUMENT },
/* Rock Band 3 PS3 Pro instruments */
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MUSTANG_GUITAR),
- .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT },
+ .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_SQUIRE_GUITAR),
- .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT },
+ .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_MUSTANG_MODE),
- .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT },
+ .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_SQUIRE_MODE),
- .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT },
+ .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_KEYBOARD),
- .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT },
+ .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_KEYBOARD_MODE),
- .driver_data = INSTRUMENT | RB3_PS3_PRO_INSTRUMENT },
+ .driver_data = INSTRUMENT | RB3_PRO_INSTRUMENT },
/* Rock Band 4 PS4 guitars */
{ HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER),
.driver_data = RB4_GUITAR_PS4_USB | INSTRUMENT },
--
2.53.0
^ permalink raw reply related
* [PATCH] HID: alps: fix NULL pointer dereference in alps_raw_event()
From: Greg Kroah-Hartman @ 2026-04-06 14:03 UTC (permalink / raw)
To: linux-input
Cc: linux-kernel, Greg Kroah-Hartman, stable, Jiri Kosina,
Benjamin Tissoires, Masaki Ota
Commit ecfa6f34492c ("HID: Add HID_CLAIMED_INPUT guards in raw_event
callbacks missing them") attempted to fix up the HID drivers that had
missed the previous fix that was done in 2ff5baa9b527 ("HID: appleir:
Fix potential NULL dereference at raw event handle"), but the alps
driver was missed.
Fix this up by properly checking in the hid-alps driver that it had been
claimed correctly before attempting to process the raw event.
Fixes: 73196ebe134d ("HID: alps: add support for Alps T4 Touchpad device")
Cc: stable <stable@kernel.org>
Cc: Jiri Kosina <jikos@kernel.org>
Cc: Benjamin Tissoires <bentiss@kernel.org>
Cc: Masaki Ota <masaki.ota@jp.alps.com>
Cc: linux-input@vger.kernel.org
Assisted-by: gregkh_clanker_t1000
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/hid/hid-alps.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c
index 21e55f3d0d1b..67179e3fe39b 100644
--- a/drivers/hid/hid-alps.c
+++ b/drivers/hid/hid-alps.c
@@ -437,6 +437,9 @@ static int alps_raw_event(struct hid_device *hdev,
int ret = 0;
struct alps_dev *hdata = hid_get_drvdata(hdev);
+ if (!(hdev->claimed & HID_CLAIMED_INPUT) || !hdata->input)
+ return 0;
+
switch (hdev->product) {
case HID_PRODUCT_ID_T4_BTNLESS:
ret = t4_raw_event(hdata, data, size);
--
2.53.0
^ permalink raw reply related
* [PATCH] HID: core: clamp report_size in s32ton() to avoid undefined shift
From: Greg Kroah-Hartman @ 2026-04-06 14:04 UTC (permalink / raw)
To: linux-input
Cc: linux-kernel, Greg Kroah-Hartman, stable, Jiri Kosina,
Benjamin Tissoires
s32ton() shifts by n-1 where n is the field's report_size, a value that
comes directly from a HID device. The HID parser bounds report_size
only to <= 256, so a broken HID device can supply a report descriptor
with a wide field that triggers shift exponents up to 256 on a 32-bit
type when an output report is built via hid_output_field() or
hid_set_field().
Commit ec61b41918587 ("HID: core: fix shift-out-of-bounds in
hid_report_raw_event") added the same n > 32 clamp to the function
snto32(), but s32ton() was never given the same fix as I guess syzbot
hadn't figured out how to fuzz a device the same way.
Fix this up by just clamping the max value of n, just like snto32()
does.
Cc: stable <stable@kernel.org>
Cc: Jiri Kosina <jikos@kernel.org>
Cc: Benjamin Tissoires <bentiss@kernel.org>
Cc: linux-input@vger.kernel.org
Assisted-by: gregkh_clanker_t1000
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/hid/hid-core.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 833df14ef68f..868c65684aa8 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -71,6 +71,9 @@ static u32 s32ton(__s32 value, unsigned int n)
if (!value || !n)
return 0;
+ if (n > 32)
+ n = 32;
+
a = value >> (n - 1);
if (a && a != -1)
return value < 0 ? 1 << (n - 1) : (1 << (n - 1)) - 1;
--
2.53.0
^ permalink raw reply related
* [PATCH] Input: gpio-keys - add hibernation support
From: Armando De Leon @ 2026-04-06 16:04 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: linux-input, linux-kernel, Armando De Leon
The gpio-keys driver uses DEFINE_SIMPLE_DEV_PM_OPS which maps .freeze
and .restore to the same callbacks as .suspend and .resume. This is
insufficient for hibernation (suspend-to-disk) because the SoC is fully
powered off, unlike suspend-to-RAM where hardware state is preserved.
After hibernation resume, GPIO keys fail to function correctly because
the interrupt controller (e.g. GIC) is fully re-initialized during
restore, resetting all IRQ trigger type configurations to defaults.
GPIO keys require IRQ_TYPE_EDGE_BOTH for proper press/release detection,
but after the interrupt controller re-initialization only a single edge
remains active. This causes buttons to report only release events but
not press events, or vice versa.
The existing gpio_keys_button_disable_wakeup() does restore
IRQ_TYPE_EDGE_BOTH, but only when wakeup_trigger_type is non-zero.
When the device tree does not specify wakeup-event-action (the common
case), wakeup_trigger_type defaults to zero and the IRQ type
restoration is skipped entirely.
Fix this by implementing dedicated .freeze and .restore callbacks:
- gpio_keys_freeze(): Marks all buttons as suspended before the
hibernate image is created. Unlike .suspend, it does not configure
wakeup IRQs since the SoC will be powered off.
- gpio_keys_restore(): Re-applies the pinctrl default state to restore
GPIO pin configuration, explicitly reconfigures IRQ trigger type to
IRQ_TYPE_EDGE_BOTH for all GPIO-backed buttons, clears the suspended
flag, and re-syncs current GPIO state with the input subsystem.
The .thaw callback reuses gpio_keys_resume() since the SoC remains
powered when hibernation is aborted and hardware state is intact.
Signed-off-by: Armando De Leon <learmand@amazon.com>
---
drivers/input/keyboard/gpio_keys.c | 60 +++++++++++++++++++++++++++++-
1 file changed, 59 insertions(+), 1 deletion(-)
diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c
index e19617485..9fb77c9aa 100644
--- a/drivers/input/keyboard/gpio_keys.c
+++ b/drivers/input/keyboard/gpio_keys.c
@@ -27,6 +27,7 @@
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_irq.h>
+#include <linux/pinctrl/consumer.h>
#include <linux/spinlock.h>
#include <dt-bindings/input/gpio-keys.h>
@@ -1088,7 +1089,64 @@ static int gpio_keys_resume(struct device *dev)
return 0;
}
-static DEFINE_SIMPLE_DEV_PM_OPS(gpio_keys_pm_ops, gpio_keys_suspend, gpio_keys_resume);
+static int gpio_keys_freeze(struct device *dev)
+{
+ struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev);
+ struct gpio_button_data *bdata;
+ int i;
+
+ for (i = 0; i < ddata->pdata->nbuttons; i++) {
+ bdata = &ddata->data[i];
+ bdata->suspended = true;
+ }
+
+ return 0;
+}
+
+static int gpio_keys_restore(struct device *dev)
+{
+ struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev);
+ struct gpio_button_data *bdata;
+ int error;
+ int i;
+
+ error = pinctrl_pm_select_default_state(dev);
+ if (error)
+ dev_warn(dev, "failed to restore pinctrl default state: %d\n",
+ error);
+
+ for (i = 0; i < ddata->pdata->nbuttons; i++) {
+ bdata = &ddata->data[i];
+ bdata->suspended = false;
+
+ /*
+ * After hibernation the interrupt controller is
+ * re-initialized and loses its configuration.
+ * Restore dual-edge triggering for GPIO-backed
+ * buttons so both press and release are detected.
+ */
+ if (bdata->gpiod) {
+ error = irq_set_irq_type(bdata->irq,
+ IRQ_TYPE_EDGE_BOTH);
+ if (error)
+ dev_warn(dev,
+ "failed to restore IRQ %d trigger: %d\n",
+ bdata->irq, error);
+ }
+ }
+
+ gpio_keys_report_state(ddata);
+ return 0;
+}
+
+static const struct dev_pm_ops gpio_keys_pm_ops = {
+ .suspend = gpio_keys_suspend,
+ .resume = gpio_keys_resume,
+ .freeze = gpio_keys_freeze,
+ .restore = gpio_keys_restore,
+ .thaw = gpio_keys_resume,
+ .poweroff = gpio_keys_suspend,
+};
static void gpio_keys_shutdown(struct platform_device *pdev)
{
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] Input: gpio-keys - add hibernation support
From: Dmitry Torokhov @ 2026-04-06 16:24 UTC (permalink / raw)
To: Armando De Leon; +Cc: linux-input, linux-kernel, Armando De Leon
In-Reply-To: <20260406160437.3084755-1-learmand@amazon.com>
Hi Armando,
On Mon, Apr 06, 2026 at 09:04:37AM -0700, Armando De Leon wrote:
> The gpio-keys driver uses DEFINE_SIMPLE_DEV_PM_OPS which maps .freeze
> and .restore to the same callbacks as .suspend and .resume. This is
> insufficient for hibernation (suspend-to-disk) because the SoC is fully
> powered off, unlike suspend-to-RAM where hardware state is preserved.
>
> After hibernation resume, GPIO keys fail to function correctly because
> the interrupt controller (e.g. GIC) is fully re-initialized during
> restore, resetting all IRQ trigger type configurations to defaults.
> GPIO keys require IRQ_TYPE_EDGE_BOTH for proper press/release detection,
> but after the interrupt controller re-initialization only a single edge
> remains active. This causes buttons to report only release events but
> not press events, or vice versa.
I believe you just described a bug in the interrupt controller handling
of hibernation. It needs to be fixed there, not in consumer driver.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] Input: gpio-keys - add hibernation support
From: Armando De Leon @ 2026-04-06 16:58 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: Armando De Leon, linux-input, linux-kernel
In-Reply-To: <adPdc2Z1SYxvqDmP@google.com>
Hi Dmitry,
Thank you for the review.
The interrupt controller (GICv3) is re-initialized by platform
firmware during hibernate restore - this is expected behavior, not
a bug. The IRQ trigger type (EDGE_BOTH) was originally configured
by devm_request_any_context_irq() during probe(), which does not
run again after hibernate restore.
The TLMM/pinctrl registers are correctly saved and restored by the
platform's syscore_ops - I verified this with register dumps. The
issue is specifically that the IRQ trigger type configured at the
GIC level during probe is lost and not re-applied.
Should the generic IRQ core be responsible for restoring trigger
types across hibernate? Otherwise, consumer drivers like gpio-keys need to handle
this in their .restore callback.
Either way, gpio-keys currently lacks .freeze/.restore callbacks
entirely, which is needed for proper hibernation support.
Thanks,
Armando
^ permalink raw reply
* Re: [PATCH 1/2] input: pc110pad: change PCI check to get rid of orphaned no_pci_devices
From: Bjorn Helgaas @ 2026-04-06 17:23 UTC (permalink / raw)
To: Heiner Kallweit
Cc: Dmitry Torokhov, Bjorn Helgaas, open list:HID CORE LAYER,
linux-pci@vger.kernel.org
In-Reply-To: <cf519463-775a-4d43-be38-20742fdad407@gmail.com>
On Fri, Apr 03, 2026 at 12:17:35AM +0200, Heiner Kallweit wrote:
> As a prerequisite for removing no_pci_devices(), replace its usage here
> with an equivalent check for presence of a PCI bus.
>
> Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
> ---
> drivers/input/mouse/pc110pad.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/input/mouse/pc110pad.c b/drivers/input/mouse/pc110pad.c
> index efa58049f..c7a6df210 100644
> --- a/drivers/input/mouse/pc110pad.c
> +++ b/drivers/input/mouse/pc110pad.c
> @@ -91,7 +91,7 @@ static int __init pc110pad_init(void)
> {
> int err;
>
> - if (!no_pci_devices())
> + if (pci_find_next_bus(NULL))
> return -ENODEV;
I'd certainly love to get rid of no_pci_devices(), although using
pci_find_next_bus() is not really much better.
I don't have much opinion about pc110pad.c change. It's essentially
still the same kludge, which has been there at least since the
beginning of git history.
Dmitry, if you want to ack this, I can take both together.
Bjorn
^ permalink raw reply
* Re: [PATCH] Input: gpio-keys - add hibernation support
From: Dmitry Torokhov @ 2026-04-06 18:18 UTC (permalink / raw)
To: Armando De Leon; +Cc: Armando De Leon, linux-input, linux-kernel
In-Reply-To: <20260406165833.3137128-1-learmand@amazon.com>
On Mon, Apr 06, 2026 at 09:58:06AM -0700, Armando De Leon wrote:
> Hi Dmitry,
>
> Thank you for the review.
>
> The interrupt controller (GICv3) is re-initialized by platform
> firmware during hibernate restore - this is expected behavior, not
> a bug. The IRQ trigger type (EDGE_BOTH) was originally configured
> by devm_request_any_context_irq() during probe(), which does not
> run again after hibernate restore.
>
> The TLMM/pinctrl registers are correctly saved and restored by the
> platform's syscore_ops - I verified this with register dumps. The
> issue is specifically that the IRQ trigger type configured at the
> GIC level during probe is lost and not re-applied.
>
> Should the generic IRQ core be responsible for restoring trigger
> types across hibernate? Otherwise, consumer drivers like gpio-keys need to handle
> this in their .restore callback.
I am not sure if it is job of IRQ core vs. particular interrupt
controller, but they should restore the trigger types along with
entirety of the interrupts and pins states to exactly the same condition
that they were before entering hibernation.
>
> Either way, gpio-keys currently lacks .freeze/.restore callbacks
> entirely, which is needed for proper hibernation support.
Drivers only need dedicated freeze and restore handlers if the behavior
should be different between suspend-to-ram vs suspend-to-disk. For the
vast majority of the drivers they behave exactly the same and I do not
see why it would be different for gpio-keys.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] HID: lg-g15: Add support for the Logitech G710/G710+ gaming keyboards
From: Hans de Goede @ 2026-04-06 18:50 UTC (permalink / raw)
To: Xavier Bestel, Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, linux-kernel
In-Reply-To: <20260402075239.3829699-1-xav@bes.tel>
Hi Xavier,
Thank you for your patch, it is always nice to have people helping
with improving Linux support for devices like this.
On 2-Apr-26 09:52, Xavier Bestel wrote:
> Add support for the Logitech G710 and G710+ (USB ID 046d:c24d) gaming
> keyboards to the hid-lg-g15 driver.
>
> These keyboards have 6 G-keys (G1-G6), M-keys (M1-M3, MR), a game mode
> toggle, and two independent backlights: one for the main keyboard and one
> for the WASD keys, each with a physical button to cycle brightness through
> 5 levels (0-4).
>
> Key implementation details:
>
> - G-keys and M-keys are reported via HID input report 0x03 (4 bytes)
> using KEY_MACRO1-6, KEY_MACRO_PRESET1-3 and KEY_MACRO_RECORD_START.
>
> - The WASD backlight LED is registered as
> "g15::kbd_zoned_backlight-wasd" rather than with a
> "::kbd_backlight" suffix, because UPower currently only supports a
> single kbd_backlight LED per device. This follows the nomenclature
> from Documentation/leds/leds-class.rst
Actually that nomenclature is something which was suggested in the past
but has never been agreed upon.
Since the discussion about how to name the LED class devices for
multi-zone keyboard backlights keeps coming up I've submitted a patch
now to document how these should be named:
https://lore.kernel.org/linux-leds/20260406174638.320135-1-johannes.goede@oss.qualcomm.com/
Since the new userspace API for this needs to be agreed upon first, that
patch should be accepted upstream first, before we can move forward with
the G710 support from this patch.
>
> - The G710+ firmware GET_REPORT for the backlight feature (0x08)
> always returns the power-on default values, ignoring any changes
> made via SET_REPORT. To work around this, the backlight brightness
> is tracked in the driver cache and brightness_get returns the
> cached value. M-key and game mode LEDs read back correctly.
>
> - The physical brightness cycle buttons are handled following the
> same pattern as the G15/G15v2: no key events are sent, instead the
> driver cycles the cached brightness and calls
> led_classdev_notify_brightness_hw_changed() from a work function,
> which allows GNOME to show the brightness OSD.
>
> - The game mode toggle is handled entirely by the firmware (it
> disables the Super key and lights an indicator LED). The driver
> exposes a read-only LED "g15::gamemode" with the
> LED_BRIGHT_HW_CHANGED flag, and notifies userspace of state
> changes via led_classdev_notify_brightness_hw_changed() by reading
> back the actual firmware state on each toggle.
>
> - Both brightness LEDs are registered with the LED_BRIGHT_HW_CHANGED
> flag to enable the brightness_hw_changed sysfs attribute.
>
> - HID_QUIRK_NOGET is set because the keyboard has buggy GET_REPORT
> handling that causes timeouts on some feature reports.
>
> - The G-keys feature report (0x09) uses 2 bytes per key rather than
> 1 as on the G510, so the report size calculation is adjusted
> accordingly.
>
> - Also fix a pre-existing comment typo: "f000.0000" -> "ff00.0000"
> for the application report ID.
>
> - The loop bounds in lg_g15_led_set() and lg_g510_led_set() are
> tightened from "< LG_G15_LED_MAX" to "<= LG_G15_MACRO_RECORD" to
> avoid iterating over the new LG_G15_GAMEMODE enum value which does
> not apply to those keyboards.
>
> Signed-off-by: Xavier Bestel <xav@bes.tel>
> ---
> drivers/hid/hid-ids.h | 1 +
> drivers/hid/hid-lg-g15.c | 393 ++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 384 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index afcee13bad61..7c0f930bd014 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -904,6 +904,7 @@
> #define USB_DEVICE_ID_LOGITECH_G15_V2_LCD 0xc227
> #define USB_DEVICE_ID_LOGITECH_G510 0xc22d
> #define USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO 0xc22e
> +#define USB_DEVICE_ID_LOGITECH_G710 0xc24d
> #define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f
> #define USB_DEVICE_ID_LOGITECH_G920_WHEEL 0xc262
> #define USB_DEVICE_ID_LOGITECH_G923_XBOX_WHEEL 0xc26e
> diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c
> index 1a88bc44ada4..8a4c4eb22c07 100644
> --- a/drivers/hid/hid-lg-g15.c
> +++ b/drivers/hid/hid-lg-g15.c
> @@ -29,6 +29,11 @@
> #define LG_G510_INPUT_MACRO_KEYS 0x03
> #define LG_G510_INPUT_KBD_BACKLIGHT 0x04
>
> +#define LG_G710_FEATURE_GAMEMODE 0x05
> +#define LG_G710_FEATURE_M_KEYS_LEDS 0x06
> +#define LG_G710_FEATURE_BACKLIGHT 0x08
> +#define LG_G710_FEATURE_EXTRA_KEYS 0x09
> +
> #define LG_G13_INPUT_REPORT 0x01
> #define LG_G13_FEATURE_M_KEYS_LEDS 0x05
> #define LG_G13_FEATURE_BACKLIGHT_RGB 0x07
> @@ -48,6 +53,7 @@ enum lg_g15_model {
> LG_G15_V2,
> LG_G510,
> LG_G510_USB_AUDIO,
> + LG_G710,
> LG_Z10,
> };
>
> @@ -59,6 +65,7 @@ enum lg_g15_led_type {
> LG_G15_MACRO_PRESET2,
> LG_G15_MACRO_PRESET3,
> LG_G15_MACRO_RECORD,
> + LG_G15_GAMEMODE,
> LG_G15_LED_MAX
> };
>
> @@ -91,7 +98,9 @@ struct lg_g15_data {
> enum lg_g15_model model;
> struct lg_g15_led leds[LG_G15_LED_MAX];
> bool game_mode_enabled;
> + u16 pressed_keys;
> bool backlight_disabled; /* true == HW backlight toggled *OFF* */
> + unsigned long brightness_changed; /* bitmask of LEDs hw-cycled */
> };
>
> /********* G13 LED functions ***********/
> @@ -334,7 +343,7 @@ static int lg_g15_led_set(struct led_classdev *led_cdev,
> g15->transfer_buf[1] = g15_led->led + 1;
> g15->transfer_buf[2] = brightness << (g15_led->led * 4);
> } else {
> - for (i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; i++) {
> + for (i = LG_G15_MACRO_PRESET1; i <= LG_G15_MACRO_RECORD; i++) {
> if (i == g15_led->led)
> val = brightness;
> else
> @@ -567,7 +576,7 @@ static int lg_g510_mkey_led_set(struct led_classdev *led_cdev,
>
> mutex_lock(&g15->mutex);
>
> - for (i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; i++) {
> + for (i = LG_G15_MACRO_PRESET1; i <= LG_G15_MACRO_RECORD; i++) {
> if (i == g15_led->led)
> val = brightness;
> else
> @@ -597,6 +606,239 @@ static int lg_g510_mkey_led_set(struct led_classdev *led_cdev,
> return ret;
> }
>
> +/******** G710 LED functions ********/
> +
> +static int lg_g710_update_game_led_brightness(struct lg_g15_data *g15)
> +{
> + int ret;
> +
> + ret = hid_hw_raw_request(g15->hdev, LG_G710_FEATURE_GAMEMODE,
> + g15->transfer_buf, 8,
> + HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
> + if (ret != 8) {
> + hid_err(g15->hdev, "Error getting gamemode LED brightness: %d\n", ret);
> + return (ret < 0) ? ret : -EIO;
> + }
> +
> + g15->leds[LG_G15_GAMEMODE].brightness =
> + !!g15->transfer_buf[1];
> +
> + return 0;
> +}
> +
> +static int lg_g710_update_mkey_led_brightness(struct lg_g15_data *g15)
> +{
> + int ret;
> +
> + ret = hid_hw_raw_request(g15->hdev, LG_G710_FEATURE_M_KEYS_LEDS,
> + g15->transfer_buf, 2,
> + HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
> + if (ret != 2) {
> + hid_err(g15->hdev, "Error getting macro LED brightness: %d\n", ret);
> + return (ret < 0) ? ret : -EIO;
> + }
> +
> + g15->leds[LG_G15_MACRO_PRESET1].brightness =
> + !!(g15->transfer_buf[1] & 0x10);
> + g15->leds[LG_G15_MACRO_PRESET2].brightness =
> + !!(g15->transfer_buf[1] & 0x20);
> + g15->leds[LG_G15_MACRO_PRESET3].brightness =
> + !!(g15->transfer_buf[1] & 0x40);
> + g15->leds[LG_G15_MACRO_RECORD].brightness =
> + !!(g15->transfer_buf[1] & 0x80);
> +
> + return 0;
> +}
> +
> +static int lg_g710_update_kbd_led_brightness(struct lg_g15_data *g15)
> +{
> + int ret;
> +
> + ret = hid_hw_raw_request(g15->hdev, LG_G710_FEATURE_BACKLIGHT,
> + g15->transfer_buf, 4,
> + HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
> + if (ret != 4) {
> + hid_err(g15->hdev, "Error getting LED brightness: %d\n", ret);
> + return (ret < 0) ? ret : -EIO;
> + }
> +
> + g15->leds[LG_G15_KBD_BRIGHTNESS].brightness = 4 - g15->transfer_buf[2];
> + g15->leds[LG_G15_LCD_BRIGHTNESS].brightness = 4 - g15->transfer_buf[1];
I think this needs a range-check on the buf contents to avoid e.g.
negative brightness values when we somehow get unexpected values back from
the keyboard.
> +
> + return 0;
> +}
> +
> +static enum led_brightness lg_g710_led_get(struct led_classdev *led_cdev)
> +{
> + struct lg_g15_led *g15_led =
> + container_of(led_cdev, struct lg_g15_led, cdev);
> + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
> + enum led_brightness brightness;
> +
> + mutex_lock(&g15->mutex);
> + /*
> + * The G710+ firmware's GET_REPORT for the backlight always returns
> + * the power-on default values, ignoring any changes made via
> + * SET_REPORT. Use the cached brightness which is kept in sync by
> + * the _set callbacks. M-key and gamemode LEDs read back correctly.
> + */
> + if (g15_led->led >= LG_G15_BRIGHTNESS_MAX && g15_led->led < LG_G15_GAMEMODE)
> + lg_g710_update_mkey_led_brightness(g15);
> + else if (g15_led->led >= LG_G15_GAMEMODE)
> + lg_g710_update_game_led_brightness(g15);
> + brightness = g15->leds[g15_led->led].brightness;
> + mutex_unlock(&g15->mutex);
> +
> + return brightness;
> +}
> +
> +static int lg_g710_mkey_led_set(struct led_classdev *led_cdev,
> + enum led_brightness brightness)
> +{
> + struct lg_g15_led *g15_led =
> + container_of(led_cdev, struct lg_g15_led, cdev);
> + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
> + u8 val, mask = 0;
> + int i, ret;
> +
> + /* Ignore LED off on unregister / keyboard unplug */
> + if (led_cdev->flags & LED_UNREGISTERING)
> + return 0;
> +
> + mutex_lock(&g15->mutex);
> +
> + g15->transfer_buf[0] = LG_G710_FEATURE_M_KEYS_LEDS;
> +
> + for (i = LG_G15_MACRO_PRESET1; i <= LG_G15_MACRO_RECORD; i++) {
> + if (i == g15_led->led)
> + val = brightness;
> + else
> + val = g15->leds[i].brightness;
> +
> + if (val)
> + mask |= 1 << (i - LG_G15_MACRO_PRESET1 + 4);
> + }
> +
> + g15->transfer_buf[1] = mask;
> +
> + ret = hid_hw_raw_request(g15->hdev, LG_G710_FEATURE_M_KEYS_LEDS,
> + g15->transfer_buf, 2,
> + HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> + if (ret == 2) {
> + /* Success */
> + g15_led->brightness = brightness;
> + ret = 0;
> + } else {
> + hid_err(g15->hdev, "Error setting LED brightness: %d\n", ret);
> + ret = (ret < 0) ? ret : -EIO;
> + }
> +
> + mutex_unlock(&g15->mutex);
> +
> + return ret;
> +}
> +
> +static int lg_g710_kbd_led_set(struct led_classdev *led_cdev,
> + enum led_brightness brightness)
> +{
> + struct lg_g15_led *g15_led =
> + container_of(led_cdev, struct lg_g15_led, cdev);
> + struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
> + int ret;
> +
> + /* Ignore LED off on unregister / keyboard unplug */
> + if (led_cdev->flags & LED_UNREGISTERING)
> + return 0;
> +
> + mutex_lock(&g15->mutex);
> +
> + g15->transfer_buf[0] = LG_G710_FEATURE_BACKLIGHT;
> + g15->transfer_buf[3] = 0;
> +
> + if (g15_led->led == LG_G15_KBD_BRIGHTNESS) {
> + g15->transfer_buf[1] = 4 - g15->leds[LG_G15_LCD_BRIGHTNESS].brightness;
> + g15->transfer_buf[2] = 4 - brightness;
> + } else {
> + g15->transfer_buf[1] = 4 - brightness;
> + g15->transfer_buf[2] = 4 - g15->leds[LG_G15_KBD_BRIGHTNESS].brightness;
> + }
> +
> + ret = hid_hw_raw_request(g15->hdev, LG_G710_FEATURE_BACKLIGHT,
> + g15->transfer_buf, 4,
> + HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> + if (ret == 4) {
> + /* Success */
> + g15_led->brightness = brightness;
> + ret = 0;
> + } else {
> + hid_err(g15->hdev, "Error setting LED brightness: %d\n", ret);
> + ret = (ret < 0) ? ret : -EIO;
> + }
> +
> + mutex_unlock(&g15->mutex);
> +
> + return ret;
> +}
> +
> +/*
> + * The G710+ has separate physical keys for cycling the main keyboard backlight
> + * and the WASD backlight. The firmware handles the actual brightness change
> + * internally, but GET_REPORT always returns the power-on defaults regardless
> + * of any changes. So we must track the brightness in our cache and cycle it
> + * ourselves when a hardware brightness key press is detected.
> + *
> + * The firmware cycles brightness DOWN: 4 → 3 → 2 → 1 → 0 → 4 (in wire
> + * format where 0 = brightest, 4 = off). In user-facing terms (inverted):
> + * 4 → 3 → 2 → 1 → 0 → 4.
> + *
> + * The game mode toggle is also handled here: the firmware toggles game mode
> + * internally and updates the LED, so we read back the actual state via
> + * GET_REPORT and notify userspace of the change.
> + */
> +static void lg_g710_leds_changed_work(struct work_struct *work)
> +{
> + struct lg_g15_data *g15 = container_of(work, struct lg_g15_data, work);
> + enum led_brightness brightness[LG_G15_BRIGHTNESS_MAX];
> + bool changed[LG_G15_BRIGHTNESS_MAX] = {};
> + bool gamemode_changed;
This can be simplified a bit, drop the brightness[] array here as
well as the gamemode_changed bool and make the changed array
LG_G15_LED_MAX entries large.
> + int i;
> +
> + mutex_lock(&g15->mutex);
> + for (i = 0; i < LG_G15_BRIGHTNESS_MAX; i++) {
> + if (!test_and_clear_bit(i, &g15->brightness_changed))
> + continue;
> +
> + changed[i] = true;
> +
> + if (g15->leds[i].brightness > 0)
> + g15->leds[i].brightness--;
> + else
> + g15->leds[i].brightness =
> + g15->leds[i].cdev.max_brightness;
> +
> + brightness[i] = g15->leds[i].brightness;
No need to store in the brightness[i] array you can simply use
g15->leds[i].brightness below.
> + }
> +
> + gamemode_changed = test_and_clear_bit(LG_G15_GAMEMODE,
> + &g15->brightness_changed);
Use changed[LG_G15_GAMEMODE] here instead of the gamemode_changed bool,
replacing it twice both in the assignment and in the if below.
> + if (gamemode_changed)
> + lg_g710_update_game_led_brightness(g15);
> + mutex_unlock(&g15->mutex);
move this mutex_unlock to below the below for loop.
> +
> + for (i = 0; i < LG_G15_BRIGHTNESS_MAX; i++) {
and make this loop condition "i < LG_G15_LED_MAX"
> + if (!changed[i])
> + continue;
> +
> + led_classdev_notify_brightness_hw_changed(&g15->leds[i].cdev,
> + brightness[i]);
use g15->leds[i].brightness here instead of brightness[i]
> + }
mutex_unlock() ends up here.
> +
> + if (gamemode_changed)
> + led_classdev_notify_brightness_hw_changed(
> + &g15->leds[LG_G15_GAMEMODE].cdev,
> + g15->leds[LG_G15_GAMEMODE].brightness);
and this bit can be dropped since this is handled by the loop now :)
> +}
> +
> /******** Generic LED functions ********/
> static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15)
> {
> @@ -619,6 +861,16 @@ static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15)
> return ret;
>
> return lg_g510_update_mkey_led_brightness(g15);
> + case LG_G710:
> + ret = lg_g710_update_game_led_brightness(g15);
> + if (ret)
> + return ret;
> +
> + ret = lg_g710_update_mkey_led_brightness(g15);
> + if (ret)
> + return ret;
> +
> + return lg_g710_update_kbd_led_brightness(g15);
> case LG_Z10:
> /*
> * Getting the LCD backlight brightness is not supported.
> @@ -890,6 +1142,74 @@ static int lg_g510_leds_event(struct lg_g15_data *g15, u8 *data)
> return 0;
> }
>
> +static int lg_g710_event(struct lg_g15_data *g15, u8 *data, int size)
> +{
> + /*
> + * Bits 0-5: G1-G6 keys
> + * Bits 6-8: M1-M3 keys
> + * Bit 9: MR key
> + * Bit 10: WASD backlight cycle (handled as hw brightness change)
> + * Bit 11: Kbd backlight cycle (handled as hw brightness change)
> + * Bit 12: Game mode toggle (LED state change, handled by firmware)
> + */
> + static const u16 keymap[] = {
> + KEY_MACRO1,
> + KEY_MACRO2,
> + KEY_MACRO3,
> + KEY_MACRO4,
> + KEY_MACRO5,
> + KEY_MACRO6,
> + KEY_MACRO_PRESET1,
> + KEY_MACRO_PRESET2,
> + KEY_MACRO_PRESET3,
> + KEY_MACRO_RECORD_START,
> + 0, /* WASD illumination cycle - not a key event */
> + 0, /* Kbd illumination cycle - not a key event */
> + 0, /* Game mode toggle */
> + };
> + u16 pressed_keys, changed;
> + int i;
> +
> + if (size != 4 || data[0] != 3)
> + return 1;
> +
> + pressed_keys = (data[1] & 0x3f) | ((data[2] & 0xf0) << 2) |
> + ((data[3] & 0x7) << 10);
> + changed = pressed_keys ^ g15->pressed_keys;
> +
> + for (i = 0; i < ARRAY_SIZE(keymap); i++) {
> + if (keymap[i] && (changed & BIT(i)))
> + input_report_key(g15->input, keymap[i],
> + pressed_keys & BIT(i));
> + }
> + input_sync(g15->input);
There is no need for the changed thing / check here and thus also
no need to remember the previously pressed keys. The input subsystem
is smart enough to not send out events when keys are unchanged from
the previous input_sync() call.
> +
> + /*
> + * Detect brightness key presses (0->1 transition) and schedule
> + * the work function to cycle cached brightness and notify userspace.
> + * Bit 10 = WASD backlight (maps to LG_G15_LCD_BRIGHTNESS slot).
> + * Bit 11 = Kbd backlight (maps to LG_G15_KBD_BRIGHTNESS slot).
> + */
> + if ((changed & BIT(10)) && (pressed_keys & BIT(10))) {
> + set_bit(LG_G15_LCD_BRIGHTNESS, &g15->brightness_changed);
> + schedule_work(&g15->work);
> + }
> + if ((changed & BIT(11)) && (pressed_keys & BIT(11))) {
> + set_bit(LG_G15_KBD_BRIGHTNESS, &g15->brightness_changed);
> + schedule_work(&g15->work);
> + }
> +
> + /* Game mode toggle — bit 12 is a state bit, trigger on any change */
> + if (changed & BIT(12)) {
> + set_bit(LG_G15_GAMEMODE, &g15->brightness_changed);
> + schedule_work(&g15->work);
> + }
> +
> + g15->pressed_keys = pressed_keys;
> +
> + return 0;
> +}
> +
> static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report,
> u8 *data, int size)
> {
> @@ -924,6 +1244,10 @@ static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report,
> if (data[0] == LG_G510_INPUT_KBD_BACKLIGHT && size == 2)
> return lg_g510_leds_event(g15, data);
> break;
> + case LG_G710:
> + if (data[0] == 0x03 && size == 4)
> + return lg_g710_event(g15, data, size);
> + break;
> }
>
> return 0;
> @@ -1055,6 +1379,37 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name)
> ret = devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
> }
> break;
> + case LG_G710:
> + switch (i) {
> + case LG_G15_LCD_BRIGHTNESS:
> + /*
> + * The G710+ does not have a separate LCD brightness,
> + * but it does have a separate brightness for WASD keys.
> + * Do not use the ::kbd_backlight suffix here, UPower
> + * only supports one kbd_backlight LED per device.
> + */
> + g15->leds[i].cdev.name = "g15::kbd_zoned_backlight-wasd";
> + fallthrough;
> + case LG_G15_KBD_BRIGHTNESS:
> + g15->leds[i].cdev.brightness_set_blocking =
> + lg_g710_kbd_led_set;
> + g15->leds[i].cdev.brightness_get =
> + lg_g710_led_get;
> + g15->leds[i].cdev.max_brightness = 4;
> + g15->leds[i].cdev.flags = LED_BRIGHT_HW_CHANGED;
> + break;
> + default:
> + if (i != LG_G15_GAMEMODE)
> + g15->leds[i].cdev.brightness_set_blocking =
> + lg_g710_mkey_led_set;
> + g15->leds[i].cdev.brightness_get =
> + lg_g710_led_get;
> + g15->leds[i].cdev.max_brightness = 1;
> + if (i == LG_G15_GAMEMODE)
> + g15->leds[i].cdev.flags = LED_BRIGHT_HW_CHANGED;
> + }
> + ret = devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
> + break;
> }
>
> return ret;
> @@ -1079,13 +1434,16 @@ static void lg_g15_init_input_dev_core(struct hid_device *hdev, struct input_dev
> static void lg_g15_init_input_dev(struct hid_device *hdev, struct input_dev *input,
> const char *name)
> {
> + struct lg_g15_data *g15 = hid_get_drvdata(hdev);
> int i;
>
> lg_g15_init_input_dev_core(hdev, input, name);
>
> - /* Keys below the LCD, intended for controlling a menu on the LCD */
> - for (i = 0; i < 5; i++)
> - input_set_capability(input, EV_KEY, KEY_KBD_LCD_MENU1 + i);
> + if (g15->model != LG_G710) {
> + /* Keys below the LCD, intended for controlling a menu on the LCD */
> + for (i = 0; i < 5; i++)
> + input_set_capability(input, EV_KEY, KEY_KBD_LCD_MENU1 + i);
> + }
Maybe instead add the following above the for-loop and
leave the for-loop as is? :
if (g15->model == LG_G710)
return;
> }
>
> static void lg_g13_init_input_dev(struct hid_device *hdev,
> @@ -1119,6 +1477,7 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
> "g15::macro_preset2",
> "g15::macro_preset3",
> "g15::macro_record",
> + "g15::gamemode",
> };
> u8 gkeys_settings_output_report = 0;
> u8 gkeys_settings_feature_report = 0;
> @@ -1137,8 +1496,8 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
> return ret;
>
> /*
> - * Some models have multiple interfaces, we want the interface with
> - * the f000.0000 application input report.
> + * Some models have multiple interfaces, we want the interface
> + * with the ff00.0000 application input report.
> */
> rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
> list_for_each_entry(rep, &rep_enum->report_list, list) {
> @@ -1212,6 +1571,13 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
> case LG_Z10:
> connect_mask = HID_CONNECT_HIDRAW;
> break;
> + case LG_G710:
> + INIT_WORK(&g15->work, lg_g710_leds_changed_work);
> + hdev->quirks |= HID_QUIRK_NOGET;
> + connect_mask = HID_CONNECT_DEFAULT;
> + gkeys_settings_feature_report = LG_G710_FEATURE_EXTRA_KEYS;
> + gkeys = 6;
> + break;
> }
>
> ret = hid_hw_start(hdev, connect_mask);
> @@ -1234,11 +1600,14 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
> }
>
> if (gkeys_settings_feature_report) {
> + int report_size = ((g15->model == LG_G710) ?
> + gkeys * 2 : gkeys) + 1;
> +
The [0] byte in the transfer buffer is the report-number,
so this should be:
int report_size = (g15->model == LG_G710) ? gkeys * 2 : gkeys;
> g15->transfer_buf[0] = gkeys_settings_feature_report;
> - memset(g15->transfer_buf + 1, 0, gkeys);
> + memset(g15->transfer_buf + 1, 0, report_size - 1);
and drop the - 1 here.
> ret = hid_hw_raw_request(g15->hdev,
> gkeys_settings_feature_report,
> - g15->transfer_buf, gkeys + 1,
> + g15->transfer_buf, report_size,
and re-add / keep the + 1 here.
> HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> }
>
> @@ -1327,7 +1696,7 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
> goto error_hw_stop;
>
> /* Register LED devices */
> - for (i = 0; i < LG_G15_LED_MAX; i++) {
> + for (i = 0; i < LG_G15_LED_MAX - (g15->model != LG_G710); i++) {
> ret = lg_g15_register_led(g15, i, led_names[i]);
> if (ret)
> goto error_hw_stop;
The " - (g15->model != LG_G710)" you add here is a bit convoluted.
I would prefer if you change the for condition to:
"i <= LG_G15_MACRO_RECORD" like you've done in other places and then below
the loop add:
if (g15->model == LG_G710) {
ret = lg_g15_register_led(g15, LG_G15_GAMEMODE, "g15::gamemode");
if (ret)
goto error_hw_stop;
}
You can then also drop the adding of "g15::gamemode" to led_names[].
This is somewhat more code, but a lot easier to parse / read so IMHO
this is better in the long run keeping readability of the code in mind.
Regards,
Hans
> @@ -1366,6 +1735,10 @@ static const struct hid_device_id lg_g15_devices[] = {
> { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
> USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO),
> .driver_data = LG_G510_USB_AUDIO },
> + /* G710 or G710+ */
> + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
> + USB_DEVICE_ID_LOGITECH_G710),
> + .driver_data = LG_G710 },
> /* Z-10 speakers */
> { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
> USB_DEVICE_ID_LOGITECH_Z_10_SPK),
^ permalink raw reply
* Re: [PATCH 1/2] input: pc110pad: change PCI check to get rid of orphaned no_pci_devices
From: Dmitry Torokhov @ 2026-04-06 19:02 UTC (permalink / raw)
To: Bjorn Helgaas
Cc: Heiner Kallweit, Bjorn Helgaas, open list:HID CORE LAYER,
linux-pci@vger.kernel.org
In-Reply-To: <20260406172324.GA170281@bhelgaas>
On Mon, Apr 06, 2026 at 12:23:24PM -0500, Bjorn Helgaas wrote:
> On Fri, Apr 03, 2026 at 12:17:35AM +0200, Heiner Kallweit wrote:
> > As a prerequisite for removing no_pci_devices(), replace its usage here
> > with an equivalent check for presence of a PCI bus.
> >
> > Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
> > ---
> > drivers/input/mouse/pc110pad.c | 2 +-
> > 1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/drivers/input/mouse/pc110pad.c b/drivers/input/mouse/pc110pad.c
> > index efa58049f..c7a6df210 100644
> > --- a/drivers/input/mouse/pc110pad.c
> > +++ b/drivers/input/mouse/pc110pad.c
> > @@ -91,7 +91,7 @@ static int __init pc110pad_init(void)
> > {
> > int err;
> >
> > - if (!no_pci_devices())
> > + if (pci_find_next_bus(NULL))
> > return -ENODEV;
>
> I'd certainly love to get rid of no_pci_devices(), although using
> pci_find_next_bus() is not really much better.
>
> I don't have much opinion about pc110pad.c change. It's essentially
> still the same kludge, which has been there at least since the
> beginning of git history.
>
> Dmitry, if you want to ack this, I can take both together.
Given "x86/cpu: Remove M486/M486SX/ELAN support" I'd just drop the
driver completely.
I tried it in 2024 but Maciej was insistent that keeping these old
drivers doe snot hurt. Now I think we can get rid of them.
You can grab the patch removing it from
https://lore.kernel.org/all/20240808172733.1194442-4-dmitry.torokhov@gmail.com/
as I think it should still apply.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] Input: gpio-keys - add hibernation support
From: Armando De Leon @ 2026-04-06 20:39 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: Armando De Leon, linux-input, linux-kernel
In-Reply-To: <adPdc2Z1SYxvqDmP@google.com>
Hi Dmitry,
Thanks for the guidance. I investigated further:
The upstream GICv3 driver (irq-gic-v3.c) has no suspend/resume or
syscore_ops for the distributor - it does not save or restore any
per-IRQ configuration across power cycles. On platforms where the
GIC is re-initialized by firmware during hibernate restore, all
trigger type configurations set by consumer drivers during probe()
are lost.
The IRQ core does preserve the trigger type in irq_data
(irqd_get_trigger_type), but resume_irq() in kernel/irq/pm.c only
re-enables the interrupt without re-applying the trigger type to
hardware.
A fix in the IRQ PM resume path (having resume_irq() call
irq_set_irq_type() using the saved trigger type) would fix all
consumer drivers generically. However, such a change would have
broad consequences and impact across all platforms and interrupt
controllers, and would need very careful review and testing.
Given that the gpio-keys fix is a single irq_set_irq_type() call
in the .restore callback - minimal, self-contained, and low risk -
would it be acceptable to handle it at the driver level for now?
This avoids the risk of a sweeping IRQ core change while solving
the immediate problem for gpio-keys users with hibernation support.
Thanks,
Armando
^ permalink raw reply
* Re: [PATCH] Input: gf2k: clamp hat values to the lookup table
From: Pengpeng Hou @ 2026-04-07 3:30 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, kees, pengpeng
In-Reply-To: <20260323074541.93413-1-pengpeng@iscas.ac.cn>
Hi Dmitry,
Thanks, that makes sense.
I have updated this to skip reporting hat axes when the decoded value
falls outside the lookup table instead of clamping it to the neutral
entry, and will resend it that way.
Thanks,
Pengpeng
^ permalink raw reply
* [PATCH v2] Input: gf2k: skip invalid hat lookup values
From: Pengpeng Hou @ 2026-04-07 1:56 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, kees, pengpeng
In-Reply-To: <20260323074541.93413-1-pengpeng@iscas.ac.cn>
gf2k_read() decodes the hat position from a 4-bit field and uses it
directly to index gf2k_hat_to_axis[]. The lookup table only has nine
entries, so malformed packets can read past the end of the fixed table.
Skip hat reporting when the decoded value falls outside the lookup
table instead of forcing it to the neutral position. This keeps the
fix local and avoids reporting a made-up axis state for malformed
packets.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
Changes since v1:
- skip reporting invalid hat values instead of clamping them to the
neutral entry
drivers/input/joystick/gf2k.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/input/joystick/gf2k.c b/drivers/input/joystick/gf2k.c
index 5a1cdce0bc48..1d843115d674 100644
--- a/drivers/input/joystick/gf2k.c
+++ b/drivers/input/joystick/gf2k.c
@@ -165,8 +165,10 @@ static void gf2k_read(struct gf2k *gf2k, unsigned char *data)
t = GB(40,4,0);
- for (i = 0; i < gf2k_hats[gf2k->id]; i++)
- input_report_abs(dev, ABS_HAT0X + i, gf2k_hat_to_axis[t][i]);
+ if (t < ARRAY_SIZE(gf2k_hat_to_axis))
+ for (i = 0; i < gf2k_hats[gf2k->id]; i++)
+ input_report_abs(dev, ABS_HAT0X + i,
+ gf2k_hat_to_axis[t][i]);
t = GB(44,2,0) | GB(32,8,2) | GB(78,2,10);
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH v2 0/5] Add OneXPlayer Configuration HID Driver
From: Derek J. Clark @ 2026-04-07 4:13 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Lambert Fan, Derek J . Clark,
linux-input, linux-doc, linux-kernel
Adds an HID driver for OneXPlayer HID configuration devices. There are
currently 2 generations of OneXPlayer HID protocol. The first (OneXPlayer
F1 series) only provides an RGB control interface over HID. The Second
(X1 mini series, G1 series, AOKZOE A1X) also includes a hardware level
button mapping interface, vibration intensity settings, and the ability
to switch output between xinput and a debug mode that can be used to debug
the button mapping. Some devices (G1 Series, APEX) use a hybrid of Gen1
RGB control and Gen 2 controller settings. To ensure there is no conflicts
when the driver is loaded, we skip creating the RGB interface for Gen 2
devices if there is a DMI match.
I'll also add a note that Gen 1 devices also have an interface for
setting the key map and debug mode, but that is done entirely over a
serial TTY device so it is not able to be added to this driver. There
are also some "Gen 0" devices (OneXPlayer 2 Series) also use it, but
the TTY interface also handles the RGB control so no support is
provided by this driver for those devices.
Signed-off-by: Derel J. Clark <derekjohn.clark@gmail.com>
---
v2:
- Add DMI quirks for certain devices that ship with both GEN1 and GEN2
MCU to avoid clashing when initializing the RGB interface.
- Add left & right vibration intensity attributes.
- Add additional mappings for keyboard inputs.
- Add a delayed work trigger to re-apply settings after the MCU
completes initializing after a suspend/resume cycle.
v1: https://lore.kernel.org/linux-input/20260322031615.1524307-1-derekjohn.clark@gmail.com/
Derek J. Clark (5):
HID: hid-oxp: Add OneXPlayer configuration driver
HID: hid-oxp: Add Second Generation RGB Control
HID: hid-oxp: Add Second Generation Gamepad Mode Switch
HID: hid-oxp: Add Button Mapping Interface
HID: hid-oxp: Add Virbation Intenstity Attributes
MAINTAINERS | 6 +
drivers/hid/Kconfig | 13 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 6 +
drivers/hid/hid-oxp.c | 1595 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 1621 insertions(+)
create mode 100644 drivers/hid/hid-oxp.c
--
2.53.0
^ permalink raw reply
* [PATCH v2 1/5] HID: hid-oxp: Add OneXPlayer configuration driver
From: Derek J. Clark @ 2026-04-07 4:13 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Lambert Fan, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260407041354.2283201-1-derekjohn.clark@gmail.com>
Adds OneXPlayer HID configuration driver. In this initial driver patch,
add the RGB interface for the first generation of HID based RGB control.
This interface provides the following attributes:
- brightness: provided by the LED core, this works in a fairly unique
way on this device. The hardware accepts 5 brightness values (0-4),
which affects the brightness of the multicolor and animated effects
built into the MCU firmware. For monocolor settings, the device
expects the hardware brightness value to be pushed to maximum, then we
apply brightness adjustments mathematically based on % (0-100). This
leads to some odd conversion as we need the brightness slider to reach
the full range, but it has no affect when incrementing between the
division points for other effects.
- multi-intensity: provided by the LED core for red, green, and blue.
- effect: Allows the MCU to set 19 individual effects.
- effect_index: Lists the 19 valid effect names for the interface.
- enabled: Allows the MCU to toggle the RGB interface on/off.
- enabled_index: Lists the valid states for enabled.
- speed: Allows the MCU to set the animation rate for the various
effects.
- speed_range: Lists the valid range of speed (0-9).
The MCU also has a few odd quirks that make sending multiple synchronous
events challenging. It will essentially freeze if it receives another
message before it has finished processing the last command. It also will
not reply if you wait on it using a completion. To get around this, we
do a 200ms sleep inside a work queue thread and debounce all but the most
recent message using a 50ms mod_delayed_work. This will cache the last
write, queue the work, then return so userspace can release its write
thread. The work queue is only used for brightness/multi-intensity as
that is the path likely to receive rapid successive writes.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
MAINTAINERS | 6 +
drivers/hid/Kconfig | 12 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 3 +
drivers/hid/hid-oxp.c | 651 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 673 insertions(+)
create mode 100644 drivers/hid/hid-oxp.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 6f6517bf4f97..dae814192fa4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19707,6 +19707,12 @@ S: Maintained
F: drivers/mtd/nand/onenand/
F: include/linux/mtd/onenand*.h
+ONEXPLAYER HID DRIVER
+M: Derek J. Clark <derekjohn.clark@gmail.com>
+L: linux-input@vger.kernel.org
+S: Maintained
+F: drivers/hid/hid-oxp.c
+
ONEXPLAYER PLATFORM EC DRIVER
M: Antheas Kapenekakis <lkml@antheas.dev>
M: Derek John Clark <derekjohn.clark@gmail.com>
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 3c034cd32fa8..2deaec9f467d 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -919,6 +919,18 @@ config HID_ORTEK
- Ortek WKB-2000
- Skycable wireless presenter
+config HID_OXP
+ tristate "OneXPlayer handheld controller configuration support"
+ depends on USB_HID
+ depends on LEDS_CLASS
+ depends on LEDS_CLASS_MULTICOLOR
+ help
+ Say Y here if you would like to enable support for OneXPlayer handheld
+ devices that come with RGB LED rings around the joysticks and macro buttons.
+
+ To compile this driver as a module, choose M here: the module will
+ be called hid-oxp.
+
config HID_PANTHERLORD
tristate "Pantherlord/GreenAsia game controller"
help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 03ef72ec4499..bda8a24c9257 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -99,6 +99,7 @@ obj-$(CONFIG_HID_NTI) += hid-nti.o
obj-$(CONFIG_HID_NTRIG) += hid-ntrig.o
obj-$(CONFIG_HID_NVIDIA_SHIELD) += hid-nvidia-shield.o
obj-$(CONFIG_HID_ORTEK) += hid-ortek.o
+obj-$(CONFIG_HID_OXP) += hid-oxp.o
obj-$(CONFIG_HID_PRODIKEYS) += hid-prodikeys.o
obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o
obj-$(CONFIG_HID_PENMOUNT) += hid-penmount.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 5bad81222c6e..dcc5a3a70eaf 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1131,6 +1131,9 @@
#define USB_VENDOR_ID_NVIDIA 0x0955
#define USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER 0x7214
+#define USB_VENDOR_ID_CRSC 0x1a2c
+#define USB_DEVICE_ID_ONEXPLAYER_GEN1 0xb001
+
#define USB_VENDOR_ID_ONTRAK 0x0a07
#define USB_DEVICE_ID_ONTRAK_ADU100 0x0064
diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
new file mode 100644
index 000000000000..c4219ecd8d71
--- /dev/null
+++ b/drivers/hid/hid-oxp.c
@@ -0,0 +1,651 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID driver for OneXPlayer gamepad configuration devices.
+ *
+ * Copyright (c) 2026 Valve Corporation
+ */
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/jiffies.h>
+#include <linux/kstrtox.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include "hid-ids.h"
+
+#define OXP_PACKET_SIZE 64
+
+#define GEN1_MESSAGE_ID 0xff
+
+#define GEN1_USAGE_PAGE 0xff01
+
+enum oxp_function_index {
+ OXP_FID_GEN1_RGB_SET = 0x07,
+ OXP_FID_GEN1_RGB_REPLY = 0x0f,
+};
+
+static struct oxp_hid_cfg {
+ struct led_classdev_mc *led_mc;
+ struct hid_device *hdev;
+ struct mutex cfg_mutex; /*ensure single synchronous output report*/
+ u8 rgb_brightness;
+ u8 rgb_effect;
+ u8 rgb_speed;
+ u8 rgb_en;
+} drvdata;
+
+enum oxp_feature_en_index {
+ OXP_FEAT_DISABLED,
+ OXP_FEAT_ENABLED,
+};
+
+static const char *const oxp_feature_en_text[] = {
+ [OXP_FEAT_DISABLED] = "false",
+ [OXP_FEAT_ENABLED] = "true",
+};
+
+enum oxp_rgb_effect_index {
+ OXP_UNKNOWN,
+ OXP_EFFECT_AURORA,
+ OXP_EFFECT_BIRTHDAY,
+ OXP_EFFECT_FLOWING,
+ OXP_EFFECT_CHROMA_1,
+ OXP_EFFECT_NEON,
+ OXP_EFFECT_CHROMA_2,
+ OXP_EFFECT_DREAMY,
+ OXP_EFFECT_WARM,
+ OXP_EFFECT_CYBERPUNK,
+ OXP_EFFECT_SEA,
+ OXP_EFFECT_SUNSET,
+ OXP_EFFECT_COLORFUL,
+ OXP_EFFECT_MONSTER,
+ OXP_EFFECT_GREEN,
+ OXP_EFFECT_BLUE,
+ OXP_EFFECT_YELLOW,
+ OXP_EFFECT_TEAL,
+ OXP_EFFECT_PURPLE,
+ OXP_EFFECT_FOGGY,
+ OXP_EFFECT_MONO_LIST, /* placeholder for effect_index_show */
+};
+
+/* These belong to rgb_effect_index, but we want to hide them from
+ * rgb_effect_text
+ */
+
+#define OXP_GET_PROPERTY 0xfc
+#define OXP_SET_PROPERTY 0xfd
+#define OXP_EFFECT_MONO_TRUE 0xfe /* actual index for monocolor */
+
+static const char *const oxp_rgb_effect_text[] = {
+ [OXP_UNKNOWN] = "unknown",
+ [OXP_EFFECT_AURORA] = "aurora",
+ [OXP_EFFECT_BIRTHDAY] = "birthday_cake",
+ [OXP_EFFECT_FLOWING] = "flowing_light",
+ [OXP_EFFECT_CHROMA_1] = "chroma_popping",
+ [OXP_EFFECT_NEON] = "neon",
+ [OXP_EFFECT_CHROMA_2] = "chroma_breathing",
+ [OXP_EFFECT_DREAMY] = "dreamy",
+ [OXP_EFFECT_WARM] = "warm_sun",
+ [OXP_EFFECT_CYBERPUNK] = "cyberpunk",
+ [OXP_EFFECT_SEA] = "sea_foam",
+ [OXP_EFFECT_SUNSET] = "sunset_afterglow",
+ [OXP_EFFECT_COLORFUL] = "colorful",
+ [OXP_EFFECT_MONSTER] = "monster_woke",
+ [OXP_EFFECT_GREEN] = "green_breathing",
+ [OXP_EFFECT_BLUE] = "blue_breathing",
+ [OXP_EFFECT_YELLOW] = "yellow_breathing",
+ [OXP_EFFECT_TEAL] = "teal_breathing",
+ [OXP_EFFECT_PURPLE] = "purple_breathing",
+ [OXP_EFFECT_FOGGY] = "foggy_haze",
+ [OXP_EFFECT_MONO_LIST] = "monocolor",
+};
+
+struct oxp_gen_1_rgb_report {
+ u8 report_id;
+ u8 message_id;
+ u8 padding_2[2];
+ u8 effect;
+ u8 enabled;
+ u8 speed;
+ u8 brightness;
+ u8 red;
+ u8 green;
+ u8 blue;
+} __packed;
+
+static u16 get_usage_page(struct hid_device *hdev)
+{
+ return hdev->collection[0].usage >> 16;
+}
+
+static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
+ struct hid_report *report, u8 *data,
+ int size)
+{
+ struct led_classdev_mc *led_mc = drvdata.led_mc;
+ struct oxp_gen_1_rgb_report *rgb_rep;
+
+ if (data[1] != OXP_FID_GEN1_RGB_REPLY)
+ return 0;
+
+ rgb_rep = (struct oxp_gen_1_rgb_report *)data;
+ /* Ensure we save monocolor as the list value */
+ drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
+ OXP_EFFECT_MONO_LIST :
+ rgb_rep->effect;
+ drvdata.rgb_speed = rgb_rep->speed;
+ drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
+ OXP_FEAT_ENABLED;
+ drvdata.rgb_brightness = rgb_rep->brightness;
+ led_mc->led_cdev.brightness = rgb_rep->brightness / 4 *
+ led_mc->led_cdev.max_brightness;
+ /* If monocolor had less than 100% brightness on the previous boot,
+ * there will be no reliable way to determine the real intensity.
+ * Since intensity scaling is used with a hardware brightness set at max,
+ * our brightness will always look like 100%. Use the last set value to
+ * prevent successive boots from lowering the brightness further.
+ * Brightness will be "wrong" but the effect will remain the same visually.
+ */
+ led_mc->subled_info[0].intensity = rgb_rep->red;
+ led_mc->subled_info[1].intensity = rgb_rep->green;
+ led_mc->subled_info[2].intensity = rgb_rep->blue;
+
+ return 0;
+}
+
+static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report,
+ u8 *data, int size)
+{
+ u16 up = get_usage_page(hdev);
+
+ dev_dbg(&hdev->dev, "raw event data: [%*ph]\n", OXP_PACKET_SIZE, data);
+
+ switch (up) {
+ case GEN1_USAGE_PAGE:
+ return oxp_hid_raw_event_gen_1(hdev, report, data, size);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int mcu_property_out(u8 *header, size_t header_size, u8 *data,
+ size_t data_size, u8 *footer, size_t footer_size)
+{
+ unsigned char *dmabuf __free(kfree) = kzalloc(OXP_PACKET_SIZE, GFP_KERNEL);
+ int ret;
+
+ if (!dmabuf)
+ return -ENOMEM;
+
+ if (header_size + data_size + footer_size > OXP_PACKET_SIZE)
+ return -EINVAL;
+
+ guard(mutex)(&drvdata.cfg_mutex);
+ memcpy(dmabuf, header, header_size);
+ memcpy(dmabuf + header_size, data, data_size);
+ if (footer_size)
+ memcpy(dmabuf + OXP_PACKET_SIZE - footer_size, footer, footer_size);
+
+ dev_dbg(&drvdata.hdev->dev, "raw data: [%*ph]\n", OXP_PACKET_SIZE, dmabuf);
+
+ ret = hid_hw_output_report(drvdata.hdev, dmabuf, OXP_PACKET_SIZE);
+ if (ret < 0)
+ return ret;
+
+ /* MCU takes 200ms to be ready for another command. */
+ msleep(200);
+ return ret == OXP_PACKET_SIZE ? 0 : -EIO;
+}
+
+static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data,
+ u8 data_size)
+{
+ u8 header[] = { fid, GEN1_MESSAGE_ID };
+ size_t header_size = ARRAY_SIZE(header);
+
+ return mcu_property_out(header, header_size, data, data_size, NULL, 0);
+}
+
+static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
+{
+ u16 up = get_usage_page(drvdata.hdev);
+ u8 *data;
+
+ /* Always default to max brightness and use intensity scaling when in
+ * monocolor mode.
+ */
+ switch (up) {
+ case GEN1_USAGE_PAGE:
+ data = (u8[4]) { OXP_SET_PROPERTY, enabled, speed, brightness };
+ if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
+ data[3] = 0x04;
+ return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4);
+ default:
+ return -ENODEV;
+ }
+}
+
+static ssize_t oxp_rgb_status_show(void)
+{
+ u16 up = get_usage_page(drvdata.hdev);
+ u8 *data;
+
+ switch (up) {
+ case GEN1_USAGE_PAGE:
+ data = (u8[1]) { OXP_GET_PROPERTY };
+ return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
+ default:
+ return -ENODEV;
+ }
+}
+
+static int oxp_rgb_color_set(void)
+{
+ u8 max_br = drvdata.led_mc->led_cdev.max_brightness;
+ u8 br = drvdata.led_mc->led_cdev.brightness;
+ u16 up = get_usage_page(drvdata.hdev);
+ u8 green, red, blue;
+ size_t size;
+ u8 *data;
+ int i;
+
+ red = br * drvdata.led_mc->subled_info[0].intensity / max_br;
+ green = br * drvdata.led_mc->subled_info[1].intensity / max_br;
+ blue = br * drvdata.led_mc->subled_info[2].intensity / max_br;
+
+ switch (up) {
+ case GEN1_USAGE_PAGE:
+ size = 55;
+ data = (u8[55]) { OXP_EFFECT_MONO_TRUE };
+
+ for (i = 0; i < (size - 1) / 3; i++) {
+ data[3 * i + 1] = red;
+ data[3 * i + 2] = green;
+ data[3 * i + 3] = blue;
+ }
+ return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size);
+ default:
+ return -ENODEV;
+ }
+}
+
+static int oxp_rgb_effect_set(u8 effect)
+{
+ u16 up = get_usage_page(drvdata.hdev);
+ u8 *data;
+ int ret;
+
+ switch (effect) {
+ case OXP_EFFECT_AURORA:
+ case OXP_EFFECT_BIRTHDAY:
+ case OXP_EFFECT_FLOWING:
+ case OXP_EFFECT_CHROMA_1:
+ case OXP_EFFECT_NEON:
+ case OXP_EFFECT_CHROMA_2:
+ case OXP_EFFECT_DREAMY:
+ case OXP_EFFECT_WARM:
+ case OXP_EFFECT_CYBERPUNK:
+ case OXP_EFFECT_SEA:
+ case OXP_EFFECT_SUNSET:
+ case OXP_EFFECT_COLORFUL:
+ case OXP_EFFECT_MONSTER:
+ case OXP_EFFECT_GREEN:
+ case OXP_EFFECT_BLUE:
+ case OXP_EFFECT_YELLOW:
+ case OXP_EFFECT_TEAL:
+ case OXP_EFFECT_PURPLE:
+ case OXP_EFFECT_FOGGY:
+ switch (up) {
+ case GEN1_USAGE_PAGE:
+ data = (u8[1]) { effect };
+ ret = oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
+ break;
+ default:
+ ret = -ENODEV;
+ }
+ break;
+ case OXP_EFFECT_MONO_LIST:
+ ret = oxp_rgb_color_set();
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ drvdata.rgb_effect = effect;
+
+ return 0;
+}
+
+static ssize_t enabled_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ u8 val;
+
+ ret = sysfs_match_string(oxp_feature_en_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+
+ ret = oxp_rgb_status_store(val, drvdata.rgb_speed,
+ drvdata.rgb_brightness);
+ if (ret)
+ return ret;
+
+ drvdata.rgb_en = val;
+ return count;
+}
+
+static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+
+ ret = oxp_rgb_status_show();
+ if (ret)
+ return ret;
+
+ if (drvdata.rgb_en >= ARRAY_SIZE(oxp_feature_en_text))
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", oxp_feature_en_text[drvdata.rgb_en]);
+}
+static DEVICE_ATTR_RW(enabled);
+
+static ssize_t enabled_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ size_t count = 0;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(oxp_feature_en_text); i++)
+ count += sysfs_emit_at(buf, count, "%s ", oxp_feature_en_text[i]);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(enabled_index);
+
+static ssize_t effect_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ u8 val;
+
+ ret = sysfs_match_string(oxp_rgb_effect_text, buf);
+ if (ret < 0)
+ return ret;
+
+ val = ret;
+
+ ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed,
+ drvdata.rgb_brightness);
+ if (ret)
+ return ret;
+
+ ret = oxp_rgb_effect_set(val);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t effect_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+
+ ret = oxp_rgb_status_show();
+ if (ret)
+ return ret;
+
+ if (drvdata.rgb_effect >= ARRAY_SIZE(oxp_rgb_effect_text))
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", oxp_rgb_effect_text[drvdata.rgb_effect]);
+}
+
+static DEVICE_ATTR_RW(effect);
+
+static ssize_t effect_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ size_t count = 0;
+ unsigned int i;
+
+ for (i = 1; i < ARRAY_SIZE(oxp_rgb_effect_text); i++)
+ count += sysfs_emit_at(buf, count, "%s ", oxp_rgb_effect_text[i]);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(effect_index);
+
+static ssize_t speed_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ u8 val;
+
+ ret = kstrtou8(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val > 9)
+ return -EINVAL;
+
+ ret = oxp_rgb_status_store(drvdata.rgb_en, val, drvdata.rgb_brightness);
+ if (ret)
+ return ret;
+
+ drvdata.rgb_speed = val;
+ return count;
+}
+
+static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+
+ ret = oxp_rgb_status_show();
+ if (ret)
+ return ret;
+
+ if (drvdata.rgb_speed > 9)
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed);
+}
+static DEVICE_ATTR_RW(speed);
+
+static ssize_t speed_range_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "0-9\n");
+}
+static DEVICE_ATTR_RO(speed_range);
+
+static void oxp_rgb_queue_fn(struct work_struct *work)
+{
+ unsigned int max_brightness = drvdata.led_mc->led_cdev.max_brightness;
+ unsigned int brightness = drvdata.led_mc->led_cdev.brightness;
+ u8 val = 4 * brightness / max_brightness;
+ int ret;
+
+ if (drvdata.rgb_brightness != val) {
+ ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, val);
+ if (ret)
+ dev_err(drvdata.led_mc->led_cdev.dev,
+ "Error: Failed to write RGB Status: %i\n", ret);
+
+ drvdata.rgb_brightness = val;
+ }
+
+ if (drvdata.rgb_effect != OXP_EFFECT_MONO_LIST)
+ return;
+
+ ret = oxp_rgb_effect_set(drvdata.rgb_effect);
+ if (ret)
+ dev_err(drvdata.led_mc->led_cdev.dev, "Error: Failed to write RGB color: %i\n",
+ ret);
+}
+
+static DECLARE_DELAYED_WORK(oxp_rgb_queue, oxp_rgb_queue_fn);
+
+static void oxp_rgb_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ led_cdev->brightness = brightness;
+ mod_delayed_work(system_wq, &oxp_rgb_queue, msecs_to_jiffies(50));
+}
+
+static struct attribute *oxp_rgb_attrs[] = {
+ &dev_attr_effect.attr,
+ &dev_attr_effect_index.attr,
+ &dev_attr_enabled.attr,
+ &dev_attr_enabled_index.attr,
+ &dev_attr_speed.attr,
+ &dev_attr_speed_range.attr,
+ NULL,
+};
+
+static const struct attribute_group oxp_rgb_attr_group = {
+ .attrs = oxp_rgb_attrs,
+};
+
+static struct mc_subled oxp_rgb_subled_info[] = {
+ {
+ .color_index = LED_COLOR_ID_RED,
+ .intensity = 0x24,
+ .channel = 0x1,
+ },
+ {
+ .color_index = LED_COLOR_ID_GREEN,
+ .intensity = 0x22,
+ .channel = 0x2,
+ },
+ {
+ .color_index = LED_COLOR_ID_BLUE,
+ .intensity = 0x99,
+ .channel = 0x3,
+ },
+};
+
+static struct led_classdev_mc oxp_cdev_rgb = {
+ .led_cdev = {
+ .name = "oxp:rgb:joystick_rings",
+ .color = LED_COLOR_ID_RGB,
+ .brightness = 0x64,
+ .max_brightness = 0x64,
+ .brightness_set = oxp_rgb_brightness_set,
+ },
+ .num_colors = ARRAY_SIZE(oxp_rgb_subled_info),
+ .subled_info = oxp_rgb_subled_info,
+};
+
+static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
+{
+ int ret;
+
+ hid_set_drvdata(hdev, &drvdata);
+ mutex_init(&drvdata.cfg_mutex);
+ drvdata.hdev = hdev;
+ drvdata.led_mc = &oxp_cdev_rgb;
+
+ ret = devm_led_classdev_multicolor_register(&hdev->dev, &oxp_cdev_rgb);
+ if (ret)
+ return dev_err_probe(&hdev->dev, ret,
+ "Failed to create RGB device\n");
+
+ ret = devm_device_add_group(drvdata.led_mc->led_cdev.dev,
+ &oxp_rgb_attr_group);
+ if (ret)
+ return dev_err_probe(drvdata.led_mc->led_cdev.dev, ret,
+ "Failed to create RGB configuration attributes\n");
+
+ ret = oxp_rgb_status_show();
+ if (ret)
+ dev_warn(drvdata.led_mc->led_cdev.dev,
+ "Failed to query RGB initial state: %i\n", ret);
+
+ return 0;
+}
+
+static int oxp_hid_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int ret;
+ u16 up;
+
+ ret = hid_parse(hdev);
+ if (ret)
+ return dev_err_probe(&hdev->dev, ret, "Failed to parse HID device\n");
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ return dev_err_probe(&hdev->dev, ret, "Failed to start HID device\n");
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_hw_stop(hdev);
+ return dev_err_probe(&hdev->dev, ret, "Failed to open HID device\n");
+ }
+
+ up = get_usage_page(hdev);
+ dev_dbg(&hdev->dev, "Got usage page %04x\n", up);
+
+ switch (up) {
+ case GEN1_USAGE_PAGE:
+ ret = oxp_cfg_probe(hdev, up);
+ if (ret) {
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+ }
+
+ return ret;
+ default:
+ return 0;
+ }
+}
+
+static void oxp_hid_remove(struct hid_device *hdev)
+{
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id oxp_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) },
+ {}
+};
+
+MODULE_DEVICE_TABLE(hid, oxp_devices);
+static struct hid_driver hid_oxp = {
+ .name = "hid-oxp",
+ .id_table = oxp_devices,
+ .probe = oxp_hid_probe,
+ .remove = oxp_hid_remove,
+ .raw_event = oxp_hid_raw_event,
+};
+module_hid_driver(hid_oxp);
+
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Driver for OneXPlayer HID Interfaces");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH v2 2/5] HID: hid-oxp: Add Second Generation RGB Control
From: Derek J. Clark @ 2026-04-07 4:13 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Lambert Fan, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260407041354.2283201-1-derekjohn.clark@gmail.com>
Adds support for the second generation of RGB Control for OneXPlayer
devices. The interface mirrors the first generation, with some
differences to how messages are formatted.
Some devices have both a GEN1 MCU for RGB control and a GEN2 MCU for
button mapping. To avoid conflicts, quirk these devices to skip RGB
setup for the GEN2_USAGE_PAGE.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v2:
- Add DMI quirks table.
---
drivers/hid/Kconfig | 1 +
drivers/hid/hid-ids.h | 3 +
drivers/hid/hid-oxp.c | 151 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 155 insertions(+)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 2deaec9f467d..b779088b80b6 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -924,6 +924,7 @@ config HID_OXP
depends on USB_HID
depends on LEDS_CLASS
depends on LEDS_CLASS_MULTICOLOR
+ depends on DMI
help
Say Y here if you would like to enable support for OneXPlayer handheld
devices that come with RGB LED rings around the joysticks and macro buttons.
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index dcc5a3a70eaf..0d1ff879e959 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1134,6 +1134,9 @@
#define USB_VENDOR_ID_CRSC 0x1a2c
#define USB_DEVICE_ID_ONEXPLAYER_GEN1 0xb001
+#define USB_VENDOR_ID_WCH 0x1a86
+#define USB_DEVICE_ID_ONEXPLAYER_GEN2 0xfe00
+
#define USB_VENDOR_ID_ONTRAK 0x0a07
#define USB_DEVICE_ID_ONTRAK_ADU100 0x0064
diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index c4219ecd8d71..25214356163e 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -10,6 +10,7 @@
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/jiffies.h>
#include <linux/kstrtox.h>
@@ -24,12 +25,15 @@
#define OXP_PACKET_SIZE 64
#define GEN1_MESSAGE_ID 0xff
+#define GEN2_MESSAGE_ID 0x3f
#define GEN1_USAGE_PAGE 0xff01
+#define GEN2_USAGE_PAGE 0xff00
enum oxp_function_index {
OXP_FID_GEN1_RGB_SET = 0x07,
OXP_FID_GEN1_RGB_REPLY = 0x0f,
+ OXP_FID_GEN2_STATUS_EVENT = 0xb8,
};
static struct oxp_hid_cfg {
@@ -121,6 +125,22 @@ struct oxp_gen_1_rgb_report {
u8 blue;
} __packed;
+struct oxp_gen_2_rgb_report {
+ u8 report_id;
+ u8 header_id;
+ u8 padding_2;
+ u8 message_id;
+ u8 padding_4[2];
+ u8 enabled;
+ u8 speed;
+ u8 brightness;
+ u8 red;
+ u8 green;
+ u8 blue;
+ u8 padding_12[3];
+ u8 effect;
+} __packed;
+
static u16 get_usage_page(struct hid_device *hdev)
{
return hdev->collection[0].usage >> 16;
@@ -161,6 +181,44 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
return 0;
}
+static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
+ struct hid_report *report, u8 *data,
+ int size)
+{
+ struct led_classdev_mc *led_mc = drvdata.led_mc;
+ struct oxp_gen_2_rgb_report *rgb_rep;
+
+ if (data[0] != OXP_FID_GEN2_STATUS_EVENT)
+ return 0;
+
+ if (data[3] != OXP_GET_PROPERTY)
+ return 0;
+
+ rgb_rep = (struct oxp_gen_2_rgb_report *)data;
+ /* Ensure we save monocolor as the list value */
+ drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
+ OXP_EFFECT_MONO_LIST :
+ rgb_rep->effect;
+ drvdata.rgb_speed = rgb_rep->speed;
+ drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
+ OXP_FEAT_ENABLED;
+ drvdata.rgb_brightness = rgb_rep->brightness;
+ led_mc->led_cdev.brightness = rgb_rep->brightness / 4 *
+ led_mc->led_cdev.max_brightness;
+ /* If monocolor had less than 100% brightness on the previous boot,
+ * there will be no reliable way to determine the real intensity.
+ * Since intensity scaling is used with a hardware brightness set at max,
+ * our brightness will always look like 100%. Use the last set value to
+ * prevent successive boots from lowering the brightness further.
+ * Brightness will be "wrong" but the effect will remain the same visually.
+ */
+ led_mc->subled_info[0].intensity = rgb_rep->red;
+ led_mc->subled_info[1].intensity = rgb_rep->green;
+ led_mc->subled_info[2].intensity = rgb_rep->blue;
+
+ return 0;
+}
+
static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{
@@ -171,6 +229,8 @@ static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report,
switch (up) {
case GEN1_USAGE_PAGE:
return oxp_hid_raw_event_gen_1(hdev, report, data, size);
+ case GEN2_USAGE_PAGE:
+ return oxp_hid_raw_event_gen_2(hdev, report, data, size);
default:
break;
}
@@ -216,6 +276,18 @@ static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data,
return mcu_property_out(header, header_size, data, data_size, NULL, 0);
}
+static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data,
+ u8 data_size)
+{
+ u8 header[] = { fid, GEN2_MESSAGE_ID, 0x01 };
+ u8 footer[] = { GEN2_MESSAGE_ID, fid };
+ size_t header_size = ARRAY_SIZE(header);
+ size_t footer_size = ARRAY_SIZE(footer);
+
+ return mcu_property_out(header, header_size, data, data_size, footer,
+ footer_size);
+}
+
static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
{
u16 up = get_usage_page(drvdata.hdev);
@@ -230,6 +302,11 @@ static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
data[3] = 0x04;
return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4);
+ case GEN2_USAGE_PAGE:
+ data = (u8[6]) { OXP_SET_PROPERTY, 0x00, 0x02, enabled, speed, brightness };
+ if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
+ data[5] = 0x04;
+ return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 6);
default:
return -ENODEV;
}
@@ -244,6 +321,9 @@ static ssize_t oxp_rgb_status_show(void)
case GEN1_USAGE_PAGE:
data = (u8[1]) { OXP_GET_PROPERTY };
return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
+ case GEN2_USAGE_PAGE:
+ data = (u8[3]) { OXP_GET_PROPERTY, 0x00, 0x02 };
+ return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3);
default:
return -ENODEV;
}
@@ -274,6 +354,16 @@ static int oxp_rgb_color_set(void)
data[3 * i + 3] = blue;
}
return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size);
+ case GEN2_USAGE_PAGE:
+ size = 57;
+ data = (u8[57]) { OXP_EFFECT_MONO_TRUE, 0x00, 0x02 };
+
+ for (i = 1; i < size / 3; i++) {
+ data[3 * i] = red;
+ data[3 * i + 1] = green;
+ data[3 * i + 2] = blue;
+ }
+ return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, size);
default:
return -ENODEV;
}
@@ -310,6 +400,10 @@ static int oxp_rgb_effect_set(u8 effect)
data = (u8[1]) { effect };
ret = oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
break;
+ case GEN2_USAGE_PAGE:
+ data = (u8[3]) { effect, 0x00, 0x02 };
+ ret = oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3);
+ break;
default:
ret = -ENODEV;
}
@@ -560,6 +654,56 @@ static struct led_classdev_mc oxp_cdev_rgb = {
.subled_info = oxp_rgb_subled_info,
};
+struct quirk_entry {
+ bool hybrid_mcu;
+};
+
+static struct quirk_entry quirk_hybrid_mcu = {
+ .hybrid_mcu = true,
+};
+
+static const struct dmi_system_id oxp_hybrid_mcu_list[] = {
+ {
+ .ident = "OneXPlayer Apex",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER APEX"),
+ },
+ .driver_data = &quirk_hybrid_mcu,
+ },
+ {
+ .ident = "OneXPlayer G1 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 A"),
+ },
+ .driver_data = &quirk_hybrid_mcu,
+ },
+ {
+ .ident = "OneXPlayer G1 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 i"),
+ },
+ .driver_data = &quirk_hybrid_mcu,
+ },
+ {},
+};
+
+static bool oxp_hybrid_mcu_device(void)
+{
+ const struct dmi_system_id *dmi_id;
+ struct quirk_entry *quirks;
+
+ dmi_id = dmi_first_match(oxp_hybrid_mcu_list);
+ if (!dmi_id)
+ return false;
+
+ quirks = dmi_id->driver_data;
+
+ return quirks->hybrid_mcu;
+}
+
static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
{
int ret;
@@ -567,6 +711,10 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
hid_set_drvdata(hdev, &drvdata);
mutex_init(&drvdata.cfg_mutex);
drvdata.hdev = hdev;
+
+ if (up == GEN2_USAGE_PAGE && oxp_hybrid_mcu_device())
+ goto skip_rgb;
+
drvdata.led_mc = &oxp_cdev_rgb;
ret = devm_led_classdev_multicolor_register(&hdev->dev, &oxp_cdev_rgb);
@@ -585,6 +733,7 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
dev_warn(drvdata.led_mc->led_cdev.dev,
"Failed to query RGB initial state: %i\n", ret);
+skip_rgb:
return 0;
}
@@ -613,6 +762,7 @@ static int oxp_hid_probe(struct hid_device *hdev,
switch (up) {
case GEN1_USAGE_PAGE:
+ case GEN2_USAGE_PAGE:
ret = oxp_cfg_probe(hdev, up);
if (ret) {
hid_hw_close(hdev);
@@ -633,6 +783,7 @@ static void oxp_hid_remove(struct hid_device *hdev)
static const struct hid_device_id oxp_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_WCH, USB_DEVICE_ID_ONEXPLAYER_GEN2) },
{}
};
--
2.53.0
^ permalink raw reply related
* [PATCH v2 3/5] HID: hid-oxp: Add Second Generation Gamepad Mode Switch
From: Derek J. Clark @ 2026-04-07 4:13 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Lambert Fan, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260407041354.2283201-1-derekjohn.clark@gmail.com>
Adds "gamepad_mode" attribute to second generation OneXPlayer
configuration HID devices. This attribute initiates a mode shift in the
device MCU that puts it into a state where all events are routed to an
hidraw interface instead of the xpad evdev interface. This allows for
debugging the hardware input mapping added in the next patch.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v2:
- Rename to gamepad_mode & show relevant gamepad modes instead of
using a debug enable/disable paradigm, to match other drivers.
---
drivers/hid/hid-oxp.c | 130 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 130 insertions(+)
diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index 25214356163e..c62952537d98 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -33,6 +33,7 @@
enum oxp_function_index {
OXP_FID_GEN1_RGB_SET = 0x07,
OXP_FID_GEN1_RGB_REPLY = 0x0f,
+ OXP_FID_GEN2_TOGGLE_MODE = 0xb2,
OXP_FID_GEN2_STATUS_EVENT = 0xb8,
};
@@ -41,11 +42,22 @@ static struct oxp_hid_cfg {
struct hid_device *hdev;
struct mutex cfg_mutex; /*ensure single synchronous output report*/
u8 rgb_brightness;
+ u8 gamepad_mode;
u8 rgb_effect;
u8 rgb_speed;
u8 rgb_en;
} drvdata;
+enum oxp_gamepad_mode_index {
+ OXP_GP_MODE_XINPUT = 0x00,
+ OXP_GP_MODE_DEBUG = 0x03,
+};
+
+static const char *const oxp_gamepad_mode_text[] = {
+ [OXP_GP_MODE_XINPUT] = "xinput",
+ [OXP_GP_MODE_DEBUG] = "debug",
+};
+
enum oxp_feature_en_index {
OXP_FEAT_DISABLED,
OXP_FEAT_ENABLED,
@@ -181,6 +193,32 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
return 0;
}
+static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size);
+
+static void oxp_mcu_init_fn(struct work_struct *work)
+{
+ u8 gp_mode_data[3] = { OXP_GP_MODE_DEBUG, 0x01, 0x02 };
+ int ret;
+
+ /* Cycle the gamepad mode */
+ ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3);
+ if (ret)
+ dev_err(&drvdata.hdev->dev,
+ "Error: Failed to set gamepad mode: %i\n", ret);
+
+ /* Remainder only applies for xinput mode */
+ if (drvdata.gamepad_mode == OXP_GP_MODE_DEBUG)
+ return;
+
+ gp_mode_data[0] = OXP_GP_MODE_XINPUT;
+ ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3);
+ if (ret)
+ dev_err(&drvdata.hdev->dev,
+ "Error: Failed to set gamepad mode: %i\n", ret);
+}
+
+static DECLARE_DELAYED_WORK(oxp_mcu_init, oxp_mcu_init_fn);
+
static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
struct hid_report *report, u8 *data,
int size)
@@ -191,6 +229,14 @@ static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
if (data[0] != OXP_FID_GEN2_STATUS_EVENT)
return 0;
+ /* Sent ~6s after resume event, indicating the MCU has fully reset.
+ * Re-apply our settings after this has been received.
+ */
+ if (data[3] == OXP_EFFECT_MONO_TRUE) {
+ mod_delayed_work(system_wq, &oxp_mcu_init, msecs_to_jiffies(50));
+ return 0;
+ }
+
if (data[3] != OXP_GET_PROPERTY)
return 0;
@@ -288,6 +334,77 @@ static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data,
footer_size);
}
+static ssize_t gamepad_mode_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ u16 up = get_usage_page(drvdata.hdev);
+ u8 data[3] = { 0x00, 0x01, 0x02 };
+ int ret = -EINVAL;
+ int i;
+
+ if (up != GEN2_USAGE_PAGE)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) {
+ if (oxp_gamepad_mode_text[i] && sysfs_streq(buf, oxp_gamepad_mode_text[i])) {
+ ret = i;
+ break;
+ }
+ }
+ if (ret < 0)
+ return ret;
+
+ data[0] = ret;
+
+ ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, data, 3);
+ if (ret)
+ return ret;
+
+ drvdata.gamepad_mode = data[0];
+
+ return count;
+}
+
+static ssize_t gamepad_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%s\n", oxp_gamepad_mode_text[drvdata.gamepad_mode]);
+}
+static DEVICE_ATTR_RW(gamepad_mode);
+
+static ssize_t gamepad_mode_index_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ ssize_t count = 0;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) {
+ if (!oxp_gamepad_mode_text[i] ||
+ oxp_gamepad_mode_text[i][0] == '\0')
+ continue;
+
+ count += sysfs_emit_at(buf, count, "%s ", oxp_gamepad_mode_text[i]);
+ }
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(gamepad_mode_index);
+
+static struct attribute *oxp_cfg_attrs[] = {
+ &dev_attr_gamepad_mode.attr,
+ &dev_attr_gamepad_mode_index.attr,
+ NULL,
+};
+
+static const struct attribute_group oxp_cfg_attrs_group = {
+ .attrs = oxp_cfg_attrs,
+};
+
static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
{
u16 up = get_usage_page(drvdata.hdev);
@@ -733,7 +850,20 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
dev_warn(drvdata.led_mc->led_cdev.dev,
"Failed to query RGB initial state: %i\n", ret);
+ /* Below features are only implemented in gen 2 */
+ if (up != GEN2_USAGE_PAGE)
+ return 0;
+
skip_rgb:
+ mod_delayed_work(system_wq, &oxp_mcu_init, msecs_to_jiffies(50));
+
+ drvdata.gamepad_mode = OXP_GP_MODE_XINPUT;
+
+ ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group);
+ if (ret)
+ return dev_err_probe(&hdev->dev, ret,
+ "Failed to attach configuration attributes\n");
+
return 0;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v2 4/5] HID: hid-oxp: Add Button Mapping Interface
From: Derek J. Clark @ 2026-04-07 4:13 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Lambert Fan, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260407041354.2283201-1-derekjohn.clark@gmail.com>
Adds button mapping interface for second generation OneXPlayer
configuration HID interfaces. This interface allows the MCU to swap
button mappings at the hardware level. The current state cannot be
retrieved, and the mappings may have been modified in Windows prior, so
we reset the button mapping at init and expose an attribute to allow
userspace to do this again at any time.
The interface requires two pages of button mapping data to be sent
before the settings will take place. Since the MCU requires a 200ms
delay after each message (total 400ms for these attributes) use the same
debounce work queue method we used for RGB. This will allow for
userspace or udev rules to rapidly map all buttons. The values will
be cached before the final write is finally sent to the device.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v2:
- Add detection of post-suspend MCU init to trigger setting the button
map again.
---
drivers/hid/hid-oxp.c | 565 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 565 insertions(+)
diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index c62952537d98..1100f1f14f35 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -34,10 +34,145 @@ enum oxp_function_index {
OXP_FID_GEN1_RGB_SET = 0x07,
OXP_FID_GEN1_RGB_REPLY = 0x0f,
OXP_FID_GEN2_TOGGLE_MODE = 0xb2,
+ OXP_FID_GEN2_KEY_STATE = 0xb4,
OXP_FID_GEN2_STATUS_EVENT = 0xb8,
};
+#define OXP_MAPPING_GAMEPAD 0x01
+#define OXP_MAPPING_KEYBOARD 0x02
+
+struct oxp_button_data {
+ u8 mode;
+ u8 index;
+ u8 key_id;
+ u8 padding[2];
+} __packed;
+
+struct oxp_button_entry {
+ struct oxp_button_data data;
+ const char *name;
+};
+
+static const struct oxp_button_entry oxp_button_table[] = {
+ /* Gamepad Buttons */
+ { { OXP_MAPPING_GAMEPAD, 0x01 }, "BTN_A" },
+ { { OXP_MAPPING_GAMEPAD, 0x02 }, "BTN_B" },
+ { { OXP_MAPPING_GAMEPAD, 0x03 }, "BTN_X" },
+ { { OXP_MAPPING_GAMEPAD, 0x04 }, "BTN_Y" },
+ { { OXP_MAPPING_GAMEPAD, 0x05 }, "BTN_LB" },
+ { { OXP_MAPPING_GAMEPAD, 0x06 }, "BTN_RB" },
+ { { OXP_MAPPING_GAMEPAD, 0x07 }, "BTN_LT" },
+ { { OXP_MAPPING_GAMEPAD, 0x08 }, "BTN_RT" },
+ { { OXP_MAPPING_GAMEPAD, 0x09 }, "BTN_START" },
+ { { OXP_MAPPING_GAMEPAD, 0x0a }, "BTN_SELECT" },
+ { { OXP_MAPPING_GAMEPAD, 0x0b }, "BTN_L3" },
+ { { OXP_MAPPING_GAMEPAD, 0x0c }, "BTN_R3" },
+ { { OXP_MAPPING_GAMEPAD, 0x0d }, "DPAD_UP" },
+ { { OXP_MAPPING_GAMEPAD, 0x0e }, "DPAD_DOWN" },
+ { { OXP_MAPPING_GAMEPAD, 0x0f }, "DPAD_LEFT" },
+ { { OXP_MAPPING_GAMEPAD, 0x10 }, "DPAD_RIGHT" },
+ { { OXP_MAPPING_GAMEPAD, 0x11 }, "JOY_L_UP" },
+ { { OXP_MAPPING_GAMEPAD, 0x12 }, "JOY_L_UP_RIGHT" },
+ { { OXP_MAPPING_GAMEPAD, 0x13 }, "JOY_L_RIGHT" },
+ { { OXP_MAPPING_GAMEPAD, 0x14 }, "JOY_L_DOWN_RIGHT" },
+ { { OXP_MAPPING_GAMEPAD, 0x15 }, "JOY_L_DOWN" },
+ { { OXP_MAPPING_GAMEPAD, 0x16 }, "JOY_L_DOWN_LEFT" },
+ { { OXP_MAPPING_GAMEPAD, 0x17 }, "JOY_L_LEFT" },
+ { { OXP_MAPPING_GAMEPAD, 0x18 }, "JOY_L_UP_LEFT" },
+ { { OXP_MAPPING_GAMEPAD, 0x19 }, "JOY_R_UP" },
+ { { OXP_MAPPING_GAMEPAD, 0x1a }, "JOY_R_UP_RIGHT" },
+ { { OXP_MAPPING_GAMEPAD, 0x1b }, "JOY_R_RIGHT" },
+ { { OXP_MAPPING_GAMEPAD, 0x1c }, "JOY_R_DOWN_RIGHT" },
+ { { OXP_MAPPING_GAMEPAD, 0x1d }, "JOY_R_DOWN" },
+ { { OXP_MAPPING_GAMEPAD, 0x1e }, "JOY_R_DOWN_LEFT" },
+ { { OXP_MAPPING_GAMEPAD, 0x1f }, "JOY_R_LEFT" },
+ { { OXP_MAPPING_GAMEPAD, 0x20 }, "JOY_R_UP_LEFT" },
+ { { OXP_MAPPING_GAMEPAD, 0x22 }, "BTN_GUIDE" },
+ /* Keyboard Keys */
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x5a }, "KEY_F1" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x5b }, "KEY_F2" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x5c }, "KEY_F3" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x5d }, "KEY_F4" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x5e }, "KEY_F5" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x5f }, "KEY_F6" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x60 }, "KEY_F7" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x61 }, "KEY_F8" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x62 }, "KEY_F9" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x63 }, "KEY_F10" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x64 }, "KEY_F11" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x65 }, "KEY_F12" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x66 }, "KEY_F13" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x67 }, "KEY_F14" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x68 }, "KEY_F15" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x69 }, "KEY_F16" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x6a }, "KEY_F17" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x6b }, "KEY_F18" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x6c }, "KEY_F19" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x6d }, "KEY_F20" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x6e }, "KEY_F21" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x6f }, "KEY_F22" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x70 }, "KEY_F23" },
+ { { OXP_MAPPING_KEYBOARD, 0x01, 0x71 }, "KEY_F24" },
+};
+
+enum oxp_joybutton_index {
+ BUTTON_A = 0x01,
+ BUTTON_B,
+ BUTTON_X,
+ BUTTON_Y,
+ BUTTON_LB,
+ BUTTON_RB,
+ BUTTON_LT,
+ BUTTON_RT,
+ BUTTON_START,
+ BUTTON_SELECT,
+ BUTTON_L3,
+ BUTTON_R3,
+ BUTTON_DUP,
+ BUTTON_DDOWN,
+ BUTTON_DLEFT,
+ BUTTON_DRIGHT,
+ BUTTON_M1 = 0x22,
+ BUTTON_M2,
+ /* These are unused currently, reserved for future devices */
+ BUTTON_M3,
+ BUTTON_M4,
+ BUTTON_M5,
+ BUTTON_M6,
+};
+
+struct oxp_button_idx {
+ enum oxp_joybutton_index button_idx;
+ u8 mapping_idx;
+} __packed;
+
+struct oxp_bmap_page_1 {
+ struct oxp_button_idx btn_a;
+ struct oxp_button_idx btn_b;
+ struct oxp_button_idx btn_x;
+ struct oxp_button_idx btn_y;
+ struct oxp_button_idx btn_lb;
+ struct oxp_button_idx btn_rb;
+ struct oxp_button_idx btn_lt;
+ struct oxp_button_idx btn_rt;
+ struct oxp_button_idx btn_start;
+} __packed;
+
+struct oxp_bmap_page_2 {
+ struct oxp_button_idx btn_select;
+ struct oxp_button_idx btn_l3;
+ struct oxp_button_idx btn_r3;
+ struct oxp_button_idx btn_dup;
+ struct oxp_button_idx btn_ddown;
+ struct oxp_button_idx btn_dleft;
+ struct oxp_button_idx btn_dright;
+ struct oxp_button_idx btn_m1;
+ struct oxp_button_idx btn_m2;
+} __packed;
+
static struct oxp_hid_cfg {
+ struct oxp_bmap_page_1 *bmap_1;
+ struct oxp_bmap_page_2 *bmap_2;
struct led_classdev_mc *led_mc;
struct hid_device *hdev;
struct mutex cfg_mutex; /*ensure single synchronous output report*/
@@ -48,6 +183,10 @@ static struct oxp_hid_cfg {
u8 rgb_en;
} drvdata;
+#define OXP_FILL_PAGE_SLOT(page, btn) \
+ { .button_idx = (page)->btn.button_idx, \
+ .mapping_idx = (page)->btn.mapping_idx }
+
enum oxp_gamepad_mode_index {
OXP_GP_MODE_XINPUT = 0x00,
OXP_GP_MODE_DEBUG = 0x03,
@@ -153,6 +292,10 @@ struct oxp_gen_2_rgb_report {
u8 effect;
} __packed;
+struct oxp_attr {
+ u8 index;
+};
+
static u16 get_usage_page(struct hid_device *hdev)
{
return hdev->collection[0].usage >> 16;
@@ -194,12 +337,19 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
}
static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size);
+static int oxp_set_buttons(void);
static void oxp_mcu_init_fn(struct work_struct *work)
{
u8 gp_mode_data[3] = { OXP_GP_MODE_DEBUG, 0x01, 0x02 };
int ret;
+ /* Re-apply the button mapping */
+ ret = oxp_set_buttons();
+ if (ret)
+ dev_err(&drvdata.hdev->dev,
+ "Error: Failed to set button mapping: %i\n", ret);
+
/* Cycle the gamepad mode */
ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3);
if (ret)
@@ -395,9 +545,410 @@ static ssize_t gamepad_mode_index_show(struct device *dev,
}
static DEVICE_ATTR_RO(gamepad_mode_index);
+static void oxp_set_defaults_bmap_1(struct oxp_bmap_page_1 *bmap)
+{
+ bmap->btn_a.button_idx = BUTTON_A;
+ bmap->btn_a.mapping_idx = 0;
+ bmap->btn_b.button_idx = BUTTON_B;
+ bmap->btn_b.mapping_idx = 1;
+ bmap->btn_x.button_idx = BUTTON_X;
+ bmap->btn_x.mapping_idx = 2;
+ bmap->btn_y.button_idx = BUTTON_Y;
+ bmap->btn_y.mapping_idx = 3;
+ bmap->btn_lb.button_idx = BUTTON_LB;
+ bmap->btn_lb.mapping_idx = 4;
+ bmap->btn_rb.button_idx = BUTTON_RB;
+ bmap->btn_rb.mapping_idx = 5;
+ bmap->btn_lt.button_idx = BUTTON_LT;
+ bmap->btn_lt.mapping_idx = 6;
+ bmap->btn_rt.button_idx = BUTTON_RT;
+ bmap->btn_rt.mapping_idx = 7;
+ bmap->btn_start.button_idx = BUTTON_START;
+ bmap->btn_start.mapping_idx = 8;
+}
+
+static void oxp_set_defaults_bmap_2(struct oxp_bmap_page_2 *bmap)
+{
+ bmap->btn_select.button_idx = BUTTON_SELECT;
+ bmap->btn_select.mapping_idx = 9;
+ bmap->btn_l3.button_idx = BUTTON_L3;
+ bmap->btn_l3.mapping_idx = 10;
+ bmap->btn_r3.button_idx = BUTTON_R3;
+ bmap->btn_r3.mapping_idx = 11;
+ bmap->btn_dup.button_idx = BUTTON_DUP;
+ bmap->btn_dup.mapping_idx = 12;
+ bmap->btn_ddown.button_idx = BUTTON_DDOWN;
+ bmap->btn_ddown.mapping_idx = 13;
+ bmap->btn_dleft.button_idx = BUTTON_DLEFT;
+ bmap->btn_dleft.mapping_idx = 14;
+ bmap->btn_dright.button_idx = BUTTON_DRIGHT;
+ bmap->btn_dright.mapping_idx = 15;
+ bmap->btn_m1.button_idx = BUTTON_M1;
+ bmap->btn_m1.mapping_idx = 48; /* KEY_F15 */
+ bmap->btn_m2.button_idx = BUTTON_M2;
+ bmap->btn_m2.mapping_idx = 49; /* KEY_F16 */
+}
+
+static void oxp_page_fill_data(char *buf, const struct oxp_button_idx *buttons,
+ size_t len)
+{
+ size_t offset_increment = sizeof(u8) + sizeof(struct oxp_button_idx);
+ size_t offset = 5;
+ unsigned int i;
+
+ for (i = 0; i < len; i++, offset += offset_increment) {
+ buf[offset] = (u8)buttons[i].button_idx;
+ memcpy(buf + offset + 1,
+ &oxp_button_table[buttons[i].mapping_idx].data,
+ sizeof(struct oxp_button_data));
+ }
+}
+
+static int oxp_set_buttons(void)
+{
+ u8 page_1[59] = { 0x02, 0x38, 0x20, 0x01, 0x01 };
+ u8 page_2[59] = { 0x02, 0x38, 0x20, 0x02, 0x01 };
+ u16 up = get_usage_page(drvdata.hdev);
+ int ret;
+
+ if (up != GEN2_USAGE_PAGE)
+ return -EINVAL;
+
+ const struct oxp_button_idx p1[] = {
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_a),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_b),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_x),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_y),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_lb),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_rb),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_lt),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_rt),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_start),
+ };
+
+ const struct oxp_button_idx p2[] = {
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_select),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_l3),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_r3),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dup),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_ddown),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dleft),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dright),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_m1),
+ OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_m2),
+ };
+
+ oxp_page_fill_data(page_1, p1, ARRAY_SIZE(p1));
+ oxp_page_fill_data(page_2, p2, ARRAY_SIZE(p2));
+
+ ret = oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, page_1, ARRAY_SIZE(page_1));
+ if (ret)
+ return ret;
+
+ return oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, page_2, ARRAY_SIZE(page_2));
+}
+
+static int oxp_reset_buttons(void)
+{
+ oxp_set_defaults_bmap_1(drvdata.bmap_1);
+ oxp_set_defaults_bmap_2(drvdata.bmap_2);
+ return oxp_set_buttons();
+}
+
+static ssize_t reset_buttons_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ int val, ret;
+
+ ret = kstrtoint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val != 1)
+ return -EINVAL;
+
+ ret = oxp_reset_buttons();
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_WO(reset_buttons);
+
+static void oxp_btn_queue_fn(struct work_struct *work)
+{
+ int ret;
+
+ ret = oxp_set_buttons();
+ if (ret)
+ dev_err(&drvdata.hdev->dev,
+ "Error: Failed to write button mapping: %i\n", ret);
+}
+
+static DECLARE_DELAYED_WORK(oxp_btn_queue, oxp_btn_queue_fn);
+
+static int oxp_button_idx_from_str(const char *buf)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(oxp_button_table); i++)
+ if (sysfs_streq(buf, oxp_button_table[i].name))
+ return i;
+
+ return -EINVAL;
+}
+
+static ssize_t map_button_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count, u8 index)
+{
+ int idx;
+
+ idx = oxp_button_idx_from_str(buf);
+ if (idx < 0)
+ return idx;
+
+ switch (index) {
+ case BUTTON_A:
+ drvdata.bmap_1->btn_a.mapping_idx = idx;
+ break;
+ case BUTTON_B:
+ drvdata.bmap_1->btn_b.mapping_idx = idx;
+ break;
+ case BUTTON_X:
+ drvdata.bmap_1->btn_x.mapping_idx = idx;
+ break;
+ case BUTTON_Y:
+ drvdata.bmap_1->btn_y.mapping_idx = idx;
+ break;
+ case BUTTON_LB:
+ drvdata.bmap_1->btn_lb.mapping_idx = idx;
+ break;
+ case BUTTON_RB:
+ drvdata.bmap_1->btn_rb.mapping_idx = idx;
+ break;
+ case BUTTON_LT:
+ drvdata.bmap_1->btn_lt.mapping_idx = idx;
+ break;
+ case BUTTON_RT:
+ drvdata.bmap_1->btn_rt.mapping_idx = idx;
+ break;
+ case BUTTON_START:
+ drvdata.bmap_1->btn_start.mapping_idx = idx;
+ break;
+ case BUTTON_SELECT:
+ drvdata.bmap_2->btn_select.mapping_idx = idx;
+ break;
+ case BUTTON_L3:
+ drvdata.bmap_2->btn_l3.mapping_idx = idx;
+ break;
+ case BUTTON_R3:
+ drvdata.bmap_2->btn_r3.mapping_idx = idx;
+ break;
+ case BUTTON_DUP:
+ drvdata.bmap_2->btn_dup.mapping_idx = idx;
+ break;
+ case BUTTON_DDOWN:
+ drvdata.bmap_2->btn_ddown.mapping_idx = idx;
+ break;
+ case BUTTON_DLEFT:
+ drvdata.bmap_2->btn_dleft.mapping_idx = idx;
+ break;
+ case BUTTON_DRIGHT:
+ drvdata.bmap_2->btn_dright.mapping_idx = idx;
+ break;
+ case BUTTON_M1:
+ drvdata.bmap_2->btn_m1.mapping_idx = idx;
+ break;
+ case BUTTON_M2:
+ drvdata.bmap_2->btn_m2.mapping_idx = idx;
+ break;
+ default:
+ return -EINVAL;
+ }
+ mod_delayed_work(system_wq, &oxp_btn_queue, msecs_to_jiffies(50));
+ return count;
+}
+
+static ssize_t map_button_show(struct device *dev,
+ struct device_attribute *attr, char *buf,
+ u8 index)
+{
+ u8 i;
+
+ switch (index) {
+ case BUTTON_A:
+ i = drvdata.bmap_1->btn_a.mapping_idx;
+ break;
+ case BUTTON_B:
+ i = drvdata.bmap_1->btn_b.mapping_idx;
+ break;
+ case BUTTON_X:
+ i = drvdata.bmap_1->btn_x.mapping_idx;
+ break;
+ case BUTTON_Y:
+ i = drvdata.bmap_1->btn_y.mapping_idx;
+ break;
+ case BUTTON_LB:
+ i = drvdata.bmap_1->btn_lb.mapping_idx;
+ break;
+ case BUTTON_RB:
+ i = drvdata.bmap_1->btn_rb.mapping_idx;
+ break;
+ case BUTTON_LT:
+ i = drvdata.bmap_1->btn_lt.mapping_idx;
+ break;
+ case BUTTON_RT:
+ i = drvdata.bmap_1->btn_rt.mapping_idx;
+ break;
+ case BUTTON_START:
+ i = drvdata.bmap_1->btn_start.mapping_idx;
+ break;
+ case BUTTON_SELECT:
+ i = drvdata.bmap_2->btn_select.mapping_idx;
+ break;
+ case BUTTON_L3:
+ i = drvdata.bmap_2->btn_l3.mapping_idx;
+ break;
+ case BUTTON_R3:
+ i = drvdata.bmap_2->btn_r3.mapping_idx;
+ break;
+ case BUTTON_DUP:
+ i = drvdata.bmap_2->btn_dup.mapping_idx;
+ break;
+ case BUTTON_DDOWN:
+ i = drvdata.bmap_2->btn_ddown.mapping_idx;
+ break;
+ case BUTTON_DLEFT:
+ i = drvdata.bmap_2->btn_dleft.mapping_idx;
+ break;
+ case BUTTON_DRIGHT:
+ i = drvdata.bmap_2->btn_dright.mapping_idx;
+ break;
+ case BUTTON_M1:
+ i = drvdata.bmap_2->btn_m1.mapping_idx;
+ break;
+ case BUTTON_M2:
+ i = drvdata.bmap_2->btn_m2.mapping_idx;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (i >= ARRAY_SIZE(oxp_button_table))
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", oxp_button_table[i].name);
+}
+
+static ssize_t button_mapping_options_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ ssize_t count = 0;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(oxp_button_table); i++)
+ count += sysfs_emit_at(buf, count, "%s ", oxp_button_table[i].name);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(button_mapping_options);
+
+#define OXP_DEVICE_ATTR_RW(_name, _group) \
+ static ssize_t _name##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ return _group##_store(dev, attr, buf, count, _name.index); \
+ } \
+ static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ return _group##_show(dev, attr, buf, _name.index); \
+ } \
+ static DEVICE_ATTR_RW(_name)
+
+static struct oxp_attr button_a = { BUTTON_A };
+OXP_DEVICE_ATTR_RW(button_a, map_button);
+
+static struct oxp_attr button_b = { BUTTON_B };
+OXP_DEVICE_ATTR_RW(button_b, map_button);
+
+static struct oxp_attr button_x = { BUTTON_X };
+OXP_DEVICE_ATTR_RW(button_x, map_button);
+
+static struct oxp_attr button_y = { BUTTON_Y };
+OXP_DEVICE_ATTR_RW(button_y, map_button);
+
+static struct oxp_attr button_lb = { BUTTON_LB };
+OXP_DEVICE_ATTR_RW(button_lb, map_button);
+
+static struct oxp_attr button_rb = { BUTTON_RB };
+OXP_DEVICE_ATTR_RW(button_rb, map_button);
+
+static struct oxp_attr button_lt = { BUTTON_LT };
+OXP_DEVICE_ATTR_RW(button_lt, map_button);
+
+static struct oxp_attr button_rt = { BUTTON_RT };
+OXP_DEVICE_ATTR_RW(button_rt, map_button);
+
+static struct oxp_attr button_start = { BUTTON_START };
+OXP_DEVICE_ATTR_RW(button_start, map_button);
+
+static struct oxp_attr button_select = { BUTTON_SELECT };
+OXP_DEVICE_ATTR_RW(button_select, map_button);
+
+static struct oxp_attr button_l3 = { BUTTON_L3 };
+OXP_DEVICE_ATTR_RW(button_l3, map_button);
+
+static struct oxp_attr button_r3 = { BUTTON_R3 };
+OXP_DEVICE_ATTR_RW(button_r3, map_button);
+
+static struct oxp_attr button_d_up = { BUTTON_DUP };
+OXP_DEVICE_ATTR_RW(button_d_up, map_button);
+
+static struct oxp_attr button_d_down = { BUTTON_DDOWN };
+OXP_DEVICE_ATTR_RW(button_d_down, map_button);
+
+static struct oxp_attr button_d_left = { BUTTON_DLEFT };
+OXP_DEVICE_ATTR_RW(button_d_left, map_button);
+
+static struct oxp_attr button_d_right = { BUTTON_DRIGHT };
+OXP_DEVICE_ATTR_RW(button_d_right, map_button);
+
+static struct oxp_attr button_m1 = { BUTTON_M1 };
+OXP_DEVICE_ATTR_RW(button_m1, map_button);
+
+static struct oxp_attr button_m2 = { BUTTON_M2 };
+OXP_DEVICE_ATTR_RW(button_m2, map_button);
+
static struct attribute *oxp_cfg_attrs[] = {
+ &dev_attr_button_a.attr,
+ &dev_attr_button_b.attr,
+ &dev_attr_button_d_down.attr,
+ &dev_attr_button_d_left.attr,
+ &dev_attr_button_d_right.attr,
+ &dev_attr_button_d_up.attr,
+ &dev_attr_button_l3.attr,
+ &dev_attr_button_lb.attr,
+ &dev_attr_button_lt.attr,
+ &dev_attr_button_m1.attr,
+ &dev_attr_button_m2.attr,
+ &dev_attr_button_mapping_options.attr,
+ &dev_attr_button_r3.attr,
+ &dev_attr_button_rb.attr,
+ &dev_attr_button_rt.attr,
+ &dev_attr_button_select.attr,
+ &dev_attr_button_start.attr,
+ &dev_attr_button_x.attr,
+ &dev_attr_button_y.attr,
&dev_attr_gamepad_mode.attr,
&dev_attr_gamepad_mode_index.attr,
+ &dev_attr_reset_buttons.attr,
NULL,
};
@@ -823,6 +1374,8 @@ static bool oxp_hybrid_mcu_device(void)
static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
{
+ struct oxp_bmap_page_1 *bmap_1;
+ struct oxp_bmap_page_2 *bmap_2;
int ret;
hid_set_drvdata(hdev, &drvdata);
@@ -855,6 +1408,18 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
return 0;
skip_rgb:
+ bmap_1 = devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_1), GFP_KERNEL);
+ if (!bmap_1)
+ return dev_err_probe(&hdev->dev, -ENOMEM,
+ "Unable to allocate button map page 1\n");
+
+ bmap_2 = devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_2), GFP_KERNEL);
+ if (!bmap_2)
+ return dev_err_probe(&hdev->dev, -ENOMEM,
+ "Unable to allocate button map page 2\n");
+
+ drvdata.bmap_1 = bmap_1;
+ drvdata.bmap_2 = bmap_2;
mod_delayed_work(system_wq, &oxp_mcu_init, msecs_to_jiffies(50));
drvdata.gamepad_mode = OXP_GP_MODE_XINPUT;
--
2.53.0
^ permalink raw reply related
* [PATCH v2 5/5] HID: hid-oxp: Add Vibration Intensity Attributes
From: Derek J. Clark @ 2026-04-07 4:13 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Lambert Fan, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260407041354.2283201-1-derekjohn.clark@gmail.com>
Adds attribute for setting the rumble intensity level. This setting must
be re-applied after the gamepad mode is set as doing so resets this to
the default value.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/hid/hid-oxp.c | 80 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 78 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index 1100f1f14f35..cad6973089a0 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -34,6 +34,7 @@ enum oxp_function_index {
OXP_FID_GEN1_RGB_SET = 0x07,
OXP_FID_GEN1_RGB_REPLY = 0x0f,
OXP_FID_GEN2_TOGGLE_MODE = 0xb2,
+ OXP_FID_GEN2_RUMBLE_SET = 0xb3,
OXP_FID_GEN2_KEY_STATE = 0xb4,
OXP_FID_GEN2_STATUS_EVENT = 0xb8,
};
@@ -178,6 +179,7 @@ static struct oxp_hid_cfg {
struct mutex cfg_mutex; /*ensure single synchronous output report*/
u8 rgb_brightness;
u8 gamepad_mode;
+ u8 rumble_intensity;
u8 rgb_effect;
u8 rgb_speed;
u8 rgb_en;
@@ -263,6 +265,11 @@ static const char *const oxp_rgb_effect_text[] = {
[OXP_EFFECT_MONO_LIST] = "monocolor",
};
+enum oxp_rumble_side_index {
+ OXP_RUMBLE_LEFT = 0x00,
+ OXP_RUMBLE_RIGHT,
+};
+
struct oxp_gen_1_rgb_report {
u8 report_id;
u8 message_id;
@@ -338,6 +345,7 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size);
static int oxp_set_buttons(void);
+static int oxp_rumble_intensity_set(u8 intensity);
static void oxp_mcu_init_fn(struct work_struct *work)
{
@@ -365,6 +373,12 @@ static void oxp_mcu_init_fn(struct work_struct *work)
if (ret)
dev_err(&drvdata.hdev->dev,
"Error: Failed to set gamepad mode: %i\n", ret);
+
+ /* Set vibration level */
+ ret = oxp_rumble_intensity_set(drvdata.rumble_intensity);
+ if (ret)
+ dev_err(&drvdata.hdev->dev,
+ "Error: Failed to set rumble intensity: %i\n", ret);
}
static DECLARE_DELAYED_WORK(oxp_mcu_init, oxp_mcu_init_fn);
@@ -513,6 +527,14 @@ static ssize_t gamepad_mode_store(struct device *dev,
drvdata.gamepad_mode = data[0];
+ if (drvdata.gamepad_mode == OXP_GP_MODE_DEBUG)
+ return count;
+
+ /* Re-apply rumble settings as switching gamepad mode will override */
+ ret = oxp_rumble_intensity_set(drvdata.rumble_intensity);
+ if (ret)
+ return ret;
+
return count;
}
@@ -858,6 +880,59 @@ static ssize_t button_mapping_options_show(struct device *dev,
}
static DEVICE_ATTR_RO(button_mapping_options);
+static int oxp_rumble_intensity_set(u8 intensity)
+{
+ u8 header[15] = { 0x02, 0x38, 0x02, 0xe3, 0x39, 0xe3, 0x39, 0xe3,
+ 0x39, 0x01, intensity, 0x05, 0xe3, 0x39, 0xe3 };
+ u8 footer[9] = { 0x39, 0xe3, 0x39, 0xe3, 0xe3, 0x02, 0x04, 0x39, 0x39 };
+ size_t footer_size = ARRAY_SIZE(footer);
+ size_t header_size = ARRAY_SIZE(header);
+ u8 data[59] = { 0x0 };
+ size_t data_size = ARRAY_SIZE(data);
+
+ memcpy(data, header, header_size);
+ memcpy(data + data_size - footer_size, footer, footer_size);
+
+ return oxp_gen_2_property_out(OXP_FID_GEN2_RUMBLE_SET, data, data_size);
+}
+
+static ssize_t rumble_intensity_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 val;
+
+ ret = kstrtou8(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val < 0 || val > 5)
+ return -EINVAL;
+
+ ret = oxp_rumble_intensity_set(val);
+ if (ret)
+ return ret;
+
+ drvdata.rumble_intensity = val;
+
+ return count;
+}
+
+static ssize_t rumble_intensity_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%i\n", drvdata.rumble_intensity);
+}
+static DEVICE_ATTR_RW(rumble_intensity);
+
+static ssize_t rumble_intensity_range_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "0-5\n");
+}
+static DEVICE_ATTR_RO(rumble_intensity_range);
+
#define OXP_DEVICE_ATTR_RW(_name, _group) \
static ssize_t _name##_store(struct device *dev, \
struct device_attribute *attr, \
@@ -949,6 +1024,8 @@ static struct attribute *oxp_cfg_attrs[] = {
&dev_attr_gamepad_mode.attr,
&dev_attr_gamepad_mode_index.attr,
&dev_attr_reset_buttons.attr,
+ &dev_attr_rumble_intensity.attr,
+ &dev_attr_rumble_intensity_range.attr,
NULL,
};
@@ -1420,10 +1497,9 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
drvdata.bmap_1 = bmap_1;
drvdata.bmap_2 = bmap_2;
+ drvdata.rumble_intensity = 5;
mod_delayed_work(system_wq, &oxp_mcu_init, msecs_to_jiffies(50));
- drvdata.gamepad_mode = OXP_GP_MODE_XINPUT;
-
ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group);
if (ret)
return dev_err_probe(&hdev->dev, ret,
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox