* [PATCH v5 0/4] Add MSI Claw HID Configuration Driver
From: Derek J. Clark @ 2026-05-17 1:39 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>
---
v5:
- Swap disabled & combination mkeys_function enum values.
- Fix bug introduced in v5 where claw_buttons_store would return
-EINVAL on all valid key entries.
- Ensure mode_mutex is properly init.
- Ensure claw_remove is calling hid_hw_close and not hid_hw_stop for
all paths.
- Ensure adding "DISABLED" key to valid entries is done in the correct
patch.
- Re-enable sending an empty string to clear button mappings in
addition to setting DISABLED.
- Move adding the RGB device into cfg_setup to prevent led core
attributes from being written to prior to setup completing.
- Ensure frame_lock is properly init.
- Change variable names in RGB functions from frame and zone to f and
z respectively to fit all scoped_guard actions in 100 columns.
v4: https://lore.kernel.org/linux-input/20260516042841.500299-1-derekjohn.clark@gmail.com/
- Add msi_suspend/claw_suspend.
- Reorder claw_remove to cancel all work before removing sysfs.
- Add mutex lock for removing sysfs attributes.
- Add mutex lock for MODE command data read/write.
- Change dev_warn to dev_dbg in claw_profile_event.
- use __free with DEFINE_FREE macro for argv instead of manually
running argv_free, cleaining up scoped_guard goto.
- Fix frame_calc validity check to use >=.
- Use spinlock instead of mutex in raw_event and related attribute
_store function.
- Ensure delayed work is canceled in suspend & canceled before sysfs
attribute removal.
v3: https://lore.kernel.org/linux-input/20260515033622.2095277-1-derekjohn.clark@gmail.com/
- 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: https://lore.kernel.org/linux-input/20260513231445.3213501-1-derekjohn.clark@gmail.com/
- 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.
v1: https://lore.kernel.org/linux-input/20260510043510.442807-1-derekjohn.clark@gmail.com/
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 | 1728 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 1753 insertions(+)
create mode 100644 drivers/hid/hid-msi.c
--
2.53.0
^ permalink raw reply
* Re: [PATCH v3 1/4] HID: pass the buffer size to hid_report_raw_event
From: Brian Gerst @ 2026-05-16 18:25 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Jiri Kosina, Filipe Laíns, Bastien Nocera, Ping Cheng,
Jason Gerecke, Viresh Kumar, Johan Hovold, Alex Elder,
Greg Kroah-Hartman, Lee Jones, Icenowy Zheng, linux-input,
linux-kernel, greybus-dev, linux-staging, linux-usb, stable
In-Reply-To: <20260504-wip-fix-core-v3-1-ce1f11f4968f@kernel.org>
On Mon, May 4, 2026 at 4:48 AM Benjamin Tissoires <bentiss@kernel.org> wrote:
>
> commit 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing
> bogus memset()") enforced the provided data to be at least the size of
> the declared buffer in the report descriptor to prevent a buffer
> overflow. However, we can try to be smarter by providing both the buffer
> size and the data size, meaning that hid_report_raw_event() can make
> better decision whether we should plaining reject the buffer (buffer
> overflow attempt) or if we can safely memset it to 0 and pass it to the
> rest of the stack.
>
> Fixes: 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing bogus memset()")
> Cc: stable@vger.kernel.org
> Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
> ---
> drivers/hid/bpf/hid_bpf_dispatch.c | 6 ++++--
> drivers/hid/hid-core.c | 42 +++++++++++++++++++++++++-------------
> drivers/hid/hid-gfrm.c | 4 ++--
> drivers/hid/hid-logitech-hidpp.c | 2 +-
> drivers/hid/hid-multitouch.c | 2 +-
> drivers/hid/hid-primax.c | 2 +-
> drivers/hid/hid-vivaldi-common.c | 2 +-
> drivers/hid/wacom_sys.c | 6 +++---
> drivers/staging/greybus/hid.c | 2 +-
> include/linux/hid.h | 4 ++--
> include/linux/hid_bpf.h | 14 ++++++++-----
> 11 files changed, 53 insertions(+), 33 deletions(-)
>
> diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
> index 50c7b45c59e3..d0130658091b 100644
> --- a/drivers/hid/bpf/hid_bpf_dispatch.c
> +++ b/drivers/hid/bpf/hid_bpf_dispatch.c
> @@ -24,7 +24,8 @@ EXPORT_SYMBOL(hid_ops);
>
> u8 *
> dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data,
> - u32 *size, int interrupt, u64 source, bool from_bpf)
> + size_t *buf_size, u32 *size, int interrupt, u64 source,
> + bool from_bpf)
> {
> struct hid_bpf_ctx_kern ctx_kern = {
> .ctx = {
> @@ -74,6 +75,7 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
> *size = ret;
> }
>
> + *buf_size = ctx_kern.ctx.allocated_size;
> return ctx_kern.data;
> }
> EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
> @@ -505,7 +507,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b
> if (ret)
> return ret;
>
> - return hid_ops->hid_input_report(ctx->hid, type, buf, size, 0, (u64)(long)ctx, true,
> + return hid_ops->hid_input_report(ctx->hid, type, buf, size, size, 0, (u64)(long)ctx, true,
> lock_already_taken);
> }
>
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index 61afec5915ec..a806820df7e5 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -2033,24 +2033,32 @@ int __hid_request(struct hid_device *hid, struct hid_report *report,
> }
> EXPORT_SYMBOL_GPL(__hid_request);
>
> -int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
> - int interrupt)
> +int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data,
> + size_t bufsize, u32 size, int interrupt)
> {
> struct hid_report_enum *report_enum = hid->report_enum + type;
> struct hid_report *report;
> struct hid_driver *hdrv;
> int max_buffer_size = HID_MAX_BUFFER_SIZE;
> u32 rsize, csize = size;
> + size_t bsize = bufsize;
> u8 *cdata = data;
> int ret = 0;
>
> report = hid_get_report(report_enum, data);
> if (!report)
> - goto out;
> + return 0;
> +
> + if (unlikely(bsize < csize)) {
> + hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n",
> + report->id, csize, bsize);
This fails to build on 32-bit with this error:
In file included from ./include/linux/device.h:15,
from ./include/linux/input.h:19,
from drivers/hid/hid-core.c:25:
drivers/hid/hid-core.c: In function ‘hid_report_raw_event’:
drivers/hid/hid-core.c:2053:43: error: format ‘%ld’ expects argument
of type ‘long int’, but argument 5 has type ‘size_t’ {aka ‘unsigned
int’} [-Werror=format=]
2053 | hid_warn_ratelimited(hid, "Event data for
report %d is incorrect (%d vs %ld)\n",
|
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The proper format specifier for size_t is "%zu".
Brian Gerst
^ permalink raw reply
* Re: [PATCH v3 2/6] iio: gyro: HID: hid-sensor-gyro-3d: Refactor channel initialization
From: Jonathan Cameron @ 2026-05-16 11:44 UTC (permalink / raw)
To: srinivas pandruvada
Cc: Natália Salvino André, andy, dlechner, jikos, nuno.sa,
Pietro Di Consolo Gregorio, linux-iio, linux-input
In-Reply-To: <873e576772dc7b283bb4f01b3c838894cc7a35de.camel@linux.intel.com>
On Fri, 15 May 2026 09:48:34 -0700
srinivas pandruvada <srinivas.pandruvada@linux.intel.com> wrote:
> On Mon, 2026-05-11 at 22:16 -0300, Natália Salvino André wrote:
> > Replace the local gyro_3d_adjust_channel_bit_mask() function with a
> > compound literal for scan_type initialization to improve code
> > readability.
> >
> > Signed-off-by: Natália Salvino André <natalia.andre@ime.usp.br>
> > Co-developed-by: Pietro Di Consolo Gregorio <pietro.gregorio@usp.br>
> > Signed-off-by: Pietro Di Consolo Gregorio <pietro.gregorio@usp.br>
> > ---
> > drivers/iio/gyro/hid-sensor-gyro-3d.c | 24 +++++++++---------------
> > 1 file changed, 9 insertions(+), 15 deletions(-)
> >
> > diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c
> > b/drivers/iio/gyro/hid-sensor-gyro-3d.c
> > index c340cc899a7c..4cbe4d93a0a1 100644
> > --- a/drivers/iio/gyro/hid-sensor-gyro-3d.c
> > +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c
> > @@ -3,6 +3,7 @@
> > * HID Sensors Driver
> > * Copyright (c) 2012, Intel Corporation.
> > */
> > +#include <linux/bitops.h>
> > #include <linux/device.h>
> > #include <linux/platform_device.h>
> > #include <linux/module.h>
> > @@ -82,17 +83,6 @@ static const struct iio_chan_spec
> > gyro_3d_channels[] = {
> > IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP)
> > };
> >
> > -/* Adjust channel real bits based on report descriptor */
> > -static void gyro_3d_adjust_channel_bit_mask(struct iio_chan_spec
> > *channels,
> > - int channel, int
> > size)
> > -{
> > - channels[channel].scan_type.sign = 's';
> > - /* Real storage bits will change based on the report desc.
> > */
> > - channels[channel].scan_type.realbits = size * 8;
> > - /* Maximum size of a sample to capture is u32 */
> > - channels[channel].scan_type.storagebits = sizeof(u32) * 8;
> > -}
> > -
> > /* Channel read_raw handler */
> > static int gyro_3d_read_raw(struct iio_dev *indio_dev,
> > struct iio_chan_spec const *chan,
> > @@ -251,16 +241,20 @@ static int gyro_3d_parse_report(struct
> > platform_device *pdev,
> > int i;
> >
> > for (i = 0; i <= CHANNEL_SCAN_INDEX_Z; ++i) {
> > + unsigned int ch = CHANNEL_SCAN_INDEX_X + i;
> > +
>
> When you do code change more than what you described, then you should
> add to commit description that you are also doing this at the same
> time.
Particularly true when it is less than ideal. Like in previous
patch the loop shouldbe from CHANNEL_SCAN_INDEX_X to CHANNEL_SCAN_INDEX_Z
not 0 (the value is 0 but if we assume that then the sum for ch makes little
sense!)
>
> Thanks,
> Srinivas
>
> > ret = sensor_hub_input_get_attribute_info(hsdev,
> > HID_INPUT_REPORT,
> > usage_id,
> > HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXI
> > S + i,
> > - &st->gyro[CHANNEL_SCAN_INDEX_X +
> > i]);
> > + &st->gyro[ch]);
> > if (ret < 0)
> > break;
> > - gyro_3d_adjust_channel_bit_mask(channels,
> > - CHANNEL_SCAN_INDEX_X + i,
> > - st->gyro[CHANNEL_SCAN_INDEX_X +
> > i].size);
> > + channels[ch].scan_type = (struct iio_scan_type) {
> > + .format = 's',
> > + .realbits = BYTES_TO_BITS(st-
> > >gyro[ch].size),
> > + .storagebits = BITS_PER_TYPE(u32),
> > + };
> > }
> > dev_dbg(&pdev->dev, "gyro_3d %x:%x, %x:%x, %x:%x\n",
> > st->gyro[0].index,
>
^ permalink raw reply
* Re: [PATCH 6.18 000/188] 6.18.32-rc1 review
From: Greg KH @ 2026-05-16 10:11 UTC (permalink / raw)
To: Miguel Ojeda
Cc: achill, akpm, broonie, conor, f.fainelli, hargar, jonathanh,
linux-kernel, linux, lkft-triage, patches, patches, pavel,
rwarsow, shuah, sr, stable, sudipm.mukherjee, torvalds,
Jiri Kosina, Benjamin Tissoires, linux-input, Johan Hovold,
Nathan Chancellor
In-Reply-To: <20260516020430.110135-1-ojeda@kernel.org>
On Sat, May 16, 2026 at 04:04:30AM +0200, Miguel Ojeda wrote:
> On Fri, 15 May 2026 17:46:57 +0200 Greg Kroah-Hartman <gregkh@linuxfoundation.org> wrote:
> >
> > This is the start of the stable review cycle for the 6.18.32 release.
> > There are 188 patches in this series, all will be posted as a response
> > to this one. If anyone has any issues with these being applied, please
> > let me know.
> >
> > Responses should be made by Sun, 17 May 2026 15:46:37 +0000.
> > Anything received after that time might be too late.
>
> Boot-tested under QEMU for Rust x86_64, arm64 and riscv64; built-tested
> for loongarch64:
>
> Tested-by: Miguel Ojeda <ojeda@kernel.org>
>
> Via arm32 I see:
>
> drivers/hid/hid-core.c:2050:29: error: format specifies type 'long' but the argument has type 'size_t' (aka 'unsigned int') [-Werror,-Wformat]
> 2049 | hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n",
> | ~~~
> | %zu
> 2050 | report->id, csize, bsize);
> | ^~~~~
>
> It is also reproducible in mainline, though. Cc'ing a few folks...
bsize is size_t, so that should be %zu, right?
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH v4 4/4] HID: hid-msi: Add Rumble Intensity Attributes
From: sashiko-bot @ 2026-05-16 6:21 UTC (permalink / raw)
To: Derek J. Clark; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260516042841.500299-5-derekjohn.clark@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] State machine desynchronization and data corruption due to lockless `profile_pending` and lack of request sequence verification.
- [Medium] Undocumented and broken modification to button mapping functionality.
- [Medium] Data race on `rumble_intensity_left` and `rumble_intensity_right` variables due to inconsistent locking.
--
commit e2268bfaf4019b28116f3a16f5112af6d91c10d6
Author: Derek J. Clark <derekjohn.clark@gmail.com>
HID: hid-msi: Add Rumble Intensity Attributes
This commit adds sysfs attributes to adjust the left and right rumble
intensity for MSI claw devices.
> Adds intensity adjustment for the left and right rumble motors.
The commit message states this patch only adds intensity adjustment for the
left and right rumble motors, but it introduces undocumented changes to the
button mapping functionality.
> diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> --- a/drivers/hid/hid-msi.c
> +++ b/drivers/hid/hid-msi.c
> @@ -234,6 +236,7 @@ static const struct {
> { 0xcc, "BTN_EXTRA" },
> { 0xcd, "REL_WHEEL_UP" },
> { 0xce, "REL_WHEEL_DOWN" },
> + { 0xff, "DISABLED" },
> };
[ ... ]
> @@ -678,6 +695,9 @@ static const char *button_mapping_code_to_name(u8 code)
> {
> int i;
>
> + if (code == 0xff)
> + return NULL;
> +
Does this logic prevent the newly added "DISABLED" mapping from ever being
returned? By explicitly returning NULL here, will callers print "(null)" to
userspace instead of the intended "DISABLED" string?
[ ... ]
> @@ -400,6 +411,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;
Are these assignments exposed to a data race?
This function updates the rumble intensity variables asynchronously without
holding any locks. However, the variables are read and written elsewhere
using entirely different locks:
rumble_intensity_left_store():
guard(mutex)(&drvdata->rom_mutex);
...
drvdata->rumble_intensity_left = val;
rumble_intensity_left_show():
guard(mutex)(&drvdata->profile_mutex);
...
return sysfs_emit(buf, "%u\n", drvdata->rumble_intensity_left);
Additionally, is there a risk of cross-talk if a delayed hardware ACK arrives
after a timeout?
If rumble_intensity_left_show() times out while waiting for
claw_hw_output_report(), it resets profile_pending to CLAW_NO_PENDING and
drops profile_mutex. The hardware command is not aborted.
If rumble_intensity_right_show() is subsequently called, it sets
profile_pending to CLAW_RUMBLE_RIGHT_PENDING.
If the delayed left motor ACK arrives at this exact moment, it appears
claw_profile_event() will observe CLAW_RUMBLE_RIGHT_PENDING.
Since this function does not verify the response address for rumble
requests, could it incorrectly assign the incoming left motor data to
drvdata->rumble_intensity_right?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260516042841.500299-1-derekjohn.clark@gmail.com?part=4
^ permalink raw reply
* Re: [PATCH v4 3/4] HID: hid-msi: Add RGB control interface
From: sashiko-bot @ 2026-05-16 5:48 UTC (permalink / raw)
To: Derek J. Clark; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260516042841.500299-4-derekjohn.clark@gmail.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Holding a spinlock while calling sleeping functions causes a kernel panic.
- [High] `frame_lock` is acquired in process context without disabling interrupts, leading to a deadlock.
- [High] Re-arming of workqueue during teardown leads to Use-After-Free.
- [High] Early LED registration exposes sysfs attributes before initialization is complete, causing NULL pointer dereferences or Use-After-Free.
- [Medium] The `frame_lock` spinlock is never initialized.
- [Low] Mixing `goto` and scope-based cleanup helpers in the same function violates subsystem guidelines.
--
commit 7eda00d45299494b87bf52e044ab97d5c4a78385
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, supporting up
to 8 frames of sequential RGB zone data. It implements several lighting
effects mirroring the Windows driver functionality.
> diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> index bc0169c992ae8..8ea8f551b8e43 100644
> --- a/drivers/hid/hid-msi.c
> +++ b/drivers/hid/hid-msi.c
[ ... ]
> @@ -309,15 +372,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]);
> 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(spinlock, &drvdata->frame_lock) {
This function mixes a goto-based error path (err_pending) with a
scope-based cleanup helper (scoped_guard). Is it possible to convert the
error handling to use cleanup helpers, or remove the scoped guard? Subsystem
guidelines state that goto and cleanup helpers should not be mixed in the
same function.
[ ... ]
> +/* 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(spinlock)(&drvdata->frame_lock);
Are we acquiring the spinlock without disabling interrupts here? Since
frame_lock is also acquired in claw_profile_event() which runs in the
HID raw event interrupt context, could this lead to a deadlock if an
interrupt fires while this process context holds the lock? Using
guard(spinlock_irqsave) might be necessary to prevent this.
Also, does calling claw_write_rgb_state() while holding the frame_lock
spinlock cause an issue?
Looking at claw_write_rgb_state(), it acquires a mutex, allocates memory
with GFP_KERNEL inside claw_hw_output_report(), and can sleep via
wait_for_completion_interruptible_timeout(). Calling these sleeping functions
while holding a spinlock will trigger a scheduling while atomic panic.
> + drvdata->rgb_frame_count = 1;
> + claw_frame_fill_solid(&drvdata->rgb_frames[0], zone);
> +
> + return claw_write_rgb_state(drvdata);
> +}
[ ... ]
> static int claw_probe(struct hid_device *hdev, u8 ep)
> @@ -843,12 +1361,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);
Is frame_lock missing an explicit initialization? It doesn't appear to be
initialized with spin_lock_init(). Relying on devm_kzalloc
zero-initialization for spinlocks can lead to kernel panics or lockdep
warnings on debug kernels.
> 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;
By registering the LED device here, the sysfs attributes (like brightness)
are exposed to userspace before hid_set_drvdata() is called later in probe.
If userspace writes to brightness immediately, it will schedule rgb_queue.
When the work executes, will it encounter a NULL pointer dereference, since
claw_hw_output_report() relies on hid_get_drvdata() which hasn't been set
yet?
Additionally, if hid_hw_open() fails, the probe returns an error and devres
cleans up drvdata. Since the queued work isn't canceled on this error path,
could this result in a use-after-free when the work eventually runs?
>
> /* For control interface: open the HID transport for sending commands. */
> ret = hid_hw_open(hdev);
[ ... ]
> @@ -910,10 +1444,14 @@ 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;
> cancel_delayed_work_sync(&drvdata->cfg_setup);
> cancel_delayed_work_sync(&drvdata->cfg_resume);
> + cancel_delayed_work_sync(&drvdata->rgb_queue);
>
> guard(mutex)(&drvdata->cfg_mutex);
> + device_remove_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group);
Does canceling the delayed work before removing the sysfs attributes leave
a race condition?
Since device_remove_group() is called after cancel_delayed_work_sync(), the
sysfs files (like effect or speed) are still active. If a concurrent write
from userspace occurs, it can call mod_delayed_work() and re-queue
rgb_queue.
Once claw_remove() finishes, devres will free drvdata. When the re-queued
work executes later, will it dereference the freed memory and cause a
use-after-free? Removing the sysfs group before canceling the work would
prevent this.
> device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
> hid_hw_close(hdev);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260516042841.500299-1-derekjohn.clark@gmail.com?part=3
^ permalink raw reply
* Re: [PATCH v4 2/4] HID: hid-msi: Add M-key mapping attributes
From: sashiko-bot @ 2026-05-16 5:27 UTC (permalink / raw)
To: Derek J. Clark; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260516042841.500299-3-derekjohn.clark@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Incorrect error check in `claw_buttons_store()` prevents hardware updates and violates sysfs contract.
- [High] Unsynchronized request tracking and short timeouts cause cross-talk between sysfs reads and corrupt device state.
- [Medium] Writing an empty string silently fails to clear M-key mappings.
--
commit 437a6d2887a8ee961bc67ec5d675b041d9f3b7b4
Author: Derek J. Clark <derekjohn.clark@gmail.com>
HID: hid-msi: Add M-key mapping attributes
This commit adds sysfs attributes to remap the M-keys (M1 and M2) on MSI
devices in macro mode. It introduces functionality to parse user input,
translate button names to codes, and synchronize these configurations
with the hardware.
> diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> index 1bf1d6150f4fd..bc0169c992ae8 100644
> --- a/drivers/hid/hid-msi.c
> +++ b/drivers/hid/hid-msi.c
[ ... ]
> +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 };
> + char **raw_keys __free(argv) = NULL;
> + size_t len = ARRAY_SIZE(data);
> + int ret, key_count, i;
> +
> + raw_keys = argv_split(GFP_KERNEL, buf, &key_count);
> + if (!raw_keys)
> + return -ENOMEM;
> +
> + if (key_count > CLAW_KEYS_MAX)
> + return -EINVAL;
> +
> + if (key_count == 0)
> + return 0;
If a user attempts to clear the button mappings by writing an empty string,
key_count will be 0.
Doesn't this early return prevent sending the pre-initialized data array
(which is filled with 0xff to denote unmapped keys) to the hardware,
effectively leaving the previous mappings active?
> +
> + for (i = 0; i < key_count; i++) {
> + ret = button_mapping_name_to_code(raw_keys[i]);
> + if (ret)
> + return ret;
Does this correctly handle valid button codes?
button_mapping_name_to_code() returns a positive integer for valid codes
(e.g., 0x01 to 0xce). Since any positive value evaluates to true, won't
this immediately abort the loop, bypass the hardware write, and return a
positive integer up to the VFS layer?
Should this be checking if (ret < 0) instead?
> +
> + data[6 + i] = ret;
> + }
> +
> + scoped_guard(mutex, &drvdata->rom_mutex) {
> + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
> + data, len, 8);
> + if (ret)
> + return ret;
> +
> + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 8);
> + }
> +
> + 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);
Is 8ms enough time for the device to respond reliably? On a system with
HZ=100, 8ms is less than 1 jiffy.
If this times out, profile_pending is reset to CLAW_NO_PENDING below and
the mutex is released when the function returns. If a delayed ACK from this
request arrives while a subsequent sysfs read for the other M-key is
pending, could claw_profile_event() mistakenly write the stale data into
the new request's buffer and prematurely complete it?
> + if (ret) {
> + drvdata->profile_pending = CLAW_NO_PENDING;
> + return ret;
> + }
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260516042841.500299-1-derekjohn.clark@gmail.com?part=2
^ permalink raw reply
* Re: [PATCH v4 1/4] HID: hid-msi: Add MSI Claw configuration driver
From: sashiko-bot @ 2026-05-16 5:01 UTC (permalink / raw)
To: Derek J. Clark; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260516042841.500299-2-derekjohn.clark@gmail.com>
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] ABBA deadlock between `cfg_mutex` and sysfs kernfs active references during device removal.
- [High] The `mode_mutex` in `struct claw_drvdata` is never initialized, leading to kernel crashes upon contention.
- [High] Defensive error handling in `claw_remove()` introduces a double execution of `hid_hw_stop()`.
- [High] Unconditional device disconnect and state loss upon system resume.
- [Medium] Sysfs attributes are permanently lost if the system suspends shortly after probe.
- [Medium] Fragile endpoint detection logic checks only the first endpoint in the interface.
- [Medium] Late ACKs from timed-out commands spuriously complete new commands.
- [Medium] Fragile 8ms timeout can cause spurious failures on systems with HZ=100.
--
commit 6fb3a7c70c0068c58170fcddbffdda870a23fe93
Author: Derek J. Clark <derekjohn.clark@gmail.com>
HID: hid-msi: Add MSI Claw configuration driver
This commit adds a configuration HID driver for the MSI Claw series of
handheld PC's. It provides an interface to change the gamepad mode,
M-key behavior, and reset the device.
> diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/hid/hid-msi.c
[ ... ]
> +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;
Does this correctly handle configurations where the target IN endpoint is not
at index 0? Since USB endpoint ordering is not strictly mandated, should this
iterate over desc.bNumEndpoints instead of blindly selecting the first one?
[ ... ]
> +static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report,
> + u8 *data, int size)
> +{
[ ... ]
> + 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);
Could late ACKs from previously timed-out commands spuriously trigger this
completion? If a prior command times out and releases the mutex, and a new
command is issued, a delayed ACK might cause the new wait to return
prematurely with stale data.
[ ... ]
> +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;
> +
> + guard(mutex)(&drvdata->mode_mutex);
> + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 8);
Could this 8ms timeout cause spurious failures on systems configured with
HZ=100? In that configuration, msecs_to_jiffies(8) evaluates to 1 jiffy,
which can expire in a fraction of a millisecond depending on when the system
timer tick occurs.
[ ... ]
> +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);
The commit message notes that sending the SWITCH_MODE command causes a USB
disconnect in the device. Does sending this unconditionally on system resume
force a physical USB disconnect and re-enumeration every time the host wakes
from sleep?
[ ... ]
> +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);
Is there a missing mutex_init() for drvdata->mode_mutex here? Since the
structure is zero-allocated via devm_kzalloc, locking an uninitialized mutex
later in gamepad_mode_store() can trigger lockdep warnings or crashes.
[ ... ]
> +static void claw_remove(struct hid_device *hdev)
> +{
> + struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> +
> + if (!drvdata) {
> + hid_hw_stop(hdev);
> + return;
> + }
If drvdata is NULL, this function calls hid_hw_stop(hdev). Will this cause a
double-free or use-after-free since the caller msi_remove() also calls
hid_hw_stop(hdev) unconditionally after this returns?
> +
> + cancel_delayed_work_sync(&drvdata->cfg_setup);
> + cancel_delayed_work_sync(&drvdata->cfg_resume);
> +
> + guard(mutex)(&drvdata->cfg_mutex);
> + device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
Can taking cfg_mutex here cause a deadlock during device removal?
device_remove_group() drains active sysfs operations and waits for them to
complete. However, callbacks like gamepad_mode_store() take cfg_mutex
internally. If a user is reading or writing an attribute concurrently, remove
will block waiting for sysfs, and sysfs will block waiting for cfg_mutex.
[ ... ]
> +static int claw_suspend(struct hid_device *hdev)
> +{
> + struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
> +
> + if (!drvdata)
> + return -ENODEV;
> +
> + cancel_delayed_work_sync(&drvdata->cfg_setup);
> + cancel_delayed_work_sync(&drvdata->cfg_resume);
If the system suspends before the 500ms cfg_setup timer expires, this cancels
the setup work. Since claw_resume() only schedules cfg_resume, will the
device silently fail to ever expose its sysfs attributes for the rest of its
lifecycle?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260516042841.500299-1-derekjohn.clark@gmail.com?part=1
^ permalink raw reply
* [PATCH v4 4/4] HID: hid-msi: Add Rumble Intensity Attributes
From: Derek J. Clark @ 2026-05-16 4:28 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: <20260516042841.500299-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 | 151 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 151 insertions(+)
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
index 8ea8f551b8e43..d4688caef296a 100644
--- a/drivers/hid/hid-msi.c
+++ b/drivers/hid/hid-msi.c
@@ -79,6 +79,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 {
@@ -234,6 +236,7 @@ static const struct {
{ 0xcc, "BTN_EXTRA" },
{ 0xcd, "REL_WHEEL_UP" },
{ 0xce, "REL_WHEEL_DOWN" },
+ { 0xff, "DISABLED" },
};
enum claw_rgb_effect_index {
@@ -265,6 +268,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];
@@ -315,7 +323,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 */
@@ -400,6 +411,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_dbg(&drvdata->hdev->dev,
@@ -678,6 +695,9 @@ static const char *button_mapping_code_to_name(u8 code)
{
int i;
+ if (code == 0xff)
+ return NULL;
+
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;
@@ -816,6 +836,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)
{
@@ -836,6 +976,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;
}
@@ -849,6 +995,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,
};
@@ -1320,6 +1469,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;
@@ -1331,6 +1481,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
* [PATCH v4 3/4] HID: hid-msi: Add RGB control interface
From: Derek J. Clark @ 2026-05-16 4:28 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: <20260516042841.500299-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>
---
v4:
- Fix frame_calc validity check to use >=.
- USe spinlock instead of mutex in raw_event and related attribute
_store function.
- Ensure delayed work is canceled in suspend & canceled before sysfs
attribute removal.
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 | 549 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 544 insertions(+), 5 deletions(-)
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
index bc0169c992ae8..8ea8f551b8e43 100644
--- a/drivers/hid/hid-msi.c
+++ b/drivers/hid/hid-msi.c
@@ -21,10 +21,13 @@
#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>
#include <linux/pm.h>
+#include <linux/spinlock.h>
+#include <linux/spinlock_types.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/unaligned.h>
@@ -43,6 +46,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,
@@ -71,6 +78,7 @@ enum claw_profile_ack_pending {
CLAW_NO_PENDING,
CLAW_M1_PENDING,
CLAW_M2_PENDING,
+ CLAW_RGB_PENDING,
};
enum claw_key_index {
@@ -228,6 +236,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 */
@@ -238,6 +262,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];
@@ -246,6 +273,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;
@@ -257,6 +306,7 @@ struct claw_drvdata {
struct mutex mode_mutex; /* mutex for mode calls */
struct mutex cfg_mutex; /* mutex for synchronous data */
struct mutex rom_mutex; /* mutex for SYNC_TO_ROM calls */
+ spinlock_t frame_lock; /* lock for read/write rgb_frames */
u16 bcd_device;
u8 ep;
@@ -267,6 +317,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)
@@ -298,8 +358,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:
@@ -309,15 +372,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]);
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(spinlock, &drvdata->frame_lock) {
+ 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_dbg(&drvdata->hdev->dev,
"Got profile event without changes pending from command: %x\n",
cmd_rep->cmd);
return -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,
@@ -763,6 +857,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(spinlock)(&drvdata->frame_lock);
+ 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(spinlock)(&drvdata->frame_lock);
+ 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(spinlock)(&drvdata->frame_lock);
+ 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(spinlock)(&drvdata->frame_lock);
+ 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(spinlock)(&drvdata->frame_lock);
+ 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(spinlock)(&drvdata->frame_lock);
+ 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);
@@ -776,6 +1268,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) {
@@ -784,7 +1283,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)
@@ -794,6 +1301,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)
@@ -807,18 +1318,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)
@@ -833,6 +1350,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;
@@ -843,12 +1361,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);
@@ -910,10 +1444,14 @@ 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;
cancel_delayed_work_sync(&drvdata->cfg_setup);
cancel_delayed_work_sync(&drvdata->cfg_resume);
+ cancel_delayed_work_sync(&drvdata->rgb_queue);
guard(mutex)(&drvdata->cfg_mutex);
+ device_remove_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group);
device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
hid_hw_close(hdev);
}
@@ -972,6 +1510,7 @@ static int claw_suspend(struct hid_device *hdev)
cancel_delayed_work_sync(&drvdata->cfg_setup);
cancel_delayed_work_sync(&drvdata->cfg_resume);
+ cancel_delayed_work_sync(&drvdata->rgb_queue);
return 0;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v4 2/4] HID: hid-msi: Add M-key mapping attributes
From: Derek J. Clark @ 2026-05-16 4:28 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: <20260516042841.500299-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>
---
v4:
- Change dev_warn to dev_dbg in claw_profile_event.
- use __free with DEFINE_FREE macro for argv instead of manually
running argv_free, cleaining up scoped_guard goto.
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 | 396 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 395 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
index 1bf1d6150f4fd..bc0169c992ae8 100644
--- a/drivers/hid/hid-msi.c
+++ b/drivers/hid/hid-msi.c
@@ -41,6 +41,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,
@@ -65,6 +67,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,
@@ -77,6 +90,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];
@@ -87,17 +248,25 @@ 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 mode_mutex; /* mutex for mode calls */
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)
@@ -127,6 +296,30 @@ 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;
+ for (i = 0; i < CLAW_KEYS_MAX; i++)
+ codes[i] = (cmd_rep->data[6 + i]);
+ break;
+ default:
+ dev_dbg(&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)
{
@@ -148,6 +341,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:
@@ -372,6 +568,160 @@ 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;
+}
+
+DEFINE_FREE(argv, char **, if (_T) argv_free(_T))
+
+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 };
+ char **raw_keys __free(argv) = NULL;
+ size_t len = ARRAY_SIZE(data);
+ int ret, key_count, i;
+
+ raw_keys = argv_split(GFP_KERNEL, buf, &key_count);
+ if (!raw_keys)
+ return -ENOMEM;
+
+ if (key_count > CLAW_KEYS_MAX)
+ return -EINVAL;
+
+ if (key_count == 0)
+ return 0;
+
+ for (i = 0; i < key_count; i++) {
+ ret = button_mapping_name_to_code(raw_keys[i]);
+ if (ret)
+ return ret;
+
+ data[6 + i] = ret;
+ }
+
+ scoped_guard(mutex, &drvdata->rom_mutex) {
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
+ data, len, 8);
+ if (ret)
+ return ret;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 8);
+ }
+
+ 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)
{
@@ -384,10 +734,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,
@@ -438,8 +800,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;
@@ -451,7 +836,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 v4 1/4] HID: hid-msi: Add MSI Claw configuration driver
From: Derek J. Clark @ 2026-05-16 4:28 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: <20260516042841.500299-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>
---
v4:
- Add msi_suspend/claw_suspend.
- Reorder claw_remove to cancel all work before removing sysfs.
- Add mutex lock for removing sysfs attributes.
- Add mutex lock for MODE command data read/write.
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 | 625 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 650 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..1bf1d6150f4fd
--- /dev/null
+++ b/drivers/hid/hid-msi.c
@@ -0,0 +1,625 @@
+// 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/pm.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 mode_mutex; /* mutex for mode calls */
+ 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);
+ int i, ret = -EINVAL;
+ u8 data[2];
+
+ 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;
+
+ guard(mutex)(&drvdata->mode_mutex);
+ data[0] = ret;
+ data[1] = drvdata->mkeys_function;
+
+ 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;
+
+ guard(mutex)(&drvdata->mode_mutex);
+ 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);
+ int i, ret = -EINVAL;
+ u8 data[2];
+
+ 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;
+
+ guard(mutex)(&drvdata->mode_mutex);
+ data[0] = drvdata->gamepad_mode;
+ 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;
+
+ guard(mutex)(&drvdata->mode_mutex);
+ 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)
+ 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;
+ }
+
+ cancel_delayed_work_sync(&drvdata->cfg_setup);
+ cancel_delayed_work_sync(&drvdata->cfg_resume);
+
+ guard(mutex)(&drvdata->cfg_mutex);
+ device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
+ 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);
+
+ if (!drvdata)
+ return -ENODEV;
+
+ /* 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 int claw_suspend(struct hid_device *hdev)
+{
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ if (!drvdata)
+ return -ENODEV;
+
+ cancel_delayed_work_sync(&drvdata->cfg_setup);
+ cancel_delayed_work_sync(&drvdata->cfg_resume);
+
+ return 0;
+}
+
+static int msi_suspend(struct hid_device *hdev, pm_message_t msg)
+{
+ 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_suspend(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,
+ .suspend = pm_ptr(msi_suspend),
+};
+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 v4 0/4] Add MSI Claw HID Configuration Driver
From: Derek J. Clark @ 2026-05-16 4:28 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>
---
v4:
- Add msi_suspend/claw_suspend.
- Reorder claw_remove to cancel all work before removing sysfs.
- Add mutex lock for removing sysfs attributes.
- Add mutex lock for MODE command data read/write.
- Change dev_warn to dev_dbg in claw_profile_event.
- use __free with DEFINE_FREE macro for argv instead of manually
running argv_free, cleaining up scoped_guard goto.
- Fix frame_calc validity check to use >=.
- Use spinlock instead of mutex in raw_event and related attribute
_store function.
- Ensure delayed work is canceled in suspend & canceled before sysfs
attribute removal.
v3: https://lore.kernel.org/linux-input/20260515033622.2095277-1-derekjohn.clark@gmail.com/
- 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: https://lore.kernel.org/linux-input/20260513231445.3213501-1-derekjohn.clark@gmail.com/
- 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.
v1: https://lore.kernel.org/linux-input/20260510043510.442807-1-derekjohn.clark@gmail.com/
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 | 1708 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 1733 insertions(+)
create mode 100644 drivers/hid/hid-msi.c
--
2.53.0
^ permalink raw reply
* Re: [PATCH RFC 3/4] Input: gpio-keys - add regulator to gpio_keys
From: Mark Brown @ 2026-05-16 2:47 UTC (permalink / raw)
To: Griffin Kroah-Hartman
Cc: Dmitry Torokhov, Liam Girdwood, Bjorn Andersson, Konrad Dybcio,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Luca Weiss,
linux-input, linux-kernel, linux-arm-msm, devicetree
In-Reply-To: <21308d1e-712b-4d3b-b083-251c8d755470@fairphone.com>
[-- Attachment #1: Type: text/plain, Size: 1386 bytes --]
On Fri, May 15, 2026 at 01:41:38PM +0200, Griffin Kroah-Hartman wrote:
> On 5/8/26 15:44, Mark Brown wrote:
> > On Fri, May 08, 2026 at 02:53:15PM +0200, Griffin Kroah-Hartman wrote:
> > > + if (fwnode_property_present(child, "vdd-supply")) {
> > > + button->regulator = devm_fwnode_regulator_get_optional(dev, child, "vdd");
> > > + if (IS_ERR(button->regulator)) {
> > As well as the issue I mentioned on a prior thread with this assigning a
> > non-physical "vdd" name to the single supply that these components can
> > have (which has had issues in the past
> Our Hall Effect sensor IC does have a named "vdd" pin, but we are totally
> open to changing this to power-supply or whatever best follows the standard.
The problem is that there is no standard here, this is not specific to a
particular consumer device. Your device has one supply called VDD but
some other device might have some other name, and another might have two
supplies.
> > painful) the fact that this is fwnode means that this opens up support
> > for using this with ACPI which is very problematic given that ACPI has a
> > strong model of how regulators should work which is that they should not
> > be OS visible at all.
> Would it be more appropriate to drop the devm_fwnode_regulator_get() and
> replace it with a type-casted devm_of_regulator_get()?
I'd feel a lot more comfortable with that.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH 6.18 000/188] 6.18.32-rc1 review
From: Miguel Ojeda @ 2026-05-16 2:04 UTC (permalink / raw)
To: gregkh
Cc: achill, akpm, broonie, conor, f.fainelli, hargar, jonathanh,
linux-kernel, linux, lkft-triage, patches, patches, pavel,
rwarsow, shuah, sr, stable, sudipm.mukherjee, torvalds,
Miguel Ojeda, Jiri Kosina, Benjamin Tissoires, linux-input,
Johan Hovold, Nathan Chancellor
In-Reply-To: <20260515154657.309489048@linuxfoundation.org>
On Fri, 15 May 2026 17:46:57 +0200 Greg Kroah-Hartman <gregkh@linuxfoundation.org> wrote:
>
> This is the start of the stable review cycle for the 6.18.32 release.
> There are 188 patches in this series, all will be posted as a response
> to this one. If anyone has any issues with these being applied, please
> let me know.
>
> Responses should be made by Sun, 17 May 2026 15:46:37 +0000.
> Anything received after that time might be too late.
Boot-tested under QEMU for Rust x86_64, arm64 and riscv64; built-tested
for loongarch64:
Tested-by: Miguel Ojeda <ojeda@kernel.org>
Via arm32 I see:
drivers/hid/hid-core.c:2050:29: error: format specifies type 'long' but the argument has type 'size_t' (aka 'unsigned int') [-Werror,-Wformat]
2049 | hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n",
| ~~~
| %zu
2050 | report->id, csize, bsize);
| ^~~~~
It is also reproducible in mainline, though. Cc'ing a few folks...
Cc: Jiri Kosina <jikos@kernel.org>
Cc: Benjamin Tissoires <bentiss@kernel.org>
Cc: linux-input@vger.kernel.org
Cc: Johan Hovold <johan@kernel.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Nathan Chancellor <nathan@kernel.org>
Thanks!
Cheers,
Miguel
^ permalink raw reply
* Re: [PATCH 5/6] tools/build: selftests: Allow versioning LLVM lld
From: Ihor Solodrai @ 2026-05-15 22:58 UTC (permalink / raw)
To: James Clark, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt, Jiri Kosina, Benjamin Tissoires, Shuah Khan,
Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Andrii Nakryiko, Eduard Zingerman,
Alexei Starovoitov, Daniel Borkmann, Martin KaFai Lau,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song
Cc: linux-kernel, llvm, linux-input, linux-kselftest, bpf,
linux-perf-users, leo.yan
In-Reply-To: <20260514-james-perf-llvm-version-v1-5-6cac1a9a4c8d@linaro.org>
On 5/14/26 2:32 AM, James Clark wrote:
> Building with LLVM=... could result in a different version of lld being
> used than the main toolchain for liburandom_read.so because it's
> hardcoded to "lld" in this makefile.
>
> Make it consistent with the rest of the LLVM toolchain by adding an LLD
> variable to Makefile.include. Keep the fallback for other architectures
> in tools/testing/selftests/bpf/Makefile as it seems like it's something
> specific to this make rule and shouldn't be global.
>
> Clang accepts either a full path or "ld.lld-15" style inputs to
> -fuse-ld= so this will work with LLD defined the same way as the other
> LLVM tools. However, for full paths, we need to use ".../ld.lld" instead
> of the generic driver "lld", but I don't think the original use of "lld"
> was significant as this is always a linux build.
>
> Signed-off-by: James Clark <james.clark@linaro.org>
> ---
> tools/scripts/Makefile.include | 2 ++
> tools/testing/selftests/bpf/Makefile | 8 ++++----
> 2 files changed, 6 insertions(+), 4 deletions(-)
>
> diff --git a/tools/scripts/Makefile.include b/tools/scripts/Makefile.include
> index 380ad84ac51e..5c2d505cba62 100644
> --- a/tools/scripts/Makefile.include
> +++ b/tools/scripts/Makefile.include
> @@ -67,6 +67,7 @@ ifneq ($(LLVM),)
> $(call allow-override,HOSTAR,$(LLVM_PREFIX)llvm-ar$(LLVM_SUFFIX))
> $(call allow-override,LD,$(LLVM_PREFIX)ld.lld$(LLVM_SUFFIX))
> $(call allow-override,HOSTLD,$(LLVM_PREFIX)ld.lld$(LLVM_SUFFIX))
> + $(call allow-override,LLD,$(LLVM_PREFIX)ld.lld$(LLVM_SUFFIX))
> $(call allow-override,CXX,$(LLVM_PREFIX)clang++$(LLVM_SUFFIX))
> $(call allow-override,STRIP,$(LLVM_PREFIX)llvm-strip$(LLVM_SUFFIX))
> $(call allow-override,LLVM_STRIP,$(LLVM_PREFIX)llvm-strip$(LLVM_SUFFIX))
> @@ -91,6 +92,7 @@ else
> # Some tools still require Clang, LLC and/or LLVM utils
> $(call allow-override,CLANG,clang)
> $(call allow-override,LLC,llc)
> + $(call allow-override,LLD,ld.lld)
> $(call allow-override,LLVM_CONFIG,llvm-config)
> $(call allow-override,LLVM_OBJCOPY,llvm-objcopy)
> $(call allow-override,LLVM_STRIP,llvm-strip)
> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> index 6ef6872adbc3..44ba829e5d4d 100644
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile
> @@ -245,9 +245,9 @@ $(OUTPUT)/%:%.c
>
> # LLVM's ld.lld doesn't support all the architectures, so use it only on x86
> ifeq ($(SRCARCH),$(filter $(SRCARCH),x86 riscv))
> -LLD := lld
> +USE_LD := $(LLD)
> else
> -LLD := $(shell command -v $(LD))
> +USE_LD := $(shell command -v $(LD))
> endif
>
> # Filter out -static for liburandom_read.so and its dependent targets so that static builds
> @@ -258,7 +258,7 @@ $(OUTPUT)/liburandom_read.so: urandom_read_lib1.c urandom_read_lib2.c liburandom
> $(filter-out -static,$(CFLAGS) $(LDFLAGS)) \
> $(filter %.c,$^) $(filter-out -static,$(LDLIBS)) \
> -Wno-unused-command-line-argument \
> - -fuse-ld=$(LLD) -Wl,-znoseparate-code -Wl,--build-id=sha1 \
> + -fuse-ld=$(USE_LD) -Wl,-znoseparate-code -Wl,--build-id=sha1 \
Hi James,
This patch breaks the selftests/bpf build:
$ clang --version
clang version 22.1.3 (CentOS 22.1.3-1.el9)
Target: x86_64-redhat-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Configuration file: /etc/clang/x86_64-redhat-linux-gnu-clang.cfg
$ make LLVM=1 -j$(nproc)
[...] # ok
$ make LLVM=1 -C tools/testing/selftests/bpf -j$(nproc)
[...] # somewhere in the middle
LIB liburandom_read.so
clang: error: invalid linker name in argument '-fuse-ld=ld.lld'
make: *** [Makefile:256: /home/isolodrai/workspace/review/linux/tools/testing/selftests/bpf/liburandom_read.so] Error 1
make: Leaving directory '/home/isolodrai/workspace/review/linux/tools/testing/selftests/bpf'
[...]
Or a simpler reproducer:
$ make LLVM=1 OUTPUT=/tmp/out /tmp/out/liburandom_read.so
LIB liburandom_read.so
clang: error: invalid linker name in argument '-fuse-ld=ld.lld'
make: *** [Makefile:256: /tmp/out/liburandom_read.so] Error 1
$ make LLVM=-22 OUTPUT=/tmp/out /tmp/out/liburandom_read.so
LIB liburandom_read.so
clang-22: error: invalid linker name in argument '-fuse-ld=ld.lld-22'
make: *** [Makefile:256: /tmp/out/liburandom_read.so] Error 1
I tried your patches, and only then noticed BPF CI caught this too:
https://github.com/kernel-patches/bpf/actions/runs/25943477658/job/76267174885?pr=12082
> -Wl,--version-script=liburandom_read.map \
> -fPIC -shared -o $@
>
> @@ -268,7 +268,7 @@ $(OUTPUT)/urandom_read: urandom_read.c urandom_read_aux.c $(OUTPUT)/liburandom_r
> $(filter-out -static,$(CFLAGS) $(LDFLAGS)) $(filter %.c,$^) \
> -Wno-unused-command-line-argument \
> -lurandom_read $(filter-out -static,$(LDLIBS)) -L$(OUTPUT) \
> - -fuse-ld=$(LLD) -Wl,-znoseparate-code -Wl,--build-id=sha1 \
> + -fuse-ld=$(USE_LD) -Wl,-znoseparate-code -Wl,--build-id=sha1 \
> -Wl,-rpath=. -o $@
>
> $(OUTPUT)/sign-file: ../../../../scripts/sign-file.c
>
^ permalink raw reply
* Re: [PATCH v3 07/11] HID: Use trace_call__##name() at guarded tracepoint call sites
From: Steven Rostedt @ 2026-05-15 18:43 UTC (permalink / raw)
To: srinivas pandruvada
Cc: Vineeth Pillai (Google), Jiri Kosina, Benjamin Tissoires,
linux-input, linux-trace-kernel, Peter Zijlstra
In-Reply-To: <fbc8c9659f707f46b5d8a6479fc42d5bb1d0efcd.camel@linux.intel.com>
On Fri, 15 May 2026 08:09:25 -0700
srinivas pandruvada <srinivas.pandruvada@linux.intel.com> wrote:
> On Fri, 2026-05-15 at 09:59 -0400, Vineeth Pillai (Google) wrote:
> > From: Vineeth Pillai <vineeth@bitbyteword.org>
> >
> > Replace trace_foo() with the new trace_call__foo() at sites already
> > guarded by trace_foo_enabled(), avoiding a redundant
> > static_branch_unlikely() re-evaluation inside the tracepoint.
> > trace_call__foo() calls the tracepoint callbacks directly without
> > utilizing the static branch again.
> >
> > Original v2 series:
> > https://lore.kernel.org/linux-trace-kernel/20260323160052.17528-1-vineeth@bitbyteword.org/
> >
> > Parts of the original v2 series have already been merged in mainline.
> > This patch is being reposted as a follow-up cleanup for the remaining
> > unmerged pieces.
> >
> > Suggested-by: Steven Rostedt <rostedt@goodmis.org>
> > Suggested-by: Peter Zijlstra <peterz@infradead.org>
> > Signed-off-by: Vineeth Pillai (Google) <vineeth@bitbyteword.org>
> > Assisted-by: Claude:claude-sonnet-4-6
>
> Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
>
Thanks, I'll take this through my tree.
-- Steve
^ permalink raw reply
* Re: [PATCH v2] HID: hid-lenovo-go: cancel cfg_setup work in hid_go_cfg_remove()
From: sashiko-bot @ 2026-05-15 18:23 UTC (permalink / raw)
To: Manish Khadka; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260515174511.78486-1-maskmemanish@gmail.com>
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] Hardware state corruption and crashes due to use of a static global struct for device data.
- [High] The patch provides an incomplete fix for the use-after-free and introduces a resource leak because it assumes the driver core calls remove() on probe failure.
- [High] ABBA Deadlock in `hid_go_cfg_remove()` between `cfg_mutex` and sysfs kernfs removal.
- [High] 'Scheduling while atomic' panic in the LED subsystem callback.
- [High] Hardware event synchronization race condition permanently corrupts the command state machine.
- [High] Resource leak on unbind if the device is operating as a generic HID device.
- [High] Undefined behavior in `mcu_property_out()` due to passing a NULL pointer to `memcpy()`.
--
commit 304d13b89ea5c6814d905d7149a191f0e8fad93d
Author: Manish Khadka <maskmemanish@gmail.com>
HID: hid-lenovo-go: cancel cfg_setup work in hid_go_cfg_remove()
This commit cancels the go_cfg_setup delayed work in hid_go_cfg_remove() to
prevent a use-after-free if the device is unbound before the work executes.
> If the device is unbound within the
> 2 ms scheduling delay (a probe failure rolling back via remove, or a
> fast rmmod after probe), the work fires after hid_destroy_device()
Is the statement about a probe failure rolling back via remove accurate?
In the Linux driver model, the core never calls remove() if probe() fails.
If hid_go_cfg_probe() fails after scheduling the delayed work, the work
remains pending and will dereference the freed hdev when it fires, causing
the exact use-after-free crash this patch attempts to fix.
Additionally, if hid_go_cfg_probe() fails, does hid_go_probe() return the
error code without calling hid_hw_close() and hid_hw_stop(), permanently
leaking the hardware session? Should explicit cleanup be added to the probe
error paths?
> diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c
> index d4d26c7833563..b9d8cde531365 100644
> --- a/drivers/hid/hid-lenovo-go.c
> +++ b/drivers/hid/hid-lenovo-go.c
> @@ -2408,6 +2408,15 @@ static int hid_go_cfg_probe(struct hid_device *hdev,
>
> static void hid_go_cfg_remove(struct hid_device *hdev)
> {
> + /*
> + * cfg_setup is scheduled from hid_go_cfg_probe() with a 2 ms delay
> + * and dereferences drvdata.hdev. Drain it here before tearing
> + * down so the workqueue cannot run after hid_destroy_device()'s
> + * put_device() has released the underlying hdev and dereference
> + * a stale drvdata.hdev pointer.
> + */
> + cancel_delayed_work_sync(&drvdata.go_cfg_setup);
This isn't a bug introduced by this patch, but does the driver use a global
static variable (static struct hid_go_cfg drvdata) to store device state?
A second device probe would unconditionally overwrite drvdata.hdev and
re-initialize both drvdata.cfg_mutex and drvdata.go_cfg_setup.
Re-initializing an active mutex destroys its wait lists, and re-initializing
an active delayed work overwrites its linked list pointers, leading to
immediate memory corruption and kernel panics.
Should the device state be dynamically allocated per-instance instead?
> +
> guard(mutex)(&drvdata.cfg_mutex);
> sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups);
This is a pre-existing issue, but does this sequence cause an ABBA deadlock?
sysfs_remove_groups() drains all active kernfs references, blocking until any
currently executing sysfs operations complete.
However, the driver's sysfs callbacks internally call mcu_property_out(),
which acquires drvdata.cfg_mutex. If a sysfs read or write races with driver
removal, the sysfs thread holds the active kernfs reference and waits for
cfg_mutex, while the removal thread holds cfg_mutex and waits for the kernfs
reference.
Could this result in an unrecoverable deadlock?
[ ... ]
This isn't a bug introduced by this patch, but does the driver assign
hid_go_brightness_set to the led_cdev.brightness_set callback?
The LED subsystem requires this callback to be non-blocking because it can
be invoked from atomic contexts. However, hid_go_brightness_set calls
mcu_property_out(), which allocates memory with GFP_KERNEL, locks a mutex,
and blocks via wait_for_completion_interruptible_timeout().
Will this violate the LED API contract and cause scheduling while atomic
panics? Should the callback be assigned to .brightness_set_blocking instead?
This is another pre-existing issue, but does mcu_property_out() call
reinit_completion() after wait_for_completion_interruptible_timeout() returns?
If a command times out and the hardware response arrives shortly afterward,
the interrupt handler signals the completion.
The next call to mcu_property_out() will send a new command but fail to
re-initialize the completion first. Its wait_for_completion will immediately
return by consuming the stale signal, without waiting for the new response.
Does this permanently desynchronize all subsequent hardware communications?
Should reinit_completion() be called prior to sending the output report?
This is also a pre-existing issue, but does hid_go_remove() bypass hardware
cleanup if the device is operating as a generic HID device?
In hid_go_probe(), if get_endpoint_address() fails (e.g., returns -ENODEV),
the driver returns 0 to allow generic HID operation, leaving the hardware
started and open.
During unbind, hid_go_remove() calls get_endpoint_address() again. If it
returns <= 0, the function returns immediately instead of falling through to
the default case.
Does this mean hid_hw_close() and hid_hw_stop() are bypassed entirely,
permanently leaking the HID hardware session and its resources?
Finally, this is a pre-existing issue, but does mcu_property_out()
unconditionally execute memcpy(dmabuf + header_size, data, len)?
Several hardware commands, such as GET_VERSION_DATA, invoke this function
with data == NULL and len == 0.
Under the C standard, passing a NULL pointer to memcpy() is undefined behavior
regardless of the length. This triggers UBSAN kernel panics if enabled and
allows aggressive compiler optimizations to discard the operation.
Should the copy be conditionally guarded by if (len > 0) or if (data)?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260515174511.78486-1-maskmemanish@gmail.com?part=1
^ permalink raw reply
* [PATCH] HID: multitouch: Fix stale MT slots when contact count drops to zero
From: Dave Carey @ 2026-05-15 17:52 UTC (permalink / raw)
To: linux-input; +Cc: Dave Carey, jikos, bentiss
The INGENIC 17EF:6161 touchscreen (Lenovo Yoga Book 9 14IAH10) reports
HID_DG_CONTACTCOUNT=0 in the frame immediately following the last finger
lift rather than omitting the frame entirely. In mt_touch_report() the
existing code only updates num_expected when contact_count is non-zero,
so a zero contact count on the first packet of a new frame leaves
num_expected at its previous value (e.g. 2 for a two-finger gesture).
The sync check "num_received >= num_expected" then evaluates "0 >= 2"
and never fires, preventing INPUT_MT_DROP_UNUSED from releasing the
stale slots. Those slots remain active in the kernel MT layer until the
next touch, at which point they are released in a batch alongside the
new contact — causing the userspace event consumer to miss the intervening
finger-up sequence and corrupt its gesture session state.
Fix by resetting num_expected to 0 when contact_count is zero and
num_received is still 0 (i.e., this is the first and only packet of the
frame, not a continuation packet in a multi-packet sequence). With
num_expected=0 the sync check "0 >= 0" fires immediately, calling
input_mt_sync_frame() which drops the stale slots via INPUT_MT_DROP_UNUSED.
The num_received==0 guard is critical: continuation packets in a
multi-packet frame arrive after at least one contact has already been
processed (num_received>0), so they are correctly excluded from this
path and the existing multi-packet logic is unaffected.
Signed-off-by: Dave Carey <carvsdriver@gmail.com>
Tested-by: Dave Carey <carvsdriver@gmail.com>
---
This follows commit 108ac841 ("HID: multitouch: Fix Yoga Book 9 14IAH10
touchscreen misclassification"), applied to hid/for-next on 2026-05-12.
That commit established MT_CLS_YOGABOOK9I.
This fix is independent of the companion ghost-contacts patch ("HID:
multitouch: Honor ContactCount for Yoga Book 9 to suppress ghost contacts",
sent 2026-05-14), which adds MT_QUIRK_CONTACT_CNT_ACCURATE to the same
class. Both apply cleanly on top of 108ac841.
Tested on Lenovo Yoga Book 9 14IAH10 (83KJ).
drivers/hid/hid-multitouch.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index ec04dba..e91ba89 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -1336,6 +1336,18 @@ static void mt_touch_report(struct hid_device *hid,
/* A non 0 contact count always indicates a first packet */
else if (contact_count)
app->num_expected = contact_count;
+ /*
+ * contact_count == 0 on the first packet of a new frame means
+ * all contacts have lifted (the firmware sends an explicit zero
+ * to signal all-up rather than simply omitting the frame).
+ * Reset num_expected so that the sync check below fires and
+ * INPUT_MT_DROP_UNUSED releases any stale slots. This is safe
+ * for multi-packet continuation frames because those arrive with
+ * num_received > 0 (at least one contact was already processed
+ * from the preceding first-packet in the same frame).
+ */
+ else if (app->num_received == 0)
+ app->num_expected = 0;
}
app->prev_scantime = scantime;
--
2.54.0
^ permalink raw reply related
* [PATCH v2] HID: hid-lenovo-go: cancel cfg_setup work in hid_go_cfg_remove()
From: Manish Khadka @ 2026-05-15 17:45 UTC (permalink / raw)
To: linux-input
Cc: Derek J . Clark, Mark Pearson, Jiri Kosina, Benjamin Tissoires,
linux-kernel
In-Reply-To: <20260515161830.E6E2BC2BCB3@smtp.kernel.org>
hid_go_cfg_probe() initialises drvdata.go_cfg_setup and schedules it
to run 2 ms later:
INIT_DELAYED_WORK(&drvdata.go_cfg_setup, &cfg_setup);
schedule_delayed_work(&drvdata.go_cfg_setup, msecs_to_jiffies(2));
cfg_setup() dereferences drvdata.hdev to issue MCU command requests.
hid_go_cfg_remove() tears down sysfs and stops the HID device, but
never drains the delayed work. If the device is unbound within the
2 ms scheduling delay (a probe failure rolling back via remove, or a
fast rmmod after probe), the work fires after hid_destroy_device()
has dropped its reference and released the underlying hdev struct,
leaving cfg_setup() with a stale drvdata.hdev pointer.
Mirror the sibling driver hid-lenovo-go-s.c, whose hid_gos_cfg_remove()
already calls cancel_delayed_work_sync() on its analogous work, and
drain go_cfg_setup at the top of hid_go_cfg_remove(). The cancel
must come before guard(mutex)(&drvdata.cfg_mutex) because cfg_setup()
acquires that mutex; reversing the order would deadlock.
Fixes: d69ccfcbc955 ("HID: hid-lenovo-go: Add Lenovo Legion Go Series HID Driver")
Cc: stable@vger.kernel.org
Signed-off-by: Manish Khadka <maskmemanish@gmail.com>
---
v1 -> v2:
- Address Sashiko AI review feedback:
* [Low] Correct the inaccurate description of how drvdata.hdev
becomes stale. hid_set_drvdata(hdev, NULL) only clears the
per-hdev driver_data, not the global drvdata.hdev; the actual
stale-pointer mechanism is hid_destroy_device()'s put_device()
releasing the underlying hdev struct. Commit message and the
inline comment in hid_go_cfg_remove() are corrected accordingly.
- The other six review points (ABBA deadlock with sysfs_remove_groups,
global static drvdata corruption on multi-device/rebind, devm vs.
explicit teardown race, probe error-path leaks, mcu_property_out
unconditional return 0, schedule_delayed_work boolean misuse)
are all valid pre-existing bugs in the driver but predate and are
independent of this patch. Each deserves its own fix; will be
addressed in separate patches.
Thanks to Sashiko AI review for all seven points.
drivers/hid/hid-lenovo-go.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c
index d4d26c783356..b9d8cde53136 100644
--- a/drivers/hid/hid-lenovo-go.c
+++ b/drivers/hid/hid-lenovo-go.c
@@ -2408,6 +2408,15 @@ static int hid_go_cfg_probe(struct hid_device *hdev,
static void hid_go_cfg_remove(struct hid_device *hdev)
{
+ /*
+ * cfg_setup is scheduled from hid_go_cfg_probe() with a 2 ms delay
+ * and dereferences drvdata.hdev. Drain it here before tearing
+ * down so the workqueue cannot run after hid_destroy_device()'s
+ * put_device() has released the underlying hdev and dereference
+ * a stale drvdata.hdev pointer.
+ */
+ cancel_delayed_work_sync(&drvdata.go_cfg_setup);
+
guard(mutex)(&drvdata.cfg_mutex);
sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups);
hid_hw_close(hdev);
--
2.43.0
^ permalink raw reply related
* [PATCH v2] HID: appleir: fix UAF on pending key_up_timer in remove()
From: Manish Khadka @ 2026-05-15 17:32 UTC (permalink / raw)
To: linux-input; +Cc: Jiri Kosina, Benjamin Tissoires, linux-kernel
In-Reply-To: <20260515160218.4D39EC2BCB0@smtp.kernel.org>
appleir_remove() runs hid_hw_stop() before timer_delete_sync().
hid_hw_stop() synchronously unregisters the HID input device via
hid_disconnect() -> hidinput_disconnect() -> input_unregister_device(),
which drops the last reference and frees the underlying input_dev when
no userspace handle holds it open.
key_up_tick() reads appleir->input_dev and calls input_report_key() /
input_sync() on it. The timer is armed from appleir_raw_event() with
a HZ/8 (~125 ms) timeout on every keydown and key-repeat report. If a
key was pressed shortly before the device is disconnected, the timer
can fire after hid_hw_stop() has freed input_dev but before the
teardown drains it.
A simple reorder is not sufficient. Putting the timer drain first
still leaves a window where a USB URB completion (raw_event) running
during hid_hw_stop() can call mod_timer() and re-arm the timer, which
then fires after hidinput_disconnect() has freed input_dev. The same
URB-completion window also lets raw_event() reach key_up(), key_down()
and battery_flat() directly, all of which dereference
appleir->input_dev.
Introduce a 'removing' flag on struct appleir, gated by the existing
spinlock. appleir_remove() sets the flag under the lock and then
shuts down the timer with timer_shutdown_sync(), which both drains any
in-flight callback and permanently disables further mod_timer() calls.
appleir_raw_event() and key_up_tick() bail out early if the flag is
set, so no path can arm or run the timer, or dereference
appleir->input_dev, after remove() has started tearing down.
The keyrepeat and flatbattery branches of appleir_raw_event()
previously called into the input layer without holding the spinlock;
take it now so the flag check is well-defined. This incidentally
closes a pre-existing read-side race on appleir->current_key in the
keyrepeat branch.
This bug is structurally a sibling of commit 4db2af929279 ("HID:
appletb-kbd: fix UAF in inactivity-timer cleanup path") and has been
present since the driver was introduced.
Fixes: 9a4a5574ce42 ("HID: appleir: add support for Apple ir devices")
Cc: stable@vger.kernel.org
Signed-off-by: Manish Khadka <maskmemanish@gmail.com>
---
v1 -> v2:
- Address Sashiko AI review feedback:
* [Critical] Gate the flatbattery branch in appleir_raw_event()
under the existing spinlock + removing flag so battery_flat(),
which does dev_err(&appleir->input_dev->dev, ...), cannot UAF
during hid_hw_stop().
* [Medium] Use timer_shutdown_sync() instead of timer_delete_sync()
so the timer is permanently disabled in addition to drained,
providing belt-and-suspenders against any future arming site
that bypasses the removing flag.
Thanks to Sashiko AI review for both points.
drivers/hid/hid-appleir.c | 45 ++++++++++++++++++++++++++++++---------
1 file changed, 35 insertions(+), 10 deletions(-)
diff --git a/drivers/hid/hid-appleir.c b/drivers/hid/hid-appleir.c
index 5e8ced7bc05a..adaa44a858ed 100644
--- a/drivers/hid/hid-appleir.c
+++ b/drivers/hid/hid-appleir.c
@@ -109,9 +109,10 @@ struct appleir {
struct hid_device *hid;
unsigned short keymap[ARRAY_SIZE(appleir_key_table)];
struct timer_list key_up_timer; /* timer for key up */
- spinlock_t lock; /* protects .current_key */
+ spinlock_t lock; /* protects .current_key, .removing */
int current_key; /* the currently pressed key */
int prev_key_idx; /* key index in a 2 packets message */
+ bool removing; /* set during teardown; gates input_dev access */
};
static int get_key(int data)
@@ -172,7 +173,7 @@ static void key_up_tick(struct timer_list *t)
unsigned long flags;
spin_lock_irqsave(&appleir->lock, flags);
- if (appleir->current_key) {
+ if (!appleir->removing && appleir->current_key) {
key_up(hid, appleir, appleir->current_key);
appleir->current_key = 0;
}
@@ -195,6 +196,10 @@ static int appleir_raw_event(struct hid_device *hid, struct hid_report *report,
int index;
spin_lock_irqsave(&appleir->lock, flags);
+ if (appleir->removing) {
+ spin_unlock_irqrestore(&appleir->lock, flags);
+ goto out;
+ }
/*
* If we already have a key down, take it up before marking
* this one down
@@ -229,17 +234,25 @@ static int appleir_raw_event(struct hid_device *hid, struct hid_report *report,
appleir->prev_key_idx = 0;
if (!memcmp(data, keyrepeat, sizeof(keyrepeat))) {
- key_down(hid, appleir, appleir->current_key);
- /*
- * Remote doesn't do key up, either pull them up, in the test
- * above, or here set a timer which pulls them up after 1/8 s
- */
- mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
+ spin_lock_irqsave(&appleir->lock, flags);
+ if (!appleir->removing) {
+ key_down(hid, appleir, appleir->current_key);
+ /*
+ * Remote doesn't do key up, either pull them up, in
+ * the test above, or here set a timer which pulls them
+ * up after 1/8 s
+ */
+ mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
+ }
+ spin_unlock_irqrestore(&appleir->lock, flags);
goto out;
}
if (!memcmp(data, flatbattery, sizeof(flatbattery))) {
- battery_flat(appleir);
+ spin_lock_irqsave(&appleir->lock, flags);
+ if (!appleir->removing)
+ battery_flat(appleir);
+ spin_unlock_irqrestore(&appleir->lock, flags);
/* Fall through */
}
@@ -318,8 +331,20 @@ static int appleir_probe(struct hid_device *hid, const struct hid_device_id *id)
static void appleir_remove(struct hid_device *hid)
{
struct appleir *appleir = hid_get_drvdata(hid);
+ unsigned long flags;
+
+ /*
+ * Mark the driver as tearing down so that any concurrent raw_event
+ * (e.g. from a USB URB completion that hid_hw_stop() has not yet
+ * killed) and the key_up_timer softirq stop touching input_dev
+ * before hid_hw_stop() frees it via hidinput_disconnect().
+ */
+ spin_lock_irqsave(&appleir->lock, flags);
+ appleir->removing = true;
+ spin_unlock_irqrestore(&appleir->lock, flags);
+
+ timer_shutdown_sync(&appleir->key_up_timer);
hid_hw_stop(hid);
- timer_delete_sync(&appleir->key_up_timer);
}
static const struct hid_device_id appleir_devices[] = {
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v1] Input: Use named initializers for arrays of i2c_device_data
From: sashiko-bot @ 2026-05-15 16:53 UTC (permalink / raw)
Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260515164848.497608-2-u.kleine-koenig@baylibre.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The commit title incorrectly references `i2c_device_data` instead of `i2c_device_id`.
--
commit f1077bbdac0316b24fea0fef5850a04e87106e2d
Author: Uwe Kleine-König (The Capable Hub) <u.kleine-koenig@baylibre.com>
Input: Use named initializers for arrays of i2c_device_data
This commit updates the I2C device ID tables in various input drivers to
use named initializers. This improves readability and robustness, especially
in preparation for a planned change to struct i2c_device_id.
> Input: Use named initializers for arrays of i2c_device_data
This isn't a bug, but is there a typo in the subject line? The patch modifies
arrays of struct i2c_device_id, not i2c_device_data (which is correctly noted
in the commit body).
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260515164848.497608-2-u.kleine-koenig@baylibre.com?part=1
^ permalink raw reply
* [PATCH] Input: iqs5xx - Drop unused i2c driver_data
From: Uwe Kleine-König (The Capable Hub) @ 2026-05-15 16:51 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Andrew Morton, Randy Dunlap, Andy Shevchenko, linux-input,
linux-kernel
The driver doesn't make use of the value that was explicitly assigned to
the .driver_data members. Drop the assignment. While touching the array,
convert it to use named initialization which is easier to understand.
Signed-off-by: Uwe Kleine-König (The Capable Hub) <u.kleine-koenig@baylibre.com>
---
Hello,
this is related to https://lore.kernel.org/linux-input/20260515164848.497608-2-u.kleine-koenig@baylibre.com/T/#u.
I intended to send it in a series, but failed to do that, so this patch comes
as a separate change.
Best regards
Uwe
drivers/input/touchscreen/iqs5xx.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/input/touchscreen/iqs5xx.c b/drivers/input/touchscreen/iqs5xx.c
index 587665e4e6fd..b9bbe8b3eab8 100644
--- a/drivers/input/touchscreen/iqs5xx.c
+++ b/drivers/input/touchscreen/iqs5xx.c
@@ -1049,9 +1049,9 @@ static int iqs5xx_probe(struct i2c_client *client)
}
static const struct i2c_device_id iqs5xx_id[] = {
- { "iqs550", 0 },
- { "iqs572", 1 },
- { "iqs525", 2 },
+ { .name = "iqs550" },
+ { .name = "iqs572" },
+ { .name = "iqs525" },
{ }
};
MODULE_DEVICE_TABLE(i2c, iqs5xx_id);
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
--
2.47.3
^ permalink raw reply related
* Re: [PATCH v3 0/6] iio: hid-sensor: standardize scan_type initialization
From: srinivas pandruvada @ 2026-05-15 16:50 UTC (permalink / raw)
To: Natália Salvino André, andy, dlechner, jic23, jikos,
nuno.sa
Cc: linux-iio, linux-input
In-Reply-To: <20260512012302.20883-1-natalia.andre@ime.usp.br>
On Mon, 2026-05-11 at 22:16 -0300, Natália Salvino André wrote:
> This series refactors the HID sensor drivers to standardize the
> initialization of the iio_chan_spec scan_type structure.
> Direct initialization using compound literals was used.
>
> This change improves code readability and ensures that all fields
> of the scan_type structure are properly zero-initialized.
> Additionally, for the hid-sensor-accel-3d driver, the channel
> initialization loop was refactored to iterate directly over the
> scan indices, eliminating redundant logic.
>
From manual check the change looks good, with some minor comment.
Thanks,
Srinivas
> Natália Salvino André (6):
> iio: accel: HID: hid-sensor-accel-3d: Refactor channel
> initialization
> iio: gyro: HID: hid-sensor-gyro-3d: Refactor channel initialization
> iio: light: HID: hid-sensor-als: Refactor channel initialization
> iio: light: HID: hid-sensor-prox: Refactor channel initialization
> iio: magnetometer: HID: hid-sensor-magn-3d: Refactor channel
> initialization
> iio: pressure: HID: hid-sensor-press: Refactor channel
> initialization
>
> drivers/iio/accel/hid-sensor-accel-3d.c | 27 +++++++----------
> --
> drivers/iio/gyro/hid-sensor-gyro-3d.c | 24 +++++++----------
> drivers/iio/light/hid-sensor-als.c | 18 +++++--------
> drivers/iio/light/hid-sensor-prox.c | 19 +++++--------
> drivers/iio/magnetometer/hid-sensor-magn-3d.c | 20 +++++---------
> drivers/iio/pressure/hid-sensor-press.c | 19 +++++--------
> 6 files changed, 42 insertions(+), 85 deletions(-)
^ permalink raw reply
* [PATCH v1] Input: Use named initializers for arrays of i2c_device_data
From: Uwe Kleine-König (The Capable Hub) @ 2026-05-15 16:48 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Anshul Dalal, Michael Hennerich, Yassine Oudjana, Linus Walleij,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea,
Support Opensource, Nick Dyer, Hans de Goede, Job Noorman,
Mika Penttilä, Maxime Coquelin, Alexandre Torgue, Kees Cook,
bui duc phuc, Thorsten Blum, Yauhen Kharuzhy, Sakari Ailus,
Marco Crivellari, Minseong Kim, Ingo Molnar, Thomas Gleixner,
Oleh Kuzhylnyi, Marek Vasut, Krzysztof Kozlowski,
Geert Uytterhoeven, Josua Mayer, Michael Tretter, Jeff LaBundy,
Javier Carrasco, David Heidelberg, Petr Hodina, Svyatoslav Ryhel,
Johannes Kirchmair, Andy Shevchenko, Xichao Zhao, linux-input,
linux-kernel, linux-arm-kernel, platform-driver-x86, linux-stm32
While being less compact, using named initializers allows to more easily
see which members of the structs are assigned which value without having
to lookup the declaration of the struct. And it's also more robust
against changes to the struct definition.
The mentioned robustness is relevant for a planned change to struct
i2c_device_id that replaces .driver_data by an anonymous union.
This patch doesn't modify the compiled arrays, only their representation
in source form benefits. The former was confirmed with x86 and arm64
builds.
Signed-off-by: Uwe Kleine-König (The Capable Hub) <u.kleine-koenig@baylibre.com>
---
Hello,
the mentioned change to i2c_device_id is the following:
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index 23ff24080dfd..aebd3a5e90af 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -477,7 +477,11 @@ struct rpmsg_device_id {
struct i2c_device_id {
char name[I2C_NAME_SIZE];
- kernel_ulong_t driver_data; /* Data private to the driver */
+ union {
+ /* Data private to the driver */
+ kernel_ulong_t driver_data;
+ const void *driver_data_ptr;
+ };
};
/* pci_epf */
and this requires that .driver_data is assigned via a named initializer
for static data. This requirement isn't a bad one because named
initializers are also much better readable than list initializers.
The union added to struct i2c_device_id enables further cleanups like:
diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c
index 66ada7ffbc80..94aa4dc002c5 100644
--- a/drivers/input/touchscreen/ili210x.c
+++ b/drivers/input/touchscreen/ili210x.c
@@ -969,7 +969,7 @@ static int ili210x_i2c_probe(struct i2c_client *client)
chip = device_get_match_data(dev);
if (!chip && id)
- chip = (const struct ili2xxx_chip *)id->driver_data;
+ chip = id->driver_data_ptr;
if (!chip)
return dev_err_probe(&client->dev, -ENODEV, "unknown device model\n");
@@ -1049,10 +1049,10 @@ static int ili210x_i2c_probe(struct i2c_client *client)
}
static const struct i2c_device_id ili210x_i2c_id[] = {
- { .name = "ili210x", .driver_data = (long)&ili210x_chip },
- { .name = "ili2117", .driver_data = (long)&ili211x_chip },
- { .name = "ili2120", .driver_data = (long)&ili212x_chip },
- { .name = "ili251x", .driver_data = (long)&ili251x_chip },
+ { .name = "ili210x", .driver_data_ptr = &ili210x_chip },
+ { .name = "ili2117", .driver_data_ptr = &ili211x_chip },
+ { .name = "ili2120", .driver_data_ptr = &ili212x_chip },
+ { .name = "ili251x", .driver_data_ptr = &ili251x_chip },
{ }
};
MODULE_DEVICE_TABLE(i2c, ili210x_i2c_id);
that are an improvement for readability (again!) and it keeps some
properties of the pointers (here: being const) without having to pay
attention for that.
My additional motivation for this effort is CHERI[1]. This is a hardware
extension that uses 128 bit pointers but unsigned long is still 64 bit.
So with CHERI you cannot store pointers in unsigned long variables.
Best regards
Uwe
drivers/input/joystick/adafruit-seesaw.c | 2 +-
drivers/input/joystick/as5011.c | 2 +-
drivers/input/joystick/qwiic-joystick.c | 2 +-
drivers/input/keyboard/adp5588-keys.c | 4 ++--
drivers/input/keyboard/cap11xx.c | 14 +++++++-------
drivers/input/keyboard/cypress-sf.c | 2 +-
drivers/input/keyboard/dlink-dir685-touchkeys.c | 2 +-
drivers/input/keyboard/lm8323.c | 2 +-
drivers/input/keyboard/lm8333.c | 2 +-
drivers/input/keyboard/max7359_keypad.c | 2 +-
drivers/input/keyboard/mpr121_touchkey.c | 2 +-
drivers/input/keyboard/qt1070.c | 2 +-
drivers/input/keyboard/qt2160.c | 2 +-
drivers/input/keyboard/tca8418_keypad.c | 2 +-
drivers/input/keyboard/tm2-touchkey.c | 2 +-
drivers/input/misc/ad714x-i2c.c | 10 +++++-----
drivers/input/misc/adxl34x-i2c.c | 2 +-
drivers/input/misc/apanel.c | 2 +-
drivers/input/misc/atmel_captouch.c | 2 +-
drivers/input/misc/bma150.c | 6 +++---
drivers/input/misc/cma3000_d0x_i2c.c | 2 +-
drivers/input/misc/da7280.c | 2 +-
drivers/input/misc/drv260x.c | 8 ++++----
drivers/input/misc/drv2665.c | 2 +-
drivers/input/misc/drv2667.c | 2 +-
drivers/input/misc/kxtj9.c | 2 +-
drivers/input/misc/mma8450.c | 2 +-
drivers/input/misc/pcf8574_keypad.c | 2 +-
drivers/input/mouse/cyapa.c | 2 +-
drivers/input/mouse/elan_i2c_core.c | 2 +-
drivers/input/mouse/synaptics_i2c.c | 2 +-
drivers/input/rmi4/rmi_i2c.c | 2 +-
drivers/input/rmi4/rmi_smbus.c | 2 +-
drivers/input/touchscreen/ad7879-i2c.c | 4 ++--
drivers/input/touchscreen/ar1021_i2c.c | 2 +-
drivers/input/touchscreen/atmel_mxt_ts.c | 10 +++++-----
drivers/input/touchscreen/auo-pixcir-ts.c | 2 +-
drivers/input/touchscreen/bu21013_ts.c | 2 +-
drivers/input/touchscreen/bu21029_ts.c | 2 +-
drivers/input/touchscreen/cy8ctma140.c | 2 +-
drivers/input/touchscreen/cy8ctmg110_ts.c | 2 +-
drivers/input/touchscreen/cyttsp5.c | 2 +-
drivers/input/touchscreen/cyttsp_i2c.c | 2 +-
drivers/input/touchscreen/eeti_ts.c | 2 +-
drivers/input/touchscreen/egalax_ts.c | 2 +-
drivers/input/touchscreen/elants_i2c.c | 6 +++---
drivers/input/touchscreen/exc3000.c | 8 ++++----
drivers/input/touchscreen/goodix.c | 2 +-
drivers/input/touchscreen/hideep.c | 2 +-
drivers/input/touchscreen/himax_hx83112b.c | 4 ++--
drivers/input/touchscreen/hynitron-cst816x.c | 2 +-
drivers/input/touchscreen/ili210x.c | 8 ++++----
drivers/input/touchscreen/ilitek_ts_i2c.c | 2 +-
drivers/input/touchscreen/max11801_ts.c | 2 +-
drivers/input/touchscreen/melfas_mip4.c | 2 +-
drivers/input/touchscreen/migor_ts.c | 2 +-
drivers/input/touchscreen/mms114.c | 2 +-
drivers/input/touchscreen/novatek-nvt-ts.c | 4 ++--
drivers/input/touchscreen/pixcir_i2c_ts.c | 4 ++--
drivers/input/touchscreen/raydium_i2c_ts.c | 4 ++--
drivers/input/touchscreen/rohm_bu21023.c | 2 +-
drivers/input/touchscreen/s6sy761.c | 2 +-
drivers/input/touchscreen/silead.c | 12 ++++++------
drivers/input/touchscreen/sis_i2c.c | 4 ++--
drivers/input/touchscreen/st1232.c | 4 ++--
drivers/input/touchscreen/stmfts.c | 2 +-
drivers/input/touchscreen/tsc2004.c | 2 +-
drivers/input/touchscreen/tsc2007_core.c | 2 +-
drivers/input/touchscreen/wacom_i2c.c | 2 +-
drivers/input/touchscreen/wdt87xx_i2c.c | 2 +-
drivers/input/touchscreen/zet6223.c | 2 +-
drivers/input/touchscreen/zforce_ts.c | 2 +-
72 files changed, 112 insertions(+), 112 deletions(-)
diff --git a/drivers/input/joystick/adafruit-seesaw.c b/drivers/input/joystick/adafruit-seesaw.c
index c248c15b849d..5835ed1e29e1 100644
--- a/drivers/input/joystick/adafruit-seesaw.c
+++ b/drivers/input/joystick/adafruit-seesaw.c
@@ -304,7 +304,7 @@ static int seesaw_probe(struct i2c_client *client)
}
static const struct i2c_device_id seesaw_id_table[] = {
- { SEESAW_DEVICE_NAME },
+ { .name = SEESAW_DEVICE_NAME },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, seesaw_id_table);
diff --git a/drivers/input/joystick/as5011.c b/drivers/input/joystick/as5011.c
index 7b4d61626898..5fca92bf9df0 100644
--- a/drivers/input/joystick/as5011.c
+++ b/drivers/input/joystick/as5011.c
@@ -337,7 +337,7 @@ static void as5011_remove(struct i2c_client *client)
}
static const struct i2c_device_id as5011_id[] = {
- { MODULE_DEVICE_ALIAS },
+ { .name = MODULE_DEVICE_ALIAS },
{ }
};
MODULE_DEVICE_TABLE(i2c, as5011_id);
diff --git a/drivers/input/joystick/qwiic-joystick.c b/drivers/input/joystick/qwiic-joystick.c
index 6f989d00d3ec..c471afc7862b 100644
--- a/drivers/input/joystick/qwiic-joystick.c
+++ b/drivers/input/joystick/qwiic-joystick.c
@@ -126,7 +126,7 @@ MODULE_DEVICE_TABLE(of, of_qwiic_match);
#endif /* CONFIG_OF */
static const struct i2c_device_id qwiic_id_table[] = {
- { KBUILD_MODNAME },
+ { .name = KBUILD_MODNAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, qwiic_id_table);
diff --git a/drivers/input/keyboard/adp5588-keys.c b/drivers/input/keyboard/adp5588-keys.c
index 414fbef4abf9..8d14d0f69d4e 100644
--- a/drivers/input/keyboard/adp5588-keys.c
+++ b/drivers/input/keyboard/adp5588-keys.c
@@ -842,8 +842,8 @@ static int adp5588_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(adp5588_dev_pm_ops, adp5588_suspend, adp5588_resume);
static const struct i2c_device_id adp5588_id[] = {
- { "adp5588-keys" },
- { "adp5587-keys" },
+ { .name = "adp5588-keys" },
+ { .name = "adp5587-keys" },
{ }
};
MODULE_DEVICE_TABLE(i2c, adp5588_id);
diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c
index 0c17cbaa3d27..2447c1ae2166 100644
--- a/drivers/input/keyboard/cap11xx.c
+++ b/drivers/input/keyboard/cap11xx.c
@@ -651,13 +651,13 @@ static const struct of_device_id cap11xx_dt_ids[] = {
MODULE_DEVICE_TABLE(of, cap11xx_dt_ids);
static const struct i2c_device_id cap11xx_i2c_ids[] = {
- { "cap1106", (kernel_ulong_t)&cap1106_model },
- { "cap1126", (kernel_ulong_t)&cap1126_model },
- { "cap1188", (kernel_ulong_t)&cap1188_model },
- { "cap1203", (kernel_ulong_t)&cap1203_model },
- { "cap1206", (kernel_ulong_t)&cap1206_model },
- { "cap1293", (kernel_ulong_t)&cap1293_model },
- { "cap1298", (kernel_ulong_t)&cap1298_model },
+ { .name = "cap1106", .driver_data = (kernel_ulong_t)&cap1106_model },
+ { .name = "cap1126", .driver_data = (kernel_ulong_t)&cap1126_model },
+ { .name = "cap1188", .driver_data = (kernel_ulong_t)&cap1188_model },
+ { .name = "cap1203", .driver_data = (kernel_ulong_t)&cap1203_model },
+ { .name = "cap1206", .driver_data = (kernel_ulong_t)&cap1206_model },
+ { .name = "cap1293", .driver_data = (kernel_ulong_t)&cap1293_model },
+ { .name = "cap1298", .driver_data = (kernel_ulong_t)&cap1298_model },
{ }
};
MODULE_DEVICE_TABLE(i2c, cap11xx_i2c_ids);
diff --git a/drivers/input/keyboard/cypress-sf.c b/drivers/input/keyboard/cypress-sf.c
index 335b72efc5aa..4b2ae07f315c 100644
--- a/drivers/input/keyboard/cypress-sf.c
+++ b/drivers/input/keyboard/cypress-sf.c
@@ -209,7 +209,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(cypress_sf_pm_ops,
cypress_sf_suspend, cypress_sf_resume);
static const struct i2c_device_id cypress_sf_id_table[] = {
- { CYPRESS_SF_DEV_NAME },
+ { .name = CYPRESS_SF_DEV_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, cypress_sf_id_table);
diff --git a/drivers/input/keyboard/dlink-dir685-touchkeys.c b/drivers/input/keyboard/dlink-dir685-touchkeys.c
index 4184dd2eaeeb..bf94367f6b64 100644
--- a/drivers/input/keyboard/dlink-dir685-touchkeys.c
+++ b/drivers/input/keyboard/dlink-dir685-touchkeys.c
@@ -128,7 +128,7 @@ static int dir685_tk_probe(struct i2c_client *client)
}
static const struct i2c_device_id dir685_tk_id[] = {
- { "dir685tk" },
+ { .name = "dir685tk" },
{ }
};
MODULE_DEVICE_TABLE(i2c, dir685_tk_id);
diff --git a/drivers/input/keyboard/lm8323.c b/drivers/input/keyboard/lm8323.c
index e19442c6f80f..7b48f0cf62ef 100644
--- a/drivers/input/keyboard/lm8323.c
+++ b/drivers/input/keyboard/lm8323.c
@@ -788,7 +788,7 @@ static int lm8323_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(lm8323_pm_ops, lm8323_suspend, lm8323_resume);
static const struct i2c_device_id lm8323_id[] = {
- { "lm8323" },
+ { .name = "lm8323" },
{ }
};
diff --git a/drivers/input/keyboard/lm8333.c b/drivers/input/keyboard/lm8333.c
index 384baabf9924..34be5b2e476d 100644
--- a/drivers/input/keyboard/lm8333.c
+++ b/drivers/input/keyboard/lm8333.c
@@ -194,7 +194,7 @@ static int lm8333_probe(struct i2c_client *client)
}
static const struct i2c_device_id lm8333_id[] = {
- { "lm8333" },
+ { .name = "lm8333" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm8333_id);
diff --git a/drivers/input/keyboard/max7359_keypad.c b/drivers/input/keyboard/max7359_keypad.c
index c10726b5e4d1..f8fb44bc4915 100644
--- a/drivers/input/keyboard/max7359_keypad.c
+++ b/drivers/input/keyboard/max7359_keypad.c
@@ -270,7 +270,7 @@ static int max7359_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(max7359_pm, max7359_suspend, max7359_resume);
static const struct i2c_device_id max7359_ids[] = {
- { "max7359" },
+ { .name = "max7359" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max7359_ids);
diff --git a/drivers/input/keyboard/mpr121_touchkey.c b/drivers/input/keyboard/mpr121_touchkey.c
index 47edc161ec77..a1a102855590 100644
--- a/drivers/input/keyboard/mpr121_touchkey.c
+++ b/drivers/input/keyboard/mpr121_touchkey.c
@@ -322,7 +322,7 @@ static int mpr_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(mpr121_touchkey_pm_ops, mpr_suspend, mpr_resume);
static const struct i2c_device_id mpr121_id[] = {
- { "mpr121_touchkey" },
+ { .name = "mpr121_touchkey" },
{ }
};
MODULE_DEVICE_TABLE(i2c, mpr121_id);
diff --git a/drivers/input/keyboard/qt1070.c b/drivers/input/keyboard/qt1070.c
index b255b997e279..e31cb115a954 100644
--- a/drivers/input/keyboard/qt1070.c
+++ b/drivers/input/keyboard/qt1070.c
@@ -233,7 +233,7 @@ static int qt1070_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(qt1070_pm_ops, qt1070_suspend, qt1070_resume);
static const struct i2c_device_id qt1070_id[] = {
- { "qt1070" },
+ { .name = "qt1070" },
{ }
};
MODULE_DEVICE_TABLE(i2c, qt1070_id);
diff --git a/drivers/input/keyboard/qt2160.c b/drivers/input/keyboard/qt2160.c
index 53f5255fd19d..e64adaca10ff 100644
--- a/drivers/input/keyboard/qt2160.c
+++ b/drivers/input/keyboard/qt2160.c
@@ -393,7 +393,7 @@ static int qt2160_probe(struct i2c_client *client)
}
static const struct i2c_device_id qt2160_idtable[] = {
- { "qt2160" },
+ { .name = "qt2160" },
{ }
};
diff --git a/drivers/input/keyboard/tca8418_keypad.c b/drivers/input/keyboard/tca8418_keypad.c
index 68c0afafee7b..eb5be644f236 100644
--- a/drivers/input/keyboard/tca8418_keypad.c
+++ b/drivers/input/keyboard/tca8418_keypad.c
@@ -354,7 +354,7 @@ static int tca8418_keypad_probe(struct i2c_client *client)
}
static const struct i2c_device_id tca8418_id[] = {
- { "tca8418", 8418, },
+ { .name = "tca8418", .driver_data = 8418 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tca8418_id);
diff --git a/drivers/input/keyboard/tm2-touchkey.c b/drivers/input/keyboard/tm2-touchkey.c
index 55d699d9037d..967bbe553c06 100644
--- a/drivers/input/keyboard/tm2-touchkey.c
+++ b/drivers/input/keyboard/tm2-touchkey.c
@@ -326,7 +326,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tm2_touchkey_pm_ops,
tm2_touchkey_suspend, tm2_touchkey_resume);
static const struct i2c_device_id tm2_touchkey_id_table[] = {
- { TM2_TOUCHKEY_DEV_NAME },
+ { .name = TM2_TOUCHKEY_DEV_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, tm2_touchkey_id_table);
diff --git a/drivers/input/misc/ad714x-i2c.c b/drivers/input/misc/ad714x-i2c.c
index 2adb7a058362..c3cb561512ed 100644
--- a/drivers/input/misc/ad714x-i2c.c
+++ b/drivers/input/misc/ad714x-i2c.c
@@ -72,11 +72,11 @@ static int ad714x_i2c_probe(struct i2c_client *client)
}
static const struct i2c_device_id ad714x_id[] = {
- { "ad7142_captouch" },
- { "ad7143_captouch" },
- { "ad7147_captouch" },
- { "ad7147a_captouch" },
- { "ad7148_captouch" },
+ { .name = "ad7142_captouch" },
+ { .name = "ad7143_captouch" },
+ { .name = "ad7147_captouch" },
+ { .name = "ad7147a_captouch" },
+ { .name = "ad7148_captouch" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ad714x_id);
diff --git a/drivers/input/misc/adxl34x-i2c.c b/drivers/input/misc/adxl34x-i2c.c
index 5ea0ce42a507..84665c612f3c 100644
--- a/drivers/input/misc/adxl34x-i2c.c
+++ b/drivers/input/misc/adxl34x-i2c.c
@@ -96,7 +96,7 @@ static int adxl34x_i2c_probe(struct i2c_client *client)
}
static const struct i2c_device_id adxl34x_id[] = {
- { "adxl34x" },
+ { .name = "adxl34x" },
{ }
};
diff --git a/drivers/input/misc/apanel.c b/drivers/input/misc/apanel.c
index d43aebd785b7..3eb02a64acfb 100644
--- a/drivers/input/misc/apanel.c
+++ b/drivers/input/misc/apanel.c
@@ -192,7 +192,7 @@ static void apanel_shutdown(struct i2c_client *client)
}
static const struct i2c_device_id apanel_id[] = {
- { "fujitsu_apanel" },
+ { .name = "fujitsu_apanel" },
{ }
};
MODULE_DEVICE_TABLE(i2c, apanel_id);
diff --git a/drivers/input/misc/atmel_captouch.c b/drivers/input/misc/atmel_captouch.c
index f9744cf0ca80..fc4abea68479 100644
--- a/drivers/input/misc/atmel_captouch.c
+++ b/drivers/input/misc/atmel_captouch.c
@@ -257,7 +257,7 @@ static const struct of_device_id atmel_captouch_of_id[] = {
MODULE_DEVICE_TABLE(of, atmel_captouch_of_id);
static const struct i2c_device_id atmel_captouch_id[] = {
- { "atmel_captouch" },
+ { .name = "atmel_captouch" },
{ }
};
MODULE_DEVICE_TABLE(i2c, atmel_captouch_id);
diff --git a/drivers/input/misc/bma150.c b/drivers/input/misc/bma150.c
index 4cc2a0dcaa75..0ef282932095 100644
--- a/drivers/input/misc/bma150.c
+++ b/drivers/input/misc/bma150.c
@@ -536,9 +536,9 @@ static int __maybe_unused bma150_resume(struct device *dev)
static UNIVERSAL_DEV_PM_OPS(bma150_pm, bma150_suspend, bma150_resume, NULL);
static const struct i2c_device_id bma150_id[] = {
- { "bma150" },
- { "smb380" },
- { "bma023" },
+ { .name = "bma150" },
+ { .name = "smb380" },
+ { .name = "bma023" },
{ }
};
diff --git a/drivers/input/misc/cma3000_d0x_i2c.c b/drivers/input/misc/cma3000_d0x_i2c.c
index f892c5b1e4bd..14be6d4682d0 100644
--- a/drivers/input/misc/cma3000_d0x_i2c.c
+++ b/drivers/input/misc/cma3000_d0x_i2c.c
@@ -90,7 +90,7 @@ static const struct dev_pm_ops cma3000_i2c_pm_ops = {
};
static const struct i2c_device_id cma3000_i2c_id[] = {
- { "cma3000_d01" },
+ { .name = "cma3000_d01" },
{ }
};
diff --git a/drivers/input/misc/da7280.c b/drivers/input/misc/da7280.c
index e4a605c6af15..77fcf868dca2 100644
--- a/drivers/input/misc/da7280.c
+++ b/drivers/input/misc/da7280.c
@@ -1305,7 +1305,7 @@ MODULE_DEVICE_TABLE(of, da7280_of_match);
#endif
static const struct i2c_device_id da7280_i2c_id[] = {
- { "da7280", },
+ { .name = "da7280" },
{ }
};
MODULE_DEVICE_TABLE(i2c, da7280_i2c_id);
diff --git a/drivers/input/misc/drv260x.c b/drivers/input/misc/drv260x.c
index e2089d6ac850..6c5c4c53753b 100644
--- a/drivers/input/misc/drv260x.c
+++ b/drivers/input/misc/drv260x.c
@@ -630,10 +630,10 @@ static int drv260x_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(drv260x_pm_ops, drv260x_suspend, drv260x_resume);
static const struct i2c_device_id drv260x_id[] = {
- { "drv2604" },
- { "drv2604l" },
- { "drv2605" },
- { "drv2605l" },
+ { .name = "drv2604" },
+ { .name = "drv2604l" },
+ { .name = "drv2605" },
+ { .name = "drv2605l" },
{ }
};
MODULE_DEVICE_TABLE(i2c, drv260x_id);
diff --git a/drivers/input/misc/drv2665.c b/drivers/input/misc/drv2665.c
index 46842cb8ddd7..4f8c1f69aa63 100644
--- a/drivers/input/misc/drv2665.c
+++ b/drivers/input/misc/drv2665.c
@@ -281,7 +281,7 @@ static int drv2665_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(drv2665_pm_ops, drv2665_suspend, drv2665_resume);
static const struct i2c_device_id drv2665_id[] = {
- { "drv2665" },
+ { .name = "drv2665" },
{ }
};
MODULE_DEVICE_TABLE(i2c, drv2665_id);
diff --git a/drivers/input/misc/drv2667.c b/drivers/input/misc/drv2667.c
index f952a24ec595..97309984fa7c 100644
--- a/drivers/input/misc/drv2667.c
+++ b/drivers/input/misc/drv2667.c
@@ -458,7 +458,7 @@ static int drv2667_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(drv2667_pm_ops, drv2667_suspend, drv2667_resume);
static const struct i2c_device_id drv2667_id[] = {
- { "drv2667" },
+ { .name = "drv2667" },
{ }
};
MODULE_DEVICE_TABLE(i2c, drv2667_id);
diff --git a/drivers/input/misc/kxtj9.c b/drivers/input/misc/kxtj9.c
index eb9788ea5215..6b3495ba5763 100644
--- a/drivers/input/misc/kxtj9.c
+++ b/drivers/input/misc/kxtj9.c
@@ -525,7 +525,7 @@ static int kxtj9_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(kxtj9_pm_ops, kxtj9_suspend, kxtj9_resume);
static const struct i2c_device_id kxtj9_id[] = {
- { NAME },
+ { .name = NAME },
{ }
};
diff --git a/drivers/input/misc/mma8450.c b/drivers/input/misc/mma8450.c
index 0c661140fb88..a2888d1ff58f 100644
--- a/drivers/input/misc/mma8450.c
+++ b/drivers/input/misc/mma8450.c
@@ -200,7 +200,7 @@ static int mma8450_probe(struct i2c_client *c)
}
static const struct i2c_device_id mma8450_id[] = {
- { MMA8450_DRV_NAME },
+ { .name = MMA8450_DRV_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, mma8450_id);
diff --git a/drivers/input/misc/pcf8574_keypad.c b/drivers/input/misc/pcf8574_keypad.c
index 14fc6c6cf699..a831fcc851f7 100644
--- a/drivers/input/misc/pcf8574_keypad.c
+++ b/drivers/input/misc/pcf8574_keypad.c
@@ -189,7 +189,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(pcf8574_kp_pm_ops,
pcf8574_kp_suspend, pcf8574_kp_resume);
static const struct i2c_device_id pcf8574_kp_id[] = {
- { DRV_NAME },
+ { .name = DRV_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, pcf8574_kp_id);
diff --git a/drivers/input/mouse/cyapa.c b/drivers/input/mouse/cyapa.c
index 6e0d956617a1..47000c30e4d8 100644
--- a/drivers/input/mouse/cyapa.c
+++ b/drivers/input/mouse/cyapa.c
@@ -1456,7 +1456,7 @@ static const struct dev_pm_ops cyapa_pm_ops = {
};
static const struct i2c_device_id cyapa_id_table[] = {
- { "cyapa" },
+ { .name = "cyapa" },
{ }
};
MODULE_DEVICE_TABLE(i2c, cyapa_id_table);
diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c
index fee1796da3d0..294e775510d5 100644
--- a/drivers/input/mouse/elan_i2c_core.c
+++ b/drivers/input/mouse/elan_i2c_core.c
@@ -1399,7 +1399,7 @@ static int elan_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(elan_pm_ops, elan_suspend, elan_resume);
static const struct i2c_device_id elan_id[] = {
- { DRIVER_NAME },
+ { .name = DRIVER_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, elan_id);
diff --git a/drivers/input/mouse/synaptics_i2c.c b/drivers/input/mouse/synaptics_i2c.c
index 6f3d5c33b807..d4cf982f1263 100644
--- a/drivers/input/mouse/synaptics_i2c.c
+++ b/drivers/input/mouse/synaptics_i2c.c
@@ -607,7 +607,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(synaptics_i2c_pm, synaptics_i2c_suspend,
synaptics_i2c_resume);
static const struct i2c_device_id synaptics_i2c_id_table[] = {
- { "synaptics_i2c" },
+ { .name = "synaptics_i2c" },
{ }
};
MODULE_DEVICE_TABLE(i2c, synaptics_i2c_id_table);
diff --git a/drivers/input/rmi4/rmi_i2c.c b/drivers/input/rmi4/rmi_i2c.c
index 3c0c5fd44702..e11d0acb9b96 100644
--- a/drivers/input/rmi4/rmi_i2c.c
+++ b/drivers/input/rmi4/rmi_i2c.c
@@ -365,7 +365,7 @@ static const struct dev_pm_ops rmi_i2c_pm = {
};
static const struct i2c_device_id rmi_id[] = {
- { "rmi4_i2c" },
+ { .name = "rmi4_i2c" },
{ }
};
MODULE_DEVICE_TABLE(i2c, rmi_id);
diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c
index f3d0b40721df..6de68c602558 100644
--- a/drivers/input/rmi4/rmi_smbus.c
+++ b/drivers/input/rmi4/rmi_smbus.c
@@ -413,7 +413,7 @@ static const struct dev_pm_ops rmi_smb_pm = {
};
static const struct i2c_device_id rmi_id[] = {
- { "rmi4_smbus" },
+ { .name = "rmi4_smbus" },
{ }
};
MODULE_DEVICE_TABLE(i2c, rmi_id);
diff --git a/drivers/input/touchscreen/ad7879-i2c.c b/drivers/input/touchscreen/ad7879-i2c.c
index e5b99312c3d9..c1ccdf86b74c 100644
--- a/drivers/input/touchscreen/ad7879-i2c.c
+++ b/drivers/input/touchscreen/ad7879-i2c.c
@@ -42,8 +42,8 @@ static int ad7879_i2c_probe(struct i2c_client *client)
}
static const struct i2c_device_id ad7879_id[] = {
- { "ad7879" },
- { "ad7889" },
+ { .name = "ad7879" },
+ { .name = "ad7889" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ad7879_id);
diff --git a/drivers/input/touchscreen/ar1021_i2c.c b/drivers/input/touchscreen/ar1021_i2c.c
index 8a588202447d..aa43fc1549df 100644
--- a/drivers/input/touchscreen/ar1021_i2c.c
+++ b/drivers/input/touchscreen/ar1021_i2c.c
@@ -164,7 +164,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(ar1021_i2c_pm,
ar1021_i2c_suspend, ar1021_i2c_resume);
static const struct i2c_device_id ar1021_i2c_id[] = {
- { "ar1021" },
+ { .name = "ar1021" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ar1021_i2c_id);
diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index 87c6a10381f2..ec86c46c8622 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -3413,11 +3413,11 @@ MODULE_DEVICE_TABLE(acpi, mxt_acpi_id);
#endif
static const struct i2c_device_id mxt_id[] = {
- { "qt602240_ts" },
- { "atmel_mxt_ts" },
- { "atmel_mxt_tp" },
- { "maxtouch" },
- { "mXT224" },
+ { .name = "qt602240_ts" },
+ { .name = "atmel_mxt_ts" },
+ { .name = "atmel_mxt_tp" },
+ { .name = "maxtouch" },
+ { .name = "mXT224" },
{ }
};
MODULE_DEVICE_TABLE(i2c, mxt_id);
diff --git a/drivers/input/touchscreen/auo-pixcir-ts.c b/drivers/input/touchscreen/auo-pixcir-ts.c
index 401b2264f585..6c6ebebacba9 100644
--- a/drivers/input/touchscreen/auo-pixcir-ts.c
+++ b/drivers/input/touchscreen/auo-pixcir-ts.c
@@ -618,7 +618,7 @@ static int auo_pixcir_probe(struct i2c_client *client)
}
static const struct i2c_device_id auo_pixcir_idtable[] = {
- { "auo_pixcir_ts" },
+ { .name = "auo_pixcir_ts" },
{ }
};
MODULE_DEVICE_TABLE(i2c, auo_pixcir_idtable);
diff --git a/drivers/input/touchscreen/bu21013_ts.c b/drivers/input/touchscreen/bu21013_ts.c
index 6baebb7ec089..d1bdccc56fc8 100644
--- a/drivers/input/touchscreen/bu21013_ts.c
+++ b/drivers/input/touchscreen/bu21013_ts.c
@@ -597,7 +597,7 @@ static int bu21013_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(bu21013_dev_pm_ops, bu21013_suspend, bu21013_resume);
static const struct i2c_device_id bu21013_id[] = {
- { DRIVER_TP },
+ { .name = DRIVER_TP },
{ }
};
MODULE_DEVICE_TABLE(i2c, bu21013_id);
diff --git a/drivers/input/touchscreen/bu21029_ts.c b/drivers/input/touchscreen/bu21029_ts.c
index 68d9cd55ceeb..339fee1622fb 100644
--- a/drivers/input/touchscreen/bu21029_ts.c
+++ b/drivers/input/touchscreen/bu21029_ts.c
@@ -442,7 +442,7 @@ static int bu21029_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(bu21029_pm_ops, bu21029_suspend, bu21029_resume);
static const struct i2c_device_id bu21029_ids[] = {
- { DRIVER_NAME },
+ { .name = DRIVER_NAME },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, bu21029_ids);
diff --git a/drivers/input/touchscreen/cy8ctma140.c b/drivers/input/touchscreen/cy8ctma140.c
index 2d4b6e343203..0ac48eff3376 100644
--- a/drivers/input/touchscreen/cy8ctma140.c
+++ b/drivers/input/touchscreen/cy8ctma140.c
@@ -322,7 +322,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(cy8ctma140_pm,
cy8ctma140_suspend, cy8ctma140_resume);
static const struct i2c_device_id cy8ctma140_idtable[] = {
- { CY8CTMA140_NAME },
+ { .name = CY8CTMA140_NAME },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, cy8ctma140_idtable);
diff --git a/drivers/input/touchscreen/cy8ctmg110_ts.c b/drivers/input/touchscreen/cy8ctmg110_ts.c
index 54d6c4869eb0..33ad4f0512a0 100644
--- a/drivers/input/touchscreen/cy8ctmg110_ts.c
+++ b/drivers/input/touchscreen/cy8ctmg110_ts.c
@@ -267,7 +267,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(cy8ctmg110_pm,
cy8ctmg110_suspend, cy8ctmg110_resume);
static const struct i2c_device_id cy8ctmg110_idtable[] = {
- { CY8CTMG110_DRIVER_NAME, 1 },
+ { .name = CY8CTMG110_DRIVER_NAME, .driver_data = 1 },
{ }
};
diff --git a/drivers/input/touchscreen/cyttsp5.c b/drivers/input/touchscreen/cyttsp5.c
index 47f4271395a6..73c397e44da4 100644
--- a/drivers/input/touchscreen/cyttsp5.c
+++ b/drivers/input/touchscreen/cyttsp5.c
@@ -938,7 +938,7 @@ static const struct of_device_id cyttsp5_of_match[] = {
MODULE_DEVICE_TABLE(of, cyttsp5_of_match);
static const struct i2c_device_id cyttsp5_i2c_id[] = {
- { CYTTSP5_NAME },
+ { .name = CYTTSP5_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, cyttsp5_i2c_id);
diff --git a/drivers/input/touchscreen/cyttsp_i2c.c b/drivers/input/touchscreen/cyttsp_i2c.c
index cb15600549cd..17524f7e944d 100644
--- a/drivers/input/touchscreen/cyttsp_i2c.c
+++ b/drivers/input/touchscreen/cyttsp_i2c.c
@@ -103,7 +103,7 @@ static int cyttsp_i2c_probe(struct i2c_client *client)
}
static const struct i2c_device_id cyttsp_i2c_id[] = {
- { CY_I2C_NAME },
+ { .name = CY_I2C_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, cyttsp_i2c_id);
diff --git a/drivers/input/touchscreen/eeti_ts.c b/drivers/input/touchscreen/eeti_ts.c
index 492e19a28a74..b12602bc368e 100644
--- a/drivers/input/touchscreen/eeti_ts.c
+++ b/drivers/input/touchscreen/eeti_ts.c
@@ -266,7 +266,7 @@ static int eeti_ts_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(eeti_ts_pm, eeti_ts_suspend, eeti_ts_resume);
static const struct i2c_device_id eeti_ts_id[] = {
- { "eeti_ts" },
+ { .name = "eeti_ts" },
{ }
};
MODULE_DEVICE_TABLE(i2c, eeti_ts_id);
diff --git a/drivers/input/touchscreen/egalax_ts.c b/drivers/input/touchscreen/egalax_ts.c
index eb3cc2befcdf..76cf8085553f 100644
--- a/drivers/input/touchscreen/egalax_ts.c
+++ b/drivers/input/touchscreen/egalax_ts.c
@@ -219,7 +219,7 @@ static int egalax_ts_probe(struct i2c_client *client)
}
static const struct i2c_device_id egalax_ts_id[] = {
- { "egalax_ts" },
+ { .name = "egalax_ts" },
{ }
};
MODULE_DEVICE_TABLE(i2c, egalax_ts_id);
diff --git a/drivers/input/touchscreen/elants_i2c.c b/drivers/input/touchscreen/elants_i2c.c
index 835d91dff0a4..17175adcaebb 100644
--- a/drivers/input/touchscreen/elants_i2c.c
+++ b/drivers/input/touchscreen/elants_i2c.c
@@ -1615,9 +1615,9 @@ static DEFINE_SIMPLE_DEV_PM_OPS(elants_i2c_pm_ops,
elants_i2c_suspend, elants_i2c_resume);
static const struct i2c_device_id elants_i2c_id[] = {
- { DEVICE_NAME, EKTH3500 },
- { "ekth3500", EKTH3500 },
- { "ektf3624", EKTF3624 },
+ { .name = DEVICE_NAME, .driver_data = EKTH3500 },
+ { .name = "ekth3500", .driver_data = EKTH3500 },
+ { .name = "ektf3624", .driver_data = EKTF3624 },
{ }
};
MODULE_DEVICE_TABLE(i2c, elants_i2c_id);
diff --git a/drivers/input/touchscreen/exc3000.c b/drivers/input/touchscreen/exc3000.c
index 78c0911ba6e2..037bb35238a3 100644
--- a/drivers/input/touchscreen/exc3000.c
+++ b/drivers/input/touchscreen/exc3000.c
@@ -432,10 +432,10 @@ static int exc3000_probe(struct i2c_client *client)
}
static const struct i2c_device_id exc3000_id[] = {
- { "exc3000", EETI_EXC3000 },
- { "exc80h60", EETI_EXC80H60 },
- { "exc80h84", EETI_EXC80H84 },
- { "exc81w32", EETI_EXC81W32 },
+ { .name = "exc3000", .driver_data = EETI_EXC3000 },
+ { .name = "exc80h60", .driver_data = EETI_EXC80H60 },
+ { .name = "exc80h84", .driver_data = EETI_EXC80H84 },
+ { .name = "exc81w32", .driver_data = EETI_EXC81W32 },
{ }
};
MODULE_DEVICE_TABLE(i2c, exc3000_id);
diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c
index f8798d11ec03..2c48ad8ee2e6 100644
--- a/drivers/input/touchscreen/goodix.c
+++ b/drivers/input/touchscreen/goodix.c
@@ -1523,7 +1523,7 @@ static int goodix_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(goodix_pm_ops, goodix_suspend, goodix_resume);
static const struct i2c_device_id goodix_ts_id[] = {
- { "GDIX1001:00" },
+ { .name = "GDIX1001:00" },
{ }
};
MODULE_DEVICE_TABLE(i2c, goodix_ts_id);
diff --git a/drivers/input/touchscreen/hideep.c b/drivers/input/touchscreen/hideep.c
index 62041bcca83d..58e00c314ace 100644
--- a/drivers/input/touchscreen/hideep.c
+++ b/drivers/input/touchscreen/hideep.c
@@ -1081,7 +1081,7 @@ static int hideep_probe(struct i2c_client *client)
}
static const struct i2c_device_id hideep_i2c_id[] = {
- { HIDEEP_I2C_NAME },
+ { .name = HIDEEP_I2C_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, hideep_i2c_id);
diff --git a/drivers/input/touchscreen/himax_hx83112b.c b/drivers/input/touchscreen/himax_hx83112b.c
index 896a145ddb2b..0aa67dfacbad 100644
--- a/drivers/input/touchscreen/himax_hx83112b.c
+++ b/drivers/input/touchscreen/himax_hx83112b.c
@@ -406,8 +406,8 @@ static const struct himax_chip hx83112b_chip = {
};
static const struct i2c_device_id himax_ts_id[] = {
- { "hx83100a", (kernel_ulong_t)&hx83100a_chip },
- { "hx83112b", (kernel_ulong_t)&hx83112b_chip },
+ { .name = "hx83100a", .driver_data = (kernel_ulong_t)&hx83100a_chip },
+ { .name = "hx83112b", .driver_data = (kernel_ulong_t)&hx83112b_chip },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, himax_ts_id);
diff --git a/drivers/input/touchscreen/hynitron-cst816x.c b/drivers/input/touchscreen/hynitron-cst816x.c
index b64d7928e18f..47d9cd7412d1 100644
--- a/drivers/input/touchscreen/hynitron-cst816x.c
+++ b/drivers/input/touchscreen/hynitron-cst816x.c
@@ -226,7 +226,7 @@ static int cst816x_probe(struct i2c_client *client)
}
static const struct i2c_device_id cst816x_id[] = {
- { .name = "cst816s", 0 },
+ { .name = "cst816s" },
{ }
};
MODULE_DEVICE_TABLE(i2c, cst816x_id);
diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c
index 3bf524a6ee20..66ada7ffbc80 100644
--- a/drivers/input/touchscreen/ili210x.c
+++ b/drivers/input/touchscreen/ili210x.c
@@ -1049,10 +1049,10 @@ static int ili210x_i2c_probe(struct i2c_client *client)
}
static const struct i2c_device_id ili210x_i2c_id[] = {
- { "ili210x", (long)&ili210x_chip },
- { "ili2117", (long)&ili211x_chip },
- { "ili2120", (long)&ili212x_chip },
- { "ili251x", (long)&ili251x_chip },
+ { .name = "ili210x", .driver_data = (long)&ili210x_chip },
+ { .name = "ili2117", .driver_data = (long)&ili211x_chip },
+ { .name = "ili2120", .driver_data = (long)&ili212x_chip },
+ { .name = "ili251x", .driver_data = (long)&ili251x_chip },
{ }
};
MODULE_DEVICE_TABLE(i2c, ili210x_i2c_id);
diff --git a/drivers/input/touchscreen/ilitek_ts_i2c.c b/drivers/input/touchscreen/ilitek_ts_i2c.c
index 0706443792ba..3de0fbf8da38 100644
--- a/drivers/input/touchscreen/ilitek_ts_i2c.c
+++ b/drivers/input/touchscreen/ilitek_ts_i2c.c
@@ -639,7 +639,7 @@ static int ilitek_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(ilitek_pm_ops, ilitek_suspend, ilitek_resume);
static const struct i2c_device_id ilitek_ts_i2c_id[] = {
- { ILITEK_TS_NAME },
+ { .name = ILITEK_TS_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, ilitek_ts_i2c_id);
diff --git a/drivers/input/touchscreen/max11801_ts.c b/drivers/input/touchscreen/max11801_ts.c
index f39633fc8dc2..3996e4a61ff7 100644
--- a/drivers/input/touchscreen/max11801_ts.c
+++ b/drivers/input/touchscreen/max11801_ts.c
@@ -213,7 +213,7 @@ static int max11801_ts_probe(struct i2c_client *client)
}
static const struct i2c_device_id max11801_ts_id[] = {
- { "max11801" },
+ { .name = "max11801" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max11801_ts_id);
diff --git a/drivers/input/touchscreen/melfas_mip4.c b/drivers/input/touchscreen/melfas_mip4.c
index 10fccf4e5bb1..fd6c0ac301a2 100644
--- a/drivers/input/touchscreen/melfas_mip4.c
+++ b/drivers/input/touchscreen/melfas_mip4.c
@@ -1536,7 +1536,7 @@ MODULE_DEVICE_TABLE(acpi, mip4_acpi_match);
#endif
static const struct i2c_device_id mip4_i2c_ids[] = {
- { MIP4_DEVICE_NAME },
+ { .name = MIP4_DEVICE_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, mip4_i2c_ids);
diff --git a/drivers/input/touchscreen/migor_ts.c b/drivers/input/touchscreen/migor_ts.c
index 993d945dcd23..9d221e340404 100644
--- a/drivers/input/touchscreen/migor_ts.c
+++ b/drivers/input/touchscreen/migor_ts.c
@@ -211,7 +211,7 @@ static int migor_ts_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(migor_ts_pm, migor_ts_suspend, migor_ts_resume);
static const struct i2c_device_id migor_ts_id[] = {
- { "migor_ts" },
+ { .name = "migor_ts" },
{ }
};
MODULE_DEVICE_TABLE(i2c, migor_ts_id);
diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c
index af462086a65c..9597214d9d3c 100644
--- a/drivers/input/touchscreen/mms114.c
+++ b/drivers/input/touchscreen/mms114.c
@@ -667,7 +667,7 @@ static int mms114_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(mms114_pm_ops, mms114_suspend, mms114_resume);
static const struct i2c_device_id mms114_id[] = {
- { "mms114" },
+ { .name = "mms114" },
{ }
};
MODULE_DEVICE_TABLE(i2c, mms114_id);
diff --git a/drivers/input/touchscreen/novatek-nvt-ts.c b/drivers/input/touchscreen/novatek-nvt-ts.c
index 708bfb933ddd..0f771f681952 100644
--- a/drivers/input/touchscreen/novatek-nvt-ts.c
+++ b/drivers/input/touchscreen/novatek-nvt-ts.c
@@ -326,8 +326,8 @@ static const struct of_device_id nvt_ts_of_match[] = {
MODULE_DEVICE_TABLE(of, nvt_ts_of_match);
static const struct i2c_device_id nvt_ts_i2c_id[] = {
- { "nt11205-ts", (unsigned long) &nvt_nt11205_ts_data },
- { "nt36672a-ts", (unsigned long) &nvt_nt36672a_ts_data },
+ { .name = "nt11205-ts", .driver_data = (unsigned long)&nvt_nt11205_ts_data },
+ { .name = "nt36672a-ts", .driver_data = (unsigned long)&nvt_nt36672a_ts_data },
{ }
};
MODULE_DEVICE_TABLE(i2c, nvt_ts_i2c_id);
diff --git a/drivers/input/touchscreen/pixcir_i2c_ts.c b/drivers/input/touchscreen/pixcir_i2c_ts.c
index c6b3615c8775..1ca5920846df 100644
--- a/drivers/input/touchscreen/pixcir_i2c_ts.c
+++ b/drivers/input/touchscreen/pixcir_i2c_ts.c
@@ -580,8 +580,8 @@ static const struct pixcir_i2c_chip_data pixcir_tangoc_data = {
};
static const struct i2c_device_id pixcir_i2c_ts_id[] = {
- { "pixcir_ts", (unsigned long) &pixcir_ts_data },
- { "pixcir_tangoc", (unsigned long) &pixcir_tangoc_data },
+ { .name = "pixcir_ts", .driver_data = (unsigned long)&pixcir_ts_data },
+ { .name = "pixcir_tangoc", .driver_data = (unsigned long)&pixcir_tangoc_data },
{ }
};
MODULE_DEVICE_TABLE(i2c, pixcir_i2c_ts_id);
diff --git a/drivers/input/touchscreen/raydium_i2c_ts.c b/drivers/input/touchscreen/raydium_i2c_ts.c
index f2d33ad86fd2..0256055abcef 100644
--- a/drivers/input/touchscreen/raydium_i2c_ts.c
+++ b/drivers/input/touchscreen/raydium_i2c_ts.c
@@ -1219,8 +1219,8 @@ static DEFINE_SIMPLE_DEV_PM_OPS(raydium_i2c_pm_ops,
raydium_i2c_suspend, raydium_i2c_resume);
static const struct i2c_device_id raydium_i2c_id[] = {
- { "raydium_i2c" },
- { "rm32380" },
+ { .name = "raydium_i2c" },
+ { .name = "rm32380" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, raydium_i2c_id);
diff --git a/drivers/input/touchscreen/rohm_bu21023.c b/drivers/input/touchscreen/rohm_bu21023.c
index 295d8d75ba32..a5c06b7423c0 100644
--- a/drivers/input/touchscreen/rohm_bu21023.c
+++ b/drivers/input/touchscreen/rohm_bu21023.c
@@ -1144,7 +1144,7 @@ static int rohm_bu21023_i2c_probe(struct i2c_client *client)
}
static const struct i2c_device_id rohm_bu21023_i2c_id[] = {
- { BU21023_NAME },
+ { .name = BU21023_NAME },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, rohm_bu21023_i2c_id);
diff --git a/drivers/input/touchscreen/s6sy761.c b/drivers/input/touchscreen/s6sy761.c
index e1518a75a51b..0f24a9b73063 100644
--- a/drivers/input/touchscreen/s6sy761.c
+++ b/drivers/input/touchscreen/s6sy761.c
@@ -520,7 +520,7 @@ MODULE_DEVICE_TABLE(of, s6sy761_of_match);
#endif
static const struct i2c_device_id s6sy761_id[] = {
- { "s6sy761" },
+ { .name = "s6sy761" },
{ }
};
MODULE_DEVICE_TABLE(i2c, s6sy761_id);
diff --git a/drivers/input/touchscreen/silead.c b/drivers/input/touchscreen/silead.c
index 5ccc96764742..44d7141103f4 100644
--- a/drivers/input/touchscreen/silead.c
+++ b/drivers/input/touchscreen/silead.c
@@ -776,12 +776,12 @@ static int silead_ts_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(silead_ts_pm, silead_ts_suspend, silead_ts_resume);
static const struct i2c_device_id silead_ts_id[] = {
- { "gsl1680" },
- { "gsl1688" },
- { "gsl3670" },
- { "gsl3675" },
- { "gsl3692" },
- { "mssl1680" },
+ { .name = "gsl1680" },
+ { .name = "gsl1688" },
+ { .name = "gsl3670" },
+ { .name = "gsl3675" },
+ { .name = "gsl3692" },
+ { .name = "mssl1680" },
{ }
};
MODULE_DEVICE_TABLE(i2c, silead_ts_id);
diff --git a/drivers/input/touchscreen/sis_i2c.c b/drivers/input/touchscreen/sis_i2c.c
index a625f2ad809d..4fa93cd31ee4 100644
--- a/drivers/input/touchscreen/sis_i2c.c
+++ b/drivers/input/touchscreen/sis_i2c.c
@@ -374,8 +374,8 @@ MODULE_DEVICE_TABLE(of, sis_ts_dt_ids);
#endif
static const struct i2c_device_id sis_ts_id[] = {
- { SIS_I2C_NAME },
- { "9200-ts" },
+ { .name = SIS_I2C_NAME },
+ { .name = "9200-ts" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, sis_ts_id);
diff --git a/drivers/input/touchscreen/st1232.c b/drivers/input/touchscreen/st1232.c
index 9b266927b7fe..c2dcfce5003d 100644
--- a/drivers/input/touchscreen/st1232.c
+++ b/drivers/input/touchscreen/st1232.c
@@ -453,8 +453,8 @@ static DEFINE_SIMPLE_DEV_PM_OPS(st1232_ts_pm_ops,
st1232_ts_suspend, st1232_ts_resume);
static const struct i2c_device_id st1232_ts_id[] = {
- { ST1232_TS_NAME, (unsigned long)&st1232_chip_info },
- { ST1633_TS_NAME, (unsigned long)&st1633_chip_info },
+ { .name = ST1232_TS_NAME, .driver_data = (unsigned long)&st1232_chip_info },
+ { .name = ST1633_TS_NAME, .driver_data = (unsigned long)&st1633_chip_info },
{ }
};
MODULE_DEVICE_TABLE(i2c, st1232_ts_id);
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index 8af87d0b6eb6..f84127e182fe 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -785,7 +785,7 @@ MODULE_DEVICE_TABLE(of, stmfts_of_match);
#endif
static const struct i2c_device_id stmfts_id[] = {
- { "stmfts" },
+ { .name = "stmfts" },
{ }
};
MODULE_DEVICE_TABLE(i2c, stmfts_id);
diff --git a/drivers/input/touchscreen/tsc2004.c b/drivers/input/touchscreen/tsc2004.c
index 787f2caf4f73..130aeb735b65 100644
--- a/drivers/input/touchscreen/tsc2004.c
+++ b/drivers/input/touchscreen/tsc2004.c
@@ -43,7 +43,7 @@ static int tsc2004_probe(struct i2c_client *i2c)
}
static const struct i2c_device_id tsc2004_idtable[] = {
- { "tsc2004" },
+ { .name = "tsc2004" },
{ }
};
MODULE_DEVICE_TABLE(i2c, tsc2004_idtable);
diff --git a/drivers/input/touchscreen/tsc2007_core.c b/drivers/input/touchscreen/tsc2007_core.c
index 524f14eb3da2..5e332395aa75 100644
--- a/drivers/input/touchscreen/tsc2007_core.c
+++ b/drivers/input/touchscreen/tsc2007_core.c
@@ -407,7 +407,7 @@ static int tsc2007_probe(struct i2c_client *client)
}
static const struct i2c_device_id tsc2007_idtable[] = {
- { "tsc2007" },
+ { .name = "tsc2007" },
{ }
};
diff --git a/drivers/input/touchscreen/wacom_i2c.c b/drivers/input/touchscreen/wacom_i2c.c
index fd97a83f5664..dd7c2a8a831b 100644
--- a/drivers/input/touchscreen/wacom_i2c.c
+++ b/drivers/input/touchscreen/wacom_i2c.c
@@ -253,7 +253,7 @@ static int wacom_i2c_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(wacom_i2c_pm, wacom_i2c_suspend, wacom_i2c_resume);
static const struct i2c_device_id wacom_i2c_id[] = {
- { "WAC_I2C_EMR" },
+ { .name = "WAC_I2C_EMR" },
{ }
};
MODULE_DEVICE_TABLE(i2c, wacom_i2c_id);
diff --git a/drivers/input/touchscreen/wdt87xx_i2c.c b/drivers/input/touchscreen/wdt87xx_i2c.c
index bdaabb14dc8c..1af309252092 100644
--- a/drivers/input/touchscreen/wdt87xx_i2c.c
+++ b/drivers/input/touchscreen/wdt87xx_i2c.c
@@ -1140,7 +1140,7 @@ static int wdt87xx_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(wdt87xx_pm_ops, wdt87xx_suspend, wdt87xx_resume);
static const struct i2c_device_id wdt87xx_dev_id[] = {
- { WDT87XX_NAME },
+ { .name = WDT87XX_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, wdt87xx_dev_id);
diff --git a/drivers/input/touchscreen/zet6223.c b/drivers/input/touchscreen/zet6223.c
index 943634ba9cd9..a341142f3708 100644
--- a/drivers/input/touchscreen/zet6223.c
+++ b/drivers/input/touchscreen/zet6223.c
@@ -236,7 +236,7 @@ static const struct of_device_id zet6223_of_match[] = {
MODULE_DEVICE_TABLE(of, zet6223_of_match);
static const struct i2c_device_id zet6223_id[] = {
- { "zet6223" },
+ { .name = "zet6223" },
{ }
};
MODULE_DEVICE_TABLE(i2c, zet6223_id);
diff --git a/drivers/input/touchscreen/zforce_ts.c b/drivers/input/touchscreen/zforce_ts.c
index a360749fa076..4bfc9560bd6c 100644
--- a/drivers/input/touchscreen/zforce_ts.c
+++ b/drivers/input/touchscreen/zforce_ts.c
@@ -831,7 +831,7 @@ static int zforce_probe(struct i2c_client *client)
}
static const struct i2c_device_id zforce_idtable[] = {
- { "zforce-ts" },
+ { .name = "zforce-ts" },
{ }
};
MODULE_DEVICE_TABLE(i2c, zforce_idtable);
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
--
2.47.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox