* [PATCH] input: remove changelogs, tracked in git
From: Elliot Tester @ 2026-05-14 19:33 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: linux-input, linux-kernel, Elliot Tester
Signed-off-by: Elliot Tester <elliotctester1@gmail.com>
---
drivers/input/misc/yealink.c | 9 ---------
drivers/input/tablet/aiptek.c | 31 -------------------------------
2 files changed, 40 deletions(-)
diff --git a/drivers/input/misc/yealink.c b/drivers/input/misc/yealink.c
index 8786ed8b3..560f895a0 100644
--- a/drivers/input/misc/yealink.c
+++ b/drivers/input/misc/yealink.c
@@ -22,15 +22,6 @@
* - Olivier Vandorpe, for providing the usbb2k-api.
* - Martin Diehl, for spotting my memory allocation bug.
*
- * History:
- * 20050527 henk First version, functional keyboard. Keyboard events
- * will pop-up on the ../input/eventX bus.
- * 20050531 henk Added led, LCD, dialtone and sysfs interface.
- * 20050610 henk Cleanups, make it ready for public consumption.
- * 20050630 henk Cleanups, fixes in response to comments.
- * 20050701 henk sysfs write serialisation, fix potential unload races
- * 20050801 henk Added ringtone, restructure USB
- * 20050816 henk Merge 2.6.13-rc6
*/
#include <linux/kernel.h>
diff --git a/drivers/input/tablet/aiptek.c b/drivers/input/tablet/aiptek.c
index 6df24cee3..ba8109322 100644
--- a/drivers/input/tablet/aiptek.c
+++ b/drivers/input/tablet/aiptek.c
@@ -16,37 +16,6 @@
*
* Many thanks to Oliver Kuechemann for his support.
*
- * ChangeLog:
- * v0.1 - Initial release
- * v0.2 - Hack to get around fake event 28's. (Bryan W. Headley)
- * v0.3 - Make URB dynamic (Bryan W. Headley, Jun-8-2002)
- * Released to Linux 2.4.19 and 2.5.x
- * v0.4 - Rewrote substantial portions of the code to deal with
- * corrected control sequences, timing, dynamic configuration,
- * support of 6000U - 12000U, procfs, and macro key support
- * (Jan-1-2003 - Feb-5-2003, Bryan W. Headley)
- * v1.0 - Added support for diagnostic messages, count of messages
- * received from URB - Mar-8-2003, Bryan W. Headley
- * v1.1 - added support for tablet resolution, changed DV and proximity
- * some corrections - Jun-22-2003, martin schneebacher
- * - Added support for the sysfs interface, deprecating the
- * procfs interface for 2.5.x kernel. Also added support for
- * Wheel command. Bryan W. Headley July-15-2003.
- * v1.2 - Reworked jitter timer as a kernel thread.
- * Bryan W. Headley November-28-2003/Jan-10-2004.
- * v1.3 - Repaired issue of kernel thread going nuts on single-processor
- * machines, introduced programmableDelay as a command line
- * parameter. Feb 7 2004, Bryan W. Headley.
- * v1.4 - Re-wire jitter so it does not require a thread. Courtesy of
- * Rene van Paassen. Added reporting of physical pointer device
- * (e.g., stylus, mouse in reports 2, 3, 4, 5. We don't know
- * for reports 1, 6.)
- * what physical device reports for reports 1, 6.) Also enabled
- * MOUSE and LENS tool button modes. Renamed "rubber" to "eraser".
- * Feb 20, 2004, Bryan W. Headley.
- * v1.5 - Added previousJitterable, so we don't do jitter delay when the
- * user is holding a button down for periods of time.
- *
* NOTE:
* This kernel driver is augmented by the "Aiptek" XFree86 input
* driver for your X server, as well as the Gaiptek GUI Front-end
--
2.54.0
^ permalink raw reply related
* [dtor-input:for-linus] BUILD SUCCESS 16ca52bc209fa4bf9239cd9e5643e95533476b58
From: kernel test robot @ 2026-05-14 19:46 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: 16ca52bc209fa4bf9239cd9e5643e95533476b58 Input: synaptics - add LEN2058 to SMBus passlist for ThinkPad E490
elapsed time: 731m
configs tested: 223
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
arc allmodconfig clang-16
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc randconfig-001-20260514 clang-23
arc randconfig-001-20260515 clang-23
arc randconfig-002-20260514 clang-23
arc randconfig-002-20260515 clang-23
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm randconfig-001-20260514 clang-23
arm randconfig-001-20260515 clang-23
arm randconfig-002-20260514 clang-23
arm randconfig-002-20260515 clang-23
arm randconfig-003-20260514 clang-23
arm randconfig-003-20260515 clang-23
arm randconfig-004-20260514 clang-23
arm randconfig-004-20260515 clang-23
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 randconfig-001-20260514 clang-23
arm64 randconfig-001-20260515 gcc-11.5.0
arm64 randconfig-002-20260514 clang-23
arm64 randconfig-002-20260515 gcc-11.5.0
arm64 randconfig-003-20260514 clang-23
arm64 randconfig-003-20260515 gcc-11.5.0
arm64 randconfig-004-20260514 clang-23
arm64 randconfig-004-20260515 gcc-11.5.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky randconfig-001-20260514 clang-23
csky randconfig-001-20260515 gcc-11.5.0
csky randconfig-002-20260514 clang-23
csky randconfig-002-20260515 gcc-11.5.0
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig gcc-15.2.0
hexagon randconfig-001-20260514 gcc-10.5.0
hexagon randconfig-002-20260514 gcc-10.5.0
i386 allmodconfig clang-20
i386 allmodconfig gcc-14
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 allyesconfig gcc-14
i386 buildonly-randconfig-001 gcc-14
i386 buildonly-randconfig-001-20260514 gcc-14
i386 buildonly-randconfig-002 gcc-14
i386 buildonly-randconfig-002-20260514 gcc-14
i386 buildonly-randconfig-003 gcc-14
i386 buildonly-randconfig-003-20260514 gcc-14
i386 buildonly-randconfig-004 gcc-14
i386 buildonly-randconfig-004-20260514 gcc-14
i386 buildonly-randconfig-005 gcc-14
i386 buildonly-randconfig-005-20260514 gcc-14
i386 buildonly-randconfig-006 gcc-14
i386 buildonly-randconfig-006-20260514 gcc-14
i386 randconfig-001-20260514 clang-20
i386 randconfig-001-20260515 clang-20
i386 randconfig-002-20260514 clang-20
i386 randconfig-002-20260515 clang-20
i386 randconfig-003-20260514 clang-20
i386 randconfig-003-20260515 clang-20
i386 randconfig-004-20260514 clang-20
i386 randconfig-004-20260515 clang-20
i386 randconfig-005-20260514 clang-20
i386 randconfig-005-20260515 clang-20
i386 randconfig-006-20260514 clang-20
i386 randconfig-006-20260515 clang-20
i386 randconfig-007-20260514 clang-20
i386 randconfig-007-20260515 clang-20
i386 randconfig-011-20260514 clang-20
i386 randconfig-012-20260514 clang-20
i386 randconfig-013-20260514 clang-20
i386 randconfig-014-20260514 clang-20
i386 randconfig-015-20260514 clang-20
i386 randconfig-016-20260514 clang-20
i386 randconfig-017-20260514 clang-20
loongarch allmodconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260514 gcc-10.5.0
loongarch randconfig-002-20260514 gcc-10.5.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
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 defconfig clang-19
nios2 randconfig-001-20260514 gcc-10.5.0
nios2 randconfig-002-20260514 gcc-10.5.0
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc randconfig-001 gcc-13.4.0
parisc randconfig-001-20260514 gcc-13.4.0
parisc randconfig-001-20260515 gcc-8.5.0
parisc randconfig-002 gcc-13.4.0
parisc randconfig-002-20260514 gcc-13.4.0
parisc randconfig-002-20260515 gcc-8.5.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc mpc885_ads_defconfig clang-23
powerpc randconfig-001 gcc-13.4.0
powerpc randconfig-001-20260514 gcc-13.4.0
powerpc randconfig-001-20260515 gcc-8.5.0
powerpc randconfig-002 gcc-13.4.0
powerpc randconfig-002-20260514 gcc-13.4.0
powerpc randconfig-002-20260515 gcc-8.5.0
powerpc tqm8541_defconfig clang-23
powerpc64 randconfig-001 gcc-13.4.0
powerpc64 randconfig-001-20260514 gcc-13.4.0
powerpc64 randconfig-001-20260515 gcc-8.5.0
powerpc64 randconfig-002 gcc-13.4.0
powerpc64 randconfig-002-20260514 gcc-13.4.0
powerpc64 randconfig-002-20260515 gcc-8.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260514 gcc-14.3.0
riscv randconfig-001-20260515 gcc-15.2.0
riscv randconfig-002-20260514 gcc-14.3.0
riscv randconfig-002-20260515 gcc-15.2.0
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-20260514 gcc-14.3.0
s390 randconfig-001-20260515 gcc-15.2.0
s390 randconfig-002-20260514 gcc-14.3.0
s390 randconfig-002-20260515 gcc-15.2.0
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allyesconfig clang-19
sh defconfig gcc-14
sh randconfig-001-20260514 gcc-14.3.0
sh randconfig-001-20260515 gcc-15.2.0
sh randconfig-002-20260514 gcc-14.3.0
sh randconfig-002-20260515 gcc-15.2.0
sh sh7757lcr_defconfig gcc-15.2.0
sparc allnoconfig clang-23
sparc defconfig gcc-15.2.0
sparc randconfig-001 gcc-15.2.0
sparc randconfig-001-20260514 gcc-15.2.0
sparc randconfig-002 gcc-15.2.0
sparc randconfig-002-20260514 gcc-15.2.0
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001 gcc-15.2.0
sparc64 randconfig-001-20260514 gcc-15.2.0
sparc64 randconfig-002 gcc-15.2.0
sparc64 randconfig-002-20260514 gcc-15.2.0
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 gcc-15.2.0
um randconfig-001-20260514 gcc-15.2.0
um randconfig-002 gcc-15.2.0
um randconfig-002-20260514 gcc-15.2.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001 clang-20
x86_64 buildonly-randconfig-001-20260514 clang-20
x86_64 buildonly-randconfig-002 clang-20
x86_64 buildonly-randconfig-002-20260514 clang-20
x86_64 buildonly-randconfig-003 clang-20
x86_64 buildonly-randconfig-003-20260514 clang-20
x86_64 buildonly-randconfig-004 clang-20
x86_64 buildonly-randconfig-004-20260514 clang-20
x86_64 buildonly-randconfig-005 clang-20
x86_64 buildonly-randconfig-005-20260514 clang-20
x86_64 buildonly-randconfig-006 clang-20
x86_64 buildonly-randconfig-006-20260514 clang-20
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001 gcc-14
x86_64 randconfig-001-20260514 gcc-14
x86_64 randconfig-002 gcc-14
x86_64 randconfig-002-20260514 gcc-14
x86_64 randconfig-003 gcc-14
x86_64 randconfig-003-20260514 gcc-14
x86_64 randconfig-004 gcc-14
x86_64 randconfig-004-20260514 gcc-14
x86_64 randconfig-005 gcc-14
x86_64 randconfig-005-20260514 gcc-14
x86_64 randconfig-006 gcc-14
x86_64 randconfig-006-20260514 gcc-14
x86_64 randconfig-071-20260514 clang-20
x86_64 randconfig-072-20260514 clang-20
x86_64 randconfig-073-20260514 clang-20
x86_64 randconfig-074-20260514 clang-20
x86_64 randconfig-075-20260514 clang-20
x86_64 randconfig-076-20260514 clang-20
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 allyesconfig clang-23
xtensa randconfig-001 gcc-15.2.0
xtensa randconfig-001-20260514 gcc-15.2.0
xtensa randconfig-002 gcc-15.2.0
xtensa randconfig-002-20260514 gcc-15.2.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH] Input: analog: replace deprecated simple_strtoul() with kstrtouint()
From: Akash Sukhavasi @ 2026-05-14 20:44 UTC (permalink / raw)
To: david.laight.linux
Cc: akash.sukhavasi, dmitry.torokhov, linux-input, linux-kernel
In-Reply-To: <20260509105520.61f823a5@pumpkin>
On Sat, May 9, 2026 at 4:55 AM David Laight <david.laight.linux@gmail.com> wrote:
> However that might break existing systems.
> So I'm not at all sure it should be done.
Hi David,
You're right. It could introduce regressions for users of analog
joysticks passing module parameters via scripts or boot configs.
Additionally, digging through list history: Dmitry reviewed this
same call in 2011 [1] and chose to leave it, noting the complex
parsing semantics were intentional for this context.
Withdrawing the patch. Thanks for the review.
[1] https://lore.kernel.org/all/20111108034805.GA21927@core.coreip.homeip.net/
Thanks,
Akash
^ permalink raw reply
* Re: [PATCH] HID: Expose LattePanda IOTA UPS as a power_supply device
From: Andrew Maney @ 2026-05-15 0:24 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-input, Jiri Kosina, bentiss
In-Reply-To: <20260514065043.1F11AC2BCC7@smtp.kernel.org>
On Thu, May 14, 2026 at 2:50 AM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
> - [High] Kernel panic due to invalid type cast when transport is not USB (e.g. uhid).
> - [High] Kernel panic from dereferencing an ERR_PTR in the raw event handler.
> - [High] Exposing fabricated 0 values for unsupported TIME_TO_EMPTY properties causes immediate system shutdown.
> - [Medium] Hardcoded sysfs name prevents multiple devices and causes probe failure.
> - [Medium] Data race on `ups->charge_limit`.
> - [Low] Missing `POWER_SUPPLY` dependency in Kconfig causes build failures.
> - [Low] Driver synchronously blocks the probe path for 3 seconds.
> --
>
> commit 7d140ce589ec6c637bec5ce8b0c27bf8634322c6
> Author: Andrew Maney <andrewmaney05@gmail.com>
> Subject: HID: Expose LattePanda IOTA UPS as a power_supply device
>
> This commit adds a driver for the LattePanda IOTA UPS. It exposes battery
> status, capacity, and charging status via the power_supply subsystem by parsing
> custom HID reports.
>
> > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> > --- a/drivers/hid/Kconfig
> > +++ b/drivers/hid/Kconfig
> > @@ -105,6 +105,16 @@ config HID_HAPTIC
> >
> > menu "Special HID drivers"
> >
> > +config HID_LATTEPANDA_IOTA_UPS
> > + tristate "LattePanda IOTA UPS"
> > + depends on USB_HID && X86
>
> If the kernel is configured with CONFIG_POWER_SUPPLY=m and
> CONFIG_HID_LATTEPANDA_IOTA_UPS=y, will the built-in HID driver fail to link
> against the modular power supply core? Should this include a dependency on
> POWER_SUPPLY?
>
> > diff --git a/drivers/hid/hid-lattepanda-iota-ups.c b/drivers/hid/hid-lattepanda-iota-ups.c
> > --- /dev/null
> > +++ b/drivers/hid/hid-lattepanda-iota-ups.c
> [ ... ]
> > +static enum power_supply_property iota_ups_properties[] = {
> > + POWER_SUPPLY_PROP_STATUS,
> > + POWER_SUPPLY_PROP_CAPACITY,
> > + POWER_SUPPLY_PROP_PRESENT,
> > + POWER_SUPPLY_PROP_ONLINE,
> > + POWER_SUPPLY_PROP_SCOPE,
> > + POWER_SUPPLY_PROP_TECHNOLOGY,
> > + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
> > + POWER_SUPPLY_PROP_CAPACITY_LEVEL,
> > + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
> > + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
>
> If the hardware doesn't support reporting time remaining, is it safe to
> include these properties and return 0?
>
> Returning 0 seconds remaining while discharging can signal to userspace power
> daemons like UPower or systemd-logind that the battery is empty, which might
> trigger an immediate emergency suspend or shutdown. Could these unsupported
> properties be omitted from the array entirely?
>
> [ ... ]
> > +static int iota_ups_set_property(struct power_supply *supply,
> > + enum power_supply_property psp,
> > + const union power_supply_propval *val)
> > +{
> > + struct iota_ups *ups = power_supply_get_drvdata(supply);
> > +
> > + if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) {
> > + /*
> > + * V1.0 supports 80% and 100% charge limits only, set via
> > + * DIP switch on the board. This property allows userspace
> > + * to inform the driver which limit is configured.
> > + */
> > + if (val->intval != 80 && val->intval != 100)
> > + return -EINVAL;
> > + ups->charge_limit = val->intval;
>
> Does this introduce a data race on ups->charge_limit?
>
> This field is modified here without holding ups->lock, but it is concurrently
> read in iota_ups_raw_event() and iota_ups_get_property() where it is
> protected by spin_lock_irqsave(). Should this assignment also be
> protected by the spinlock?
>
> [ ... ]
> > +static int iota_ups_raw_event(struct hid_device *hdev,
> > + struct hid_report *report,
> > + u8 *data, int size)
> > +{
> > + struct iota_ups *ups = hid_get_drvdata(hdev);
> [ ... ]
> > + /*
> > + * Notify the power_supply core outside the spinlock. This
> > + * triggers UPower's PropertiesChanged signal with the new values.
> > + */
> > + if (changed && ups->psu)
> > + power_supply_changed(ups->psu);
>
> Could ups->psu contain an error pointer here?
>
> In iota_ups_probe(), if devm_power_supply_register() fails, it returns an
> error pointer like ERR_PTR(-ENOMEM), which gets directly assigned to ups->psu.
>
> Because hid_hw_start() is called earlier, raw HID events can be processed
> concurrently before the hardware is stopped in the error path. Since an error
> pointer is not NULL, the check (changed && ups->psu) would pass, and
> power_supply_changed() would be called with the error pointer, leading to a
> crash.
>
> Would it be safer to assign the return value of devm_power_supply_register()
> to a local variable first, and only update ups->psu on success?
>
> [ ... ]
> > +static int iota_ups_probe(struct hid_device *hdev,
> > + const struct hid_device_id *id)
> > +{
> [ ... ]
> > + /* Retrieve serial number from the USB descriptor */
> > + udev = to_usb_device(hdev->dev.parent->parent);
> > + if (udev->serial)
> > + strscpy(ups->serial, udev->serial, sizeof(ups->serial));
>
> Can this cause a kernel panic if the HID device is instantiated via userspace
> mock drivers like uhid?
>
> When using uhid (which allows setting the bus type to BUS_USB), the parent
> device is the uhid character device, not a usb_interface. Dereferencing the
> resulting struct usb_device pointer would perform an out-of-bounds memory
> read.
>
> Should the driver verify the transport layer using hid_is_usb() before
> dereferencing USB-specific structures, or simply use hdev->uniq instead?
>
> [ ... ]
> > + /*
> > + * Wait for both status and capacity reports before registering.
> > + * The device sends reports every ~1 second; 3 seconds is safe.
> > + */
> > + wait_for_completion_timeout(&ups->got_initial_data,
> > + msecs_to_jiffies(3000));
>
> Does blocking the probe path for 3 seconds cause issues for device
> enumeration?
>
> Calling wait_for_completion_timeout() synchronously stalls the kernel's
> device enumeration thread for that bus, which can unnecessarily delay the
> overall system boot process or stall the handling of other devices during
> hotplug.
>
> Could the registration be deferred, or could the device be registered
> immediately with an unknown state instead of blocking?
>
> > +
> > + ups->psu_desc.name = "lattepanda-iota-ups";
>
> Is it possible to have multiple of these UPS devices connected to a single
> system?
>
> If a second device is plugged in, it will attempt to register a sysfs
> directory with the exact same hardcoded name, which fails with -EEXIST.
>
> This probe failure would then trigger the error pointer panic mentioned
> earlier. Should the driver dynamically construct a unique name using
> devm_kasprintf() with dev_name(&hdev->dev)?
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260513155723.230707-1-andrewmaney05@gmail.com?part=1
Thanks for the detailed review! I'll do the following in v3:
* Remove TIME_TO_EMPTY_NOW and TIME_TO_FULL_NOW to avoid spurious
shutdowns. The UPS board doesn't currently have any way to report
these values as of right now.
* Add the POWER_SUPPLY requirement to Kconfig.
* Fix the data race for ups->charge_limit in set_property.
* Address the 3 second probe delay, it may not be needed. Registering
the UPS immediately with an unknown state may not be safe if its
reports are misinterpreted.
* Fix ERR_PTR panic with a local variable.
* Fix uhid panic with hid_is_usb() before dereferencing.
* Use devm_kasprintf() for a unique sysfs name. It is not currently
possible to have more than one UPS board connected to the IOTA, but
I'll implement this as a safeguard in case future hardware revisions
add support for multiple boards.
Thanks,
Andrew
^ permalink raw reply
* [PATCH v3 0/4] Add MSI Claw HID Configuration Driver
From: Derek J. Clark @ 2026-05-15 3:36 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
Derek J . Clark, linux-input, linux-doc, linux-kernel
This series adds an HID Configuration driver for the MSI Claw line of
Handheld Gaming PC's. The MSI Claw HID interface provides multiple
features, such as the ability to switch between xinput, dinput, and a
desktop mode, RGB control, rumble intensity, and mapping of the rear "M"
keys. There are additional gamepad modes that are not included in this
driver as they appear to be used in assembly line testing or are
incomplete in the firmware. During my testing I found them to be unstable.
The initial version of this driver was written by Denis Benato, which
contained the initial reverse-engineering and implementation for the
gamepad mode switching. This work was later expanded by Zhouwang Huang
to include more gamepad modes and additional features. Finally, I
refactored the entire driver, fixed multiple bugs, and refined the overall
format to conform to kernel driver best practices and style guide.
Claude was used initially by Zhouwang Huang to quickly parse HID captures
during the reverse-engineering of some of the features. Since Claude had
already been used, as a test of its capabilities I had it implement the
rumble intensity attribute after I had already rewritten most of the
driver, which I then manually edited to fix some mistakes. I also used
Claude to review the driver and these patches for any mistakes and bugs.
Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Denis Benato <denis.benato@linux.dev>
Signed-off-by: Denis Benato <denis.benato@linux.dev>
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v3:
- Add mutex for read/write if rgb frame data.
- Ensure claw_hw_output_report is properly guarded.
- Remove setting rgb_frame_count when reading rgb profiles as it always
returns garbage data.
- Ensure rgb_speed is getting drvdata from a valid lookup (not hdev).
- Use scoped_guard where necessary.
- Reoder claw_probe to ensure all mutex, completion, and variable
assignments are in place prior to setting drvdata.
- Ensure gamepad_mode is set to a valid enum value in claw_probe.
v2:
- Use mutexes to guard SYNC_TO_ROM calls and pending_profile calls.
- Rename driver to hid-msi and add generic entrypoints for
probe/resume/remove that call claw specific functions in order to
future proof the driver for other MSI HID interfaces.
- Fix various bugs and formatting issues.
Derek J. Clark (4):
HID: hid-msi: Add MSI Claw configuration driver
HID: hid-msi: Add M-key mapping attributes
HID: hid-msi: Add RGB control interface
HID: hid-msi: Add Rumble Intensity Attributes
MAINTAINERS | 6 +
drivers/hid/Kconfig | 12 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 6 +
drivers/hid/hid-msi.c | 1662 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 1687 insertions(+)
create mode 100644 drivers/hid/hid-msi.c
--
2.53.0
^ permalink raw reply
* [PATCH v3 1/4] HID: hid-msi: Add MSI Claw configuration driver
From: Derek J. Clark @ 2026-05-15 3:36 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260515033622.2095277-1-derekjohn.clark@gmail.com>
Adds configuration HID driver for the MSI Claw series of handheld PC's.
In this initial patch add the initial driver outline and attributes for
changing the gamepad mode, M-key behavior, and add a WO reset function.
Sending the SWITCH_MODE and RESET commands causes a USB disconnect in
the device. The completion will therefore never get hit and would trigger
an -EIO. To avoid showing the user an error for every write to these
attrs a bypass for the completion handling is introduced when timeout ==
0.
The initial version of this patch was written by Denis Benato, which
contained the initial reverse-engineering and implementation for the
gamepad mode switching. This work was later expanded by Zhouwang Huang
to include more gamepad modes. Finally, I refactored the drivers data
in/out flow and overall format to conform to kernel driver best
practices and style guides. Claude was used as an initial reviewer of
this patch.
Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Denis Benato <denis.benato@linux.dev>
Signed-off-by: Denis Benato <denis.benato@linux.dev>
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v3:
- Ensure claw_hw_output_report is properly guarded.
- Reoder claw_probe to ensure all mutex, completion, and variable
assignments are in place prior to setting drvdata.
- Ensure gamepad_mode is set to a valid enum value in claw_probe.
v2:
- Rename driver to hid-msi from hid-msi-claw.
- Rename reusable/generic functions to msi_* from claw_*, retaining
claw specific functions.
- Add generic entrypoints for probe, remove, and raw event that route
to claw specific functions.
---
MAINTAINERS | 6 +
drivers/hid/Kconfig | 12 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 6 +
drivers/hid/hid-msi.c | 582 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 607 insertions(+)
create mode 100644 drivers/hid/hid-msi.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 6f6517bf4f970..8e2de98b768f7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17965,6 +17965,12 @@ S: Odd Fixes
F: Documentation/devicetree/bindings/net/ieee802154/mrf24j40.txt
F: drivers/net/ieee802154/mrf24j40.c
+MSI HID DRIVER
+M: Derek J. Clark <derekjohn.clark@gmail.com>
+L: linux-input@vger.kernel.org
+S: Maintained
+F: drivers/hid/hid-msi.c
+
MSI EC DRIVER
M: Nikita Kravets <teackot@gmail.com>
L: platform-driver-x86@vger.kernel.org
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 10c12d8e65579..af146691bd481 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -492,6 +492,18 @@ config HID_GT683R
Currently the following devices are know to be supported:
- MSI GT683R
+config HID_MSI
+ tristate "MSI Claw Gamepad Support"
+ depends on USB_HID
+ select LEDS_CLASS
+ select LEDS_CLASS_MULTICOLOR
+ help
+ Support for the MSI Claw RGB and controller configuration
+
+ Say Y here to include configuration interface support for the MSI Claw Line
+ of Handheld Console Controllers. Say M here to compile this driver as a
+ module. The module will be called hid-msi.
+
config HID_KEYTOUCH
tristate "Keytouch HID devices"
help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 07dfdb6a49c59..80925a17b059c 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o
obj-$(CONFIG_HID_MEGAWORLD_FF) += hid-megaworld.o
obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o
obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o
+obj-$(CONFIG_HID_MSI) += hid-msi.o
obj-$(CONFIG_HID_MULTITOUCH) += hid-multitouch.o
obj-$(CONFIG_HID_NINTENDO) += hid-nintendo.o
obj-$(CONFIG_HID_NTI) += hid-nti.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 933b7943bdb50..6d0d34806931f 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1047,7 +1047,13 @@
#define USB_DEVICE_ID_MOZA_R16_R21_2 0x0010
#define USB_VENDOR_ID_MSI 0x1770
+#define USB_VENDOR_ID_MSI_2 0x0db0
#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
+#define USB_DEVICE_ID_MSI_CLAW_XINPUT 0x1901
+#define USB_DEVICE_ID_MSI_CLAW_DINPUT 0x1902
+#define USB_DEVICE_ID_MSI_CLAW_DESKTOP 0x1903
+#define USB_DEVICE_ID_MSI_CLAW_BIOS 0x1904
+
#define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
#define USB_DEVICE_ID_N_S_HARMONY 0xc359
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
new file mode 100644
index 0000000000000..89bb32f00bfc3
--- /dev/null
+++ b/drivers/hid/hid-msi.c
@@ -0,0 +1,582 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID driver for MSI Claw Handheld PC gamepads.
+ *
+ * Provides configuration support for the MSI Claw series of handheld PC
+ * gamepads. Multiple iterations of the device firmware has led to some
+ * quirks for how certain attributes are handled. The original firmware
+ * did not support remapping of the M1 (right) and M2 (left) rear paddles.
+ * Additionally, the MCU RAM address for writing configuration data has
+ * changed twice. Checks are done during probe to enumerate these variances.
+ *
+ * Copyright (c) 2026 Zhouwang Huang <honjow311@gmail.com>
+ * Copyright (c) 2026 Denis Benato <denis.benato@linux.dev>
+ * Copyright (c) 2026 Valve Corporation
+ */
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/kobject.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#include "hid-ids.h"
+
+#define CLAW_OUTPUT_REPORT_ID 0x0f
+#define CLAW_INPUT_REPORT_ID 0x10
+
+#define CLAW_PACKET_SIZE 64
+
+#define CLAW_DINPUT_CFG_INTF_IN 0x82
+#define CLAW_XINPUT_CFG_INTF_IN 0x83
+
+enum claw_command_index {
+ CLAW_COMMAND_TYPE_READ_PROFILE = 0x04,
+ CLAW_COMMAND_TYPE_READ_PROFILE_ACK = 0x05,
+ CLAW_COMMAND_TYPE_ACK = 0x06,
+ CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA = 0x21,
+ CLAW_COMMAND_TYPE_SYNC_TO_ROM = 0x22,
+ CLAW_COMMAND_TYPE_SWITCH_MODE = 0x24,
+ CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE = 0x26,
+ CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK = 0x27,
+ CLAW_COMMAND_TYPE_RESET_DEVICE = 0x28,
+};
+
+enum claw_gamepad_mode_index {
+ CLAW_GAMEPAD_MODE_XINPUT = 0x01,
+ CLAW_GAMEPAD_MODE_DINPUT = 0x02,
+ CLAW_GAMEPAD_MODE_DESKTOP = 0x04,
+};
+
+static const char * const claw_gamepad_mode_text[] = {
+ [CLAW_GAMEPAD_MODE_XINPUT] = "xinput",
+ [CLAW_GAMEPAD_MODE_DINPUT] = "dinput",
+ [CLAW_GAMEPAD_MODE_DESKTOP] = "desktop",
+};
+
+enum claw_mkeys_function_index {
+ CLAW_MKEY_FUNCTION_MACRO,
+ CLAW_MKEY_FUNCTION_COMBO,
+ CLAW_MKEY_FUNCTION_DISABLED,
+};
+
+static const char * const claw_mkeys_function_text[] = {
+ [CLAW_MKEY_FUNCTION_MACRO] = "macro",
+ [CLAW_MKEY_FUNCTION_COMBO] = "combination",
+ [CLAW_MKEY_FUNCTION_DISABLED] = "disabled",
+};
+
+struct claw_command_report {
+ u8 report_id;
+ u8 padding[2];
+ u8 header_tail;
+ u8 cmd;
+ u8 data[59];
+} __packed;
+
+struct claw_drvdata {
+ /* MCU General Variables */
+ struct completion send_cmd_complete;
+ struct delayed_work cfg_resume;
+ struct delayed_work cfg_setup;
+ struct hid_device *hdev;
+ struct mutex cfg_mutex; /* mutex for synchronous data */
+ u8 ep;
+
+ /* Gamepad Variables */
+ enum claw_mkeys_function_index mkeys_function;
+ enum claw_gamepad_mode_index gamepad_mode;
+};
+
+static int get_endpoint_address(struct hid_device *hdev)
+{
+ struct usb_host_endpoint *ep;
+ struct usb_interface *intf;
+
+ intf = to_usb_interface(hdev->dev.parent);
+ ep = intf->cur_altsetting->endpoint;
+ if (ep)
+ return ep->desc.bEndpointAddress;
+
+ return -ENODEV;
+}
+
+static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
+ struct claw_command_report *cmd_rep)
+{
+ if (cmd_rep->data[0] >= ARRAY_SIZE(claw_gamepad_mode_text) ||
+ !claw_gamepad_mode_text[cmd_rep->data[0]] ||
+ cmd_rep->data[1] >= ARRAY_SIZE(claw_mkeys_function_text))
+ return -EINVAL;
+
+ drvdata->gamepad_mode = cmd_rep->data[0];
+ drvdata->mkeys_function = cmd_rep->data[1];
+
+ return 0;
+}
+
+static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report,
+ u8 *data, int size)
+{
+ struct claw_command_report *cmd_rep;
+ int ret = 0;
+
+ if (size != CLAW_PACKET_SIZE)
+ return 0;
+
+ cmd_rep = (struct claw_command_report *)data;
+
+ if (cmd_rep->report_id != CLAW_INPUT_REPORT_ID || cmd_rep->header_tail != 0x3c)
+ return 0;
+
+ dev_dbg(&drvdata->hdev->dev, "Rx data as raw input report: [%*ph]\n",
+ CLAW_PACKET_SIZE, data);
+
+ switch (cmd_rep->cmd) {
+ case CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK:
+ ret = claw_gamepad_mode_event(drvdata, cmd_rep);
+ break;
+ case CLAW_COMMAND_TYPE_ACK:
+ break;
+ default:
+ dev_dbg(&drvdata->hdev->dev, "Unknown command: %x\n", cmd_rep->cmd);
+ return 0;
+ }
+
+ complete(&drvdata->send_cmd_complete);
+
+ return ret;
+}
+
+static int msi_raw_event(struct hid_device *hdev, struct hid_report *report,
+ u8 *data, int size)
+{
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ if (!drvdata || (drvdata->ep != CLAW_XINPUT_CFG_INTF_IN &&
+ drvdata->ep != CLAW_DINPUT_CFG_INTF_IN))
+ return 0;
+
+ return claw_raw_event(drvdata, report, data, size);
+}
+
+static int claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data,
+ size_t len, unsigned int timeout)
+{
+ unsigned char *dmabuf __free(kfree) = NULL;
+ u8 header[] = { CLAW_OUTPUT_REPORT_ID, 0, 0, 0x3c, index };
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ size_t header_size = ARRAY_SIZE(header);
+ int ret;
+
+ if (header_size + len > CLAW_PACKET_SIZE)
+ return -EINVAL;
+
+ /* We can't use a devm_alloc reusable buffer without side effects during suspend */
+ dmabuf = kzalloc(CLAW_PACKET_SIZE, GFP_KERNEL);
+ if (!dmabuf)
+ return -ENOMEM;
+
+ memcpy(dmabuf, header, header_size);
+ if (data && len)
+ memcpy(dmabuf + header_size, data, len);
+
+ guard(mutex)(&drvdata->cfg_mutex);
+ if (timeout)
+ reinit_completion(&drvdata->send_cmd_complete);
+
+ dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n",
+ CLAW_PACKET_SIZE, dmabuf);
+
+ ret = hid_hw_output_report(hdev, dmabuf, CLAW_PACKET_SIZE);
+ if (ret < 0)
+ return ret;
+
+ ret = ret == CLAW_PACKET_SIZE ? 0 : -EIO;
+ if (ret)
+ return ret;
+
+ if (timeout) {
+ ret = wait_for_completion_interruptible_timeout(&drvdata->send_cmd_complete,
+ msecs_to_jiffies(timeout));
+
+ dev_dbg(&hdev->dev, "Remaining timeout: %u\n", ret);
+ if (ret >= 0) /* preserve errors */
+ ret = ret == 0 ? -EBUSY : 0; /* timeout occurred : time remained */
+ }
+
+ return ret;
+}
+
+static ssize_t gamepad_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u8 data[2] = { 0x00, drvdata->mkeys_function };
+ int i, ret = -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) {
+ if (claw_gamepad_mode_text[i] && sysfs_streq(buf, claw_gamepad_mode_text[i])) {
+ ret = i;
+ break;
+ }
+ }
+ if (ret < 0)
+ return ret;
+
+ data[0] = ret;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data, ARRAY_SIZE(data), 0);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t gamepad_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ int ret, i;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 8);
+ if (ret)
+ return ret;
+
+ i = drvdata->gamepad_mode;
+
+ if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0')
+ return sysfs_emit(buf, "unsupported\n");
+
+ return sysfs_emit(buf, "%s\n", claw_gamepad_mode_text[i]);
+}
+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;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) {
+ if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0')
+ continue;
+ count += sysfs_emit_at(buf, count, "%s ", claw_gamepad_mode_text[i]);
+ }
+
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(gamepad_mode_index);
+
+static ssize_t mkeys_function_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u8 data[2] = { drvdata->gamepad_mode, 0x00 };
+ int i, ret = -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++) {
+ if (claw_mkeys_function_text[i] && sysfs_streq(buf, claw_mkeys_function_text[i])) {
+ ret = i;
+ break;
+ }
+ }
+ if (ret < 0)
+ return ret;
+
+ data[1] = ret;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data, ARRAY_SIZE(data), 0);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t mkeys_function_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ int ret, i;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 8);
+ if (ret)
+ return ret;
+
+ i = drvdata->mkeys_function;
+
+ if (i >= ARRAY_SIZE(claw_mkeys_function_text))
+ return sysfs_emit(buf, "unsupported\n");
+
+ return sysfs_emit(buf, "%s\n", claw_mkeys_function_text[i]);
+}
+static DEVICE_ATTR_RW(mkeys_function);
+
+static ssize_t mkeys_function_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i, count = 0;
+
+ for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++)
+ count += sysfs_emit_at(buf, count, "%s ", claw_mkeys_function_text[i]);
+
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(mkeys_function_index);
+
+static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ bool val;
+ int ret;
+
+ ret = kstrtobool(buf, &val);
+ if (ret)
+ return ret;
+
+ if (!val)
+ return -EINVAL;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_RESET_DEVICE, NULL, 0, 0);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_WO(reset);
+
+static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribute *attr,
+ int n)
+{
+ struct hid_device *hdev = to_hid_device(kobj_to_dev(kobj));
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ if (!drvdata) {
+ dev_warn(&hdev->dev,
+ "Failed to get drvdata from kobj. Gamepad attributes are not available.\n");
+ return 0;
+ }
+
+ return attr->mode;
+}
+
+static struct attribute *claw_gamepad_attrs[] = {
+ &dev_attr_gamepad_mode.attr,
+ &dev_attr_gamepad_mode_index.attr,
+ &dev_attr_mkeys_function.attr,
+ &dev_attr_mkeys_function_index.attr,
+ &dev_attr_reset.attr,
+ NULL,
+};
+
+static const struct attribute_group claw_gamepad_attr_group = {
+ .attrs = claw_gamepad_attrs,
+ .is_visible = claw_gamepad_attr_is_visible,
+};
+
+static void cfg_setup_fn(struct work_struct *work)
+{
+ struct delayed_work *dwork = container_of(work, struct delayed_work, work);
+ struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_setup);
+ int ret;
+
+ ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 8);
+ if (ret) {
+ dev_err(&drvdata->hdev->dev,
+ "Failed to setup device, can't read gamepad mode: %d\n", ret);
+ return;
+ }
+
+ /* Add sysfs attributes after we get the device state */
+ ret = device_add_group(&drvdata->hdev->dev, &claw_gamepad_attr_group);
+ if (ret) {
+ dev_err(&drvdata->hdev->dev,
+ "Failed to setup device, can't create gamepad attrs: %d\n", ret);
+ return;
+ }
+
+ kobject_uevent(&drvdata->hdev->dev.kobj, KOBJ_CHANGE);
+}
+
+static void cfg_resume_fn(struct work_struct *work)
+{
+ struct delayed_work *dwork = container_of(work, struct delayed_work, work);
+ struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_resume);
+ u8 data[2] = { drvdata->gamepad_mode, drvdata->mkeys_function };
+ int ret;
+
+ ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data,
+ ARRAY_SIZE(data), 0);
+ if (ret)
+ dev_err(&drvdata->hdev->dev, "Failed to set gamepad mode settings: %d\n", ret);
+}
+
+static int claw_probe(struct hid_device *hdev, u8 ep)
+{
+ struct claw_drvdata *drvdata;
+ int ret;
+
+ drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ drvdata->gamepad_mode = CLAW_GAMEPAD_MODE_XINPUT;
+ drvdata->hdev = hdev;
+ drvdata->ep = ep;
+
+ mutex_init(&drvdata->cfg_mutex);
+ init_completion(&drvdata->send_cmd_complete);
+ INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn);
+ INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn);
+
+ /* For control interface: open the HID transport for sending commands. */
+ ret = hid_hw_open(hdev);
+ if (ret)
+ return ret;
+
+ hid_set_drvdata(hdev, drvdata);
+ schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500));
+
+ return 0;
+}
+
+static int msi_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int ret;
+ u8 ep;
+
+ if (!hid_is_usb(hdev)) {
+ ret = -ENODEV;
+ goto err_probe;
+ }
+
+ ret = hid_parse(hdev);
+ if (ret)
+ goto err_probe;
+
+ /* Set quirk to create separate input devices per HID application */
+ hdev->quirks |= HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT;
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ goto err_probe;
+
+ /* For non-control interfaces (keyboard/mouse), allow userspace to grab the devices. */
+ ret = get_endpoint_address(hdev);
+ if (ret < 0)
+ goto err_stop_hw;
+
+ ep = ret;
+ if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) {
+ ret = claw_probe(hdev, ep);
+ if (ret)
+ goto err_stop_hw;
+ }
+
+ return 0;
+
+err_stop_hw:
+ hid_hw_stop(hdev);
+err_probe:
+ return dev_err_probe(&hdev->dev, ret, "Failed to init device\n");
+}
+
+static void claw_remove(struct hid_device *hdev)
+{
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ if (!drvdata) {
+ hid_hw_stop(hdev);
+ return;
+ }
+
+ device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
+ cancel_delayed_work_sync(&drvdata->cfg_setup);
+ cancel_delayed_work_sync(&drvdata->cfg_resume);
+ hid_hw_close(hdev);
+}
+
+static void msi_remove(struct hid_device *hdev)
+{
+ int ret;
+ u8 ep;
+
+ ret = get_endpoint_address(hdev);
+ if (ret <= 0)
+ goto hw_stop;
+
+ ep = ret;
+ if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN)
+ claw_remove(hdev);
+
+hw_stop:
+ hid_hw_stop(hdev);
+}
+
+static int claw_resume(struct hid_device *hdev)
+{
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ /* MCU can take up to 500ms to be ready after resume */
+ schedule_delayed_work(&drvdata->cfg_resume, msecs_to_jiffies(500));
+ return 0;
+}
+
+static int msi_resume(struct hid_device *hdev)
+{
+ int ret;
+ u8 ep;
+
+ ret = get_endpoint_address(hdev);
+ if (ret <= 0)
+ return 0;
+
+ ep = ret;
+ if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN)
+ return claw_resume(hdev);
+
+ return 0;
+}
+
+static const struct hid_device_id msi_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_XINPUT) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DINPUT) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DESKTOP) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_BIOS) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, msi_devices);
+
+static struct hid_driver msi_driver = {
+ .name = "hid-msi",
+ .id_table = msi_devices,
+ .raw_event = msi_raw_event,
+ .probe = msi_probe,
+ .remove = msi_remove,
+ .resume = msi_resume,
+};
+module_hid_driver(msi_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Denis Benato <denis.benato@linux.dev>");
+MODULE_AUTHOR("Zhouwang Huang <honjow311@gmail.com>");
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("HID driver for MSI Claw Handheld PC gamepads");
--
2.53.0
^ permalink raw reply related
* [PATCH v3 2/4] HID: hid-msi: Add M-key mapping attributes
From: Derek J. Clark @ 2026-05-15 3:36 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260515033622.2095277-1-derekjohn.clark@gmail.com>
Adds attributes that allow for remapping the M-keys with up to 5 values
when in macro mode. There are 2 mappable buttons on the rear of the
device, M1 on the right and M2 on the left. When mapped, the events will
fire from one of three event devices: gamepad buttons will fire from the
device handled by xpad, while keyboard and mouse events will fire from
respectively typed evdevs provided by the input core. Names of each
mapping have been kept as close to the event that will fire from the evdev
as possible, with context added to the ABS_ events on the direction of the
movement.
Initial reverse-engineering and implementation of this feature was done
by Zhouwang Huang. I refactored the overall format to conform to kernel
driver best practices and style guides. Claude was used as an initial
reviewer of this patch.
Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v3:
- Use scoped_guard where necessary.
v2:
- Add mutex for SYNC_TO_ROM commands to ensure every SYNC is completed
before more data is written to the MCU volatile memory.
- Add mutex for profile_pending to ensure every profile action
response is serialized to the generating command.
---
drivers/hid/hid-msi.c | 400 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 399 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
index 89bb32f00bfc3..6ccb9666daedf 100644
--- a/drivers/hid/hid-msi.c
+++ b/drivers/hid/hid-msi.c
@@ -40,6 +40,8 @@
#define CLAW_DINPUT_CFG_INTF_IN 0x82
#define CLAW_XINPUT_CFG_INTF_IN 0x83
+#define CLAW_KEYS_MAX 5
+
enum claw_command_index {
CLAW_COMMAND_TYPE_READ_PROFILE = 0x04,
CLAW_COMMAND_TYPE_READ_PROFILE_ACK = 0x05,
@@ -64,6 +66,17 @@ static const char * const claw_gamepad_mode_text[] = {
[CLAW_GAMEPAD_MODE_DESKTOP] = "desktop",
};
+enum claw_profile_ack_pending {
+ CLAW_NO_PENDING,
+ CLAW_M1_PENDING,
+ CLAW_M2_PENDING,
+};
+
+enum claw_key_index {
+ CLAW_KEY_M1,
+ CLAW_KEY_M2,
+};
+
enum claw_mkeys_function_index {
CLAW_MKEY_FUNCTION_MACRO,
CLAW_MKEY_FUNCTION_COMBO,
@@ -76,6 +89,154 @@ static const char * const claw_mkeys_function_text[] = {
[CLAW_MKEY_FUNCTION_DISABLED] = "disabled",
};
+static const struct {
+ u8 code;
+ const char *name;
+} claw_button_mapping_key_map[] = {
+ /* Gamepad buttons */
+ { 0x01, "ABS_HAT0Y_UP" },
+ { 0x02, "ABS_HAT0Y_DOWN" },
+ { 0x03, "ABS_HAT0X_LEFT" },
+ { 0x04, "ABS_HAT0X_RIGHT" },
+ { 0x05, "BTN_TL" },
+ { 0x06, "BTN_TR" },
+ { 0x07, "BTN_THUMBL" },
+ { 0x08, "BTN_THUMBR" },
+ { 0x09, "BTN_SOUTH" },
+ { 0x0a, "BTN_EAST" },
+ { 0x0b, "BTN_NORTH" },
+ { 0x0c, "BTN_WEST" },
+ { 0x0d, "BTN_MODE" },
+ { 0x0e, "BTN_SELECT" },
+ { 0x0f, "BTN_START" },
+ { 0x13, "BTN_TL2"},
+ { 0x14, "BTN_TR2"},
+ { 0x15, "ABS_Y_UP"},
+ { 0x16, "ABS_Y_DOWN"},
+ { 0x17, "ABS_X_LEFT"},
+ { 0x18, "ABS_X_LEFT_RIGHT"},
+ { 0x19, "ABS_RY_UP"},
+ { 0x1a, "ABS_RY_DOWN"},
+ { 0x1b, "ABS_RX_LEFT"},
+ { 0x1c, "ABS_RX_RIGHT"},
+ /* Keyboard keys */
+ { 0x32, "KEY_ESC" },
+ { 0x33, "KEY_F1" },
+ { 0x34, "KEY_F2" },
+ { 0x35, "KEY_F3" },
+ { 0x36, "KEY_F4" },
+ { 0x37, "KEY_F5" },
+ { 0x38, "KEY_F6" },
+ { 0x39, "KEY_F7" },
+ { 0x3a, "KEY_F8" },
+ { 0x3b, "KEY_F9" },
+ { 0x3c, "KEY_F10" },
+ { 0x3d, "KEY_F11" },
+ { 0x3e, "KEY_F12" },
+ { 0x3f, "KEY_GRAVE" },
+ { 0x40, "KEY_1" },
+ { 0x41, "KEY_2" },
+ { 0x42, "KEY_3" },
+ { 0x43, "KEY_4" },
+ { 0x44, "KEY_5" },
+ { 0x45, "KEY_6" },
+ { 0x46, "KEY_7" },
+ { 0x47, "KEY_8" },
+ { 0x48, "KEY_9" },
+ { 0x49, "KEY_0" },
+ { 0x4a, "KEY_MINUS" },
+ { 0x4b, "KEY_EQUAL" },
+ { 0x4c, "KEY_BACKSPACE" },
+ { 0x4d, "KEY_TAB" },
+ { 0x4e, "KEY_Q" },
+ { 0x4f, "KEY_W" },
+ { 0x50, "KEY_E" },
+ { 0x51, "KEY_R" },
+ { 0x52, "KEY_T" },
+ { 0x53, "KEY_Y" },
+ { 0x54, "KEY_U" },
+ { 0x55, "KEY_I" },
+ { 0x56, "KEY_O" },
+ { 0x57, "KEY_P" },
+ { 0x58, "KEY_LEFTBRACE" },
+ { 0x59, "KEY_RIGHTBRACE" },
+ { 0x5a, "KEY_BACKSLASH" },
+ { 0x5b, "KEY_CAPSLOCK" },
+ { 0x5c, "KEY_A" },
+ { 0x5d, "KEY_S" },
+ { 0x5e, "KEY_D" },
+ { 0x5f, "KEY_F" },
+ { 0x60, "KEY_G" },
+ { 0x61, "KEY_H" },
+ { 0x62, "KEY_J" },
+ { 0x63, "KEY_K" },
+ { 0x64, "KEY_L" },
+ { 0x65, "KEY_SEMICOLON" },
+ { 0x66, "KEY_APOSTROPHE" },
+ { 0x67, "KEY_ENTER" },
+ { 0x68, "KEY_LEFTSHIFT" },
+ { 0x69, "KEY_Z" },
+ { 0x6a, "KEY_X" },
+ { 0x6b, "KEY_C" },
+ { 0x6c, "KEY_V" },
+ { 0x6d, "KEY_B" },
+ { 0x6e, "KEY_N" },
+ { 0x6f, "KEY_M" },
+ { 0x70, "KEY_COMMA" },
+ { 0x71, "KEY_DOT" },
+ { 0x72, "KEY_SLASH" },
+ { 0x73, "KEY_RIGHTSHIFT" },
+ { 0x74, "KEY_LEFTCTRL" },
+ { 0x75, "KEY_LEFTMETA" },
+ { 0x76, "KEY_LEFTALT" },
+ { 0x77, "KEY_SPACE" },
+ { 0x78, "KEY_RIGHTALT" },
+ { 0x79, "KEY_RIGHTCTRL" },
+ { 0x7a, "KEY_INSERT" },
+ { 0x7b, "KEY_HOME" },
+ { 0x7c, "KEY_PAGEUP" },
+ { 0x7d, "KEY_DELETE" },
+ { 0x7e, "KEY_END" },
+ { 0x7f, "KEY_PAGEDOWN" },
+ { 0x8a, "KEY_KPENTER" },
+ { 0x8b, "KEY_KP0" },
+ { 0x8c, "KEY_KP1" },
+ { 0x8d, "KEY_KP2" },
+ { 0x8e, "KEY_KP3" },
+ { 0x8f, "KEY_KP4" },
+ { 0x90, "KEY_KP5" },
+ { 0x91, "KEY_KP6" },
+ { 0x92, "KEY_KP7" },
+ { 0x93, "KEY_KP8" },
+ { 0x94, "KEY_KP9" },
+ { 0x95, "MD_PLAY" },
+ { 0x96, "MD_STOP" },
+ { 0x97, "MD_NEXT" },
+ { 0x98, "MD_PREV" },
+ { 0x99, "MD_VOL_UP" },
+ { 0x9a, "MD_VOL_DOWN" },
+ { 0x9b, "MD_VOL_MUTE" },
+ { 0x9c, "KEY_F23" },
+ /* Mouse events */
+ { 0xc8, "BTN_LEFT" },
+ { 0xc9, "BTN_MIDDLE" },
+ { 0xca, "BTN_RIGHT" },
+ { 0xcb, "BTN_SIDE" },
+ { 0xcc, "BTN_EXTRA" },
+ { 0xcd, "REL_WHEEL_UP" },
+ { 0xce, "REL_WHEEL_DOWN" },
+};
+
+static const u16 button_mapping_addr_old[] = {
+ 0x007a, /* M1 */
+ 0x011f, /* M2 */
+};
+
+static const u16 button_mapping_addr_new[] = {
+ 0x00bb, /* M1 */
+ 0x0164, /* M2 */
+};
+
struct claw_command_report {
u8 report_id;
u8 padding[2];
@@ -86,16 +247,24 @@ struct claw_command_report {
struct claw_drvdata {
/* MCU General Variables */
+ enum claw_profile_ack_pending profile_pending;
struct completion send_cmd_complete;
struct delayed_work cfg_resume;
struct delayed_work cfg_setup;
+ struct mutex profile_mutex; /* mutex for profile_pending calls */
struct hid_device *hdev;
struct mutex cfg_mutex; /* mutex for synchronous data */
+ struct mutex rom_mutex; /* mutex for SYNC_TO_ROM calls */
+ u16 bcd_device;
u8 ep;
/* Gamepad Variables */
enum claw_mkeys_function_index mkeys_function;
enum claw_gamepad_mode_index gamepad_mode;
+ u8 m1_codes[CLAW_KEYS_MAX];
+ u8 m2_codes[CLAW_KEYS_MAX];
+ const u16 *bmap_addr;
+ bool bmap_support;
};
static int get_endpoint_address(struct hid_device *hdev)
@@ -125,6 +294,31 @@ static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
return 0;
}
+static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep)
+{
+ u8 *codes;
+ int i;
+
+ switch (drvdata->profile_pending) {
+ case CLAW_M1_PENDING:
+ case CLAW_M2_PENDING:
+ codes = (drvdata->profile_pending == CLAW_M1_PENDING) ?
+ drvdata->m1_codes : drvdata->m2_codes;
+ /* Extract key codes; replace disabled (0xff) with 0x00, which is (null) in _show */
+ for (i = 0; i < CLAW_KEYS_MAX; i++)
+ codes[i] = (cmd_rep->data[6 + i] != 0xff) ? cmd_rep->data[6 + i] : 0x00;
+ break;
+ default:
+ dev_warn(&drvdata->hdev->dev,
+ "Got profile event without changes pending from command: %x\n",
+ cmd_rep->cmd);
+ return -EINVAL;
+ }
+ drvdata->profile_pending = CLAW_NO_PENDING;
+
+ return 0;
+}
+
static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report,
u8 *data, int size)
{
@@ -146,6 +340,9 @@ static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *repor
case CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK:
ret = claw_gamepad_mode_event(drvdata, cmd_rep);
break;
+ case CLAW_COMMAND_TYPE_READ_PROFILE_ACK:
+ ret = claw_profile_event(drvdata, cmd_rep);
+ break;
case CLAW_COMMAND_TYPE_ACK:
break;
default:
@@ -364,6 +561,163 @@ static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_WO(reset);
+static int button_mapping_name_to_code(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) {
+ if (!strcmp(name, claw_button_mapping_key_map[i].name))
+ return claw_button_mapping_key_map[i].code;
+ }
+
+ return -EINVAL;
+}
+
+static const char *button_mapping_code_to_name(u8 code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) {
+ if (claw_button_mapping_key_map[i].code == code)
+ return claw_button_mapping_key_map[i].name;
+ }
+
+ return NULL;
+}
+
+static int claw_buttons_store(struct device *dev, const char *buf, u8 mkey_idx)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u8 data[] = { 0x01, (drvdata->bmap_addr[mkey_idx] >> 8) & 0xff,
+ drvdata->bmap_addr[mkey_idx] & 0xff, 0x07,
+ 0x04, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff };
+ size_t len = ARRAY_SIZE(data);
+ int ret, key_count, i;
+ char **raw_keys;
+
+ raw_keys = argv_split(GFP_KERNEL, buf, &key_count);
+ if (!raw_keys)
+ return -ENOMEM;
+
+ if (key_count > CLAW_KEYS_MAX) {
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ if (key_count == 0)
+ goto set_buttons;
+
+ for (i = 0; i < key_count; i++) {
+ ret = button_mapping_name_to_code(raw_keys[i]);
+ if (ret < 0)
+ goto err_free;
+
+ data[6 + i] = ret;
+ }
+
+set_buttons:
+ scoped_guard(mutex, &drvdata->rom_mutex) {
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
+ data, len, 8);
+ if (ret < 0)
+ goto err_free;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 8);
+ }
+
+err_free:
+ argv_free(raw_keys);
+ return ret;
+}
+
+static int claw_buttons_show(struct device *dev, char *buf, enum claw_key_index m_key)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u8 data[] = { 0x01, (drvdata->bmap_addr[m_key] >> 8) & 0xff,
+ drvdata->bmap_addr[m_key] & 0xff, 0x07 };
+ size_t len = ARRAY_SIZE(data);
+ int i, ret, count = 0;
+ const char *name;
+ u8 *codes;
+
+ codes = (m_key == CLAW_KEY_M1) ? drvdata->m1_codes : drvdata->m2_codes;
+
+ guard(mutex)(&drvdata->profile_mutex);
+ drvdata->profile_pending = (m_key == CLAW_KEY_M1) ? CLAW_M1_PENDING : CLAW_M2_PENDING;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data, len, 8);
+ if (ret) {
+ drvdata->profile_pending = CLAW_NO_PENDING;
+ return ret;
+ }
+ for (i = 0; i < CLAW_KEYS_MAX; i++) {
+ name = button_mapping_code_to_name(codes[i]);
+ if (name)
+ count += sysfs_emit_at(buf, count, "%s ", name);
+ }
+
+ if (!count)
+ return sysfs_emit(buf, "(not set)\n");
+
+ buf[count - 1] = '\n';
+
+ return count;
+}
+
+static ssize_t button_m1_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+
+ ret = claw_buttons_store(dev, buf, CLAW_KEY_M1);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t button_m1_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return claw_buttons_show(dev, buf, CLAW_KEY_M1);
+}
+static DEVICE_ATTR_RW(button_m1);
+
+static ssize_t button_m2_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+
+ ret = claw_buttons_store(dev, buf, CLAW_KEY_M2);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t button_m2_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return claw_buttons_show(dev, buf, CLAW_KEY_M2);
+}
+static DEVICE_ATTR_RW(button_m2);
+
+static ssize_t button_mapping_options_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i, count = 0;
+
+ for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++)
+ count += sysfs_emit_at(buf, count, "%s ", claw_button_mapping_key_map[i].name);
+
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(button_mapping_options);
+
static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribute *attr,
int n)
{
@@ -376,10 +730,22 @@ static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribu
return 0;
}
- return attr->mode;
+ /* Always show attrs available on all firmware */
+ if (attr == &dev_attr_gamepad_mode.attr ||
+ attr == &dev_attr_gamepad_mode_index.attr ||
+ attr == &dev_attr_mkeys_function.attr ||
+ attr == &dev_attr_mkeys_function_index.attr ||
+ attr == &dev_attr_reset.attr)
+ return attr->mode;
+
+ /* Hide button mapping attrs if it isn't supported */
+ return drvdata->bmap_support ? attr->mode : 0;
}
static struct attribute *claw_gamepad_attrs[] = {
+ &dev_attr_button_m1.attr,
+ &dev_attr_button_m2.attr,
+ &dev_attr_button_mapping_options.attr,
&dev_attr_gamepad_mode.attr,
&dev_attr_gamepad_mode_index.attr,
&dev_attr_mkeys_function.attr,
@@ -430,8 +796,31 @@ static void cfg_resume_fn(struct work_struct *work)
dev_err(&drvdata->hdev->dev, "Failed to set gamepad mode settings: %d\n", ret);
}
+static void claw_features_supported(struct claw_drvdata *drvdata)
+{
+ u8 major = (drvdata->bcd_device >> 8) & 0xff;
+ u8 minor = drvdata->bcd_device & 0xff;
+
+ if (major == 0x01) {
+ drvdata->bmap_support = true;
+ if (minor >= 0x66)
+ drvdata->bmap_addr = button_mapping_addr_new;
+ else
+ drvdata->bmap_addr = button_mapping_addr_old;
+ return;
+ }
+
+ if ((major == 0x02 && minor >= 0x17) || major >= 0x03) {
+ drvdata->bmap_support = true;
+ drvdata->bmap_addr = button_mapping_addr_new;
+ return;
+ }
+}
+
static int claw_probe(struct hid_device *hdev, u8 ep)
{
+ struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+ struct usb_device *udev = interface_to_usbdev(intf);
struct claw_drvdata *drvdata;
int ret;
@@ -443,7 +832,16 @@ static int claw_probe(struct hid_device *hdev, u8 ep)
drvdata->hdev = hdev;
drvdata->ep = ep;
+ /* Determine feature level from firmware version */
+ drvdata->bcd_device = le16_to_cpu(udev->descriptor.bcdDevice);
+ claw_features_supported(drvdata);
+
+ if (!drvdata->bmap_support)
+ dev_dbg(&hdev->dev, "M-Key mapping is not supported. Update firmware to enable.\n");
+
mutex_init(&drvdata->cfg_mutex);
+ mutex_init(&drvdata->profile_mutex);
+ mutex_init(&drvdata->rom_mutex);
init_completion(&drvdata->send_cmd_complete);
INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn);
INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn);
--
2.53.0
^ permalink raw reply related
* [PATCH v3 3/4] HID: hid-msi: Add RGB control interface
From: Derek J. Clark @ 2026-05-15 3:36 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260515033622.2095277-1-derekjohn.clark@gmail.com>
Adds RGB control interface for MSI Claw devices. The MSI Claw uses a
fairly unique RGB interface. It has 9 total zones (4 per joystick ring
and 1 for the ABXY buttons), and supports up to 8 sequential frames of
RGB zone data. Each frame is written to a specific area of MCU memory by
the profile command, the value of which changes based on the firmware of
the device. Unlike other devices (such as the Legion Go or the OneXPlayer
devices), there are no hard coded effects built into the MCU. Instead,
the basic effects are provided as a series of frame data. I have
mirrored the effects available in Windows in this driver, while keeping
the effect names consistent with the Lenovo drivers for the effects that
are similar.
Initial reverse-engineering and implementation of this feature was done
by Zhouwang Huang. I refactored the overall format to conform to kernel
driver best practices and style guides. Claude was used as an initial
reviewer of this patch.
Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v3:
- Add mutex for read/write of rgb frame data.
- Remove setting rgb_frame_count when reading rgb profiles as it always
returns garbage data.
- Ensure rgb_speed is getting drvdata from a valid lookup (not hdev).
v2:
- Use pending_profile mutex
---
drivers/hid/hid-msi.c | 548 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 542 insertions(+), 6 deletions(-)
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
index 6ccb9666daedf..06dc4290d2bb5 100644
--- a/drivers/hid/hid-msi.c
+++ b/drivers/hid/hid-msi.c
@@ -21,6 +21,7 @@
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/kobject.h>
+#include <linux/led-class-multicolor.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -42,6 +43,10 @@
#define CLAW_KEYS_MAX 5
+#define CLAW_RGB_ZONES 9
+#define CLAW_RGB_MAX_FRAMES 8
+#define CLAW_RGB_FRAME_OFFSET 0x24
+
enum claw_command_index {
CLAW_COMMAND_TYPE_READ_PROFILE = 0x04,
CLAW_COMMAND_TYPE_READ_PROFILE_ACK = 0x05,
@@ -70,6 +75,7 @@ enum claw_profile_ack_pending {
CLAW_NO_PENDING,
CLAW_M1_PENDING,
CLAW_M2_PENDING,
+ CLAW_RGB_PENDING,
};
enum claw_key_index {
@@ -227,6 +233,22 @@ static const struct {
{ 0xce, "REL_WHEEL_DOWN" },
};
+enum claw_rgb_effect_index {
+ CLAW_RGB_EFFECT_MONOCOLOR,
+ CLAW_RGB_EFFECT_BREATHE,
+ CLAW_RGB_EFFECT_CHROMA,
+ CLAW_RGB_EFFECT_RAINBOW,
+ CLAW_RGB_EFFECT_FROSTFIRE,
+};
+
+static const char * const claw_rgb_effect_text[] = {
+ [CLAW_RGB_EFFECT_MONOCOLOR] = "monocolor",
+ [CLAW_RGB_EFFECT_BREATHE] = "breathe",
+ [CLAW_RGB_EFFECT_CHROMA] = "chroma",
+ [CLAW_RGB_EFFECT_RAINBOW] = "rainbow",
+ [CLAW_RGB_EFFECT_FROSTFIRE] = "frostfire",
+};
+
static const u16 button_mapping_addr_old[] = {
0x007a, /* M1 */
0x011f, /* M2 */
@@ -237,6 +259,9 @@ static const u16 button_mapping_addr_new[] = {
0x0164, /* M2 */
};
+static const u16 rgb_addr_old = 0x01fa;
+static const u16 rgb_addr_new = 0x024a;
+
struct claw_command_report {
u8 report_id;
u8 padding[2];
@@ -245,6 +270,28 @@ struct claw_command_report {
u8 data[59];
} __packed;
+struct rgb_zone {
+ u8 red;
+ u8 green;
+ u8 blue;
+};
+
+struct rgb_frame {
+ struct rgb_zone zone[CLAW_RGB_ZONES];
+};
+
+struct rgb_report {
+ u8 profile;
+ __be16 read_addr;
+ u8 frame_bytes;
+ u8 padding;
+ u8 frame_count;
+ u8 state; /* Always 0x09 */
+ u8 speed;
+ u8 brightness;
+ struct rgb_frame zone_data;
+} __packed;
+
struct claw_drvdata {
/* MCU General Variables */
enum claw_profile_ack_pending profile_pending;
@@ -252,6 +299,7 @@ struct claw_drvdata {
struct delayed_work cfg_resume;
struct delayed_work cfg_setup;
struct mutex profile_mutex; /* mutex for profile_pending calls */
+ struct mutex frame_mutex; /* mutex for read/write rgb_frames */
struct hid_device *hdev;
struct mutex cfg_mutex; /* mutex for synchronous data */
struct mutex rom_mutex; /* mutex for SYNC_TO_ROM calls */
@@ -265,6 +313,16 @@ struct claw_drvdata {
u8 m2_codes[CLAW_KEYS_MAX];
const u16 *bmap_addr;
bool bmap_support;
+
+ /* RGB Variables */
+ struct rgb_frame rgb_frames[CLAW_RGB_MAX_FRAMES];
+ enum claw_rgb_effect_index rgb_effect;
+ struct led_classdev_mc led_mc;
+ struct delayed_work rgb_queue;
+ u8 rgb_frame_count;
+ bool rgb_enabled;
+ u8 rgb_speed;
+ u16 rgb_addr;
};
static int get_endpoint_address(struct hid_device *hdev)
@@ -296,8 +354,11 @@ static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep)
{
- u8 *codes;
- int i;
+ struct rgb_report *frame;
+ u16 rgb_addr, read_addr;
+ u8 *codes, f_idx;
+ u16 frame_calc;
+ int i, ret = 0;
switch (drvdata->profile_pending) {
case CLAW_M1_PENDING:
@@ -308,15 +369,46 @@ static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_
for (i = 0; i < CLAW_KEYS_MAX; i++)
codes[i] = (cmd_rep->data[6 + i] != 0xff) ? cmd_rep->data[6 + i] : 0x00;
break;
+ case CLAW_RGB_PENDING:
+ frame = (struct rgb_report *)cmd_rep->data;
+ rgb_addr = drvdata->rgb_addr;
+ read_addr = be16_to_cpu(frame->read_addr);
+ frame_calc = (read_addr - rgb_addr) / CLAW_RGB_FRAME_OFFSET;
+ if (frame_calc > CLAW_RGB_MAX_FRAMES) {
+ dev_err(drvdata->led_mc.led_cdev.dev, "Got unsupported frame index: %x\n",
+ frame_calc);
+ ret = -EINVAL;
+ goto err_pending;
+ }
+ f_idx = frame_calc;
+
+ scoped_guard(mutex, &drvdata->frame_mutex) {
+ memcpy(&drvdata->rgb_frames[f_idx], &frame->zone_data,
+ sizeof(struct rgb_frame));
+
+ /* Only use frame 0 for remaining variable assignment */
+ if (f_idx != 0)
+ break;
+
+ drvdata->rgb_speed = frame->speed;
+ drvdata->led_mc.led_cdev.brightness = frame->brightness;
+ drvdata->led_mc.subled_info[0].intensity = frame->zone_data.zone[0].red;
+ drvdata->led_mc.subled_info[1].intensity = frame->zone_data.zone[0].green;
+ drvdata->led_mc.subled_info[2].intensity = frame->zone_data.zone[0].blue;
+ }
+
+ break;
default:
dev_warn(&drvdata->hdev->dev,
"Got profile event without changes pending from command: %x\n",
cmd_rep->cmd);
- return -EINVAL;
+ ret = -EINVAL;
}
+
+err_pending:
drvdata->profile_pending = CLAW_NO_PENDING;
- return 0;
+ return ret;
}
static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report,
@@ -759,6 +851,404 @@ static const struct attribute_group claw_gamepad_attr_group = {
.is_visible = claw_gamepad_attr_is_visible,
};
+/* Read RGB config from device */
+static int claw_read_rgb_config(struct hid_device *hdev)
+{
+ u8 data[4] = { 0x01, 0x00, 0x00, CLAW_RGB_FRAME_OFFSET };
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u16 read_addr = drvdata->rgb_addr;
+ size_t len = ARRAY_SIZE(data);
+ int ret, i;
+
+ if (!drvdata->rgb_addr)
+ return -ENODEV;
+
+ /* Loop through all 8 pages of RGB data */
+ guard(mutex)(&drvdata->profile_mutex);
+ for (i = 0; i < 8; i++) {
+ drvdata->profile_pending = CLAW_RGB_PENDING;
+ data[1] = (read_addr >> 8) & 0xff;
+ data[2] = read_addr & 0x00ff;
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data, len, 8);
+ if (ret) {
+ drvdata->profile_pending = CLAW_NO_PENDING;
+ return ret;
+ }
+ read_addr += CLAW_RGB_FRAME_OFFSET;
+ }
+
+ return 0;
+}
+
+/* Send RGB configuration to device */
+static int claw_write_rgb_state(struct claw_drvdata *drvdata)
+{
+ struct rgb_report report = { 0x01, 0x0000, CLAW_RGB_FRAME_OFFSET, 0x00,
+ drvdata->rgb_frame_count, 0x09, drvdata->rgb_speed,
+ drvdata->led_mc.led_cdev.brightness };
+ u16 write_addr = drvdata->rgb_addr;
+ size_t len = sizeof(report);
+ int f, ret;
+
+ if (!drvdata->rgb_addr)
+ return -ENODEV;
+
+ if (!drvdata->rgb_frame_count)
+ return -EINVAL;
+
+ guard(mutex)(&drvdata->rom_mutex);
+ /* Loop through (up to) 8 pages of RGB data */
+ for (f = 0; f < drvdata->rgb_frame_count; f++) {
+ report.zone_data = drvdata->rgb_frames[f];
+
+ /* Set the MCU address to write the frame data to */
+ report.read_addr = cpu_to_be16(write_addr);
+
+ /* Serialize the rgb_report and write it to MCU */
+ ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
+ (u8 *)&report, len, 8);
+ if (ret)
+ return ret;
+
+ /* Increment the write addr by the offset for the next frame */
+ write_addr += CLAW_RGB_FRAME_OFFSET;
+ }
+
+ ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 8);
+
+ return ret;
+}
+
+/* Fill all zones with the same color */
+static void claw_frame_fill_solid(struct rgb_frame *frame, struct rgb_zone zone)
+{
+ int z;
+
+ for (z = 0; z < CLAW_RGB_ZONES; z++)
+ frame->zone[z] = zone;
+}
+
+/* Apply solid effect (1 frame, all zones same color) */
+static int claw_apply_monocolor(struct claw_drvdata *drvdata)
+{
+ struct mc_subled *subleds = drvdata->led_mc.subled_info;
+ struct rgb_zone zone = { subleds[0].intensity, subleds[1].intensity,
+ subleds[2].intensity };
+
+ guard(mutex)(&drvdata->frame_mutex);
+ drvdata->rgb_frame_count = 1;
+ claw_frame_fill_solid(&drvdata->rgb_frames[0], zone);
+
+ return claw_write_rgb_state(drvdata);
+}
+
+/* Apply breathe effect (2 frames: color -> off) */
+static int claw_apply_breathe(struct claw_drvdata *drvdata)
+{
+ struct mc_subled *subleds = drvdata->led_mc.subled_info;
+ struct rgb_zone zone = { subleds[0].intensity, subleds[1].intensity,
+ subleds[2].intensity };
+ static const struct rgb_zone off = { 0, 0, 0 };
+
+ guard(mutex)(&drvdata->frame_mutex);
+ drvdata->rgb_frame_count = 2;
+ claw_frame_fill_solid(&drvdata->rgb_frames[0], zone);
+ claw_frame_fill_solid(&drvdata->rgb_frames[1], off);
+
+ return claw_write_rgb_state(drvdata);
+}
+
+/* Apply chroma effect (6 frames: rainbow cycle, all zones sync) */
+static int claw_apply_chroma(struct claw_drvdata *drvdata)
+{
+ static const struct rgb_zone colors[] = {
+ {255, 0, 0}, /* red */
+ {255, 255, 0}, /* yellow */
+ { 0, 255, 0}, /* green */
+ { 0, 255, 255}, /* cyan */
+ { 0, 0, 255}, /* blue */
+ {255, 0, 255}, /* magenta */
+ };
+ u8 frame_count = ARRAY_SIZE(colors);
+ int frame;
+
+ guard(mutex)(&drvdata->frame_mutex);
+ drvdata->rgb_frame_count = frame_count;
+
+ for (frame = 0; frame < frame_count; frame++)
+ claw_frame_fill_solid(&drvdata->rgb_frames[frame], colors[frame]);
+
+ return claw_write_rgb_state(drvdata);
+}
+
+/* Apply rainbow effect (4 frames: rotating colors around joysticks) */
+static int claw_apply_rainbow(struct claw_drvdata *drvdata)
+{
+ static const struct rgb_zone colors[] = {
+ {255, 0, 0}, /* red */
+ { 0, 255, 0}, /* green */
+ { 0, 255, 255}, /* cyan */
+ { 0, 0, 255}, /* blue */
+ };
+ u8 frame_count = ARRAY_SIZE(colors);
+ int frame, zone;
+
+ guard(mutex)(&drvdata->frame_mutex);
+ drvdata->rgb_frame_count = frame_count;
+
+ for (frame = 0; frame < frame_count; frame++) {
+ for (zone = 0; zone < 4; zone++) {
+ drvdata->rgb_frames[frame].zone[zone] = colors[(zone + frame) % 4];
+ drvdata->rgb_frames[frame].zone[zone + 4] = colors[(zone + frame) % 4];
+ }
+ drvdata->rgb_frames[frame].zone[8] = colors[frame];
+ }
+
+ return claw_write_rgb_state(drvdata);
+}
+
+/*
+ * Apply frostfire effect (4 frames: fire vs ice rotating)
+ * Right joystick: fire red -> dark -> ice blue -> dark (clockwise)
+ * Left joystick: ice blue -> dark -> fire red -> dark (counter-clockwise)
+ * ABXY: fire red -> dark -> ice blue -> dark
+ */
+static int claw_apply_frostfire(struct claw_drvdata *drvdata)
+{
+ static const struct rgb_zone colors[] = {
+ {255, 0, 0}, /* fire red */
+ { 0, 0, 0}, /* dark */
+ { 0, 0, 255}, /* ice blue */
+ { 0, 0, 0}, /* dark */
+ };
+ u8 frame_count = ARRAY_SIZE(colors);
+ int frame, zone;
+
+ guard(mutex)(&drvdata->frame_mutex);
+ drvdata->rgb_frame_count = frame_count;
+
+ for (frame = 0; frame < frame_count; frame++) {
+ for (zone = 0; zone < 4; zone++) {
+ drvdata->rgb_frames[frame].zone[zone] = colors[(zone + frame) % 4];
+ drvdata->rgb_frames[frame].zone[zone + 4] = colors[(zone - frame + 6) % 4];
+ }
+ drvdata->rgb_frames[frame].zone[8] = colors[frame];
+ }
+
+ return claw_write_rgb_state(drvdata);
+}
+
+/* Apply current state to device */
+static int claw_apply_rgb_state(struct claw_drvdata *drvdata)
+{
+ static const struct rgb_zone off = { 0, 0, 0 };
+
+ if (!drvdata->rgb_enabled) {
+ guard(mutex)(&drvdata->frame_mutex);
+ drvdata->rgb_frame_count = 1;
+ claw_frame_fill_solid(&drvdata->rgb_frames[0], off);
+ return claw_write_rgb_state(drvdata);
+ }
+
+ switch (drvdata->rgb_effect) {
+ case CLAW_RGB_EFFECT_MONOCOLOR:
+ return claw_apply_monocolor(drvdata);
+ case CLAW_RGB_EFFECT_BREATHE:
+ return claw_apply_breathe(drvdata);
+ case CLAW_RGB_EFFECT_CHROMA:
+ return claw_apply_chroma(drvdata);
+ case CLAW_RGB_EFFECT_RAINBOW:
+ return claw_apply_rainbow(drvdata);
+ case CLAW_RGB_EFFECT_FROSTFIRE:
+ return claw_apply_frostfire(drvdata);
+ default:
+ dev_err(drvdata->led_mc.led_cdev.dev,
+ "No supported rgb_effect selected\n");
+ return -EINVAL;
+ }
+}
+
+static void claw_rgb_queue_fn(struct work_struct *work)
+{
+ struct delayed_work *dwork = container_of(work, struct delayed_work, work);
+ struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, rgb_queue);
+ int ret;
+
+ ret = claw_apply_rgb_state(drvdata);
+ if (ret)
+ dev_err(drvdata->led_mc.led_cdev.dev,
+ "Failed to apply RGB state: %d\n", ret);
+}
+
+static ssize_t effect_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+ int ret;
+
+ ret = sysfs_match_string(claw_rgb_effect_text, buf);
+ if (ret < 0)
+ return ret;
+
+ drvdata->rgb_effect = ret;
+ mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
+
+ return count;
+}
+
+static ssize_t effect_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+
+ if (drvdata->rgb_effect >= ARRAY_SIZE(claw_rgb_effect_text))
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", claw_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)
+{
+ int i, count = 0;
+
+ for (i = 0; i < ARRAY_SIZE(claw_rgb_effect_text); i++)
+ count += sysfs_emit_at(buf, count, "%s ", claw_rgb_effect_text[i]);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(effect_index);
+
+static ssize_t enabled_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+ bool val;
+ int ret;
+
+ ret = kstrtobool(buf, &val);
+ if (ret)
+ return ret;
+
+ drvdata->rgb_enabled = val;
+ mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
+
+ return count;
+}
+
+static ssize_t enabled_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+
+ return sysfs_emit(buf, "%s\n", drvdata->rgb_enabled ? "true" : "false");
+}
+static DEVICE_ATTR_RW(enabled);
+
+static ssize_t enabled_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "true false\n");
+}
+static DEVICE_ATTR_RO(enabled_index);
+
+static ssize_t speed_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+ unsigned int val, speed;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val > 20)
+ return -EINVAL;
+
+ /* 0 is fastest, invert value for intuitive userspace speed */
+ speed = 20 - val;
+
+ drvdata->rgb_speed = speed;
+ mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
+
+ return count;
+}
+
+static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+ u8 speed = 20 - drvdata->rgb_speed;
+
+ return sysfs_emit(buf, "%u\n", 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-20\n");
+}
+static DEVICE_ATTR_RO(speed_range);
+
+static void claw_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness _brightness)
+{
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+
+ mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
+}
+
+static struct attribute *claw_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 claw_rgb_attr_group = {
+ .attrs = claw_rgb_attrs,
+};
+
+static struct mc_subled claw_rgb_subled_info[] = {
+ {
+ .color_index = LED_COLOR_ID_RED,
+ .channel = 0x1,
+ },
+ {
+ .color_index = LED_COLOR_ID_GREEN,
+ .channel = 0x2,
+ },
+ {
+ .color_index = LED_COLOR_ID_BLUE,
+ .channel = 0x3,
+ },
+};
+
static void cfg_setup_fn(struct work_struct *work)
{
struct delayed_work *dwork = container_of(work, struct delayed_work, work);
@@ -772,6 +1262,13 @@ static void cfg_setup_fn(struct work_struct *work)
return;
}
+ ret = claw_read_rgb_config(drvdata->hdev);
+ if (ret) {
+ dev_err(drvdata->led_mc.led_cdev.dev,
+ "Failed to setup device, can't read RGB config: %d\n", ret);
+ return;
+ }
+
/* Add sysfs attributes after we get the device state */
ret = device_add_group(&drvdata->hdev->dev, &claw_gamepad_attr_group);
if (ret) {
@@ -780,7 +1277,15 @@ static void cfg_setup_fn(struct work_struct *work)
return;
}
+ ret = device_add_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group);
+ if (ret) {
+ dev_err(&drvdata->hdev->dev,
+ "Failed to setup device, can't create led attributes: %d\n", ret);
+ return;
+ }
+
kobject_uevent(&drvdata->hdev->dev.kobj, KOBJ_CHANGE);
+ kobject_uevent(&drvdata->led_mc.led_cdev.dev->kobj, KOBJ_CHANGE);
}
static void cfg_resume_fn(struct work_struct *work)
@@ -790,6 +1295,10 @@ static void cfg_resume_fn(struct work_struct *work)
u8 data[2] = { drvdata->gamepad_mode, drvdata->mkeys_function };
int ret;
+ ret = claw_read_rgb_config(drvdata->hdev);
+ if (ret)
+ dev_err(drvdata->led_mc.led_cdev.dev, "Failed to read RGB config: %d\n", ret);
+
ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data,
ARRAY_SIZE(data), 0);
if (ret)
@@ -803,18 +1312,24 @@ static void claw_features_supported(struct claw_drvdata *drvdata)
if (major == 0x01) {
drvdata->bmap_support = true;
- if (minor >= 0x66)
+ if (minor >= 0x66) {
drvdata->bmap_addr = button_mapping_addr_new;
- else
+ drvdata->rgb_addr = rgb_addr_new;
+ } else {
drvdata->bmap_addr = button_mapping_addr_old;
+ drvdata->rgb_addr = rgb_addr_old;
+ }
return;
}
if ((major == 0x02 && minor >= 0x17) || major >= 0x03) {
drvdata->bmap_support = true;
drvdata->bmap_addr = button_mapping_addr_new;
+ drvdata->rgb_addr = rgb_addr_new;
return;
}
+
+ drvdata->rgb_addr = rgb_addr_old;
}
static int claw_probe(struct hid_device *hdev, u8 ep)
@@ -829,6 +1344,7 @@ static int claw_probe(struct hid_device *hdev, u8 ep)
return -ENOMEM;
drvdata->gamepad_mode = CLAW_GAMEPAD_MODE_XINPUT;
+ drvdata->rgb_enabled = true;
drvdata->hdev = hdev;
drvdata->ep = ep;
@@ -839,12 +1355,28 @@ static int claw_probe(struct hid_device *hdev, u8 ep)
if (!drvdata->bmap_support)
dev_dbg(&hdev->dev, "M-Key mapping is not supported. Update firmware to enable.\n");
+ drvdata->led_mc.led_cdev.name = "msi_claw:rgb:joystick_rings";
+ drvdata->led_mc.led_cdev.brightness = 0x50;
+ drvdata->led_mc.led_cdev.max_brightness = 0x64;
+ drvdata->led_mc.led_cdev.color = LED_COLOR_ID_RGB;
+ drvdata->led_mc.led_cdev.brightness_set = claw_led_brightness_set;
+ drvdata->led_mc.num_colors = 3;
+ drvdata->led_mc.subled_info = devm_kmemdup(&hdev->dev, claw_rgb_subled_info,
+ sizeof(claw_rgb_subled_info), GFP_KERNEL);
+ if (!drvdata->led_mc.subled_info)
+ return -ENOMEM;
+
mutex_init(&drvdata->cfg_mutex);
mutex_init(&drvdata->profile_mutex);
mutex_init(&drvdata->rom_mutex);
init_completion(&drvdata->send_cmd_complete);
INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn);
INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn);
+ INIT_DELAYED_WORK(&drvdata->rgb_queue, &claw_rgb_queue_fn);
+
+ ret = devm_led_classdev_multicolor_register(&hdev->dev, &drvdata->led_mc);
+ if (ret)
+ return ret;
/* For control interface: open the HID transport for sending commands. */
ret = hid_hw_open(hdev);
@@ -906,7 +1438,11 @@ static void claw_remove(struct hid_device *hdev)
return;
}
+ /* Block writes to brightness/multi_intensity during teardown */
+ drvdata->led_mc.led_cdev.brightness_set = NULL;
+ device_remove_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group);
device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
+ cancel_delayed_work_sync(&drvdata->rgb_queue);
cancel_delayed_work_sync(&drvdata->cfg_setup);
cancel_delayed_work_sync(&drvdata->cfg_resume);
hid_hw_close(hdev);
--
2.53.0
^ permalink raw reply related
* [PATCH v3 4/4] HID: hid-msi: Add Rumble Intensity Attributes
From: Derek J. Clark @ 2026-05-15 3:36 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260515033622.2095277-1-derekjohn.clark@gmail.com>
Adds intensity adjustment for the left and right rumble motors.
Claude was used during the reverse-engineering data gathering for this
feature done by Zhouwang Huang. As the code had already been affected,
I used Claude to create the initial framing for the feature, then did
manual cleanup of the _show and _store functions afterwards to fix bugs
and keep the coding style consistent. Claude was also used as an initial
reviewer of this patch.
Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v2:
- Use pending_profile and sync to rom mutexes.
---
drivers/hid/hid-msi.c | 147 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 147 insertions(+)
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
index 06dc4290d2bb5..349a59f75e471 100644
--- a/drivers/hid/hid-msi.c
+++ b/drivers/hid/hid-msi.c
@@ -76,6 +76,8 @@ enum claw_profile_ack_pending {
CLAW_M1_PENDING,
CLAW_M2_PENDING,
CLAW_RGB_PENDING,
+ CLAW_RUMBLE_LEFT_PENDING,
+ CLAW_RUMBLE_RIGHT_PENDING,
};
enum claw_key_index {
@@ -262,6 +264,11 @@ static const u16 button_mapping_addr_new[] = {
static const u16 rgb_addr_old = 0x01fa;
static const u16 rgb_addr_new = 0x024a;
+static const u16 rumble_addr[] = {
+ 0x0022, /* left */
+ 0x0023, /* right */
+};
+
struct claw_command_report {
u8 report_id;
u8 padding[2];
@@ -311,7 +318,10 @@ struct claw_drvdata {
enum claw_gamepad_mode_index gamepad_mode;
u8 m1_codes[CLAW_KEYS_MAX];
u8 m2_codes[CLAW_KEYS_MAX];
+ u8 rumble_intensity_right;
+ u8 rumble_intensity_left;
const u16 *bmap_addr;
+ bool rumble_support;
bool bmap_support;
/* RGB Variables */
@@ -397,6 +407,12 @@ static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_
drvdata->led_mc.subled_info[2].intensity = frame->zone_data.zone[0].blue;
}
+ break;
+ case CLAW_RUMBLE_LEFT_PENDING:
+ drvdata->rumble_intensity_left = cmd_rep->data[4];
+ break;
+ case CLAW_RUMBLE_RIGHT_PENDING:
+ drvdata->rumble_intensity_right = cmd_rep->data[4];
break;
default:
dev_warn(&drvdata->hdev->dev,
@@ -810,6 +826,126 @@ static ssize_t button_mapping_options_show(struct device *dev,
}
static DEVICE_ATTR_RO(button_mapping_options);
+static ssize_t rumble_intensity_left_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u8 data[] = { 0x01, (rumble_addr[0] >> 8) & 0xff, rumble_addr[0] & 0xff, 0x01, 0x00 };
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u8 val;
+ int ret;
+
+ ret = kstrtou8(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val > 100)
+ return -EINVAL;
+
+ data[4] = val;
+
+ guard(mutex)(&drvdata->rom_mutex);
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
+ data, ARRAY_SIZE(data), 8);
+ if (ret)
+ return ret;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 8);
+ if (ret)
+ return ret;
+
+ drvdata->rumble_intensity_left = val;
+
+ return count;
+}
+
+static ssize_t rumble_intensity_left_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 data[4] = { 0x01, (rumble_addr[0] >> 8) & 0xff, rumble_addr[0] & 0xff, 0x01 };
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ int ret;
+
+ guard(mutex)(&drvdata->profile_mutex);
+ drvdata->profile_pending = CLAW_RUMBLE_LEFT_PENDING;
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data,
+ ARRAY_SIZE(data), 8);
+ if (ret) {
+ drvdata->profile_pending = CLAW_NO_PENDING;
+ return ret;
+ }
+
+ return sysfs_emit(buf, "%u\n", drvdata->rumble_intensity_left);
+}
+static DEVICE_ATTR_RW(rumble_intensity_left);
+
+static ssize_t rumble_intensity_right_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u8 data[] = { 0x01, (rumble_addr[1] >> 8) & 0xff, rumble_addr[1] & 0xff, 0x01, 0x00 };
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u8 val;
+ int ret;
+
+ ret = kstrtou8(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val > 100)
+ return -EINVAL;
+
+ data[4] = val;
+
+ guard(mutex)(&drvdata->rom_mutex);
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
+ data, ARRAY_SIZE(data), 8);
+ if (ret)
+ return ret;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 8);
+ if (ret)
+ return ret;
+
+ drvdata->rumble_intensity_right = val;
+
+ return count;
+}
+
+static ssize_t rumble_intensity_right_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 data[4] = { 0x01, (rumble_addr[1] >> 8) & 0xff, rumble_addr[1] & 0xff, 0x01 };
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ int ret;
+
+ guard(mutex)(&drvdata->profile_mutex);
+ drvdata->profile_pending = CLAW_RUMBLE_RIGHT_PENDING;
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data,
+ ARRAY_SIZE(data), 8);
+ if (ret) {
+ drvdata->profile_pending = CLAW_NO_PENDING;
+ return ret;
+ }
+
+ return sysfs_emit(buf, "%u\n", drvdata->rumble_intensity_right);
+}
+static DEVICE_ATTR_RW(rumble_intensity_right);
+
+static ssize_t rumble_intensity_range_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "0-100\n");
+}
+static DEVICE_ATTR_RO(rumble_intensity_range);
+
static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribute *attr,
int n)
{
@@ -830,6 +966,12 @@ static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribu
attr == &dev_attr_reset.attr)
return attr->mode;
+ /* Hide rumble attrs if not supported */
+ if (attr == &dev_attr_rumble_intensity_left.attr ||
+ attr == &dev_attr_rumble_intensity_right.attr ||
+ attr == &dev_attr_rumble_intensity_range.attr)
+ return drvdata->rumble_support ? attr->mode : 0;
+
/* Hide button mapping attrs if it isn't supported */
return drvdata->bmap_support ? attr->mode : 0;
}
@@ -843,6 +985,9 @@ static struct attribute *claw_gamepad_attrs[] = {
&dev_attr_mkeys_function.attr,
&dev_attr_mkeys_function_index.attr,
&dev_attr_reset.attr,
+ &dev_attr_rumble_intensity_left.attr,
+ &dev_attr_rumble_intensity_right.attr,
+ &dev_attr_rumble_intensity_range.attr,
NULL,
};
@@ -1314,6 +1459,7 @@ static void claw_features_supported(struct claw_drvdata *drvdata)
drvdata->bmap_support = true;
if (minor >= 0x66) {
drvdata->bmap_addr = button_mapping_addr_new;
+ drvdata->rumble_support = true;
drvdata->rgb_addr = rgb_addr_new;
} else {
drvdata->bmap_addr = button_mapping_addr_old;
@@ -1325,6 +1471,7 @@ static void claw_features_supported(struct claw_drvdata *drvdata)
if ((major == 0x02 && minor >= 0x17) || major >= 0x03) {
drvdata->bmap_support = true;
drvdata->bmap_addr = button_mapping_addr_new;
+ drvdata->rumble_support = true;
drvdata->rgb_addr = rgb_addr_new;
return;
}
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v3 1/4] HID: hid-msi: Add MSI Claw configuration driver
From: sashiko-bot @ 2026-05-15 4:09 UTC (permalink / raw)
To: Derek J. Clark; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260515033622.2095277-2-derekjohn.clark@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Incorrect teardown order in `claw_remove()` creates a race condition that leaks sysfs attributes and leads to a Use-After-Free.
- [High] Unreasonably short 8ms timeout causes spurious `-EBUSY` failures on `HZ=100` systems.
- [Medium] Read-modify-write race condition in sysfs configuration handlers causes silent state corruption.
- [Medium] Missing `.suspend` callback allows hardware-interacting delayed works to execute during system sleep.
--
commit ce9aa923d32bac6eb56e73d413c94aa753ea00cf
Author: Derek J. Clark <derekjohn.clark@gmail.com>
Subject: HID: hid-msi: Add MSI Claw configuration driver
Adds configuration HID driver for the MSI Claw series of handheld PC's.
In this initial patch add the initial driver outline and attributes for
changing the gamepad mode, M-key behavior, and add a WO reset function.
> diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> new file mode 100644
> index 0000000000000..89bb32f00bfc3
> --- /dev/null
> +++ b/drivers/hid/hid-msi.c
[ ... ]
> +static ssize_t gamepad_mode_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct hid_device *hdev = to_hid_device(dev);
> + struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> + u8 data[2] = { 0x00, drvdata->mkeys_function };
Could this lead to a read-modify-write race condition if two threads write to
the sysfs attributes simultaneously?
They read drvdata->mkeys_function locklessly to construct the command payload.
After the payload is prepared, claw_hw_output_report() is called, which then
acquires cfg_mutex to send the command. If two threads write concurrently,
the second one could overwrite the first one's change because it read a stale
state.
Should the payload construction be performed inside the cfg_mutex critical
section?
[ ... ]
> +static ssize_t gamepad_mode_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct hid_device *hdev = to_hid_device(dev);
> + struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> + int ret, i;
> +
> + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 8);
Will this 8 millisecond timeout cause spurious failures on systems configured
with a 100 hz timer tick?
On such systems, msecs_to_jiffies(8) evaluates to exactly 1 jiffy. A 1-jiffy
timeout using wait_for_completion_interruptible_timeout() can expire in under
1 millisecond if the timer tick fires immediately after the wait is scheduled,
which might not provide enough time for the interrupt to complete and will
return -EBUSY.
[ ... ]
> +static void claw_remove(struct hid_device *hdev)
> +{
> + struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> +
> + if (!drvdata) {
> + hid_hw_stop(hdev);
> + return;
> + }
> +
> + device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
> + cancel_delayed_work_sync(&drvdata->cfg_setup);
Does this teardown sequence leak sysfs attributes and cause a use-after-free?
If cfg_setup_fn() is executing concurrently, device_remove_group() will do
nothing because the sysfs group has not been added yet. Then
cancel_delayed_work_sync() will wait for the work to finish.
Meanwhile, cfg_setup_fn() proceeds to call device_add_group(), adding the
sysfs attributes. When the work finishes, claw_remove() completes and drvdata
is freed. The sysfs attributes remain registered to the device, and any
subsequent read or write from userspace will trigger a use-after-free when
accessing the freed drvdata pointer.
Should cancel_delayed_work_sync() happen before device_remove_group() to
ensure correct teardown order?
[ ... ]
> +static struct hid_driver msi_driver = {
> + .name = "hid-msi",
> + .id_table = msi_devices,
> + .raw_event = msi_raw_event,
> + .probe = msi_probe,
> + .remove = msi_remove,
> + .resume = msi_resume,
> +};
Does this driver need a suspend callback?
The cfg_setup and cfg_resume delayed works are scheduled on the standard
non-freezable system workqueue. Without a suspend callback to cancel them,
these works can execute while the system is asleep.
When they fire, they will call hid_hw_output_report() on a suspended
usb device, which could cause input/output timeouts, lockups, or inadvertently
wake up the hardware.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260515033622.2095277-1-derekjohn.clark@gmail.com?part=1
^ permalink raw reply
* Re: [PATCH v3 2/4] HID: hid-msi: Add M-key mapping attributes
From: sashiko-bot @ 2026-05-15 4:32 UTC (permalink / raw)
To: Derek J. Clark; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260515033622.2095277-3-derekjohn.clark@gmail.com>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Unsynchronized state machine and shared completions cause data corruption and races.
- [High] Blind cast of parent device to `usb_interface` causes a kernel panic via uhid.
- [High] 8ms timeout for hardware operations is dangerously short and will cause spurious failures.
- [Medium] Mixing goto-based cleanup with `scoped_guard` violates cleanup subsystem guidelines.
- [Medium] Unbounded kernel log spam on unsolicited device events.
--
commit f6e1e19ecedd5b80861e87d4606aa90bac28941a
Author: Derek J. Clark <derekjohn.clark@gmail.com>
HID: hid-msi: Add M-key mapping attributes
Adds attributes that allow for remapping the M-keys with up to 5 values
when in macro mode.
> diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> index 89bb32f00bfc3..6ccb9666daedf 100644
> --- a/drivers/hid/hid-msi.c
> +++ b/drivers/hid/hid-msi.c
> @@ -125,6 +294,31 @@ static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
[ ... ]
> +static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep)
> +{
> + u8 *codes;
> + int i;
> +
> + switch (drvdata->profile_pending) {
> + case CLAW_M1_PENDING:
> + case CLAW_M2_PENDING:
> + codes = (drvdata->profile_pending == CLAW_M1_PENDING) ?
Can reading drvdata->profile_pending locklessly here cause data races?
It is modified under profile_mutex in process context (e.g. in
claw_buttons_show), which could allow a delayed response to silently
overwrite the wrong profile's memory buffer if another thread is executing
a new request.
> + drvdata->m1_codes : drvdata->m2_codes;
> + /* Extract key codes; replace disabled (0xff) with 0x00, which is (null) in _show */
> + for (i = 0; i < CLAW_KEYS_MAX; i++)
> + codes[i] = (cmd_rep->data[6 + i] != 0xff) ? cmd_rep->data[6 + i] : 0x00;
> + break;
> + default:
> + dev_warn(&drvdata->hdev->dev,
> + "Got profile event without changes pending from command: %x\n",
> + cmd_rep->cmd);
Could this cause unbounded kernel log spam?
Since HID devices act as untrusted input and can send up to 1000 reports
per second, a malicious or malfunctioning device could flood the system
and cause a local denial of service via excessive logging. Should this
use dev_warn_ratelimited() or silently drop unsolicited packets?
> + return -EINVAL;
> + }
> + drvdata->profile_pending = CLAW_NO_PENDING;
> +
> + return 0;
> +}
> +
> static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report,
> u8 *data, int size)
> {
> @@ -364,6 +561,163 @@ static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
[ ... ]
> +static int claw_buttons_store(struct device *dev, const char *buf, u8 mkey_idx)
> +{
> + struct hid_device *hdev = to_hid_device(dev);
> + struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> + u8 data[] = { 0x01, (drvdata->bmap_addr[mkey_idx] >> 8) & 0xff,
> + drvdata->bmap_addr[mkey_idx] & 0xff, 0x07,
> + 0x04, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff };
> + size_t len = ARRAY_SIZE(data);
> + int ret, key_count, i;
> + char **raw_keys;
> +
> + raw_keys = argv_split(GFP_KERNEL, buf, &key_count);
> + if (!raw_keys)
> + return -ENOMEM;
> +
> + if (key_count > CLAW_KEYS_MAX) {
> + ret = -EINVAL;
> + goto err_free;
> + }
> +
> + if (key_count == 0)
> + goto set_buttons;
> +
> + for (i = 0; i < key_count; i++) {
> + ret = button_mapping_name_to_code(raw_keys[i]);
> + if (ret < 0)
> + goto err_free;
> +
> + data[6 + i] = ret;
> + }
> +
> +set_buttons:
> + scoped_guard(mutex, &drvdata->rom_mutex) {
> + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
> + data, len, 8);
Are these 8ms timeouts dangerously short?
On kernel configurations where HZ=100, msecs_to_jiffies(8) yields 1 jiffy. A
1-jiffy timeout can expire almost immediately depending on the timing of the
next timer tick. Will this falsely abort the wait and return -EBUSY before
the hardware has time to process the USB HID request?
> + if (ret < 0)
> + goto err_free;
> +
> + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 8);
> + }
> +
> +err_free:
Does mixing goto-based cleanup labels with scoped_guard violate the cleanup
subsystem guidelines?
The kernel guidelines explicitly forbid mixing goto-based cleanups with
scope-based cleanups in the same function, as it creates confusing ownership
semantics and can lead to LIFO ordering bugs.
> + argv_free(raw_keys);
> + return ret;
> +}
> +
> +static int claw_buttons_show(struct device *dev, char *buf, enum claw_key_index m_key)
> +{
> + struct hid_device *hdev = to_hid_device(dev);
> + struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> + u8 data[] = { 0x01, (drvdata->bmap_addr[m_key] >> 8) & 0xff,
> + drvdata->bmap_addr[m_key] & 0xff, 0x07 };
> + size_t len = ARRAY_SIZE(data);
> + int i, ret, count = 0;
> + const char *name;
> + u8 *codes;
> +
> + codes = (m_key == CLAW_KEY_M1) ? drvdata->m1_codes : drvdata->m2_codes;
> +
> + guard(mutex)(&drvdata->profile_mutex);
> + drvdata->profile_pending = (m_key == CLAW_KEY_M1) ? CLAW_M1_PENDING : CLAW_M2_PENDING;
> +
> + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data, len, 8);
Because the driver uses a single shared completion (send_cmd_complete) for all
hardware responses in claw_raw_event(), could a delayed reply from an
asynchronous command cause the wait_for_completion_interruptible_timeout() in
claw_hw_output_report() to return prematurely here?
This could result in uninitialized or stale data being returned to userspace.
> + if (ret) {
> + drvdata->profile_pending = CLAW_NO_PENDING;
> + return ret;
> + }
[ ... ]
> @@ -430,8 +796,31 @@ static void cfg_resume_fn(struct work_struct *work)
[ ... ]
> static int claw_probe(struct hid_device *hdev, u8 ep)
> {
> + struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
> + struct usb_device *udev = interface_to_usbdev(intf);
Can this blind cast cause a kernel panic?
If a virtual HID device is injected from user-space using the uhid subsystem
with a matching BUS_USB identifier, the parent device is the uhid virtual
device, not a usb_interface. Accessing udev->descriptor.bcdDevice in this case
would result in an out-of-bounds memory read.
Should this use hid_is_usb(hdev) to verify the transport before executing
USB-specific initialization?
> struct claw_drvdata *drvdata;
> int ret;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260515033622.2095277-1-derekjohn.clark@gmail.com?part=2
^ permalink raw reply
* Re: [PATCH v3 3/4] HID: hid-msi: Add RGB control interface
From: sashiko-bot @ 2026-05-15 5:01 UTC (permalink / raw)
To: Derek J. Clark; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260515033622.2095277-4-derekjohn.clark@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Out-of-bounds write in `claw_profile_event` due to an off-by-one bounds check on `frame_calc`.
- [High] Sleeping in atomic context due to `mutex` acquisition in the HID `raw_event` callback.
- [High] Use-After-Free and data race due to devm LED unregistration happening after `rgb_queue` is cancelled.
- [High] Teardown order race condition causes leaked sysfs attributes and Use-After-Free.
--
commit 56238f43183718f9b78311c16ef55fefd8acc6e2
Author: Derek J. Clark <derekjohn.clark@gmail.com>
HID: hid-msi: Add RGB control interface
This commit adds an RGB control interface for MSI Claw devices, managing
up to 8 sequential frames of RGB zone data. It mirrors Windows effects
and adds sysfs/LED class interfaces for configuration.
> diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> index 6ccb9666daedf..06dc4290d2bb5 100644
> --- a/drivers/hid/hid-msi.c
> +++ b/drivers/hid/hid-msi.c
[ ... ]
> @@ -308,15 +369,46 @@ static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_
> for (i = 0; i < CLAW_KEYS_MAX; i++)
> codes[i] = (cmd_rep->data[6 + i] != 0xff) ? cmd_rep->data[6 + i] : 0x00;
> break;
> + case CLAW_RGB_PENDING:
> + frame = (struct rgb_report *)cmd_rep->data;
> + rgb_addr = drvdata->rgb_addr;
> + read_addr = be16_to_cpu(frame->read_addr);
> + frame_calc = (read_addr - rgb_addr) / CLAW_RGB_FRAME_OFFSET;
> + if (frame_calc > CLAW_RGB_MAX_FRAMES) {
Can this result in an out-of-bounds write?
Since CLAW_RGB_MAX_FRAMES is 8 and the rgb_frames array is sized to 8, a
frame_calc value of 8 would pass this check but write past the end of the
array.
Should this check be >= instead?
> + dev_err(drvdata->led_mc.led_cdev.dev, "Got unsupported frame index: %x\n",
> + frame_calc);
> + ret = -EINVAL;
> + goto err_pending;
> + }
> + f_idx = frame_calc;
> +
> + scoped_guard(mutex, &drvdata->frame_mutex) {
Could this mutex acquisition cause a sleep-in-atomic bug?
This function is called synchronously from claw_raw_event(), which is the
HID raw_event callback and typically runs in atomic context (softirq or
hardirq).
Should this use a spinlock or defer the work to a workqueue instead?
> + memcpy(&drvdata->rgb_frames[f_idx], &frame->zone_data,
> + sizeof(struct rgb_frame));
> +
[ ... ]
> @@ -839,12 +1355,28 @@ static int claw_probe(struct hid_device *hdev, u8 ep)
[ ... ]
> + INIT_DELAYED_WORK(&drvdata->rgb_queue, &claw_rgb_queue_fn);
> +
> + ret = devm_led_classdev_multicolor_register(&hdev->dev, &drvdata->led_mc);
> + if (ret)
> + return ret;
[ ... ]
> @@ -906,7 +1438,11 @@ static void claw_remove(struct hid_device *hdev)
> return;
> }
>
> + /* Block writes to brightness/multi_intensity during teardown */
> + drvdata->led_mc.led_cdev.brightness_set = NULL;
> + device_remove_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group);
> device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
> + cancel_delayed_work_sync(&drvdata->rgb_queue);
> cancel_delayed_work_sync(&drvdata->cfg_setup);
Does the order here create a race condition with the delayed work?
If cfg_setup executes concurrently, it might call device_add_group()
immediately after device_remove_group() finishes. By the time
cancel_delayed_work_sync() runs, the setup might have already re-added
the sysfs attributes.
Would it be safer to cancel the delayed work before removing the sysfs
groups?
Additionally, because the LED class device is registered via devm in the
probe function, it won't be unregistered until after claw_remove() returns.
Since brightness_set is checked by the LED core without locks, could a
concurrent write to the brightness sysfs attribute slip past the NULL
check and rearm rgb_queue after it has been cancelled?
When devres later frees drvdata, could this rearmed delayed work cause
a use-after-free?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260515033622.2095277-1-derekjohn.clark@gmail.com?part=3
^ permalink raw reply
* Re: [PATCH v3 4/4] HID: hid-msi: Add Rumble Intensity Attributes
From: sashiko-bot @ 2026-05-15 5:26 UTC (permalink / raw)
To: Derek J. Clark; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260515033622.2095277-5-derekjohn.clark@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The asynchronous request state machine lacks sequence tracking and proper synchronization, causing delayed USB events to misroute and corrupt data for subsequent operations.
--
commit 95ec4f316951f2159554c90bdf669eb45a19e099
Author: Derek J. Clark <derekjohn.clark@gmail.com>
HID: hid-msi: Add Rumble Intensity Attributes
This commit adds sysfs attributes to control the left and right rumble motor
intensity on MSI Claw devices. It implements store and show functions that
communicate with the device firmware and uses a state machine to handle the
asynchronous responses.
> diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> index 06dc4290d2bb5..349a59f75e471 100644
> --- a/drivers/hid/hid-msi.c
> +++ b/drivers/hid/hid-msi.c
[ ... ]
> @@ -810,6 +826,126 @@ static ssize_t button_mapping_options_show(struct device *dev,
> }
> static DEVICE_ATTR_RO(button_mapping_options);
>
[ ... ]
> +static ssize_t rumble_intensity_left_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + u8 data[4] = { 0x01, (rumble_addr[0] >> 8) & 0xff, rumble_addr[0] & 0xff, 0x01 };
> + struct hid_device *hdev = to_hid_device(dev);
> + struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> + int ret;
> +
> + guard(mutex)(&drvdata->profile_mutex);
> + drvdata->profile_pending = CLAW_RUMBLE_LEFT_PENDING;
> + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data,
> + ARRAY_SIZE(data), 8);
> + if (ret) {
> + drvdata->profile_pending = CLAW_NO_PENDING;
> + return ret;
> + }
Is the state machine vulnerable to crossed data from delayed USB events?
If claw_hw_output_report() times out (using the 8ms timeout), this function
clears profile_pending and returns. If a subsequent read for the right
motor then sets profile_pending to CLAW_RUMBLE_RIGHT_PENDING, could a
delayed response from this left motor request arrive?
If so, claw_profile_event() would see CLAW_RUMBLE_RIGHT_PENDING,
incorrectly save the left rumble data into drvdata->rumble_intensity_right,
and clear the pending state. This would complete the right rumble request
with the left motor's data.
Similarly, could a stray ACK from a timed-out store operation prematurely
complete a show operation, exiting without clearing profile_pending and
permanently corrupting the state machine?
> +
> + return sysfs_emit(buf, "%u\n", drvdata->rumble_intensity_left);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260515033622.2095277-1-derekjohn.clark@gmail.com?part=4
^ permalink raw reply
* [PATCH v2 0/7] Add support for the Microsoft Surface Pro 12in 1st Edition (Snapdragon),
From: Harrison Vanderbyl @ 2026-05-15 5:41 UTC (permalink / raw)
To: andersson, konradybcio, robh, krzk+dt, conor+dt, Herbert Xu,
davem, neil.armstrong, maarten.lankhorst, mripard, tzimmermann,
airlied, simona, jikos, bentiss, luzmaximilian, hansg,
ilpo.jarvinen
Cc: Douglas Anderson, Jessica Zhang, linux-arm-msm, devicetree,
linux-kernel, linux-crypto, dri-devel, linux-input,
platform-driver-x86
In-Reply-To: <177869930359.1496076.12020223802779537187.b4-ty@kernel.org>
Changes in v2:
Ice device tree:
- Updated to use 0x0 formatting
Panel:
- Add edid to commit message
Device tree:
- Fixed formatting and ordering nits
- Removed extra dmic audio declarations
- Added suspected devices on i2c busses
- Fixed incorrect usb regulator
- Describe panel pin regulator
- Added defaults for volume button GPIOs
QSEECOM patch (4/8 in v1):
- Applied by Bjorn Andersson
Hid patch (5/8 in v1):
- Acked by Jiri Kosina, but not applied yet.
Link to v1:
https://lore.kernel.org/all/cover.1778498477.git.harrison.vanderbyl@gmail.com/
Currently supported:
- UFS (with inline crypto)
- Touchscreen, pen, cover keyboard and touchpad (via SAM)
- USB 3.2 x2 with DP Alt Mode
- Audio, WiFi, Bluetooth
- CDSP, ADSP, GPU
- Display (BOE NE120DRM-N28 panel)
Not yet supported:
- Accelerometer
- Front, back, and IR cameras
- IRIS video decoder
Note on the compatible string: unlike other Microsoft Surface devices
upstream (denali, arcata, romulus13, blackrock), this device does not
appear to carry an internal codename in firmware. The DSDT, SMBIOS, and
EFI variables only refer to it as "Surface Pro 12in 1st Ed with
Snapdragon" (SKU 2110). I have used `microsoft,surface-pro-12in` as the
compatible. Suggestions welcome.
Although the device is marketed as just a smaller version of the denali,
it has enough differences that it warranted a different compatible and
device tree.
Tested on Surface Pro 12in 1st Ed with Snapdragon (SKU 2110).
Harrison Vanderbyl (7):
dt-bindings: arm: qcom: Add Microsoft Surface Pro 12in
dt-bindings: crypto: Add x1e80100 inline crypto
platform/surface: SAM: Add support for Surface Pro 12in
hid: Pen battery quirk for Surface Pro 12in
drm/panel-edp: Add panel for Surface Pro 12in
arm64: dts: qcom: hamoa: Add inline crypto for UFS
arm64: dts: qcom: Add Microsoft Surface Pro 12in
.../devicetree/bindings/arm/qcom.yaml | 4 +
.../crypto/qcom,inline-crypto-engine.yaml | 1 +
arch/arm64/boot/dts/qcom/Makefile | 2 +
arch/arm64/boot/dts/qcom/hamoa.dtsi | 10 +
.../dts/qcom/x1p42100-microsoft-sp12in.dts | 1201 +++++++++++++++++
drivers/gpu/drm/panel/panel-edp.c | 1 +
drivers/hid/hid-ids.h | 1 +
drivers/hid/hid-input.c | 2 +
.../surface/surface_aggregator_registry.c | 15 +
9 files changed, 1237 insertions(+)
create mode 100644 arch/arm64/boot/dts/qcom/x1p42100-microsoft-sp12in.dts
base-commit: 5d6919055dec134de3c40167a490f33c74c12581
prerequisite-patch-id: a34133ba03bd1d31c0ed08612c31131b62015654
--
2.53.0
^ permalink raw reply
* [PATCH v2 1/7] dt-bindings: arm: qcom: Add Microsoft Surface Pro 12in
From: Harrison Vanderbyl @ 2026-05-15 5:41 UTC (permalink / raw)
To: andersson, konradybcio, robh, krzk+dt, conor+dt, Herbert Xu,
davem, neil.armstrong, maarten.lankhorst, mripard, tzimmermann,
airlied, simona, jikos, bentiss, luzmaximilian, hansg,
ilpo.jarvinen
Cc: Douglas Anderson, Jessica Zhang, linux-arm-msm, devicetree,
linux-kernel, linux-crypto, dri-devel, linux-input,
platform-driver-x86
In-Reply-To: <cover.1778822464.git.harrison.vanderbyl@gmail.com>
Document the compatible string for the Microsoft Surface Pro
12-inch, 1st Edition with Snapdragon, based on the Qualcomm X1P42100
SoC.
Signed-off-by: Harrison Vanderbyl <harrison.vanderbyl@gmail.com>
---
Documentation/devicetree/bindings/arm/qcom.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/qcom.yaml b/Documentation/devicetree/bindings/arm/qcom.yaml
index b4943123d2e4..aaa9a129908a 100644
--- a/Documentation/devicetree/bindings/arm/qcom.yaml
+++ b/Documentation/devicetree/bindings/arm/qcom.yaml
@@ -1168,6 +1168,10 @@ properties:
- const: microsoft,denali
- const: qcom,x1e80100
+ - items:
+ - const: microsoft,surface-pro-12in
+ - const: qcom,x1p42100
+
- items:
- enum:
- qcom,purwa-iot-evk
--
2.53.0
^ permalink raw reply related
* [PATCH v2 2/7] dt-bindings: crypto: Add x1e80100 inline crypto
From: Harrison Vanderbyl @ 2026-05-15 5:41 UTC (permalink / raw)
To: andersson, konradybcio, robh, krzk+dt, conor+dt, Herbert Xu,
davem, neil.armstrong, maarten.lankhorst, mripard, tzimmermann,
airlied, simona, jikos, bentiss, luzmaximilian, hansg,
ilpo.jarvinen
Cc: Douglas Anderson, Jessica Zhang, linux-arm-msm, devicetree,
linux-kernel, linux-crypto, dri-devel, linux-input,
platform-driver-x86
In-Reply-To: <cover.1778822464.git.harrison.vanderbyl@gmail.com>
Add compatibility string for the x1e80100/x1p42100
inline crypto engine.
Signed-off-by: Harrison Vanderbyl <harrison.vanderbyl@gmail.com>
---
.../devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml b/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml
index 876bf90ed96e..a338c4a33e98 100644
--- a/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml
+++ b/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml
@@ -24,6 +24,7 @@ properties:
- qcom,sm8550-inline-crypto-engine
- qcom,sm8650-inline-crypto-engine
- qcom,sm8750-inline-crypto-engine
+ - qcom,x1e80100-inline-crypto-engine
- const: qcom,inline-crypto-engine
reg:
--
2.53.0
^ permalink raw reply related
* [PATCH v2 3/7] platform/surface: SAM: Add support for Surface Pro 12in
From: Harrison Vanderbyl @ 2026-05-15 5:41 UTC (permalink / raw)
To: andersson, konradybcio, robh, krzk+dt, conor+dt, Herbert Xu,
davem, neil.armstrong, maarten.lankhorst, mripard, tzimmermann,
airlied, simona, jikos, bentiss, luzmaximilian, hansg,
ilpo.jarvinen
Cc: Douglas Anderson, Jessica Zhang, linux-arm-msm, devicetree,
linux-kernel, linux-crypto, dri-devel, linux-input,
platform-driver-x86
In-Reply-To: <cover.1778822464.git.harrison.vanderbyl@gmail.com>
Add a SAM client device node group and registry entry for the
Microsoft Surface Pro, 12-inch with Snapdragon.
This set enables the use of the following devices.
1: cover keyboard
2: cover touchpad
3: pen stash events.
The battery info and charger info devices have been
purposefully omitted as they are also reported by
other drivers and cause conflicts.
Signed-off-by: Harrison Vanderbyl <harrison.vanderbyl@gmail.com>
---
.../surface/surface_aggregator_registry.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
index 0599d5adf02e..884049961415 100644
--- a/drivers/platform/surface/surface_aggregator_registry.c
+++ b/drivers/platform/surface/surface_aggregator_registry.c
@@ -422,6 +422,19 @@ static const struct software_node *ssam_node_group_sp11[] = {
NULL,
};
+/* Devices for Surface Pro 12" first edition (ARM/QCOM) */
+static const struct software_node *ssam_node_group_sp12in[] = {
+ &ssam_node_root,
+ &ssam_node_hub_kip,
+ &ssam_node_tmp_sensors,
+ &ssam_node_hid_kip_keyboard,
+ &ssam_node_hid_sam_penstash,
+ &ssam_node_hid_kip_touchpad,
+ &ssam_node_hid_kip_fwupd,
+ &ssam_node_pos_tablet_switch,
+ NULL,
+};
+
/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
static const struct acpi_device_id ssam_platform_hub_acpi_match[] = {
@@ -500,6 +513,8 @@ static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
{ .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g },
/* Surface Pro 11 (ARM/QCOM) */
{ .compatible = "microsoft,denali", (void *)ssam_node_group_sp11 },
+ /* Surface Pro 12in First Edition (ARM/QCOM) */
+ { .compatible = "microsoft,surface-pro-12in", (void *)ssam_node_group_sp12in },
/* Surface Laptop 7 */
{ .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
{ .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },
--
2.53.0
^ permalink raw reply related
* [PATCH v2 4/7] hid: Pen battery quirk for Surface Pro 12in
From: Harrison Vanderbyl @ 2026-05-15 5:41 UTC (permalink / raw)
To: andersson, konradybcio, robh, krzk+dt, conor+dt, Herbert Xu,
davem, neil.armstrong, maarten.lankhorst, mripard, tzimmermann,
airlied, simona, jikos, bentiss, luzmaximilian, hansg,
ilpo.jarvinen
Cc: Douglas Anderson, Jessica Zhang, linux-arm-msm, devicetree,
linux-kernel, linux-crypto, dri-devel, linux-input,
platform-driver-x86, Jiri Kosina
In-Reply-To: <cover.1778822464.git.harrison.vanderbyl@gmail.com>
The pen setup for this device uses bluetooth for
communicating battery levels and status instead of
reporting it over i2c.
Without this quirk, the device either reports an
extra, broken phantom battery, or hangs.
Signed-off-by: Harrison Vanderbyl <harrison.vanderbyl@gmail.com>
Acked-by: Jiri Kosina <jkosina@suse.com>
---
drivers/hid/hid-ids.h | 1 +
drivers/hid/hid-input.c | 2 ++
2 files changed, 3 insertions(+)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 0cf63742315b..d16f55479786 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -459,6 +459,7 @@
#define USB_DEVICE_ID_HP_X2 0x074d
#define USB_DEVICE_ID_HP_X2_10_COVER 0x0755
#define I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM 0x2F81
+#define I2C_DEVICE_ID_SURFACE_PRO_12IN 0x4376
#define USB_VENDOR_ID_ELECOM 0x056e
#define USB_DEVICE_ID_ELECOM_BM084 0x0061
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index d73cfa2e73d3..61ecd840d0bd 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -395,6 +395,8 @@ static const struct hid_device_id hid_battery_quirks[] = {
HID_BATTERY_QUIRK_AVOID_QUERY },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM),
HID_BATTERY_QUIRK_AVOID_QUERY },
+ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_PRO_12IN),
+ HID_BATTERY_QUIRK_IGNORE },
/*
* Elan HID touchscreens seem to all report a non present battery,
* set HID_BATTERY_QUIRK_IGNORE for all Elan I2C and USB HID devices.
--
2.53.0
^ permalink raw reply related
* [PATCH v2 5/7] drm/panel-edp: Add panel for Surface Pro 12in
From: Harrison Vanderbyl @ 2026-05-15 5:41 UTC (permalink / raw)
To: andersson, konradybcio, robh, krzk+dt, conor+dt, Herbert Xu,
davem, neil.armstrong, maarten.lankhorst, mripard, tzimmermann,
airlied, simona, jikos, bentiss, luzmaximilian, hansg,
ilpo.jarvinen
Cc: Douglas Anderson, Jessica Zhang, linux-arm-msm, devicetree,
linux-kernel, linux-crypto, dri-devel, linux-input,
platform-driver-x86
In-Reply-To: <cover.1778822464.git.harrison.vanderbyl@gmail.com>
Add an entry for the BOE NE120DRM-N28 panel,
used in the Microsoft Surface Pro 12-inch.
The values chosen were tested to be working fine
for wake from sleep and hibernation.
Panel edid:
00 ff ff ff ff ff ff 00 09 e5 c9 0c a0 06 00 07
0a 22 01 04 a5 19 11 78 07 9f 15 a6 55 4c 9b 25
0e 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01
01 01 01 01 01 01 62 53 94 a0 80 b8 2e 50 18 10
3a 00 fe a9 00 00 00 1a 13 7d 94 a0 80 b8 2e 50
18 10 3a 00 fe a9 00 00 00 1a 00 00 00 fd 00 18
5a 5b 88 20 01 0a 20 20 20 20 20 20 00 00 00 fc
00 4e 45 31 32 30 44 52 4d 2d 4e 32 38 0a 00 0a
Signed-off-by: Harrison Vanderbyl <harrison.vanderbyl@gmail.com>
---
drivers/gpu/drm/panel/panel-edp.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c
index 497dcd48f57b..2cf52f03c217 100644
--- a/drivers/gpu/drm/panel/panel-edp.c
+++ b/drivers/gpu/drm/panel/panel-edp.c
@@ -2020,6 +2020,7 @@ static const struct edp_panel_entry edp_panels[] = {
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0c26, &delay_200_500_p2e200, "NV140WUM-T08"),
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0c93, &delay_200_500_e200, "Unknown"),
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cb6, &delay_200_500_e200, "NT116WHM-N44"),
+ EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cc9, &delay_200_500_e50, "NE120DRM-N28"),
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cf2, &delay_200_500_e200, "NV156FHM-N4S"),
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cf6, &delay_200_500_e200_d100, "NV140WUM-N64"),
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cfa, &delay_200_500_e50, "NV116WHM-A4D"),
--
2.53.0
^ permalink raw reply related
* [PATCH v2 6/7] arm64: dts: qcom: hamoa: Add inline crypto for UFS
From: Harrison Vanderbyl @ 2026-05-15 5:41 UTC (permalink / raw)
To: andersson, konradybcio, robh, krzk+dt, conor+dt, Herbert Xu,
davem, neil.armstrong, maarten.lankhorst, mripard, tzimmermann,
airlied, simona, jikos, bentiss, luzmaximilian, hansg,
ilpo.jarvinen
Cc: Douglas Anderson, Jessica Zhang, linux-arm-msm, devicetree,
linux-kernel, linux-crypto, dri-devel, linux-input,
platform-driver-x86
In-Reply-To: <cover.1778822464.git.harrison.vanderbyl@gmail.com>
Add the Inline Crypto node and wire it to ufs_mem,
enabling UFS storage encryption on x1e80100 and
derivative SOCs.
This is needed to support encrypted storage on
the Microsoft Surface Pro 12-inch.
Signed-off-by: Harrison Vanderbyl <harrison.vanderbyl@gmail.com>
---
arch/arm64/boot/dts/qcom/hamoa.dtsi | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/hamoa.dtsi b/arch/arm64/boot/dts/qcom/hamoa.dtsi
index 051dee076416..22420d0a323a 100644
--- a/arch/arm64/boot/dts/qcom/hamoa.dtsi
+++ b/arch/arm64/boot/dts/qcom/hamoa.dtsi
@@ -3952,6 +3952,8 @@ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>,
phys = <&ufs_mem_phy>;
phy-names = "ufsphy";
+ qcom,ice = <&ice>;
+
#reset-cells = <1>;
status = "disabled";
@@ -3997,6 +3999,14 @@ opp-300000000 {
};
};
+ ice: crypto@1d88000 {
+ compatible = "qcom,x1e80100-inline-crypto-engine",
+ "qcom,inline-crypto-engine";
+ reg = <0x0 0x01d88000 0x0 0x8000>;
+
+ clocks = <&gcc GCC_UFS_PHY_ICE_CORE_CLK>;
+ };
+
cryptobam: dma-controller@1dc4000 {
compatible = "qcom,bam-v1.7.4", "qcom,bam-v1.7.0";
reg = <0x0 0x01dc4000 0x0 0x28000>;
--
2.53.0
^ permalink raw reply related
* [PATCH v2 7/7] arm64: dts: qcom: Add Microsoft Surface Pro 12in
From: Harrison Vanderbyl @ 2026-05-15 5:41 UTC (permalink / raw)
To: andersson, konradybcio, robh, krzk+dt, conor+dt, Herbert Xu,
davem, neil.armstrong, maarten.lankhorst, mripard, tzimmermann,
airlied, simona, jikos, bentiss, luzmaximilian, hansg,
ilpo.jarvinen
Cc: Douglas Anderson, Jessica Zhang, linux-arm-msm, devicetree,
linux-kernel, linux-crypto, dri-devel, linux-input,
platform-driver-x86
In-Reply-To: <cover.1778822464.git.harrison.vanderbyl@gmail.com>
Initial device tree for Microsoft Surface Pro 12in
Currently supported:
- UFS
- Touchscreen
- Pen
- USB 3.2 x2 (DP Alt Mode)
- Audio
- Wifi
- Bluetooth
- CDSP
- ADSP
- GPU
Not currently supported:
- Accelerometer
- Front, Back and IR cameras
- IRIS video decoder
Tested on Surface_Pro_12in_1st_Ed_with_Snapdragon_2110
Signed-off-by: Harrison Vanderbyl <harrison.vanderbyl@gmail.com>
---
arch/arm64/boot/dts/qcom/Makefile | 2 +
.../dts/qcom/x1p42100-microsoft-sp12in.dts | 1201 +++++++++++++++++
2 files changed, 1203 insertions(+)
create mode 100644 arch/arm64/boot/dts/qcom/x1p42100-microsoft-sp12in.dts
diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile
index 4ba8e7306419..8b6d3e4b479c 100644
--- a/arch/arm64/boot/dts/qcom/Makefile
+++ b/arch/arm64/boot/dts/qcom/Makefile
@@ -449,3 +449,5 @@ x1p42100-lenovo-thinkbook-16-el2-dtbs := x1p42100-lenovo-thinkbook-16.dtb x1-el2
dtb-$(CONFIG_ARCH_QCOM) += x1p42100-lenovo-thinkbook-16.dtb x1p42100-lenovo-thinkbook-16-el2.dtb
x1p64100-microsoft-denali-el2-dtbs := x1p64100-microsoft-denali.dtb x1-el2.dtbo
dtb-$(CONFIG_ARCH_QCOM) += x1p64100-microsoft-denali.dtb x1p64100-microsoft-denali-el2.dtb
+x1p42100-microsoft-sp12in-el2-dtbs := x1p42100-microsoft-sp12in.dtb x1-el2.dtbo
+dtb-$(CONFIG_ARCH_QCOM) += x1p42100-microsoft-sp12in.dtb x1p42100-microsoft-sp12in-el2.dtb
diff --git a/arch/arm64/boot/dts/qcom/x1p42100-microsoft-sp12in.dts b/arch/arm64/boot/dts/qcom/x1p42100-microsoft-sp12in.dts
new file mode 100644
index 000000000000..32b8df249791
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/x1p42100-microsoft-sp12in.dts
@@ -0,0 +1,1201 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2024, Linaro Limited
+ * Copyright (c) 2025, Jens Glathe
+ * Copyright (c) 2025, Harrison Vanderbyl
+ */
+
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/gpio-keys.h>
+#include <dt-bindings/input/input.h>
+#include <dt-bindings/pinctrl/qcom,pmic-gpio.h>
+#include <dt-bindings/regulator/qcom,rpmh-regulator.h>
+#include <dt-bindings/clock/qcom,x1e80100-tcsr.h>
+
+#include "purwa.dtsi"
+#include "hamoa-pmics.dtsi"
+
+/delete-node/ &pmc8380_6;
+/delete-node/ &pmc8380_6_thermal;
+
+/ {
+ model = "Surface Pro 12in 1st Edition";
+ compatible = "microsoft,surface-pro-12in", "qcom,x1p42100";
+ chassis-type = "tablet";
+
+ aliases {
+ serial0 = &uart2;
+ serial1 = &uart14;
+ };
+
+ backlight: backlight {
+ compatible = "pwm-backlight";
+ pwms = <&pmk8550_pwm 0 5000000>;
+
+ power-supply = <&vreg_edp_3p3>;
+ };
+
+ gpio-keys {
+ compatible = "gpio-keys";
+
+ pinctrl-0 = <&hall_int_n_default>, <&vol_up_n_default>, <&vol_down_n_default>;
+ pinctrl-names = "default";
+
+ switch-lid {
+ gpios = <&tlmm 2 GPIO_ACTIVE_LOW>;
+ linux,input-type = <EV_SW>;
+ linux,code = <SW_LID>;
+ wakeup-source;
+ wakeup-event-action = <EV_ACT_DEASSERTED>;
+ };
+
+ key-vol-up {
+ gpios = <&pm8550_gpios 8 GPIO_ACTIVE_LOW>;
+ linux,code = <KEY_VOLUMEUP>;
+ debounce-interval = <15>;
+ linux,can-disable;
+ wakeup-source;
+ };
+
+ key-vol-down {
+ gpios = <&pm8550_gpios 6 GPIO_ACTIVE_LOW>;
+ linux,code = <KEY_VOLUMEDOWN>;
+ debounce-interval = <15>;
+ linux,can-disable;
+ wakeup-source;
+ };
+ };
+
+ pmic-glink {
+ compatible = "qcom,x1e80100-pmic-glink",
+ "qcom,sm8550-pmic-glink",
+ "qcom,pmic-glink";
+ orientation-gpios = <&tlmm 121 GPIO_ACTIVE_HIGH>,
+ <&tlmm 123 GPIO_ACTIVE_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* Right-side upper port */
+ connector@0 {
+ compatible = "usb-c-connector";
+ reg = <0>;
+ power-role = "dual";
+ data-role = "dual";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ pmic_glink_ss0_hs_in: endpoint {
+ remote-endpoint = <&usb_1_ss0_dwc3_hs>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ pmic_glink_ss0_ss_in: endpoint {
+ remote-endpoint = <&usb_1_ss0_qmpphy_out>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+
+ pmic_glink_ss0_sbu: endpoint {
+ remote-endpoint = <&usb_1_ss0_sbu_mux>;
+ };
+ };
+ };
+ };
+
+ /* Right-side lower port */
+ connector@1 {
+ compatible = "usb-c-connector";
+ reg = <1>;
+ power-role = "dual";
+ data-role = "dual";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ pmic_glink_ss1_hs_in: endpoint {
+ remote-endpoint = <&usb_1_ss1_dwc3_hs>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ pmic_glink_ss1_ss_in: endpoint {
+ remote-endpoint = <&usb_1_ss1_qmpphy_out>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+
+ pmic_glink_ss1_sbu: endpoint {
+ remote-endpoint = <&usb_1_ss1_sbu_mux>;
+ };
+ };
+ };
+ };
+ };
+
+ reserved-memory {
+ linux,cma {
+ compatible = "shared-dma-pool";
+ size = <0x0 0x20000000>;
+ reusable;
+ linux,cma-default;
+ };
+ };
+
+ vreg_edp_3p3: regulator-edp-3p3 {
+ compatible = "regulator-fixed";
+
+ regulator-name = "VREG_EDP_3P3";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+
+ gpio = <&tlmm 70 GPIO_ACTIVE_HIGH>;
+ enable-active-high;
+
+ pinctrl-0 = <&edp_reg_en>;
+ pinctrl-names = "default";
+
+ regulator-boot-on;
+ vin-supply = <&vreg_panel_en>;
+ };
+
+ vreg_panel_en: regulator-panel-en {
+ compatible = "regulator-fixed";
+
+ regulator-name = "VREG_PANEL_EN";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+
+ gpio = <&tlmm 29 GPIO_ACTIVE_HIGH>;
+ enable-active-high;
+
+ regulator-enable-ramp-delay = <150000>;
+
+ pinctrl-0 = <&panel_en>;
+ pinctrl-names = "default";
+
+ regulator-boot-on;
+ };
+
+ vph_pwr: regulator-vph-pwr {
+ compatible = "regulator-fixed";
+
+ regulator-name = "vph_pwr";
+ regulator-min-microvolt = <3700000>;
+ regulator-max-microvolt = <3700000>;
+ };
+
+ vreg_wcn_0p95: regulator-wcn-0p95 {
+ compatible = "regulator-fixed";
+
+ regulator-name = "VREG_WCN_0P95";
+ regulator-min-microvolt = <950000>;
+ regulator-max-microvolt = <950000>;
+
+ vin-supply = <&vreg_wcn_3p3>;
+ };
+
+ vreg_wcn_1p9: regulator-wcn-1p9 {
+ compatible = "regulator-fixed";
+
+ regulator-name = "VREG_WCN_1P9";
+ regulator-min-microvolt = <1900000>;
+ regulator-max-microvolt = <1900000>;
+
+ vin-supply = <&vreg_wcn_3p3>;
+ };
+
+ vreg_wcn_3p3: regulator-wcn-3p3 {
+ compatible = "regulator-fixed";
+
+ regulator-name = "VREG_WCN_3P3";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+
+ gpio = <&tlmm 214 GPIO_ACTIVE_HIGH>;
+ enable-active-high;
+
+ pinctrl-0 = <&wcn_sw_en>;
+ pinctrl-names = "default";
+ };
+
+ sound {
+ compatible = "qcom,x1e80100-sndcard";
+ model = "X1P42100-Microsoft-Surface-Pro-12in";
+ audio-routing = "SpkrLeft IN", "WSA WSA_SPK1 OUT",
+ "SpkrRight IN", "WSA WSA_SPK2 OUT",
+ "VA DMIC0", "vdd-micb",
+ "VA DMIC1", "vdd-micb";
+
+ va-dai-link {
+ link-name = "VA Capture";
+
+ codec {
+ sound-dai = <&lpass_vamacro 0>;
+ };
+
+ cpu {
+ sound-dai = <&q6apmbedai VA_CODEC_DMA_TX_0>;
+ };
+
+ platform {
+ sound-dai = <&q6apm>;
+ };
+ };
+
+ wsa-dai-link {
+ link-name = "WSA Playback";
+
+ codec {
+ sound-dai = <&left_spkr>, <&right_spkr>,
+ <&swr0 0>, <&lpass_wsamacro 0>;
+ };
+
+ cpu {
+ sound-dai = <&q6apmbedai WSA_CODEC_DMA_RX_0>;
+ };
+
+ platform {
+ sound-dai = <&q6apm>;
+ };
+ };
+ };
+
+ wcn7850-pmu {
+ compatible = "qcom,wcn7850-pmu";
+
+ vdd-supply = <&vreg_wcn_0p95>;
+ vddio-supply = <&vreg_l15b_1p8>;
+ vddaon-supply = <&vreg_wcn_0p95>;
+ vdddig-supply = <&vreg_wcn_0p95>;
+ vddrfa1p2-supply = <&vreg_wcn_1p9>;
+ vddrfa1p8-supply = <&vreg_wcn_1p9>;
+
+ wlan-enable-gpios = <&tlmm 117 GPIO_ACTIVE_HIGH>;
+ bt-enable-gpios = <&tlmm 116 GPIO_ACTIVE_HIGH>;
+
+ pinctrl-0 = <&wcn_wlan_bt_en>;
+ pinctrl-names = "default";
+
+ regulators {
+ vreg_pmu_rfa_cmn: ldo0 {
+ regulator-name = "vreg_pmu_rfa_cmn";
+ };
+
+ vreg_pmu_aon_0p59: ldo1 {
+ regulator-name = "vreg_pmu_aon_0p59";
+ };
+
+ vreg_pmu_wlcx_0p8: ldo2 {
+ regulator-name = "vreg_pmu_wlcx_0p8";
+ };
+
+ vreg_pmu_wlmx_0p85: ldo3 {
+ regulator-name = "vreg_pmu_wlmx_0p85";
+ };
+
+ vreg_pmu_btcmx_0p85: ldo4 {
+ regulator-name = "vreg_pmu_btcmx_0p85";
+ };
+
+ vreg_pmu_rfa_0p8: ldo5 {
+ regulator-name = "vreg_pmu_rfa_0p8";
+ };
+
+ vreg_pmu_rfa_1p2: ldo6 {
+ regulator-name = "vreg_pmu_rfa_1p2";
+ };
+
+ vreg_pmu_rfa_1p8: ldo7 {
+ regulator-name = "vreg_pmu_rfa_1p8";
+ };
+
+ vreg_pmu_pcie_0p9: ldo8 {
+ regulator-name = "vreg_pmu_pcie_0p9";
+ };
+
+ vreg_pmu_pcie_1p8: ldo9 {
+ regulator-name = "vreg_pmu_pcie_1p8";
+ };
+ };
+ };
+
+ usb-1-ss0-sbu-mux {
+ compatible = "onnn,fsusb42", "gpio-sbu-mux";
+
+ enable-gpios = <&tlmm 168 GPIO_ACTIVE_LOW>;
+ select-gpios = <&tlmm 167 GPIO_ACTIVE_HIGH>;
+
+ pinctrl-0 = <&usb_1_ss0_sbu_default>;
+ pinctrl-names = "default";
+
+ mode-switch;
+ orientation-switch;
+
+ port {
+ usb_1_ss0_sbu_mux: endpoint {
+ remote-endpoint = <&pmic_glink_ss0_sbu>;
+ };
+ };
+ };
+
+ usb-1-ss1-sbu-mux {
+ compatible = "onnn,fsusb42", "gpio-sbu-mux";
+
+ enable-gpios = <&tlmm 179 GPIO_ACTIVE_LOW>;
+ select-gpios = <&tlmm 178 GPIO_ACTIVE_HIGH>;
+
+ pinctrl-0 = <&usb_1_ss1_sbu_default>;
+ pinctrl-names = "default";
+
+ mode-switch;
+ orientation-switch;
+
+ port {
+ usb_1_ss1_sbu_mux: endpoint {
+ remote-endpoint = <&pmic_glink_ss1_sbu>;
+ };
+ };
+ };
+};
+
+&apps_rsc {
+ regulators-0 {
+ compatible = "qcom,pm8550-rpmh-regulators";
+ qcom,pmic-id = "b";
+
+ vdd-bob1-supply = <&vph_pwr>;
+ vdd-bob2-supply = <&vph_pwr>;
+ vdd-l1-l4-l10-supply = <&vreg_s4c_1p8>;
+ vdd-l2-l13-l14-supply = <&vreg_bob1>;
+ vdd-l5-l16-supply = <&vreg_bob1>;
+ vdd-l6-l7-supply = <&vreg_bob2>;
+ vdd-l8-l9-supply = <&vreg_bob1>;
+ vdd-l12-supply = <&vreg_s5j_1p2>;
+ vdd-l15-supply = <&vreg_s4c_1p8>;
+ vdd-l17-supply = <&vreg_bob2>;
+
+ vreg_bob1: bob1 {
+ regulator-name = "vreg_bob1";
+ regulator-min-microvolt = <3008000>;
+ regulator-max-microvolt = <3960000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_bob2: bob2 {
+ regulator-name = "vreg_bob2";
+ regulator-min-microvolt = <2504000>;
+ regulator-max-microvolt = <3008000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l1b_1p8: ldo1 {
+ regulator-name = "vreg_l1b_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l2b_3p0: ldo2 {
+ regulator-name = "vreg_l2b_3p0";
+ regulator-min-microvolt = <3072000>;
+ regulator-max-microvolt = <3100000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l4b_1p8: ldo4 {
+ regulator-name = "vreg_l4b_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l5b_3p0: ldo5 {
+ regulator-name = "vreg_l5b_3p0";
+ regulator-min-microvolt = <3000000>;
+ regulator-max-microvolt = <3000000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l6b_1p8: ldo6 {
+ regulator-name = "vreg_l6b_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <2960000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l7b_2p8: ldo7 {
+ regulator-name = "vreg_l7b_2p8";
+ regulator-min-microvolt = <2800000>;
+ regulator-max-microvolt = <2800000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ regulator-always-on;
+ };
+
+ vreg_l8b_3p0: ldo8 {
+ regulator-name = "vreg_l8b_3p0";
+ regulator-min-microvolt = <3072000>;
+ regulator-max-microvolt = <3072000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l9b_2p9: ldo9 {
+ regulator-name = "vreg_l9b_2p9";
+ regulator-min-microvolt = <2960000>;
+ regulator-max-microvolt = <2960000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l10b_1p8: ldo10 {
+ regulator-name = "vreg_l10b_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l12b_1p2: ldo12 {
+ regulator-name = "vreg_l12b_1p2";
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1200000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l13b_3p0: ldo13 {
+ regulator-name = "vreg_l13b_3p0";
+ regulator-min-microvolt = <3072000>;
+ regulator-max-microvolt = <3100000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l14b_3p0: ldo14 {
+ regulator-name = "vreg_l14b_3p0";
+ regulator-min-microvolt = <3072000>;
+ regulator-max-microvolt = <3072000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l15b_1p8: ldo15 {
+ regulator-name = "vreg_l15b_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l16b_2p9: ldo16 {
+ regulator-name = "vreg_l16b_2p9";
+ regulator-min-microvolt = <2900000>;
+ regulator-max-microvolt = <2912000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ regulator-allow-set-load;
+ regulator-allowed-modes = <RPMH_REGULATOR_MODE_LPM
+ RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l17b_2p5: ldo17 {
+ regulator-name = "vreg_l17b_2p5";
+ regulator-min-microvolt = <2504000>;
+ regulator-max-microvolt = <2504000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ regulator-allow-set-load;
+ regulator-allowed-modes = <RPMH_REGULATOR_MODE_LPM
+ RPMH_REGULATOR_MODE_HPM>;
+ };
+ };
+
+ regulators-1 {
+ compatible = "qcom,pm8550ve-rpmh-regulators";
+ qcom,pmic-id = "c";
+
+ vdd-l1-supply = <&vreg_s5j_1p2>;
+ vdd-s4-supply = <&vph_pwr>;
+
+ vreg_s4c_1p8: smps4 {
+ regulator-name = "vreg_s4c_1p8";
+ regulator-min-microvolt = <1856000>;
+ regulator-max-microvolt = <2000000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l1c_1p2: ldo1 {
+ regulator-name = "vreg_l1c_1p2";
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1200000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l2c_0p8: ldo2 {
+ regulator-name = "vreg_l2c_0p8";
+ regulator-min-microvolt = <880000>;
+ regulator-max-microvolt = <920000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l3c_0p8: ldo3 {
+ regulator-name = "vreg_l3c_0p8";
+ regulator-min-microvolt = <880000>;
+ regulator-max-microvolt = <920000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+ };
+
+ regulators-2 {
+ compatible = "qcom,pmc8380-rpmh-regulators";
+ qcom,pmic-id = "d";
+
+ vdd-l3-supply = <&vreg_s4c_1p8>;
+ vdd-s1-supply = <&vph_pwr>;
+
+ vreg_l1d_0p8: ldo1 {
+ regulator-name = "vreg_l1d_0p8";
+ regulator-min-microvolt = <880000>;
+ regulator-max-microvolt = <920000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l2d_0p9: ldo2 {
+ regulator-name = "vreg_l2d_0p9";
+ regulator-min-microvolt = <912000>;
+ regulator-max-microvolt = <920000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l3d_1p8: ldo3 {
+ regulator-name = "vreg_l3d_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+ };
+
+ regulators-3 {
+ compatible = "qcom,pmc8380-rpmh-regulators";
+ qcom,pmic-id = "e";
+
+ vdd-l3-supply = <&vreg_s5j_1p2>;
+
+ vreg_l2e_0p8: ldo2 {
+ regulator-name = "vreg_l2e_0p8";
+ regulator-min-microvolt = <880000>;
+ regulator-max-microvolt = <880000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l3e_1p2: ldo3 {
+ regulator-name = "vreg_l3e_1p2";
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1200000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ regulator-allow-set-load;
+ regulator-allowed-modes = <RPMH_REGULATOR_MODE_LPM
+ RPMH_REGULATOR_MODE_HPM>;
+ };
+ };
+
+ regulators-6 {
+ compatible = "qcom,pm8550ve-rpmh-regulators";
+ qcom,pmic-id = "i";
+
+ vdd-l1-supply = <&vreg_s4c_1p8>;
+ vdd-l2-supply = <&vreg_s5j_1p2>;
+ vdd-s1-supply = <&vph_pwr>;
+ vdd-s2-supply = <&vph_pwr>;
+
+ vreg_s1i_0p9: smps1 {
+ regulator-name = "vreg_s1i_0p9";
+ regulator-min-microvolt = <900000>;
+ regulator-max-microvolt = <920000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_s2i_1p0: smps2 {
+ regulator-name = "vreg_s2i_1p0";
+ regulator-min-microvolt = <1000000>;
+ regulator-max-microvolt = <1100000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l1i_1p8: ldo1 {
+ regulator-name = "vreg_l1i_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l2i_1p2: ldo2 {
+ regulator-name = "vreg_l2i_1p2";
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1200000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ regulator-allow-set-load;
+ regulator-allowed-modes = <RPMH_REGULATOR_MODE_LPM
+ RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l3i_0p8: ldo3 {
+ regulator-name = "vreg_l3i_0p8";
+ regulator-min-microvolt = <880000>;
+ regulator-max-microvolt = <880000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ regulator-allow-set-load;
+ regulator-allowed-modes = <RPMH_REGULATOR_MODE_LPM
+ RPMH_REGULATOR_MODE_HPM>;
+ };
+ };
+
+ regulators-7 {
+ compatible = "qcom,pm8550ve-rpmh-regulators";
+ qcom,pmic-id = "j";
+
+ vdd-l2-supply = <&vreg_s5j_1p2>;
+ vdd-s5-supply = <&vph_pwr>;
+
+ vreg_s5j_1p2: smps5 {
+ regulator-name = "vreg_s5j_1p2";
+ regulator-min-microvolt = <1256000>;
+ regulator-max-microvolt = <1304000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l1j_0p8: ldo1 {
+ regulator-name = "vreg_l1j_0p8";
+ regulator-min-microvolt = <880000>;
+ regulator-max-microvolt = <920000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l2j_1p2: ldo2 {
+ regulator-name = "vreg_l2j_1p2";
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1256000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ regulator-allow-set-load;
+ regulator-allowed-modes = <RPMH_REGULATOR_MODE_LPM
+ RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l3j_0p8: ldo3 {
+ regulator-name = "vreg_l3j_0p8";
+ regulator-min-microvolt = <880000>;
+ regulator-max-microvolt = <880000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ regulator-allow-set-load;
+ regulator-allowed-modes = <RPMH_REGULATOR_MODE_LPM
+ RPMH_REGULATOR_MODE_HPM>;
+ };
+ };
+};
+
+&gpu {
+ status = "okay";
+};
+
+&gpu_zap_shader {
+ firmware-name = "qcom/x1p42100/Microsoft/Surface12/qcdxkmsucpurwa.mbn";
+};
+
+&i2c8 {
+ clock-frequency = <1000000>;
+
+ status = "okay";
+
+ touchscreen@16 {
+ compatible = "hid-over-i2c";
+ reg = <0x16>;
+
+ hid-descr-addr = <0x1>;
+ interrupts-extended = <&tlmm 38 IRQ_TYPE_LEVEL_LOW>;
+ wakeup-source;
+
+ vddl-supply = <&vreg_l15b_1p8>;
+
+ pinctrl-0 = <&ts0_default>;
+ pinctrl-names = "default";
+ };
+};
+
+&i2c0 {
+ clock-frequency = <400000>;
+
+ status = "okay";
+};
+
+&i2c4 {
+ clock-frequency = <400000>;
+
+ /* MAX34417 @12 */
+ /* MAX34417 @14 */
+ /* MAX34417 @16 */
+ /* MAX34417 @18 */
+ /* MAX34417 @1a */
+
+ status = "okay";
+};
+
+&i2c9 {
+ clock-frequency = <400000>;
+
+ // NFC @28, commercial devices only
+
+ status = "okay";
+};
+
+&lpass_tlmm {
+ spkr_01_sd_n_active: spkr-01-sd-n-active-state {
+ pins = "gpio12";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ output-low;
+ };
+};
+
+&lpass_vamacro {
+ qcom,dmic-sample-rate = <4800000>;
+
+ vdd-micb-supply = <&vreg_l1b_1p8>;
+
+ pinctrl-0 = <&dmic01_default>;
+ pinctrl-names = "default";
+};
+
+&mdss {
+ status = "okay";
+};
+
+&mdss_dp0 {
+ status = "okay";
+};
+
+&mdss_dp0_out {
+ link-frequencies = /bits/ 64 <1620000000 2700000000 5400000000 8100000000>;
+};
+
+&mdss_dp1 {
+ status = "okay";
+};
+
+&mdss_dp1_out {
+ link-frequencies = /bits/ 64 <1620000000 2700000000 5400000000 8100000000>;
+};
+
+&mdss_dp3 {
+ /delete-property/ #sound-dai-cells;
+
+ pinctrl-0 = <&edp0_hpd_default>;
+ pinctrl-names = "default";
+
+ status = "okay";
+
+ aux-bus {
+ panel: panel {
+ compatible = "edp-panel";
+
+ backlight = <&backlight>;
+
+ power-supply = <&vreg_edp_3p3>;
+
+ port {
+ edp_panel_in: endpoint {
+ remote-endpoint = <&mdss_dp3_out>;
+ };
+ };
+ };
+ };
+
+ ports {
+ port@1 {
+ reg = <1>;
+
+ mdss_dp3_out: endpoint {
+ data-lanes = <0 1 2 3>;
+ link-frequencies = /bits/ 64 <1620000000 2700000000 5400000000 8100000000>;
+
+ remote-endpoint = <&edp_panel_in>;
+ };
+ };
+ };
+};
+
+&mdss_dp3_phy {
+ vdda-phy-supply = <&vreg_l3j_0p8>;
+ vdda-pll-supply = <&vreg_l2j_1p2>;
+
+ status = "okay";
+};
+
+&pcie4 {
+ pinctrl-0 = <&pcie4_default>;
+ pinctrl-names = "default";
+
+ status = "okay";
+};
+
+&pcie4_phy {
+ vdda-phy-supply = <&vreg_l3i_0p8>;
+ vdda-pll-supply = <&vreg_l3e_1p2>;
+
+ status = "okay";
+};
+
+&pcie4_port0 {
+ reset-gpios = <&tlmm 146 GPIO_ACTIVE_LOW>;
+ wake-gpios = <&tlmm 148 GPIO_ACTIVE_LOW>;
+
+ wifi@0 {
+ compatible = "pci17cb,1107";
+ reg = <0x10000 0x0 0x0 0x0 0x0>;
+
+ vddaon-supply = <&vreg_pmu_aon_0p59>;
+ vddwlcx-supply = <&vreg_pmu_wlcx_0p8>;
+ vddwlmx-supply = <&vreg_pmu_wlmx_0p85>;
+ vddrfacmn-supply = <&vreg_pmu_rfa_cmn>;
+ vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>;
+ vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>;
+ vddrfa1p8-supply = <&vreg_pmu_rfa_1p8>;
+ vddpcie0p9-supply = <&vreg_pmu_pcie_0p9>;
+ vddpcie1p8-supply = <&vreg_pmu_pcie_1p8>;
+ };
+};
+
+&pm8550_gpios {
+ vol_up_n_default: vol-up-n-state {
+ pins = "gpio8";
+ function = "normal";
+ power-source = <1>;
+ bias-pull-up;
+ input-enable;
+ };
+
+ vol_down_n_default: vol-down-n-state {
+ pins = "gpio6";
+ function = "normal";
+ power-source = <1>;
+ bias-pull-up;
+ input-enable;
+ };
+};
+
+&pmk8550_pwm {
+ status = "okay";
+};
+
+&qupv3_0 {
+ status = "okay";
+};
+
+&qupv3_1 {
+ status = "okay";
+};
+
+&qupv3_2 {
+ status = "okay";
+};
+
+&remoteproc_adsp {
+ firmware-name = "qcom/x1p42100/Microsoft/Surface12/qcadsp8380.mbn",
+ "qcom/x1p42100/Microsoft/Surface12/adsp_dtbs.elf";
+
+ status = "okay";
+};
+
+&remoteproc_cdsp {
+ firmware-name = "qcom/x1p42100/Microsoft/Surface12/qccdsp8380.mbn",
+ "qcom/x1p42100/Microsoft/Surface12/cdsp_dtbs.elf";
+
+ status = "okay";
+};
+
+&smb2360_0 {
+ status = "okay";
+};
+
+&smb2360_0_eusb2_repeater {
+ vdd18-supply = <&vreg_l3d_1p8>;
+ vdd3-supply = <&vreg_l2b_3p0>;
+};
+
+&smb2360_1 {
+ status = "okay";
+};
+
+&smb2360_1_eusb2_repeater {
+ vdd18-supply = <&vreg_l3d_1p8>;
+ vdd3-supply = <&vreg_l14b_3p0>;
+};
+
+&swr0 {
+ pinctrl-0 = <&wsa_swr_active>, <&spkr_01_sd_n_active>;
+ pinctrl-names = "default";
+
+ status = "okay";
+
+ /* WSA8845, Left speaker */
+ left_spkr: speaker@0,0 {
+ compatible = "sdw20217020400";
+ reg = <0 0>;
+ reset-gpios = <&lpass_tlmm 12 GPIO_ACTIVE_LOW>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "SpkrLeft";
+ vdd-1p8-supply = <&vreg_l15b_1p8>;
+ vdd-io-supply = <&vreg_l12b_1p2>;
+ qcom,port-mapping = <1 2 3 7 10 13>;
+ };
+
+ /* WSA8845, Right speaker */
+ right_spkr: speaker@0,1 {
+ compatible = "sdw20217020400";
+ reg = <0 1>;
+ reset-gpios = <&lpass_tlmm 12 GPIO_ACTIVE_LOW>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "SpkrRight";
+ vdd-1p8-supply = <&vreg_l15b_1p8>;
+ vdd-io-supply = <&vreg_l12b_1p2>;
+ qcom,port-mapping = <4 5 6 7 11 13>;
+ };
+};
+
+&tlmm {
+ gpio-reserved-ranges = <34 2>, /* Unused */
+ <44 4>; /* SPI (TPM) */
+
+ edp_reg_en: edp-reg-en-state {
+ pins = "gpio70";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ hall_int_n_default: hall-int-n-state {
+ pins = "gpio2";
+ function = "gpio";
+ bias-disable;
+ };
+
+ panel_en: panel-en-state {
+ pins = "gpio29";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ pcie4_default: pcie4-default-state {
+ clkreq-n-pins {
+ pins = "gpio147";
+ function = "pcie4_clk";
+ drive-strength = <2>;
+ bias-pull-up;
+ };
+
+ perst-n-pins {
+ pins = "gpio146";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ wake-n-pins {
+ pins = "gpio148";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-pull-up;
+ };
+ };
+
+ ssam_state: ssam-state-state {
+ pins = "gpio91";
+ function = "gpio";
+ bias-disable;
+ };
+
+ ts0_default: ts0-default-state {
+ int-n-pins {
+ pins = "gpio38";
+ function = "gpio";
+ bias-disable;
+ };
+
+ reset-n-pins {
+ pins = "gpio48";
+ function = "gpio";
+ output-high;
+ drive-strength = <16>;
+ };
+ };
+
+ wcn_sw_en: wcn-sw-en-state {
+ pins = "gpio214";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ wcn_wlan_bt_en: wcn-wlan-bt-en-state {
+ pins = "gpio116", "gpio117";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ usb_1_ss0_sbu_default: usb-1-ss0-sbu-state {
+ oe-n-pins {
+ pins = "gpio168";
+ function = "gpio";
+ bias-disable;
+ drive-strength = <2>;
+ };
+
+ sel-pins {
+ pins = "gpio167";
+ function = "gpio";
+ bias-disable;
+ drive-strength = <2>;
+ };
+
+ };
+
+ usb_1_ss1_sbu_default: usb-1-ss1-sbu-state {
+ oe-n-pins {
+ pins = "gpio179";
+ function = "gpio";
+ bias-disable;
+ drive-strength = <2>;
+ };
+
+ sel-pins {
+ pins = "gpio178";
+ function = "gpio";
+ bias-disable;
+ drive-strength = <2>;
+ };
+ };
+};
+
+&uart2 {
+ status = "okay";
+
+ embedded-controller {
+ compatible = "microsoft,surface-sam";
+
+ interrupts-extended = <&tlmm 91 IRQ_TYPE_EDGE_RISING>;
+
+ current-speed = <4000000>;
+
+ pinctrl-0 = <&ssam_state>;
+ pinctrl-names = "default";
+ };
+};
+
+&uart14 {
+ status = "okay";
+
+ bluetooth {
+ compatible = "qcom,wcn7850-bt";
+ max-speed = <3200000>;
+
+ vddaon-supply = <&vreg_pmu_aon_0p59>;
+ vddwlcx-supply = <&vreg_pmu_wlcx_0p8>;
+ vddwlmx-supply = <&vreg_pmu_wlmx_0p85>;
+ vddrfacmn-supply = <&vreg_pmu_rfa_cmn>;
+ vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>;
+ vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>;
+ vddrfa1p8-supply = <&vreg_pmu_rfa_1p8>;
+ };
+};
+
+&ufs_mem_hc {
+ vcc-supply = <&vreg_l17b_2p5>;
+ vcc-max-microamp = <800000>;
+ vccq-supply = <&vreg_l2i_1p2>;
+ vccq-max-microamp = <900000>;
+
+ vdd-hba-supply = <&vreg_l3j_0p8>;
+
+ status = "okay";
+};
+
+&ufs_mem_phy {
+ status = "okay";
+
+ vdda-phy-supply = <&vreg_l3i_0p8>;
+ vdda-pll-supply = <&vreg_l3e_1p2>;
+};
+
+&usb_1_ss0_hsphy {
+ vdd-supply = <&vreg_l3j_0p8>;
+ vdda12-supply = <&vreg_l2j_1p2>;
+
+ phys = <&smb2360_0_eusb2_repeater>;
+
+ status = "okay";
+};
+
+&usb_1_ss0_qmpphy {
+ vdda-phy-supply = <&vreg_l2j_1p2>;
+ vdda-pll-supply = <&vreg_l1j_0p8>;
+
+ status = "okay";
+};
+
+&usb_1_ss0 {
+ dr_mode = "host";
+
+ status = "okay";
+};
+
+&usb_1_ss0_dwc3_hs {
+ remote-endpoint = <&pmic_glink_ss0_hs_in>;
+};
+
+&usb_1_ss0_qmpphy_out {
+ remote-endpoint = <&pmic_glink_ss0_ss_in>;
+};
+
+&usb_1_ss1_hsphy {
+ vdd-supply = <&vreg_l3j_0p8>;
+ vdda12-supply = <&vreg_l2j_1p2>;
+
+ phys = <&smb2360_1_eusb2_repeater>;
+
+ status = "okay";
+};
+
+&usb_1_ss1_qmpphy {
+ vdda-phy-supply = <&vreg_l2j_1p2>;
+ vdda-pll-supply = <&vreg_l2d_0p9>;
+
+ status = "okay";
+};
+
+&usb_1_ss1 {
+ dr_mode = "host";
+
+ status = "okay";
+};
+
+&usb_1_ss1_dwc3_hs {
+ remote-endpoint = <&pmic_glink_ss1_hs_in>;
+};
+
+&usb_1_ss1_qmpphy_out {
+ remote-endpoint = <&pmic_glink_ss1_ss_in>;
+};
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2 3/7] platform/surface: SAM: Add support for Surface Pro 12in
From: sashiko-bot @ 2026-05-15 6:02 UTC (permalink / raw)
To: Harrison Vanderbyl
Cc: krzk+dt, conor+dt, robh, dmitry.torokhov, devicetree, linux-input
In-Reply-To: <ab458aadea651396d9ea7629419a32dc7510c593.1778822464.git.harrison.vanderbyl@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Incorrect software node used for the pen stash device, breaking pen stash events.
--
commit 66a330e4ed95d1a03b167abb4b106aaef584a66f
Author: Harrison Vanderbyl <harrison.vanderbyl@gmail.com>
platform/surface: SAM: Add support for Surface Pro 12in
This commit adds a SAM client device node group and registry entry for the
Microsoft Surface Pro 12-inch. It specifically mentions enabling the cover
keyboard, cover touchpad, and pen stash events.
> diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
> index 0599d5adf02e6..8840499614154 100644
> --- a/drivers/platform/surface/surface_aggregator_registry.c
> +++ b/drivers/platform/surface/surface_aggregator_registry.c
[ ... ]
> @@ -422,6 +422,19 @@ static const struct software_node *ssam_node_group_sp11[] = {
> NULL,
> };
>
> +/* Devices for Surface Pro 12" first edition (ARM/QCOM) */
> +static const struct software_node *ssam_node_group_sp12in[] = {
> + &ssam_node_root,
> + &ssam_node_hub_kip,
> + &ssam_node_tmp_sensors,
> + &ssam_node_hid_kip_keyboard,
> + &ssam_node_hid_sam_penstash,
Will this software node work correctly for the Surface Pro pen stash events?
Since the pen is typically stashed inside the detachable cover keyboard on
Surface Pro devices, its events are usually routed through the Keyboard
Interface Protocol (KIP) hub.
Other Surface Pro models, like the SP11, use ssam_node_hid_kip_penstash
instead. Should this use the KIP variant here as well? Using the SAM
variant might cause the driver to listen on the wrong target ID and miss
the pen stash events entirely.
> + &ssam_node_hid_kip_touchpad,
> + &ssam_node_hid_kip_fwupd,
> + &ssam_node_pos_tablet_switch,
> + NULL,
> +};
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1778822464.git.harrison.vanderbyl@gmail.com?part=3
^ permalink raw reply
* Re: [PATCH v2 4/7] hid: Pen battery quirk for Surface Pro 12in
From: sashiko-bot @ 2026-05-15 6:12 UTC (permalink / raw)
To: Harrison Vanderbyl
Cc: dmitry.torokhov, conor+dt, devicetree, linux-input, krzk+dt, robh
In-Reply-To: <de275cc63e764d2acddf72bf3f50e89f307f41b5.1778822464.git.harrison.vanderbyl@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The newly added macro uses spaces instead of a tab for value alignment.
--
commit 41dd30be72b9f45b17d28788bd19cb4c73988147
Author: Harrison Vanderbyl <harrison.vanderbyl@gmail.com>
hid: Pen battery quirk for Surface Pro 12in
This commit adds a battery quirk for the Surface Pro 12in pen, which uses
bluetooth for communicating battery levels rather than i2c. Adding this
quirk prevents the device from reporting a phantom battery or hanging.
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 0cf63742315bf..d16f55479786c 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
[ ... ]
> @@ -459,6 +459,7 @@
> #define USB_DEVICE_ID_HP_X2 0x074d
> #define USB_DEVICE_ID_HP_X2_10_COVER 0x0755
> #define I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM 0x2F81
> +#define I2C_DEVICE_ID_SURFACE_PRO_12IN 0x4376
This isn't a bug, but it looks like spaces were used instead of a tab for
alignment here. The surrounding definitions use tabs.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1778822464.git.harrison.vanderbyl@gmail.com?part=4
^ permalink raw reply
* Re: [PATCH v2 6/7] arm64: dts: qcom: hamoa: Add inline crypto for UFS
From: sashiko-bot @ 2026-05-15 6:24 UTC (permalink / raw)
To: Harrison Vanderbyl
Cc: devicetree, dmitry.torokhov, krzk+dt, conor+dt, linux-input, robh
In-Reply-To: <30c12b79c6cc481afb13ac93630c5a16bc856ae4.1778822464.git.harrison.vanderbyl@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The mapped register size for the `ice` (Inline Crypto Engine) node is too small, which will cause a kernel panic when Hardware Key Manager (HWKM) features are used.
--
commit d49e015ad1235b81d3dc9cb4256b44b206a03560
Author: Harrison Vanderbyl <harrison.vanderbyl@gmail.com>
arm64: dts: qcom: hamoa: Add inline crypto for UFS
This commit adds the Inline Crypto Engine node and wires it to ufs_mem to
enable UFS storage encryption on x1e80100 and derivative SOCs.
> diff --git a/arch/arm64/boot/dts/qcom/hamoa.dtsi b/arch/arm64/boot/dts/qcom/hamoa.dtsi
> --- a/arch/arm64/boot/dts/qcom/hamoa.dtsi
> +++ b/arch/arm64/boot/dts/qcom/hamoa.dtsi
[ ... ]
> @@ -3997,6 +3999,14 @@ opp-300000000 {
> };
> };
>
> + ice: crypto@1d88000 {
> + compatible = "qcom,x1e80100-inline-crypto-engine",
> + "qcom,inline-crypto-engine";
> + reg = <0x0 0x01d88000 0x0 0x8000>;
Will this mapped size be large enough when hardware-wrapped keys are enabled
via the Hardware Key Manager (HWKM)?
When the qcom_ice driver initializes HWKM, it accesses registers at offsets
that appear to exceed the 0x8000 length provided here. For example, it accesses
QCOM_ICE_REG_HWKM_TZ_KM_STATUS at 0x9004, and QCOM_ICE_REG_HWKM_BANK0_BBAC_4
at 0xD010.
Could reading or writing to these offsets cause a synchronous external abort
and crash the kernel since they fall outside the mapped memory region?
Should the size be expanded to cover the full HWKM address space, such as
0x18000, similar to the configuration on other HWKM-capable SoCs?
> +
> + clocks = <&gcc GCC_UFS_PHY_ICE_CORE_CLK>;
> + };
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1778822464.git.harrison.vanderbyl@gmail.com?part=6
^ permalink raw reply
* Re: [PATCH v2 7/7] arm64: dts: qcom: Add Microsoft Surface Pro 12in
From: sashiko-bot @ 2026-05-15 6:56 UTC (permalink / raw)
To: Harrison Vanderbyl
Cc: krzk+dt, devicetree, robh, dmitry.torokhov, linux-input, conor+dt
In-Reply-To: <8ac29ee38ba80a3fbde8bfe43b74b9b936b31cb1.1778822464.git.harrison.vanderbyl@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Device Tree and Driver mismatch for WSA8845 speaker reset GPIO
- [Low] Incorrect indentation in `&pm8550_gpios` node
--
commit 0876a51b5a0549c867accb3d391daa4e0f3c28b3
Author: Harrison Vanderbyl <harrison.vanderbyl@gmail.com>
arm64: dts: qcom: Add Microsoft Surface Pro 12in
This commit introduces the initial device tree for the Microsoft Surface Pro
12in, adding support for various peripherals including UFS, touchscreen,
USB, audio, Wi-Fi, and Bluetooth.
> diff --git a/arch/arm64/boot/dts/qcom/x1p42100-microsoft-sp12in.dts b/arch/arm64/boot/dts/qcom/x1p42100-microsoft-sp12in.dts
> new file mode 100644
> index 0000000000000..32b8df2497913
> --- /dev/null
> +++ b/arch/arm64/boot/dts/qcom/x1p42100-microsoft-sp12in.dts
[ ... ]
> +&pm8550_gpios {
> + vol_up_n_default: vol-up-n-state {
> + pins = "gpio8";
> + function = "normal";
> + power-source = <1>;
> + bias-pull-up;
> + input-enable;
> + };
> +
> + vol_down_n_default: vol-down-n-state {
This isn't a bug, but should these subnodes be indented with a hardware tab
instead of spaces to match standard device tree coding guidelines?
[ ... ]
> +&swr0 {
> + pinctrl-0 = <&wsa_swr_active>, <&spkr_01_sd_n_active>;
> + pinctrl-names = "default";
> +
> + status = "okay";
> +
> + /* WSA8845, Left speaker */
> + left_spkr: speaker@0,0 {
> + compatible = "sdw20217020400";
> + reg = <0 0>;
> + reset-gpios = <&lpass_tlmm 12 GPIO_ACTIVE_LOW>;
Will this prevent the speakers from enumerating on the SoundWire bus?
Looking at the WSA884x codec driver in sound/soc/codecs/wsa884x.c, it
requests this GPIO by the name "powerdown":
devm_gpiod_get_optional(dev, "powerdown", ...)
Because the driver looks for the powerdown-gpios property, it will fail to
acquire the GPIO named reset-gpios here. Concurrently, the pinctrl state
spkr_01_sd_n_active drives this pin output-low, asserting the active-low
reset.
Since the driver never releases the reset, the speakers will stay powered
down and audio playback will fail. Should this property be renamed to
powerdown-gpios?
> + #sound-dai-cells = <0>;
> + sound-name-prefix = "SpkrLeft";
> + vdd-1p8-supply = <&vreg_l15b_1p8>;
> + vdd-io-supply = <&vreg_l12b_1p2>;
> + qcom,port-mapping = <1 2 3 7 10 13>;
> + };
> +
> + /* WSA8845, Right speaker */
> + right_spkr: speaker@0,1 {
> + compatible = "sdw20217020400";
> + reg = <0 1>;
> + reset-gpios = <&lpass_tlmm 12 GPIO_ACTIVE_LOW>;
Does this speaker node have the same naming issue where it should use
powerdown-gpios instead of reset-gpios?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1778822464.git.harrison.vanderbyl@gmail.com?part=7
^ permalink raw reply
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