* Re: [PATCH v3 9/9] arm64: dts: mt6392: add mt6392 PMIC dtsi
From: Chen-Yu Tsai @ 2026-03-26 5:43 UTC (permalink / raw)
To: AngeloGioacchino Del Regno
Cc: wens, Luca Leonardo Scorcia, linux-mediatek, Val Packett,
Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Sen Chu, Sean Wang, Macpaul Lin, Lee Jones, Matthias Brugger,
Linus Walleij, Liam Girdwood, Mark Brown, Gary Bisson,
Julien Massot, Louis-Alexis Eyraud, Fabien Parent, Chen Zhong,
linux-input, devicetree, linux-kernel, linux-pm, linux-arm-kernel,
linux-gpio
In-Reply-To: <28102417-4a2a-4e29-afbd-d0f2aa76074b@collabora.com>
On Wed, Mar 18, 2026 at 06:22:42PM +0100, AngeloGioacchino Del Regno wrote:
> Il 18/03/26 14:54, Chen-Yu Tsai ha scritto:
> > On Wed, Mar 18, 2026 at 8:39 PM AngeloGioacchino Del Regno
> > <angelogioacchino.delregno@collabora.com> wrote:
> > >
> > > Il 17/03/26 19:43, Luca Leonardo Scorcia ha scritto:
> > > > From: Val Packett <val@packett.cool>
> > > >
> > > > Add the dts to be included by all boards using the MT6392 PMIC.
> > > >
> > > > Signed-off-by: Val Packett <val@packett.cool>
> > > > Signed-off-by: Luca Leonardo Scorcia <l.scorcia@gmail.com>
> > > > ---
> > > > arch/arm64/boot/dts/mediatek/mt6392.dtsi | 141 +++++++++++++++++++++++
> > > > 1 file changed, 141 insertions(+)
> > > > create mode 100644 arch/arm64/boot/dts/mediatek/mt6392.dtsi
> > > >
> > > > diff --git a/arch/arm64/boot/dts/mediatek/mt6392.dtsi b/arch/arm64/boot/dts/mediatek/mt6392.dtsi
> > > > new file mode 100644
> > > > index 000000000000..fbf6f671524c
> > > > --- /dev/null
> > > > +++ b/arch/arm64/boot/dts/mediatek/mt6392.dtsi
> > > > @@ -0,0 +1,141 @@
> > > > +// SPDX-License-Identifier: GPL-2.0
> > > > +/*
> > > > + * Copyright (c) 2019 MediaTek Inc.
> > > > + * Copyright (c) 2024 Val Packett <val@packett.cool>
> > > > + */
> > > > +
> > > > +#include <dt-bindings/input/input.h>
> > > > +
> > > > +&pwrap {
> > > > + pmic: pmic {
> > > > + compatible = "mediatek,mt6392", "mediatek,mt6323";
> > > > + interrupt-controller;
> > > > + #interrupt-cells = <2>;
> > > > +
> > > > + keys {
> > > > + compatible = "mediatek,mt6392-keys";
> > > > +
> > > > + key-power {
> > > > + linux,keycodes = <KEY_POWER>;
> > > > + wakeup-source;
> > > > + };
> > > > +
> > > > + key-home {
> > > > + linux,keycodes = <KEY_HOME>;
> > > > + wakeup-source;
> > > > + };
> > > > + };
> > > > +
> > > > + pio6392: pinctrl {
> > > > + compatible = "mediatek,mt6392-pinctrl";
> > > > +
> > > > + gpio-controller;
> > > > + #gpio-cells = <2>;
> > > > + };
> > > > +
> > > > + rtc {
> > > > + compatible = "mediatek,mt6392-rtc",
> > > > + "mediatek,mt6323-rtc";
> > > > + };
> > > > +
> > > > + regulators {
> > > > + compatible = "mediatek,mt6392-regulator";
> > > > +
> > > > + mt6392_vproc_reg: buck_vproc {
> > >
> > > s/buck//g
> > >
> > > Also, no min/max voltages?!
> >
> > We really shouldn't set min/max voltages in the PMIC dtsi file.
> >
> > The min/max voltages are supposed to be the intersection of the
> > consumers acceptable operating ranges. The min/max of the regulator
> > itself is already implied by the model / compatible.
> >
>
> Your point is fair, but it's also true that some of the regulators are not
> really meant to ever output anything different than what they are supposed
> to, though, with slight variations being possible... I guess the best option
> here is to leave declaring voltages to board DTs instead, which is sensible
> in the end.
>
> Okay, agreed. Let's go with no voltages.
>
> Reminder for myself: there's a bunch of PMIC devicetrees to cleanup in here...
And to be honest, even the default names are not needed. They are again
implied by the compatible, and even the regulator's own node name.
So either we could have a whole bunch of empty regulator nodes with
labels for easy reference, or just have the upper level "regulators"
wrapper node with a "mt63??_regulators" label for board dts files
to reference and add the _used_ regulators to.
If you agree, I can do the cleanup work.
ChenYu
^ permalink raw reply
* Re: [PATCH v2 01/19] tracepoint: Add trace_call__##name() API
From: Masami Hiramatsu @ 2026-03-26 1:28 UTC (permalink / raw)
To: Vineeth Pillai (Google)
Cc: Steven Rostedt, Peter Zijlstra, Dmitry Ilvokhin, Masami Hiramatsu,
Mathieu Desnoyers, Ingo Molnar, Jens Axboe, io-uring,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Marcelo Ricardo Leitner,
Xin Long, Jon Maloy, Aaron Conole, Eelco Chaudron, Ilya Maximets,
netdev, bpf, linux-sctp, tipc-discussion, dev, Jiri Pirko,
Oded Gabbay, Koby Elbaz, dri-devel, Rafael J. Wysocki,
Viresh Kumar, Gautham R. Shenoy, Huang Rui, Mario Limonciello,
Len Brown, Srinivas Pandruvada, linux-pm, MyungJoo Ham,
Kyungmin Park, Chanwoo Choi, Christian König, Sumit Semwal,
linaro-mm-sig, Eddie James, Andrew Jeffery, Joel Stanley,
linux-fsi, David Airlie, Simona Vetter, Alex Deucher,
Danilo Krummrich, Matthew Brost, Philipp Stanner, Harry Wentland,
Leo Li, amd-gfx, Jiri Kosina, Benjamin Tissoires, linux-input,
Wolfram Sang, linux-i2c, Mark Brown, Michael Hennerich,
Nuno Sá, linux-spi, James E.J. Bottomley, Martin K. Petersen,
linux-scsi, Chris Mason, David Sterba, linux-btrfs,
Thomas Gleixner, Andrew Morton, SeongJae Park, linux-mm,
Borislav Petkov, Dave Hansen, x86, linux-trace-kernel,
linux-kernel
In-Reply-To: <20260323160052.17528-2-vineeth@bitbyteword.org>
On Mon, 23 Mar 2026 12:00:20 -0400
"Vineeth Pillai (Google)" <vineeth@bitbyteword.org> wrote:
> Add trace_call__##name() as a companion to trace_##name(). When a
> caller already guards a tracepoint with an explicit enabled check:
>
> if (trace_foo_enabled() && cond)
> trace_foo(args);
>
> trace_foo() internally repeats the static_branch_unlikely() test, which
> the compiler cannot fold since static branches are patched binary
> instructions. This results in two static-branch evaluations for every
> guarded call site.
>
> trace_call__##name() calls __do_trace_##name() directly, skipping the
> redundant static-branch re-check. This avoids leaking the internal
> __do_trace_##name() symbol into call sites while still eliminating the
> double evaluation:
>
> if (trace_foo_enabled() && cond)
> trace_invoke_foo(args); /* calls __do_trace_foo() directly */
nit: trace_call_foo() instead of trace_invoke_foo()?
Anyway looks good to me.
Acked-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
>
> Three locations are updated:
> - __DECLARE_TRACE: invoke form omits static_branch_unlikely, retains
> the LOCKDEP RCU-watching assertion.
> - __DECLARE_TRACE_SYSCALL: same, plus retains might_fault().
> - !TRACEPOINTS_ENABLED stub: empty no-op so callers compile cleanly
> when tracepoints are compiled out.
>
> 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
> ---
> include/linux/tracepoint.h | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/include/linux/tracepoint.h b/include/linux/tracepoint.h
> index 22ca1c8b54f32..ed969705341f1 100644
> --- a/include/linux/tracepoint.h
> +++ b/include/linux/tracepoint.h
> @@ -294,6 +294,10 @@ static inline struct tracepoint *tracepoint_ptr_deref(tracepoint_ptr_t *p)
> WARN_ONCE(!rcu_is_watching(), \
> "RCU not watching for tracepoint"); \
> } \
> + } \
> + static inline void trace_call__##name(proto) \
> + { \
> + __do_trace_##name(args); \
> }
>
> #define __DECLARE_TRACE_SYSCALL(name, proto, args, data_proto) \
> @@ -313,6 +317,11 @@ static inline struct tracepoint *tracepoint_ptr_deref(tracepoint_ptr_t *p)
> WARN_ONCE(!rcu_is_watching(), \
> "RCU not watching for tracepoint"); \
> } \
> + } \
> + static inline void trace_call__##name(proto) \
> + { \
> + might_fault(); \
> + __do_trace_##name(args); \
> }
>
> /*
> @@ -398,6 +407,8 @@ static inline struct tracepoint *tracepoint_ptr_deref(tracepoint_ptr_t *p)
> #define __DECLARE_TRACE_COMMON(name, proto, args, data_proto) \
> static inline void trace_##name(proto) \
> { } \
> + static inline void trace_call__##name(proto) \
> + { } \
> static inline int \
> register_trace_##name(void (*probe)(data_proto), \
> void *data) \
> --
> 2.53.0
>
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply
* [PATCH] xpad: remove stale TODO and changelog header
From: Elliot Tester @ 2026-03-25 22:16 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, Elliot Tester, Elliot Tester
From: Elliot Tester <elliotctester@gmail.com>
All items in the TODO block have since been addressed: axis tuning,
analog button handling, rumble support, and dance pad USB IDs are all
implemented. The manual changelog is also removed as history is tracked
in git.
Signed-off-by: Elliot Tester <elliotctester1@gmail.com>
---
drivers/input/joystick/xpad.c | 34 ----------------------------------
1 file changed, 34 deletions(-)
diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index bf4accf3f..ce0f0b7e8 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c
@@ -25,40 +25,6 @@
* - Greg Kroah-Hartman - usb-skeleton driver
* - Xbox Linux project - extra USB IDs
* - Pekka Pöyry (quantus) - Xbox One controller reverse-engineering
- *
- * TODO:
- * - fine tune axes (especially trigger axes)
- * - fix "analog" buttons (reported as digital now)
- * - get rumble working
- * - need USB IDs for other dance pads
- *
- * History:
- *
- * 2002-06-27 - 0.0.1 : first version, just said "XBOX HID controller"
- *
- * 2002-07-02 - 0.0.2 : basic working version
- * - all axes and 9 of the 10 buttons work (german InterAct device)
- * - the black button does not work
- *
- * 2002-07-14 - 0.0.3 : rework by Vojtech Pavlik
- * - indentation fixes
- * - usb + input init sequence fixes
- *
- * 2002-07-16 - 0.0.4 : minor changes, merge with Vojtech's v0.0.3
- * - verified the lack of HID and report descriptors
- * - verified that ALL buttons WORK
- * - fixed d-pad to axes mapping
- *
- * 2002-07-17 - 0.0.5 : simplified d-pad handling
- *
- * 2004-10-02 - 0.0.6 : DDR pad support
- * - borrowed from the Xbox Linux kernel
- * - USB id's for commonly used dance pads are present
- * - dance pads will map D-PAD to buttons, not axes
- * - pass the module paramater 'dpad_to_buttons' to force
- * the D-PAD to map to buttons if your pad is not detected
- *
- * Later changes can be tracked in SCM.
*/
#include <linux/bits.h>
--
2.53.0
^ permalink raw reply related
* Re: [PATCH 2/7] Input: aiptek: validate macro key indices
From: Dmitry Torokhov @ 2026-03-25 18:08 UTC (permalink / raw)
To: Pengpeng Hou; +Cc: linux-input, linux-kernel
In-Reply-To: <20260323070311.42849-1-pengpeng@iscas.ac.cn>
On Mon, Mar 23, 2026 at 03:03:11PM +0800, Pengpeng Hou wrote:
> aiptek_irq() derives macro key indices directly from tablet reports and
> then uses them to index macroKeyEvents[]. Report types 4 and 5 use
> (data[3] >> 1), while report type 6 reads a 16-bit macro number from the
> packet body. None of those indices are checked against the array bounds
> before input_report_key() dereferences them.
>
> Reject out-of-range macro indices at each use site so malformed reports
> cannot read past macroKeyEvents[].
>
> Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
> ---
> drivers/input/tablet/aiptek.c | 21 ++++++++++++++-------
> 1 file changed, 14 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/input/tablet/aiptek.c b/drivers/input/tablet/aiptek.c
> index 6df24cee3c9d..ab5886a9241d 100644
> --- a/drivers/input/tablet/aiptek.c
> +++ b/drivers/input/tablet/aiptek.c
> @@ -676,12 +676,15 @@ static void aiptek_irq(struct urb *urb)
> }
> }
>
> - if (aiptek->lastMacro != -1 && aiptek->lastMacro != macro) {
> + if (aiptek->lastMacro >= 0 &&
> + aiptek->lastMacro < ARRAY_SIZE(macroKeyEvents) &&
I think we do not need to check lastMacro if we make sure that macro is
in correct range.
Thanks.
--
Dmitry
^ permalink raw reply
* [dtor-input:next] BUILD REGRESSION 303cdff39cdb1895a6d2b08e8946cc89feaf405c
From: kernel test robot @ 2026-03-25 16:57 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
branch HEAD: 303cdff39cdb1895a6d2b08e8946cc89feaf405c Input: zinitix - use guard notation when acquiring mutex
Error/Warning (recently discovered and may have been fixed):
https://lore.kernel.org/oe-kbuild-all/202603252030.fwSlmiBx-lkp@intel.com
https://lore.kernel.org/oe-kbuild-all/202603252057.JgSqGyIe-lkp@intel.com
drivers/input/touchscreen/mxs-lradc-ts.c:508:1: error: macro "class_spinlock_irqsave_constructor" passed 2 arguments, but takes just 1
drivers/input/touchscreen/mxs-lradc-ts.c:508:3: error: initializing 'class_spinlock_irqsave_t' with an expression of incompatible type 'class_spinlock_irqsave_t (spinlock_t *)' (aka 'class_spinlock_irqsave_t (struct spinlock *)')
drivers/input/touchscreen/mxs-lradc-ts.c:508:45: error: too many arguments provided to function-like macro invocation
drivers/input/touchscreen/mxs-lradc-ts.c:508:8: error: macro "class_spinlock_irqsave_constructor" passed 2 arguments, but takes just 1
drivers/input/touchscreen/mxs-lradc-ts.c:508:8: error: macro 'class_spinlock_irqsave_constructor' passed 2 arguments, but takes just 1
include/linux/cleanup.h:300:17: error: invalid initializer
include/linux/cleanup.h:303:17: error: invalid initializer
include/linux/cleanup.h:303:3: error: invalid initializer
Error/Warning ids grouped by kconfigs:
recent_errors
|-- alpha-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- arc-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- arc-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- arm-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- arm-randconfig-003-20260325
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- arm64-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- arm64-randconfig-002-20260325
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- arm64-randconfig-r122-20260325
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- csky-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- csky-randconfig-002-20260325
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- hexagon-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- i386-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- i386-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- i386-buildonly-randconfig-005-20260325
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- loongarch-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- loongarch-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- loongarch-randconfig-002-20260325
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- m68k-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- m68k-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- mips-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- mips-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- nios2-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- nios2-randconfig-r133-20260325
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- openrisc-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- openrisc-randconfig-r071-20260325
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- parisc-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- parisc-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- powerpc-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- powerpc-randconfig-002-20260325
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- riscv-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- riscv-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- riscv-randconfig-001-20260325
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- s390-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- s390-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- sh-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- sh-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- sparc-randconfig-r134-20260325
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- sparc64-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- um-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- um-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
| `-- include-linux-cleanup.h:error:invalid-initializer
|-- x86_64-allmodconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
|-- x86_64-allyesconfig
| |-- drivers-input-touchscreen-mxs-lradc-ts.c:error:initializing-class_spinlock_irqsave_t-with-an-expression-of-incompatible-type-class_spinlock_irqsave_t-(spinlock_t-)-(aka-class_spinlock_irqsave_t-(struc
| `-- drivers-input-touchscreen-mxs-lradc-ts.c:error:too-many-arguments-provided-to-function-like-macro-invocation
`-- xtensa-allyesconfig
|-- drivers-input-touchscreen-mxs-lradc-ts.c:error:macro-class_spinlock_irqsave_constructor-passed-arguments-but-takes-just
`-- include-linux-cleanup.h:error:invalid-initializer
elapsed time: 740m
configs tested: 170
configs skipped: 2
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc defconfig gcc-15.2.0
arc randconfig-001-20260325 gcc-8.5.0
arc randconfig-002-20260325 gcc-8.5.0
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm defconfig gcc-15.2.0
arm netwinder_defconfig gcc-15.2.0
arm randconfig-001-20260325 gcc-8.5.0
arm randconfig-002-20260325 gcc-8.5.0
arm randconfig-003-20260325 gcc-8.5.0
arm randconfig-004-20260325 gcc-8.5.0
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260325 clang-23
arm64 randconfig-002-20260325 clang-23
arm64 randconfig-003-20260325 clang-23
arm64 randconfig-004-20260325 clang-23
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260325 clang-23
csky randconfig-002-20260325 clang-23
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260325 gcc-11.5.0
hexagon randconfig-002-20260325 gcc-11.5.0
i386 allmodconfig clang-20
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001-20260325 gcc-14
i386 buildonly-randconfig-002-20260325 gcc-14
i386 buildonly-randconfig-003-20260325 gcc-14
i386 buildonly-randconfig-004-20260325 gcc-14
i386 buildonly-randconfig-005-20260325 gcc-14
i386 buildonly-randconfig-006-20260325 gcc-14
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260325 clang-20
i386 randconfig-002-20260325 clang-20
i386 randconfig-003-20260325 clang-20
i386 randconfig-004-20260325 clang-20
i386 randconfig-005-20260325 clang-20
i386 randconfig-006-20260325 clang-20
i386 randconfig-007-20260325 clang-20
i386 randconfig-011-20260325 clang-20
i386 randconfig-012-20260325 clang-20
i386 randconfig-013-20260325 clang-20
i386 randconfig-014-20260325 clang-20
i386 randconfig-015-20260325 clang-20
i386 randconfig-016-20260325 clang-20
i386 randconfig-017-20260325 clang-20
loongarch allmodconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260325 gcc-11.5.0
loongarch randconfig-002-20260325 gcc-11.5.0
m68k alldefconfig gcc-15.2.0
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 defconfig clang-19
nios2 randconfig-001-20260325 gcc-11.5.0
nios2 randconfig-002-20260325 gcc-11.5.0
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc defconfig gcc-15.2.0
openrisc virt_defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260325 clang-23
parisc randconfig-002-20260325 clang-23
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc randconfig-001-20260325 clang-23
powerpc randconfig-002-20260325 clang-23
powerpc64 randconfig-001-20260325 clang-23
powerpc64 randconfig-002-20260325 clang-23
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260325 gcc-8.5.0
riscv randconfig-002-20260325 gcc-8.5.0
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260325 gcc-8.5.0
s390 randconfig-002-20260325 gcc-8.5.0
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allyesconfig clang-19
sh defconfig gcc-14
sh randconfig-001-20260325 gcc-8.5.0
sh randconfig-002-20260325 gcc-8.5.0
sparc allnoconfig clang-23
sparc defconfig gcc-15.2.0
sparc randconfig-001-20260325 gcc-13
sparc randconfig-002-20260325 gcc-13
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260325 gcc-13
sparc64 randconfig-002-20260325 gcc-13
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260325 gcc-13
um randconfig-002-20260325 gcc-13
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260325 gcc-14
x86_64 buildonly-randconfig-002-20260325 gcc-14
x86_64 buildonly-randconfig-003-20260325 gcc-14
x86_64 buildonly-randconfig-004-20260325 gcc-14
x86_64 buildonly-randconfig-005-20260325 gcc-14
x86_64 buildonly-randconfig-006-20260325 gcc-14
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260325 gcc-12
x86_64 randconfig-002-20260325 gcc-12
x86_64 randconfig-003-20260325 gcc-12
x86_64 randconfig-004-20260325 gcc-12
x86_64 randconfig-005-20260325 gcc-12
x86_64 randconfig-006-20260325 gcc-12
x86_64 randconfig-011-20260325 clang-20
x86_64 randconfig-012-20260325 clang-20
x86_64 randconfig-013-20260325 clang-20
x86_64 randconfig-014-20260325 clang-20
x86_64 randconfig-015-20260325 clang-20
x86_64 randconfig-016-20260325 clang-20
x86_64 randconfig-071-20260325 gcc-14
x86_64 randconfig-072-20260325 gcc-14
x86_64 randconfig-073-20260325 gcc-14
x86_64 randconfig-074-20260325 gcc-14
x86_64 randconfig-075-20260325 gcc-14
x86_64 randconfig-076-20260325 gcc-14
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allyesconfig clang-23
xtensa randconfig-001-20260325 gcc-13
xtensa randconfig-002-20260325 gcc-13
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH] HID: cherry: Fix switch case formatting
From: aravindanilraj0702 @ 2026-03-25 16:13 UTC (permalink / raw)
To: jikos, bentiss; +Cc: linux-input, linux-kernel, Aravind Anilraj
From: Aravind Anilraj <aravindanilraj0702@gmail.com>
Fix checkpatch warnings by splitting single-line case
statements into multiple lines.
No functional changes.
Signed-off-by: Aravind Anilraj <aravindanilraj0702@gmail.com>
---
drivers/hid/hid-cherry.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/hid-cherry.c b/drivers/hid/hid-cherry.c
index a504632febfc..828a3cc67512 100644
--- a/drivers/hid/hid-cherry.c
+++ b/drivers/hid/hid-cherry.c
@@ -43,9 +43,15 @@ static int ch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
return 0;
switch (usage->hid & HID_USAGE) {
- case 0x301: ch_map_key_clear(KEY_PROG1); break;
- case 0x302: ch_map_key_clear(KEY_PROG2); break;
- case 0x303: ch_map_key_clear(KEY_PROG3); break;
+ case 0x301:
+ ch_map_key_clear(KEY_PROG1);
+ break;
+ case 0x302:
+ ch_map_key_clear(KEY_PROG2);
+ break;
+ case 0x303:
+ ch_map_key_clear(KEY_PROG3);
+ break;
default:
return 0;
}
--
2.47.3
^ permalink raw reply related
* Re: [PATCH 2/2] input: pegasus_notetaker: use HID defines
From: Dmitry Torokhov @ 2026-03-25 16:10 UTC (permalink / raw)
To: Oliver Neukum; +Cc: kees, linux-input
In-Reply-To: <20260325143256.371854-2-oneukum@suse.com>
On Wed, Mar 25, 2026 at 03:32:47PM +0100, Oliver Neukum wrote:
> The driver uses its own definitions for HID requests.
> This leads to duplication and obfuscation. Use HID's
> definitions.
>
> Signed-off-by: Oliver Neukum <oneukum@suse.com>
Applied, thank you.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 1/2] input: aiptek: use HID headers
From: Dmitry Torokhov @ 2026-03-25 16:10 UTC (permalink / raw)
To: Oliver Neukum; +Cc: kees, linux-input
In-Reply-To: <20260325143256.371854-1-oneukum@suse.com>
On Wed, Mar 25, 2026 at 03:32:46PM +0100, Oliver Neukum wrote:
> The driver uses its own definitions for HID requests.
> This leads to duplication and obfuscation. Use HID's
> definitions.
>
> Signed-off-by: Oliver Neukum <oneukum@suse.com>
Applied, thank you.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] Input: goodix-berlin: report a resolution of 10 units/mm
From: Dmitry Torokhov @ 2026-03-25 16:08 UTC (permalink / raw)
To: Val Packett
Cc: Hans de Goede, Henrik Rydberg, Stanislav Zaikin, linux-input,
phone-devel, ~postmarketos/upstreaming, linux-kernel
In-Reply-To: <20260321073242.556253-1-val@packett.cool>
On Sat, Mar 21, 2026 at 04:30:07AM -0300, Val Packett wrote:
> Without a reported resolution, userspace was assuming 1 unit/mm which
> is wildly wrong: a regular smartphone is clearly not 2.4 meters tall.
> Most applications do not care much for this kind of raw mm value,
> but Phosh's on-screen keyboard would accidentally trigger swipe-to-close
> gestures due to misinterpreting small movements as huge ones.
>
> Do what the older goodix.c driver does and set the resolution to 10
> units/mm to make sure the numbers calculated by userspace are reasonable.
>
> Signed-off-by: Val Packett <val@packett.cool>
Applied, thank you.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] HID: hid-lenovo-go: fix LEDS dependencies
From: Mark Pearson @ 2026-03-25 14:46 UTC (permalink / raw)
To: Arnd Bergmann, Jiri Kosina, Benjamin Tissoires, Derek J . Clark,
Limonciello, Mario, Ethan Tidmore
Cc: Arnd Bergmann, Aditya Garg, Jonathan Denose, Geert Uytterhoeven,
linux-input, linux-kernel
In-Reply-To: <20260324192045.2895560-1-arnd@kernel.org>
On Tue, Mar 24, 2026, at 3:20 PM, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
>
> The newly added hid-lenovo-go and hid-lenovo-go-s drivers attempt to
> 'select LEDS_CLASS', which has a dependency on NEW_LEDS, causing a build
> failure when NEW_LEDS itself it disabled:
>
> WARNING: unmet direct dependencies detected for LEDS_CLASS
> Depends on [n]: NEW_LEDS [=n]
> Selected by [m]:
> - HID_LENOVO_GO [=m] && HID_SUPPORT [=y] && HID [=m] && USB_HID [=m]
> - HID_LENOVO_GO_S [=m] && HID_SUPPORT [=y] && HID [=m] && USB_HID [=m]
>
> WARNING: unmet direct dependencies detected for LEDS_CLASS_MULTICOLOR
> Depends on [n]: NEW_LEDS [=n] && LEDS_CLASS [=m]
> Selected by [m]:
> - HID_LENOVO_GO [=m] && HID_SUPPORT [=y] && HID [=m] && USB_HID [=m]
> - HID_LENOVO_GO_S [=m] && HID_SUPPORT [=y] && HID [=m] && USB_HID [=m]
> ERROR: modpost: "led_set_brightness_nopm" [drivers/leds/led-class.ko]
> undefined!
> ERROR: modpost: "led_set_brightness" [drivers/leds/led-class.ko]
> undefined!
> ERROR: modpost: "led_get_color_name"
> [drivers/leds/led-class-multicolor.ko] undefined!
> ERROR: modpost: "led_set_brightness"
> [drivers/leds/led-class-multicolor.ko] undefined!
>
> Device drivers generally should not select other subsystems like this, as
> that tends to cause dependency problems including loops in the dependency
> graph.
>
> Change these two and the older hid-lenovo driver to behave the same way
> as all
> other HID drivers and use 'depends on LEDS_CLASS' or 'depends on
> LEDS_CLASS_MULTICOLOR'
> instead, which itself has NEW_LEDS and LEDS_CLASS as dependencies.
>
> Fixes: a23f3497bf20 ("HID: hid-lenovo-go-s: Add Lenovo Legion Go S
> Series HID Driver")
> Fixes: d69ccfcbc955 ("HID: hid-lenovo-go: Add Lenovo Legion Go Series
> HID Driver")
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> ---
> drivers/hid/Kconfig | 9 +++------
> 1 file changed, 3 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 10c12d8e6557..f658ed0168ea 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -610,8 +610,7 @@ config HID_LED
>
> config HID_LENOVO
> tristate "Lenovo / Thinkpad devices"
> - select NEW_LEDS
> - select LEDS_CLASS
> + depends on LEDS_CLASS
> help
> Support for IBM/Lenovo devices that are not fully compliant with HID standard.
>
> @@ -626,8 +625,7 @@ config HID_LENOVO
> config HID_LENOVO_GO
> tristate "HID Driver for Lenovo Legion Go Series Controllers"
> depends on USB_HID
> - select LEDS_CLASS
> - select LEDS_CLASS_MULTICOLOR
> + depends on LEDS_CLASS_MULTICOLOR
> help
> Support for Lenovo Legion Go devices with detachable controllers.
>
> @@ -638,8 +636,7 @@ config HID_LENOVO_GO
> config HID_LENOVO_GO_S
> tristate "HID Driver for Lenovo Legion Go S Controller"
> depends on USB_HID
> - select LEDS_CLASS
> - select LEDS_CLASS_MULTICOLOR
> + depends on LEDS_CLASS_MULTICOLOR
> help
> Support for Lenovo Legion Go S Handheld Console Controller.
>
> --
> 2.39.5
Looks good to me.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Mark
^ permalink raw reply
* [PATCH 2/2] input: pegasus_notetaker: use HID defines
From: Oliver Neukum @ 2026-03-25 14:32 UTC (permalink / raw)
To: dmitry.torokhov, kees, linux-input; +Cc: Oliver Neukum
In-Reply-To: <20260325143256.371854-1-oneukum@suse.com>
The driver uses its own definitions for HID requests.
This leads to duplication and obfuscation. Use HID's
definitions.
Signed-off-by: Oliver Neukum <oneukum@suse.com>
---
drivers/input/tablet/pegasus_notetaker.c | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/drivers/input/tablet/pegasus_notetaker.c b/drivers/input/tablet/pegasus_notetaker.c
index 3a8493ed5e44..6415bfe1886e 100644
--- a/drivers/input/tablet/pegasus_notetaker.c
+++ b/drivers/input/tablet/pegasus_notetaker.c
@@ -36,6 +36,7 @@
* T Tip
*/
+#include <linux/hid.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/input.h>
@@ -44,10 +45,6 @@
#include <linux/workqueue.h>
#include <linux/mutex.h>
-/* USB HID defines */
-#define USB_REQ_GET_REPORT 0x01
-#define USB_REQ_SET_REPORT 0x09
-
#define USB_VENDOR_ID_PEGASUSTECH 0x0e20
#define USB_DEVICE_ID_PEGASUS_NOTETAKER_EN100 0x0101
@@ -108,7 +105,7 @@ static int pegasus_control_msg(struct pegasus *pegasus, u8 *data, int len)
result = usb_control_msg(pegasus->usbdev,
usb_sndctrlpipe(pegasus->usbdev, 0),
- USB_REQ_SET_REPORT,
+ HID_REQ_SET_REPORT,
USB_TYPE_VENDOR | USB_DIR_OUT,
0, 0, cmd_buf, sizeof_buf,
USB_CTRL_SET_TIMEOUT);
--
2.53.0
^ permalink raw reply related
* [PATCH 1/2] input: aiptek: use HID headers
From: Oliver Neukum @ 2026-03-25 14:32 UTC (permalink / raw)
To: dmitry.torokhov, kees, linux-input; +Cc: Oliver Neukum
The driver uses its own definitions for HID requests.
This leads to duplication and obfuscation. Use HID's
definitions.
Signed-off-by: Oliver Neukum <oneukum@suse.com>
---
drivers/input/tablet/aiptek.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/drivers/input/tablet/aiptek.c b/drivers/input/tablet/aiptek.c
index 6df24cee3c9d..1ad3c19aa155 100644
--- a/drivers/input/tablet/aiptek.c
+++ b/drivers/input/tablet/aiptek.c
@@ -57,6 +57,7 @@
* http://aiptektablet.sourceforge.net.
*/
+#include <linux/hid.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -164,8 +165,6 @@
#define USB_VENDOR_ID_AIPTEK 0x08ca
#define USB_VENDOR_ID_KYE 0x0458
-#define USB_REQ_GET_REPORT 0x01
-#define USB_REQ_SET_REPORT 0x09
/* PointerMode codes
*/
@@ -856,7 +855,7 @@ aiptek_set_report(struct aiptek *aiptek,
return usb_control_msg(udev,
usb_sndctrlpipe(udev, 0),
- USB_REQ_SET_REPORT,
+ HID_REQ_SET_REPORT,
USB_TYPE_CLASS | USB_RECIP_INTERFACE |
USB_DIR_OUT, (report_type << 8) + report_id,
aiptek->ifnum, buffer, size, 5000);
@@ -871,7 +870,7 @@ aiptek_get_report(struct aiptek *aiptek,
return usb_control_msg(udev,
usb_rcvctrlpipe(udev, 0),
- USB_REQ_GET_REPORT,
+ HID_REQ_GET_REPORT,
USB_TYPE_CLASS | USB_RECIP_INTERFACE |
USB_DIR_IN, (report_type << 8) + report_id,
aiptek->ifnum, buffer, size, 5000);
--
2.53.0
^ permalink raw reply related
* regarding runtime PM in pegasus_notetaker
From: Oliver Neukum @ 2026-03-25 14:00 UTC (permalink / raw)
To: Martin Kepplinger; +Cc: linux-input@vger.kernel.org, USB list
[-- Attachment #1: Type: text/plain, Size: 388 bytes --]
Hi,
the driver takes a PM reference in open(), yet it marks
the device busy in pegasus_irq(). These approaches contradict
each other. There is no point in marking a device as busy
while its PM count is elevated. It will not be runtime suspended
anyway.
Did you mean for the device to be subjected to runtime PM
while in use? If so, could you test the attached patch?
Regards
Oliver
[-- Attachment #2: 0001-hid-pegasus_notetaker-runtime-PM-while-open.patch --]
[-- Type: text/x-patch, Size: 1988 bytes --]
From 62e8faf509e2ad464b39b6bf7bc324d6d93c50d5 Mon Sep 17 00:00:00 2001
From: Oliver Neukum <oneukum@suse.com>
Date: Wed, 25 Mar 2026 14:58:32 +0100
Subject: [PATCH] hid: pegasus_notetaker: runtime PM while open
This implements runtime PM while the device is open
by dropping the reference in open and using remote
wakeup.
Signed-off-by: Oliver Neukum <oneukum@suse.com>
---
drivers/input/tablet/pegasus_notetaker.c | 25 +++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/drivers/input/tablet/pegasus_notetaker.c b/drivers/input/tablet/pegasus_notetaker.c
index 4ce20befc657..3a8493ed5e44 100644
--- a/drivers/input/tablet/pegasus_notetaker.c
+++ b/drivers/input/tablet/pegasus_notetaker.c
@@ -211,10 +211,21 @@ static void pegasus_init(struct work_struct *work)
struct pegasus *pegasus = container_of(work, struct pegasus, init);
int error;
+ error = usb_autopm_get_interface(pegasus->intf);
+ if (error)
+ goto bail;
error = pegasus_set_mode(pegasus, PEN_MODE_XY, NOTETAKER_LED_MOUSE);
if (error)
- dev_err(&pegasus->usbdev->dev, "pegasus_set_mode error: %d\n",
- error);
+ goto bail_pm;
+
+ usb_autopm_put_interface(pegasus->intf);
+ return;
+
+bail_pm:
+ usb_autopm_put_interface(pegasus->intf);
+bail:
+ dev_err(&pegasus->usbdev->dev, "pegasus_set_mode error: %d\n",
+ error);
}
static int __pegasus_open(struct pegasus *pegasus)
@@ -249,12 +260,10 @@ static int pegasus_open(struct input_dev *dev)
return error;
error = __pegasus_open(pegasus);
- if (error) {
- usb_autopm_put_interface(pegasus->intf);
- return error;
- }
+ pegasus->intf->needs_remote_wakeup = 1;
+ usb_autopm_put_interface(pegasus->intf);
- return 0;
+ return error;
}
static void pegasus_close(struct input_dev *dev)
@@ -267,8 +276,6 @@ static void pegasus_close(struct input_dev *dev)
pegasus->is_open = false;
}
-
- usb_autopm_put_interface(pegasus->intf);
}
static int pegasus_probe(struct usb_interface *intf,
--
2.53.0
^ permalink raw reply related
* [dtor-input:next 32/43] drivers/input/touchscreen/mxs-lradc-ts.c:508:45: error: too many arguments provided to function-like macro invocation
From: kernel test robot @ 2026-03-25 12:30 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: llvm, oe-kbuild-all, linux-input
tree: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
head: 303cdff39cdb1895a6d2b08e8946cc89feaf405c
commit: a995f3ca3bd51c348bd4c8d1833d318bef1a5001 [32/43] Input: mxs-lradc-ts - use guard notation when acquiring spinlock
config: riscv-randconfig-001-20260325 (https://download.01.org/0day-ci/archive/20260325/202603252030.fwSlmiBx-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260325/202603252030.fwSlmiBx-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603252030.fwSlmiBx-lkp@intel.com/
All errors (new ones prefixed by >>):
>> drivers/input/touchscreen/mxs-lradc-ts.c:508:45: error: too many arguments provided to function-like macro invocation
508 | scoped_guard(spinlock_irqsave, &ts->lock, flags) {
| ^
include/linux/spinlock.h:623:9: note: macro 'class_spinlock_irqsave_constructor' defined here
623 | #define class_spinlock_irqsave_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_irqsave, _T)
| ^
>> drivers/input/touchscreen/mxs-lradc-ts.c:508:3: error: initializing 'class_spinlock_irqsave_t' with an expression of incompatible type 'class_spinlock_irqsave_t (spinlock_t *)' (aka 'class_spinlock_irqsave_t (struct spinlock *)')
508 | scoped_guard(spinlock_irqsave, &ts->lock, flags) {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/cleanup.h:447:2: note: expanded from macro 'scoped_guard'
447 | __scoped_guard(_name, __UNIQUE_ID(label), args)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/cleanup.h:438:20: note: expanded from macro '__scoped_guard'
438 | for (CLASS(_name, scope)(args); \
| ~~~~~~~~~~~~~^~~~~~
include/linux/cleanup.h:299:20: note: expanded from macro 'CLASS'
299 | class_##_name##_t var __cleanup(class_##_name##_destructor) = \
| ^
300 | class_##_name##_constructor
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 errors generated.
vim +508 drivers/input/touchscreen/mxs-lradc-ts.c
491
492 /* IRQ Handling */
493 static irqreturn_t mxs_lradc_ts_handle_irq(int irq, void *data)
494 {
495 struct mxs_lradc_ts *ts = data;
496 struct mxs_lradc *lradc = ts->lradc;
497 unsigned long reg = readl(ts->base + LRADC_CTRL1);
498 u32 clr_irq = mxs_lradc_irq_mask(lradc);
499 const u32 ts_irq_mask =
500 LRADC_CTRL1_TOUCH_DETECT_IRQ |
501 LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) |
502 LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2);
503
504 if (!(reg & mxs_lradc_irq_mask(lradc)))
505 return IRQ_NONE;
506
507 if (reg & ts_irq_mask) {
> 508 scoped_guard(spinlock_irqsave, &ts->lock, flags) {
509 mxs_lradc_handle_touch(ts);
510 }
511 /* Make sure we don't clear the next conversion's interrupt. */
512 clr_irq &= ~(LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) |
513 LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2));
514 writel(reg & clr_irq,
515 ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
516 }
517
518 return IRQ_HANDLED;
519 }
520
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [dtor-input:next 32/43] drivers/input/touchscreen/mxs-lradc-ts.c:508:8: error: macro "class_spinlock_irqsave_constructor" passed 2 arguments, but takes just 1
From: kernel test robot @ 2026-03-25 12:18 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: oe-kbuild-all, linux-input
tree: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
head: 303cdff39cdb1895a6d2b08e8946cc89feaf405c
commit: a995f3ca3bd51c348bd4c8d1833d318bef1a5001 [32/43] Input: mxs-lradc-ts - use guard notation when acquiring spinlock
config: sparc-randconfig-r134-20260325 (https://download.01.org/0day-ci/archive/20260325/202603252057.JgSqGyIe-lkp@intel.com/config)
compiler: sparc-linux-gcc (GCC) 14.3.0
sparse: v0.6.5-rc1
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260325/202603252057.JgSqGyIe-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603252057.JgSqGyIe-lkp@intel.com/
All errors (new ones prefixed by >>):
drivers/input/touchscreen/mxs-lradc-ts.c: In function 'mxs_lradc_ts_handle_irq':
>> drivers/input/touchscreen/mxs-lradc-ts.c:508:8: error: macro "class_spinlock_irqsave_constructor" passed 2 arguments, but takes just 1
508 | scoped_guard(spinlock_irqsave, &ts->lock, flags) {
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from include/linux/sched.h:37,
from include/linux/ratelimit.h:6,
from include/linux/dev_printk.h:16,
from include/linux/device.h:15,
from drivers/input/touchscreen/mxs-lradc-ts.c:13:
include/linux/spinlock.h:623:9: note: macro "class_spinlock_irqsave_constructor" defined here
623 | #define class_spinlock_irqsave_constructor(_T) WITH_LOCK_GUARD_1_ATTRS(spinlock_irqsave, _T)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from include/linux/irqflags.h:17,
from include/asm-generic/cmpxchg-local.h:6,
from arch/sparc/include/asm/cmpxchg_32.h:67,
from arch/sparc/include/asm/cmpxchg.h:7,
from arch/sparc/include/asm/atomic_32.h:17,
from arch/sparc/include/asm/atomic.h:7,
from include/linux/atomic.h:7,
from include/asm-generic/bitops/lock.h:5,
from arch/sparc/include/asm/bitops_32.h:102,
from arch/sparc/include/asm/bitops.h:7,
from include/linux/bitops.h:67,
from include/linux/log2.h:12,
from include/asm-generic/div64.h:55,
from ./arch/sparc/include/generated/asm/div64.h:1,
from include/linux/math.h:6,
from include/linux/math64.h:6,
from include/linux/time64.h:5,
from include/linux/restart_block.h:9,
from include/linux/thread_info.h:14,
from arch/sparc/include/asm/current.h:15,
from include/linux/sched.h:12:
>> include/linux/cleanup.h:300:17: error: invalid initializer
300 | class_##_name##_constructor
| ^~~~~~
include/linux/cleanup.h:438:14: note: in expansion of macro 'CLASS'
438 | for (CLASS(_name, scope)(args); \
| ^~~~~
include/linux/cleanup.h:447:9: note: in expansion of macro '__scoped_guard'
447 | __scoped_guard(_name, __UNIQUE_ID(label), args)
| ^~~~~~~~~~~~~~
drivers/input/touchscreen/mxs-lradc-ts.c:508:17: note: in expansion of macro 'scoped_guard'
508 | scoped_guard(spinlock_irqsave, &ts->lock, flags) {
| ^~~~~~~~~~~~
vim +/class_spinlock_irqsave_constructor +508 drivers/input/touchscreen/mxs-lradc-ts.c
491
492 /* IRQ Handling */
493 static irqreturn_t mxs_lradc_ts_handle_irq(int irq, void *data)
494 {
495 struct mxs_lradc_ts *ts = data;
496 struct mxs_lradc *lradc = ts->lradc;
497 unsigned long reg = readl(ts->base + LRADC_CTRL1);
498 u32 clr_irq = mxs_lradc_irq_mask(lradc);
499 const u32 ts_irq_mask =
500 LRADC_CTRL1_TOUCH_DETECT_IRQ |
501 LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) |
502 LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2);
503
504 if (!(reg & mxs_lradc_irq_mask(lradc)))
505 return IRQ_NONE;
506
507 if (reg & ts_irq_mask) {
> 508 scoped_guard(spinlock_irqsave, &ts->lock, flags) {
509 mxs_lradc_handle_touch(ts);
510 }
511 /* Make sure we don't clear the next conversion's interrupt. */
512 clr_irq &= ~(LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) |
513 LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2));
514 writel(reg & clr_irq,
515 ts->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
516 }
517
518 return IRQ_HANDLED;
519 }
520
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v8 1/7] dt-bindings: input: syna,rmi4: Document syna,rmi4-s3706b
From: David Heidelberg @ 2026-03-25 11:33 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Kaustabh Chakraborty, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Jason A. Donenfeld, Matthias Schiffer,
Vincent Huang, Casey Connolly, linux-input, devicetree,
linux-kernel, phone-devel, Krzysztof Kozlowski
In-Reply-To: <acLpBXc9Qi5riQO0@google.com>
On 24/03/2026 20:42, Dmitry Torokhov wrote:
> On Tue, Mar 24, 2026 at 08:40:34PM +0100, David Heidelberg via B4 Relay wrote:
>> From: David Heidelberg <david@ixit.cz>
>>
>> Mostly irrelevant for authentic Synaptics touchscreens, but very important
>> for applying workarounds to cheap TS knockoffs.
>>
>> These knockoffs work well with the downstream driver, and since the user
>> has no way to distinguish them, later in this patch set, we introduce
>> workarounds to ensure they function as well as possible.
>>
>> Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
>> Signed-off-by: David Heidelberg <david@ixit.cz>
>> ---
>> Documentation/devicetree/bindings/input/syna,rmi4.yaml | 11 ++++++++---
>> 1 file changed, 8 insertions(+), 3 deletions(-)
>>
>> diff --git a/Documentation/devicetree/bindings/input/syna,rmi4.yaml b/Documentation/devicetree/bindings/input/syna,rmi4.yaml
>> index 8685ef4481f4a..fb4804ac3544d 100644
>> --- a/Documentation/devicetree/bindings/input/syna,rmi4.yaml
>> +++ b/Documentation/devicetree/bindings/input/syna,rmi4.yaml
>> @@ -18,9 +18,14 @@ description: |
>>
>> properties:
>> compatible:
>> - enum:
>> - - syna,rmi4-i2c
>> - - syna,rmi4-spi
>> + oneOf:
>> + - enum:
>> + - syna,rmi4-i2c
>> + - syna,rmi4-spi
>> + - items:
>> + - enum:
>> + - syna,rmi4-s3706b # OnePlus 6/6T
>
> I thought that all the workarounds will be keyed off this new
> compatible, but I do not see that. What am I missing?
The compatible is used for sequence in the
Input: synaptics-rmi4 - support fallback values for PDT descriptor bytes
where it is used to provide values missing for OP6 (and possible others in the
future, when added).
From my understanding the series, only two patches (1st and last) are specific
for the OP6, rest will likely benefit various TS not implementing full Synaptics
set. All measures apply only when touchscreen reports something wrong.
David
>
> Thanks.
>
--
David Heidelberg
^ permalink raw reply
* Re: [PATCH v4] HID: generic: add LampArray support via hid-lamparray helper
From: Werner Sembach @ 2026-03-25 10:35 UTC (permalink / raw)
To: Tim Guttzeit, Jiri Kosina, Benjamin Tissoires
Cc: linux-kernel, linux-input, linux-leds, Hans de Goede,
Hans de Goede
In-Reply-To: <52ddeec1-d651-4aa3-bb0f-7a45d8620b26@tuxedocomputers.com>
Am 25.03.26 um 11:21 schrieb Werner Sembach:
> Hi,
>
> as Tim went on to other endeavors, I'm taking over this patch. I have yet to
> test it myself, but i was already in the feedback loop while he was writing
> it, so I'm not unfamiliar with the code.
>
> Is there already some initial feedback or thoughts about this?
>
> This patch implements the
>
> - (Optional) Implement a generic LampArray leds subsystem driver that maps to
> the single zone control and ads the use_leds_uapi sysfs switch to the virtual
> HID device
>
> sub point from the rough outline described back here
> https://lore.kernel.org/all/1fb08a74-62c7-4d0c-ba5d-648e23082dcb@tuxedocomputers.com/
>
+cc Hans' correct current email
>
> Best regards,
>
> Werner
>
> Am 23.02.26 um 09:39 schrieb Tim Guttzeit:
>> Add a new hid-lamparray helper module and integrate it with the
>> hid-generic driver.
>>
>> The helper provides basic support for devices exposing a
>> Lighting/LampArray application collection (usage page 0x59) and
>> registers a single-zone RGB LED representation via the LED
>> subsystem.
>>
>> hid-generic now checks for LampArray support after hid_parse() and
>> optionally registers a lamparray instance. Failures in the helper
>> do not abort device probe to keep the device functional.
>>
>> LampArray resources are released on driver remove.
>>
>> Signed-off-by: Tim Guttzeit <tgu@tuxedocomputers.com>
>> ---
>> V1 -> V2: Fix Kconfig to avoid build errors when LEDS_CLASS_MULTICOLOR is
>> disabled
>> V2 -> V3: Squash V1 and V2 into one patch
>> V3 -> V4: Restrict CONFIG_HID_LAMPARRAY to built-in configurations only
>> to fix additional randconfig build errors
>>
>> drivers/hid/Kconfig | 10 +
>> drivers/hid/Makefile | 2 +
>> drivers/hid/hid-generic.c | 41 +-
>> drivers/hid/hid-lamparray.c | 855 ++++++++++++++++++++++++++++++++++++
>> drivers/hid/hid-lamparray.h | 91 ++++
>> 5 files changed, 997 insertions(+), 2 deletions(-)
>> create mode 100644 drivers/hid/hid-lamparray.c
>> create mode 100644 drivers/hid/hid-lamparray.h
>>
>> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
>> index 920a64b66b25..548dc708e5cd 100644
>> --- a/drivers/hid/Kconfig
>> +++ b/drivers/hid/Kconfig
>> @@ -92,6 +92,16 @@ config HID_GENERIC
>> If unsure, say Y.
>> +config HID_LAMPARRAY
>> + bool "HID LampArray helper"
>> + depends on HID=y && HID_GENERIC=y && LEDS_CLASS=y &&
>> LEDS_CLASS_MULTICOLOR=y
>> + default y
>> + help
>> + Helper for HID devices exposing a Lighting/LampArray collection.
>> + Treats LampArray devices as a single-zone device and exposes a sysfs
>> + interface for changing color and intensity values. Also exposes a
>> + sysfs flag to be disabled e.g. by a userspace driver.
>> +
>> config HID_HAPTIC
>> bool "Haptic touchpad support"
>> default n
>> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
>> index 361a7daedeb8..5a14b4b0970d 100644
>> --- a/drivers/hid/Makefile
>> +++ b/drivers/hid/Makefile
>> @@ -13,6 +13,8 @@ obj-$(CONFIG_UHID) += uhid.o
>> obj-$(CONFIG_HID_GENERIC) += hid-generic.o
>> +obj-$(CONFIG_HID_LAMPARRAY) += hid-lamparray.o
>> +
>> hid-$(CONFIG_HIDRAW) += hidraw.o
>> hid-logitech-y := hid-lg.o
>> diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
>> index c2de916747de..57b81c86982c 100644
>> --- a/drivers/hid/hid-generic.c
>> +++ b/drivers/hid/hid-generic.c
>> @@ -20,6 +20,7 @@
>> #include <asm/byteorder.h>
>> #include <linux/hid.h>
>> +#include "hid-lamparray.h"
>> static struct hid_driver hid_generic;
>> @@ -31,7 +32,7 @@ static int __check_hid_generic(struct device_driver *drv,
>> void *data)
>> if (hdrv == &hid_generic)
>> return 0;
>> - return hid_match_device(hdev, hdrv) != NULL;
>> + return !!hid_match_device(hdev, hdrv);
>> }
>> static bool hid_generic_match(struct hid_device *hdev,
>> @@ -60,6 +61,7 @@ static int hid_generic_probe(struct hid_device *hdev,
>> const struct hid_device_id *id)
>> {
>> int ret;
>> + struct lamparray *la = NULL;
>> hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
>> @@ -67,7 +69,31 @@ static int hid_generic_probe(struct hid_device *hdev,
>> if (ret)
>> return ret;
>> - return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
>> + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
>> + if (ret)
>> + return ret;
>> +
>> + /*
>> + * Optional: attach LampArray support if present.
>> + * Never fail probe on LampArray errors; keep device functional.
>> + */
>> + if (IS_ENABLED(CONFIG_HID_LAMPARRAY) &&
>> lamparray_is_supported_device(hdev)) {
>> + const struct lamparray_init_state init = {
>> + .r = 0xff,
>> + .g = 0xff,
>> + .b = 0xff,
>> + .brightness = LED_FULL,
>> + };
>> +
>> + la = lamparray_register(hdev, &init);
>> + if (IS_ERR(la)) {
>> + hid_warn(hdev, "LampArray init failed: %ld\n", PTR_ERR(la));
>> + la = NULL;
>> + }
>> + }
>> +
>> + hid_set_drvdata(hdev, la);
>> + return 0;
>> }
>> static int hid_generic_reset_resume(struct hid_device *hdev)
>> @@ -78,6 +104,16 @@ static int hid_generic_reset_resume(struct hid_device *hdev)
>> return 0;
>> }
>> +static void hid_generic_remove(struct hid_device *hdev)
>> +{
>> + struct lamparray *la = hid_get_drvdata(hdev);
>> +
>> + if (IS_ENABLED(CONFIG_HID_LAMPARRAY) && la)
>> + lamparray_unregister(la);
>> +
>> + hid_hw_stop(hdev);
>> +}
>> +
>> static const struct hid_device_id hid_table[] = {
>> { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, HID_ANY_ID, HID_ANY_ID) },
>> { }
>> @@ -90,6 +126,7 @@ static struct hid_driver hid_generic = {
>> .match = hid_generic_match,
>> .probe = hid_generic_probe,
>> .reset_resume = hid_generic_reset_resume,
>> + .remove = hid_generic_remove,
>> };
>> module_hid_driver(hid_generic);
>> diff --git a/drivers/hid/hid-lamparray.c b/drivers/hid/hid-lamparray.c
>> new file mode 100644
>> index 000000000000..5be46fff0191
>> --- /dev/null
>> +++ b/drivers/hid/hid-lamparray.c
>> @@ -0,0 +1,855 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * hid-lamparray.c - HID LampArray helper module (single-zone RGB)
>> + *
>> + * Helper module for HID drivers supporting devices that expose a
>> + * Lighting and Illumination (LampArray) application collection
>> + * (usage page 0x59).
>> + *
>> + * The module provides a minimal integration with the LED subsystem
>> + * and treats the device as a single zone: all lamps share one RGB
>> + * value and a global brightness level. It does not implement multi-
>> + * zone layouts or hardware effects.
>> + *
>> + *
>> + * If enabled, one multicolor LED class device is registered under
>> + * /sys/class/leds/<HID-ID> to expose the single-zone RGB control.
>> + *
>> + * The use_leds_uapi sysfs attribute is attached directly to the HID device
>> + * under /sys/bus/hid/devices/<HID-ID>/use_leds_uapi.Writing 0 to use_leds_uapi
>> + * unregisters the LED class device. The last state is kept cached. Writing 1
>> + * registers it again and restores the cached state to hardware.
>> + *
>> + * State is cached as last known RGB + brightness. Setting sends a HID
>> + * SET_REPORT. Getting issues a HID GET_REPORT and updates the cache on
>> + * mismatch. Since the device is handled as single-zone, readback only queries
>> + * lamp 0 when a lamp range is available.
>> + *
>> + * The module does not bind to devices on its own. Instead, a HID
>> + * driver may query support via lamparray_is_supported_device() after
>> + * hid_parse() and create an instance using lamparray_register().
>> + *
>> + * Copyright (C) 2026 Tim Guttzeit <tgu@tuxedocomputers.com>
>> + */
>> +
>> +#include "hid-lamparray.h"
>> +#include <linux/module.h>
>> +#include <linux/device.h>
>> +#include <linux/hid.h>
>> +#include <linux/leds.h>
>> +#include <linux/led-class-multicolor.h>
>> +#include <linux/slab.h>
>> +#include <linux/sysfs.h>
>> +#include <linux/mutex.h>
>> +#include <linux/bitops.h>
>> +#include <linux/xarray.h>
>> +
>> +/* Constants */
>> +
>> +/* HID usages (LampArray, etc.) */
>> +#define HID_LIGHTING_ILLUMINATION_USAGE_PAGE 0x0059
>> +
>> +#define HID_LAIP_LAMP_COUNT 0x0003
>> +#define HID_LAIP_LAMP_ID 0x0021
>> +#define HID_LAIP_RED_UPDATE_CHANNEL 0x0051
>> +#define HID_LAIP_GREEN_UPDATE_CHANNEL 0x0052
>> +#define HID_LAIP_BLUE_UPDATE_CHANNEL 0x0053
>> +#define HID_LAIP_INTENSITY_UPDATE_CHANNEL 0x0054
>> +#define HID_LAIP_LAMP_ID_START 0x0061
>> +#define HID_LAIP_LAMP_ID_END 0x0062
>> +#define HID_LAIP_AUTONOMOUS_MODE 0x0071
>> +
>> +#define HID_APPLICATION_COLLECTION_USAGE_TYPE 0x0001
>> +
>> +/* Device state */
>> +
>> +struct lamparray_quirks {
>> + unsigned long flags;
>> + int fixed_lamp_count;
>> +};
>> +
>> +#define LAMPARRAY_QUIRK_LAMPCOUNT_FIXED BIT(0)
>> +
>> +struct lamparray_quirk_entry {
>> + u16 vendor;
>> + u16 product;
>> + unsigned long flags;
>> + int fixed_lamp_count;
>> +};
>> +
>> +static const struct lamparray_quirk_entry lamparray_quirk_table[] = {
>> + { 0xcafe, 0x4005, LAMPARRAY_QUIRK_LAMPCOUNT_FIXED, 12 },
>> + {}
>> +};
>> +
>> +static const struct lamparray_quirk_entry *
>> +lamparray_lookup_quirks(struct hid_device *hdev)
>> +{
>> + const struct lamparray_quirk_entry *e;
>> +
>> + for (e = lamparray_quirk_table; e->vendor; e++) {
>> + if (hdev->vendor == e->vendor && hdev->product == e->product)
>> + return e;
>> + }
>> + return NULL;
>> +}
>> +
>> +struct lamparray_device {
>> + const struct lamparray_quirk_entry *quirks;
>> +
>> + struct hid_device *hdev;
>> + struct hid_report *update_report;
>> +
>> + struct hid_field *red_field;
>> + int red_index;
>> + struct hid_field *green_field;
>> + int green_index;
>> + struct hid_field *blue_field;
>> + int blue_index;
>> + struct hid_field *intensity_field;
>> + int intensity_index;
>> +
>> + struct hid_report *autonomous_report;
>> + struct hid_field *autonomous_field;
>> +
>> + struct hid_field *range_start_field;
>> + int range_start_index;
>> +
>> + struct hid_field *range_end_field;
>> + int range_end_index;
>> +
>> + struct hid_field *lamp_count_field;
>> + int lamp_count;
>> + int lamp_count_index;
>> +
>> + struct led_classdev_mc mc_cdev;
>> + struct mc_subled subleds[3];
>> +
>> + struct mutex lock; /* protects cached state and HID access */
>> +
>> + u8 last_r;
>> + u8 last_g;
>> + u8 last_b;
>> + enum led_brightness last_brightness;
>> +
>> + bool use_leds_uapi;
>> + bool led_registered;
>> +};
>> +
>> +/*
>> + * Opaque handle exposed to callers via the header.
>> + * Keep the actual state in lamparray_device, but return a stable pointer.
>> + */
>> +struct lamparray {
>> + struct lamparray_device ldev;
>> +};
>> +
>> +static DEFINE_XARRAY(lamparray_by_hdev);
>> +
>> +/* HID helper functions */
>> +
>> +#ifdef DEBUG
>> +static void lamparray_dump_reports(struct hid_device *hdev)
>> +{
>> + struct hid_report_enum *re;
>> + struct hid_report *report;
>> + int i, j, report_type;
>> +
>> + for (report_type = 0; report_type < HID_REPORT_TYPES; report_type++) {
>> + re = &hdev->report_enum[report_type];
>> + hid_dbg(hdev, "Dumping reports for report type %d",
>> + report_type);
>> + list_for_each_entry(report, &re->report_list, list) {
>> + hid_dbg(hdev,
>> + "lamparray: report id=%u type=%d maxfield=%u\n",
>> + report->id, report->type, report->maxfield);
>> +
>> + for (i = 0; i < report->maxfield; i++) {
>> + struct hid_field *field = report->field[i];
>> +
>> + for (j = 0; j < field->maxusage; j++) {
>> + u32 usage = field->usage[j].hid;
>> + u16 page = usage >> 16;
>> + u16 id = usage & 0xFFFF;
>> +
>> + hid_dbg(hdev,
>> + "lamparray: report %u field %d usage[%d]:
>> page=0x%04x id=0x%04x\n",
>> + report->id, i, j, page, id);
>> + }
>> + }
>> + }
>> + }
>> +}
>> +#else
>> +static inline void lamparray_dump_reports(struct hid_device *hdev)
>> +{}
>> +#endif
>> +
>> +static int lamparray_read_lamp_count(struct lamparray_device *ldev)
>> +{
>> + struct hid_device *hdev = ldev->hdev;
>> + struct hid_report *report;
>> +
>> + if (ldev->quirks &&
>> + (ldev->quirks->flags & LAMPARRAY_QUIRK_LAMPCOUNT_FIXED)) {
>> + ldev->lamp_count = ldev->quirks->fixed_lamp_count;
>> + hid_dbg(hdev, "LampCount from quirk: %d\n", ldev->lamp_count);
>> + return 0;
>> + }
>> + if (!ldev->lamp_count_field) {
>> + hid_warn(hdev, "No LampCount field found\n");
>> + return -ENODEV;
>> + }
>> +
>> + report = ldev->lamp_count_field->report;
>> +
>> + if (!report) {
>> + hid_warn(hdev, "LampCount field has no report\n");
>> + return -ENODEV;
>> + }
>> + hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
>> + ldev->lamp_count =
>> + ldev->lamp_count_field->value[ldev->lamp_count_index];
>> +
>> + hid_dbg(hdev, "LampCount from device: %d\n", ldev->lamp_count);
>> +
>> + if (ldev->lamp_count <= 0) {
>> + hid_warn(hdev, "LampCount is %d (invalid)\n", ldev->lamp_count);
>> + return -EINVAL;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int lamparray_parse_update_report(struct lamparray_device *ldev)
>> +{
>> + struct hid_device *hdev = ldev->hdev;
>> + struct hid_report_enum *re;
>> + struct hid_report *report;
>> + struct hid_field *field;
>> + int i, j;
>> +
>> + lamparray_dump_reports(hdev);
>> +
>> + re = &hdev->report_enum[HID_FEATURE_REPORT];
>> +
>> + list_for_each_entry(report, &re->report_list, list) {
>> + for (i = 0; i < report->maxfield; i++) {
>> + field = report->field[i];
>> + if (!field)
>> + continue;
>> +
>> + if (!field->usage || !field->maxusage)
>> + continue;
>> +
>> + for (j = 0; j < field->maxusage; j++) {
>> + u32 usage = field->usage[j].hid;
>> + u16 page = usage >> 16;
>> + u16 id = usage & 0xffff;
>> +
>> + if (page !=
>> + HID_LIGHTING_ILLUMINATION_USAGE_PAGE)
>> + continue;
>> + switch (id) {
>> + case HID_LAIP_LAMP_COUNT:
>> + ldev->lamp_count_field = field;
>> + ldev->lamp_count_index = j;
>> + break;
>> + case HID_LAIP_RED_UPDATE_CHANNEL:
>> + ldev->update_report = report;
>> + ldev->red_field = field;
>> + ldev->red_index = j;
>> + break;
>> + case HID_LAIP_GREEN_UPDATE_CHANNEL:
>> + ldev->update_report = report;
>> + ldev->green_field = field;
>> + ldev->green_index = j;
>> + break;
>> + case HID_LAIP_BLUE_UPDATE_CHANNEL:
>> + ldev->update_report = report;
>> + ldev->blue_field = field;
>> + ldev->blue_index = j;
>> + break;
>> + case HID_LAIP_INTENSITY_UPDATE_CHANNEL:
>> + ldev->update_report = report;
>> + ldev->intensity_field = field;
>> + ldev->intensity_index = j;
>> + break;
>> + case HID_LAIP_LAMP_ID_START:
>> + ldev->range_start_field = field;
>> + ldev->range_start_index = j;
>> + break;
>> + case HID_LAIP_LAMP_ID_END:
>> + ldev->range_end_field = field;
>> + ldev->range_end_index = j;
>> + break;
>> + case HID_LAIP_AUTONOMOUS_MODE:
>> + ldev->autonomous_field = field;
>> + ldev->autonomous_report = report;
>> + break;
>> + default:
>> + break;
>> + }
>> + }
>> + }
>> + }
>> +
>> + if (!ldev->update_report || !ldev->red_field || !ldev->green_field ||
>> + !ldev->blue_field || !ldev->autonomous_report ||
>> !ldev->autonomous_field)
>> + return -ENODEV;
>> +
>> + return 0;
>> +}
>> +
>> +static int lamparray_hw_set_autonomous(struct lamparray_device *ldev,
>> + bool enable)
>> +{
>> + struct hid_device *hdev = ldev->hdev;
>> + struct hid_field *field = ldev->autonomous_field;
>> + struct hid_report *report = ldev->autonomous_report;
>> +
>> + if (!field || !report)
>> + return -ENODEV;
>> +
>> + field->value[0] = enable ? 1 : 0;
>> +
>> + hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
>> + return 0;
>> +}
>> +
>> +static int lamparray_hw_set_state(struct lamparray_device *ldev, u8 r, u8 g,
>> + u8 b, u8 intensity)
>> +{
>> + struct hid_device *hdev = ldev->hdev;
>> + struct hid_report *report = ldev->update_report;
>> + int i, j;
>> +
>> + if (!report || !ldev->red_field || !ldev->green_field ||
>> + !ldev->blue_field || !ldev->intensity_field)
>> + return -ENODEV;
>> +
>> + if (ldev->range_start_field && ldev->range_end_field) {
>> + ldev->range_start_field->value[ldev->range_start_index] = 0;
>> + ldev->range_end_field->value[ldev->range_end_index] = ldev->lamp_count - 1;
>> + }
>> +
>> + for (i = 0; i < report->maxfield; i++) {
>> + struct hid_field *field = report->field[i];
>> +
>> + if (!field || !field->usage || !field->maxusage)
>> + continue;
>> +
>> + for (j = 0; j < field->maxusage; j++) {
>> + u32 usage = field->usage[j].hid;
>> + u16 page = usage >> 16;
>> + u16 id = usage & 0xffff;
>> +
>> + if (page != HID_LIGHTING_ILLUMINATION_USAGE_PAGE)
>> + continue;
>> +
>> + switch (id) {
>> + case HID_LAIP_RED_UPDATE_CHANNEL:
>> + field->value[j] = r;
>> + break;
>> + case HID_LAIP_GREEN_UPDATE_CHANNEL:
>> + field->value[j] = g;
>> + break;
>> + case HID_LAIP_BLUE_UPDATE_CHANNEL:
>> + field->value[j] = b;
>> + break;
>> + case HID_LAIP_INTENSITY_UPDATE_CHANNEL:
>> + field->value[j] = intensity;
>> + break;
>> + default:
>> + break;
>> + }
>> + }
>> + }
>> +
>> + hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
>> + return 0;
>> +}
>> +
>> +static int lamparray_hw_get_state(struct lamparray_device *ldev, u8 *r, u8 *g,
>> + u8 *b, enum led_brightness *brightness)
>> +{
>> + struct hid_device *hdev = ldev->hdev;
>> + struct hid_report *report = ldev->update_report;
>> +
>> + if (!report || !ldev->red_field || !ldev->green_field ||
>> + !ldev->blue_field || !ldev->intensity_field)
>> + return -ENODEV;
>> +
>> + if (!r || !g || !b || !brightness)
>> + return -EINVAL;
>> +
>> + /* Single-zone: Reading lamp 0 only suffices */
>> + if (ldev->range_start_field && ldev->range_end_field) {
>> + ldev->range_start_field->value[ldev->range_start_index] = 0;
>> + ldev->range_end_field->value[ldev->range_end_index] = 0;
>> + }
>> +
>> + hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
>> +
>> + *r = ldev->red_field->value[ldev->red_index];
>> + *g = ldev->green_field->value[ldev->green_index];
>> + *b = ldev->blue_field->value[ldev->blue_index];
>> + *brightness = ldev->intensity_field->value[ldev->intensity_index];
>> +
>> + return 0;
>> +}
>> +
>> +/* Helper functions */
>> +
>> +static int lamparray_restore_state(struct lamparray_device *ldev)
>> +{
>> + u8 r, g, b;
>> + int ret;
>> + enum led_brightness brightness;
>> +
>> + mutex_lock(&ldev->lock);
>> +
>> + if (!ldev->use_leds_uapi) {
>> + mutex_unlock(&ldev->lock);
>> + return 0;
>> + }
>> +
>> + r = ldev->last_r;
>> + g = ldev->last_g;
>> + b = ldev->last_b;
>> + brightness = ldev->last_brightness;
>> +
>> + ldev->mc_cdev.subled_info[0].brightness = r;
>> + ldev->mc_cdev.subled_info[1].brightness = g;
>> + ldev->mc_cdev.subled_info[2].brightness = b;
>> + ldev->mc_cdev.led_cdev.brightness = brightness;
>> +
>> + mutex_unlock(&ldev->lock);
>> +
>> + ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
>> + return ret;
>> +}
>> +
>> +/* LEDs API */
>> +
>> +static int lamparray_led_brightness_set(struct led_classdev *cdev,
>> + enum led_brightness brightness)
>> +{
>> + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
>> + struct lamparray_device *ldev =
>> + container_of(mc, struct lamparray_device, mc_cdev);
>> + struct lamparray *la = container_of(ldev, struct lamparray, ldev);
>> + u8 r, g, b;
>> + int ret;
>> +
>> + if (!la)
>> + return -ENODEV;
>> + ldev = &la->ldev;
>> +
>> + ret = led_mc_calc_color_components(mc, brightness);
>> + if (ret)
>> + return ret;
>> +
>> + r = mc->subled_info[0].brightness;
>> + g = mc->subled_info[1].brightness;
>> + b = mc->subled_info[2].brightness;
>> +
>> + ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
>> + if (ret)
>> + hid_err(ldev->hdev, "Failed to send LampArray update: %d\n",
>> + ret);
>> +
>> + mutex_lock(&ldev->lock);
>> + ldev->last_r = r;
>> + ldev->last_g = g;
>> + ldev->last_b = b;
>> + ldev->last_brightness = brightness;
>> + mutex_unlock(&ldev->lock);
>> +
>> + return 0;
>> +}
>> +
>> +static enum led_brightness
>> +lamparray_led_brightness_get(struct led_classdev *cdev)
>> +{
>> + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
>> + struct lamparray_device *ldev =
>> + container_of(mc, struct lamparray_device, mc_cdev);
>> + enum led_brightness brightness;
>> + struct lamparray *la = container_of(ldev, struct lamparray, ldev);
>> + u8 rr, gg, bb;
>> + enum led_brightness br;
>> + int ret;
>> +
>> + /* Default: cache (also used while registering LED classdev) */
>> + mutex_lock(&ldev->lock);
>> + brightness = ldev->last_brightness;
>> + mutex_unlock(&ldev->lock);
>> +
>> + /* Only do HID readback after registration completed */
>> + if (READ_ONCE(ldev->led_registered)) {
>> + if (!la)
>> + return brightness;
>> + ldev = &la->ldev;
>> +
>> + ret = lamparray_hw_get_state(ldev, &rr, &gg, &bb, &br);
>> + if (ret) {
>> + hid_warn(ldev->hdev,
>> + "Failed to read LampArray state (%d), using cached
>> brightness %u\n",
>> + ret, brightness);
>> + return brightness;
>> + }
>> +
>> + mutex_lock(&ldev->lock);
>> + if (ldev->last_r != rr || ldev->last_g != gg ||
>> + ldev->last_b != bb || ldev->last_brightness != br) {
>> + ldev->last_r = rr;
>> + ldev->last_g = gg;
>> + ldev->last_b = bb;
>> + ldev->last_brightness = br;
>> +
>> + if (ldev->led_registered && ldev->mc_cdev.subled_info) {
>> + ldev->mc_cdev.subled_info[0].brightness = rr;
>> + ldev->mc_cdev.subled_info[1].brightness = gg;
>> + ldev->mc_cdev.subled_info[2].brightness = bb;
>> + }
>> + }
>> + mutex_unlock(&ldev->lock);
>> + return br;
>> + }
>> + return brightness;
>> +}
>> +
>> +static int lamparray_register_led(struct lamparray_device *ldev)
>> +{
>> + struct device *dev = &ldev->hdev->dev;
>> + struct led_classdev *cdev = &ldev->mc_cdev.led_cdev;
>> + u8 r_i, g_i, b_i;
>> + int ret;
>> +
>> + mutex_lock(&ldev->lock);
>> +
>> + if (ldev->led_registered) {
>> + mutex_unlock(&ldev->lock);
>> + return 0;
>> + }
>> +
>> + if (!cdev->name) {
>> + cdev->name =
>> + devm_kasprintf(dev, GFP_KERNEL, "%s", dev_name(dev));
>> + if (!cdev->name) {
>> + mutex_unlock(&ldev->lock);
>> + return -ENOMEM;
>> + }
>> + }
>> +
>> + cdev->max_brightness = 255;
>> + cdev->brightness_set_blocking = lamparray_led_brightness_set;
>> + cdev->brightness_get = lamparray_led_brightness_get;
>> + cdev->brightness = ldev->last_brightness;
>> +
>> + ldev->subleds[0].color_index = LED_COLOR_ID_RED;
>> + ldev->subleds[1].color_index = LED_COLOR_ID_GREEN;
>> + ldev->subleds[2].color_index = LED_COLOR_ID_BLUE;
>> +
>> + /*
>> + * Initialize the color mix (multi_intensity) from the last known HW/init
>> + * state so that writing only /brightness scales the expected default color
>> + * instead of white.
>> + *
>> + * If last_brightness is non-zero, treat last_r/g/b as per-channel
>> + * brightness and normalize back to intensities (0..255).
>> + * If last_brightness is zero, keep last_r/g/b as the intended mix.
>> + */
>> + if (ldev->last_brightness) {
>> + r_i = (u8)min_t(unsigned int, 255,
>> + (ldev->last_r * 255u) / ldev->last_brightness);
>> + g_i = (u8)min_t(unsigned int, 255,
>> + (ldev->last_g * 255u) / ldev->last_brightness);
>> + b_i = (u8)min_t(unsigned int, 255,
>> + (ldev->last_b * 255u) / ldev->last_brightness);
>> + } else {
>> + r_i = ldev->last_r;
>> + g_i = ldev->last_g;
>> + b_i = ldev->last_b;
>> + }
>> +
>> + ldev->subleds[0].intensity = r_i;
>> + ldev->subleds[1].intensity = g_i;
>> + ldev->subleds[2].intensity = b_i;
>> +
>> + ldev->mc_cdev.subled_info = ldev->subleds;
>> + ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
>> +
>> + /* Ensure subled_info[].brightness matches intensity + brightness */
>> + led_mc_calc_color_components(&ldev->mc_cdev, cdev->brightness);
>> +
>> + ldev->mc_cdev.subled_info = ldev->subleds;
>> + ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
>> +
>> + mutex_unlock(&ldev->lock);
>> +
>> + ret = led_classdev_multicolor_register(dev, &ldev->mc_cdev);
>> + if (ret)
>> + return ret;
>> +
>> + mutex_lock(&ldev->lock);
>> + ldev->led_registered = true;
>> + mutex_unlock(&ldev->lock);
>> +
>> + return 0;
>> +}
>> +
>> +static void lamparray_unregister_led(struct lamparray_device *ldev)
>> +{
>> + bool was_registered;
>> +
>> + mutex_lock(&ldev->lock);
>> + was_registered = ldev->led_registered;
>> + ldev->led_registered = false;
>> + mutex_unlock(&ldev->lock);
>> +
>> + if (!was_registered)
>> + return;
>> +
>> + led_classdev_multicolor_unregister(&ldev->mc_cdev);
>> +}
>> +
>> +/* Sysfs */
>> +
>> +static struct lamparray_device *
>> +lamparray_ldev_from_sysfs_dev(struct device *dev)
>> +{
>> + struct hid_device *hdev = to_hid_device(dev);
>> +
>> + return xa_load(&lamparray_by_hdev, (unsigned long)hdev);
>> +}
>> +
>> +static ssize_t use_leds_uapi_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct lamparray_device *ldev = lamparray_ldev_from_sysfs_dev(dev);
>> +
>> + if (!ldev)
>> + return -ENODEV;
>> +
>> + return sysfs_emit(buf, "%d\n", ldev->use_leds_uapi);
>> +}
>> +
>> +static ssize_t use_leds_uapi_store(struct device *dev,
>> + struct device_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + struct lamparray_device *ldev = lamparray_ldev_from_sysfs_dev(dev);
>> + int val;
>> + int old_val;
>> + int ret;
>> +
>> + if (!ldev)
>> + return -ENODEV;
>> +
>> + ret = kstrtoint(buf, 0, &val);
>> + if (ret)
>> + return ret;
>> +
>> + if (val != 0 && val != 1)
>> + return -EINVAL;
>> +
>> + mutex_lock(&ldev->lock);
>> + old_val = ldev->use_leds_uapi;
>> +
>> + if (val == old_val) {
>> + mutex_unlock(&ldev->lock);
>> + return count;
>> + }
>> +
>> + ldev->use_leds_uapi = val;
>> + mutex_unlock(&ldev->lock);
>> +
>> + if (val == 1) {
>> + ret = lamparray_register_led(ldev);
>> + if (ret) {
>> + mutex_lock(&ldev->lock);
>> + ldev->use_leds_uapi = old_val;
>> + mutex_unlock(&ldev->lock);
>> + return ret;
>> + }
>> + ret = lamparray_restore_state(ldev);
>> + if (ret) {
>> + hid_err(ldev->hdev, "Could not restore state: %d", ret);
>> + return ret;
>> + }
>> +
>> + } else {
>> + lamparray_unregister_led(ldev);
>> + }
>> +
>> + return count;
>> +}
>> +static DEVICE_ATTR_RW(use_leds_uapi);
>> +
>> +static struct attribute *lamparray_attrs[] = {
>> + &dev_attr_use_leds_uapi.attr,
>> + NULL,
>> +};
>> +
>> +static const struct attribute_group lamparray_attr_group = {
>> + .attrs = lamparray_attrs,
>> +};
>> +
>> +static int lamparray_register_sysfs(struct lamparray_device *ldev)
>> +{
>> + struct device *dev = &ldev->hdev->dev;
>> + int ret;
>> +
>> + ret = sysfs_create_group(&dev->kobj, &lamparray_attr_group);
>> + if (ret)
>> + hid_err(ldev->hdev,
>> + "Failed to create lamparray sysfs group: %d\n", ret);
>> +
>> + return ret;
>> +}
>> +
>> +static void lamparray_remove_sysfs(struct lamparray_device *ldev)
>> +{
>> + sysfs_remove_group(&ldev->hdev->dev.kobj, &lamparray_attr_group);
>> +}
>> +
>> +/* Public API */
>> +
>> +bool lamparray_is_supported_device(struct hid_device *hdev)
>> +{
>> + unsigned int i;
>> +
>> + hid_dbg(hdev, "lamparray: walking %u collections\n",
>> + hdev->maxcollection);
>> +
>> + for (i = 0; i < hdev->maxcollection; i++) {
>> + struct hid_collection *col = &hdev->collection[i];
>> + u16 page = (col->usage & HID_USAGE_PAGE) >> 16;
>> + u16 code = col->usage & HID_USAGE;
>> +
>> + hid_dbg(hdev,
>> + "lamparray: collection[%u]: type=%u level=%u usage=0x%08x
>> page=0x%04x code=0x%04x\n",
>> + i, col->type, col->level, col->usage, page, code);
>> +
>> + if (col->type == HID_COLLECTION_APPLICATION &&
>> + page == HID_LIGHTING_ILLUMINATION_USAGE_PAGE &&
>> + code == HID_APPLICATION_COLLECTION_USAGE_TYPE) {
>> + return true;
>> + }
>> + }
>> + return false;
>> +}
>> +EXPORT_SYMBOL_GPL(lamparray_is_supported_device);
>> +
>> +struct lamparray *
>> +lamparray_register(struct hid_device *hdev,
>> + const struct lamparray_init_state *led_init_state)
>> +{
>> + int ret;
>> + struct lamparray *la;
>> + struct lamparray_device *ldev;
>> +
>> + if (!hdev)
>> + return ERR_PTR(-ENODEV);
>> +
>> + la = kzalloc(sizeof(*la), GFP_KERNEL);
>> + if (!la)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + ldev = &la->ldev;
>> +
>> + mutex_init(&ldev->lock);
>> + ldev->hdev = hdev;
>> + ldev->quirks = lamparray_lookup_quirks(hdev);
>> + ldev->use_leds_uapi = 1;
>> + ldev->led_registered = false;
>> + if (!led_init_state) {
>> + ldev->last_r = 255;
>> + ldev->last_g = 255;
>> + ldev->last_b = 255;
>> + ldev->last_brightness = LED_OFF;
>> + } else {
>> + ldev->last_r = led_init_state->r;
>> + ldev->last_g = led_init_state->g;
>> + ldev->last_b = led_init_state->b;
>> + ldev->last_brightness = led_init_state->brightness;
>> + }
>> + ret = lamparray_parse_update_report(ldev);
>> + if (ret) {
>> + hid_err(hdev, "No LampArray update report found: %d\n", ret);
>> + goto err_free;
>> + }
>> +
>> + ret = lamparray_read_lamp_count(ldev);
>> + if (ret) {
>> + hid_err(hdev,
>> + "Could not determine LampCount. This device needs a quirk for a
>> fixed LampCount: %d\n",
>> + ret);
>> + goto err_unregister_led;
>> + }
>> +
>> + ret = lamparray_register_led(ldev);
>> + if (ret) {
>> + hid_warn(hdev, "Failed to register LED UAPI: %d\n", ret);
>> + mutex_lock(&ldev->lock);
>> + ldev->use_leds_uapi = 0;
>> + mutex_unlock(&ldev->lock);
>> + }
>> +
>> + ret = xa_err(xa_store(&lamparray_by_hdev, (unsigned long)hdev, ldev,
>> + GFP_KERNEL));
>> + if (ret)
>> + goto err_unregister_led;
>> +
>> + ret = lamparray_register_sysfs(ldev);
>> + if (ret)
>> + goto err_xa_erase;
>> +
>> + ret = lamparray_hw_set_autonomous(ldev, false);
>> + if (ret) {
>> + hid_err(hdev, "Could not disable autonomous mode: %d", ret);
>> + goto err_remove_sysfs;
>> + }
>> +
>> + hid_info(hdev, "LampArray device registered\n");
>> +
>> + ret = lamparray_restore_state(ldev);
>> + if (ret) {
>> + hid_err(hdev, "Failed to set standard state: %d", ret);
>> + goto err_remove_sysfs;
>> + }
>> + return la;
>> +
>> +err_remove_sysfs:
>> + lamparray_remove_sysfs(ldev);
>> +err_xa_erase:
>> + xa_erase(&lamparray_by_hdev, (unsigned long)hdev);
>> +err_unregister_led:
>> + lamparray_unregister_led(ldev);
>> +err_free:
>> + kfree(la);
>> + return ERR_PTR(ret);
>> +}
>> +EXPORT_SYMBOL_GPL(lamparray_register);
>> +
>> +void lamparray_unregister(struct lamparray *la)
>> +{
>> + struct lamparray_device *ldev;
>> +
>> + if (!la)
>> + return;
>> +
>> + ldev = &la->ldev;
>> +
>> + lamparray_unregister_led(ldev);
>> + lamparray_remove_sysfs(ldev);
>> + xa_erase(&lamparray_by_hdev, (unsigned long)ldev->hdev);
>> +
>> + kfree(la);
>> +}
>> +EXPORT_SYMBOL_GPL(lamparray_unregister);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("HID LampArray helper module (single-zone RGB)");
>> diff --git a/drivers/hid/hid-lamparray.h b/drivers/hid/hid-lamparray.h
>> new file mode 100644
>> index 000000000000..ac3edd366a5b
>> --- /dev/null
>> +++ b/drivers/hid/hid-lamparray.h
>> @@ -0,0 +1,91 @@
>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
>> +
>> +#ifndef _HID_LAMPARRAY_H
>> +#define _HID_LAMPARRAY_H
>> +
>> +#include <linux/types.h>
>> +#include <linux/leds.h>
>> +#include <linux/err.h>
>> +#include <linux/errno.h>
>> +
>> +struct hid_device;
>> +struct lamparray;
>> +
>> +/*
>> + * Optional initial LED state for lamparray_register().
>> + * Used to define the initial state of a LampArray's LEDs.
>> + */
>> +struct lamparray_init_state {
>> + u8 r;
>> + u8 g;
>> + u8 b;
>> + enum led_brightness brightness;
>> +};
>> +
>> +#if IS_ENABLED(CONFIG_HID_LAMPARRAY)
>> +
>> +/**
>> + * lamparray_is_supported_device() - check whether a HID device supports
>> LampArray
>> + * @hdev: HID device to inspect
>> + *
>> + * Check whether the given HID device exposes a Lighting/LampArray application
>> + * collection as defined by the HID Lighting specification.
>> + *
>> + * This helper can be used by HID drivers to determine whether LampArray
>> + * functionality should be enabled for a device.
>> + *
>> + * Return: %true if LampArray support is detected, %false otherwise.
>> + */
>> +bool lamparray_is_supported_device(struct hid_device *hdev);
>> +
>> +/**
>> + * lamparray_register() - initialize LampArray support for a HID device
>> + * @hdev: HID device
>> + * @led_init_state: Optional LED state at init specification
>> + *
>> + * Allocate and initialize internal LampArray state for the given HID device.
>> + * The function parses required HID reports and fields and registers the
>> + * associated miscdevice and sysfs attributes.
>> + *
>> + * If enabled, a multicolor LED class device is also registered to expose the
>> + * LampArray functionality via the LED subsystem. If specified, the desired
>> + * initial LED state is applied. If led_init_state is NULL, a default state is
>> + * applied.
>> + *
>> + * Return: pointer to a LampArray handle on success, or ERR_PTR() on failure.
>> + */
>> +struct lamparray *lamparray_register(struct hid_device *hdev,
>> + const struct lamparray_init_state *led_init_state);
>> +
>> +/**
>> + * lamparray_unregister() - tear down LampArray support
>> + * @la: LampArray handle returned by lamparray_register()
>> + *
>> + * Remove all resources associated with a LampArray instance.
>> + *
>> + * This unregisters the LED class device (if present), removes the miscdevice
>> + * and sysfs interfaces and frees all internal state associated with @la.
>> + */
>> +void lamparray_unregister(struct lamparray *la);
>> +
>> +#else /* !CONFIG_HID_LAMPARRAY */
>> +
>> +static inline bool lamparray_is_supported_device(struct hid_device *hdev)
>> +{
>> + return false;
>> +}
>> +
>> +static inline struct lamparray *
>> +lamparray_register(struct hid_device *hdev,
>> + const struct lamparray_init_state *led_init_state)
>> +{
>> + return ERR_PTR(-EOPNOTSUPP);
>> +}
>> +
>> +static inline void lamparray_unregister(struct lamparray *la)
>> +{
>> +}
>> +
>> +#endif /* CONFIG_HID_LAMPARRAY */
>> +
>> +#endif /* _HID_LAMPARRAY_H */
^ permalink raw reply
* Re: [PATCH v4] HID: generic: add LampArray support via hid-lamparray helper
From: Werner Sembach @ 2026-03-25 10:21 UTC (permalink / raw)
To: Tim Guttzeit, Jiri Kosina, Benjamin Tissoires
Cc: linux-kernel, linux-input, Hans de Goede, linux-leds
In-Reply-To: <20260223083954.131099-1-tgu@tuxedocomputers.com>
Hi,
as Tim went on to other endeavors, I'm taking over this patch. I have yet to
test it myself, but i was already in the feedback loop while he was writing it,
so I'm not unfamiliar with the code.
Is there already some initial feedback or thoughts about this?
This patch implements the
- (Optional) Implement a generic LampArray leds subsystem driver that maps to
the single zone control and ads the use_leds_uapi sysfs switch to the virtual
HID device
sub point from the rough outline described back here
https://lore.kernel.org/all/1fb08a74-62c7-4d0c-ba5d-648e23082dcb@tuxedocomputers.com/
Best regards,
Werner
Am 23.02.26 um 09:39 schrieb Tim Guttzeit:
> Add a new hid-lamparray helper module and integrate it with the
> hid-generic driver.
>
> The helper provides basic support for devices exposing a
> Lighting/LampArray application collection (usage page 0x59) and
> registers a single-zone RGB LED representation via the LED
> subsystem.
>
> hid-generic now checks for LampArray support after hid_parse() and
> optionally registers a lamparray instance. Failures in the helper
> do not abort device probe to keep the device functional.
>
> LampArray resources are released on driver remove.
>
> Signed-off-by: Tim Guttzeit <tgu@tuxedocomputers.com>
> ---
> V1 -> V2: Fix Kconfig to avoid build errors when LEDS_CLASS_MULTICOLOR is disabled
> V2 -> V3: Squash V1 and V2 into one patch
> V3 -> V4: Restrict CONFIG_HID_LAMPARRAY to built-in configurations only
> to fix additional randconfig build errors
>
> drivers/hid/Kconfig | 10 +
> drivers/hid/Makefile | 2 +
> drivers/hid/hid-generic.c | 41 +-
> drivers/hid/hid-lamparray.c | 855 ++++++++++++++++++++++++++++++++++++
> drivers/hid/hid-lamparray.h | 91 ++++
> 5 files changed, 997 insertions(+), 2 deletions(-)
> create mode 100644 drivers/hid/hid-lamparray.c
> create mode 100644 drivers/hid/hid-lamparray.h
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 920a64b66b25..548dc708e5cd 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -92,6 +92,16 @@ config HID_GENERIC
>
> If unsure, say Y.
>
> +config HID_LAMPARRAY
> + bool "HID LampArray helper"
> + depends on HID=y && HID_GENERIC=y && LEDS_CLASS=y && LEDS_CLASS_MULTICOLOR=y
> + default y
> + help
> + Helper for HID devices exposing a Lighting/LampArray collection.
> + Treats LampArray devices as a single-zone device and exposes a sysfs
> + interface for changing color and intensity values. Also exposes a
> + sysfs flag to be disabled e.g. by a userspace driver.
> +
> config HID_HAPTIC
> bool "Haptic touchpad support"
> default n
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index 361a7daedeb8..5a14b4b0970d 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -13,6 +13,8 @@ obj-$(CONFIG_UHID) += uhid.o
>
> obj-$(CONFIG_HID_GENERIC) += hid-generic.o
>
> +obj-$(CONFIG_HID_LAMPARRAY) += hid-lamparray.o
> +
> hid-$(CONFIG_HIDRAW) += hidraw.o
>
> hid-logitech-y := hid-lg.o
> diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
> index c2de916747de..57b81c86982c 100644
> --- a/drivers/hid/hid-generic.c
> +++ b/drivers/hid/hid-generic.c
> @@ -20,6 +20,7 @@
> #include <asm/byteorder.h>
>
> #include <linux/hid.h>
> +#include "hid-lamparray.h"
>
> static struct hid_driver hid_generic;
>
> @@ -31,7 +32,7 @@ static int __check_hid_generic(struct device_driver *drv, void *data)
> if (hdrv == &hid_generic)
> return 0;
>
> - return hid_match_device(hdev, hdrv) != NULL;
> + return !!hid_match_device(hdev, hdrv);
> }
>
> static bool hid_generic_match(struct hid_device *hdev,
> @@ -60,6 +61,7 @@ static int hid_generic_probe(struct hid_device *hdev,
> const struct hid_device_id *id)
> {
> int ret;
> + struct lamparray *la = NULL;
>
> hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
>
> @@ -67,7 +69,31 @@ static int hid_generic_probe(struct hid_device *hdev,
> if (ret)
> return ret;
>
> - return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> + if (ret)
> + return ret;
> +
> + /*
> + * Optional: attach LampArray support if present.
> + * Never fail probe on LampArray errors; keep device functional.
> + */
> + if (IS_ENABLED(CONFIG_HID_LAMPARRAY) && lamparray_is_supported_device(hdev)) {
> + const struct lamparray_init_state init = {
> + .r = 0xff,
> + .g = 0xff,
> + .b = 0xff,
> + .brightness = LED_FULL,
> + };
> +
> + la = lamparray_register(hdev, &init);
> + if (IS_ERR(la)) {
> + hid_warn(hdev, "LampArray init failed: %ld\n", PTR_ERR(la));
> + la = NULL;
> + }
> + }
> +
> + hid_set_drvdata(hdev, la);
> + return 0;
> }
>
> static int hid_generic_reset_resume(struct hid_device *hdev)
> @@ -78,6 +104,16 @@ static int hid_generic_reset_resume(struct hid_device *hdev)
> return 0;
> }
>
> +static void hid_generic_remove(struct hid_device *hdev)
> +{
> + struct lamparray *la = hid_get_drvdata(hdev);
> +
> + if (IS_ENABLED(CONFIG_HID_LAMPARRAY) && la)
> + lamparray_unregister(la);
> +
> + hid_hw_stop(hdev);
> +}
> +
> static const struct hid_device_id hid_table[] = {
> { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, HID_ANY_ID, HID_ANY_ID) },
> { }
> @@ -90,6 +126,7 @@ static struct hid_driver hid_generic = {
> .match = hid_generic_match,
> .probe = hid_generic_probe,
> .reset_resume = hid_generic_reset_resume,
> + .remove = hid_generic_remove,
> };
> module_hid_driver(hid_generic);
>
> diff --git a/drivers/hid/hid-lamparray.c b/drivers/hid/hid-lamparray.c
> new file mode 100644
> index 000000000000..5be46fff0191
> --- /dev/null
> +++ b/drivers/hid/hid-lamparray.c
> @@ -0,0 +1,855 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * hid-lamparray.c - HID LampArray helper module (single-zone RGB)
> + *
> + * Helper module for HID drivers supporting devices that expose a
> + * Lighting and Illumination (LampArray) application collection
> + * (usage page 0x59).
> + *
> + * The module provides a minimal integration with the LED subsystem
> + * and treats the device as a single zone: all lamps share one RGB
> + * value and a global brightness level. It does not implement multi-
> + * zone layouts or hardware effects.
> + *
> + *
> + * If enabled, one multicolor LED class device is registered under
> + * /sys/class/leds/<HID-ID> to expose the single-zone RGB control.
> + *
> + * The use_leds_uapi sysfs attribute is attached directly to the HID device
> + * under /sys/bus/hid/devices/<HID-ID>/use_leds_uapi.Writing 0 to use_leds_uapi
> + * unregisters the LED class device. The last state is kept cached. Writing 1
> + * registers it again and restores the cached state to hardware.
> + *
> + * State is cached as last known RGB + brightness. Setting sends a HID
> + * SET_REPORT. Getting issues a HID GET_REPORT and updates the cache on
> + * mismatch. Since the device is handled as single-zone, readback only queries
> + * lamp 0 when a lamp range is available.
> + *
> + * The module does not bind to devices on its own. Instead, a HID
> + * driver may query support via lamparray_is_supported_device() after
> + * hid_parse() and create an instance using lamparray_register().
> + *
> + * Copyright (C) 2026 Tim Guttzeit <tgu@tuxedocomputers.com>
> + */
> +
> +#include "hid-lamparray.h"
> +#include <linux/module.h>
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +#include <linux/leds.h>
> +#include <linux/led-class-multicolor.h>
> +#include <linux/slab.h>
> +#include <linux/sysfs.h>
> +#include <linux/mutex.h>
> +#include <linux/bitops.h>
> +#include <linux/xarray.h>
> +
> +/* Constants */
> +
> +/* HID usages (LampArray, etc.) */
> +#define HID_LIGHTING_ILLUMINATION_USAGE_PAGE 0x0059
> +
> +#define HID_LAIP_LAMP_COUNT 0x0003
> +#define HID_LAIP_LAMP_ID 0x0021
> +#define HID_LAIP_RED_UPDATE_CHANNEL 0x0051
> +#define HID_LAIP_GREEN_UPDATE_CHANNEL 0x0052
> +#define HID_LAIP_BLUE_UPDATE_CHANNEL 0x0053
> +#define HID_LAIP_INTENSITY_UPDATE_CHANNEL 0x0054
> +#define HID_LAIP_LAMP_ID_START 0x0061
> +#define HID_LAIP_LAMP_ID_END 0x0062
> +#define HID_LAIP_AUTONOMOUS_MODE 0x0071
> +
> +#define HID_APPLICATION_COLLECTION_USAGE_TYPE 0x0001
> +
> +/* Device state */
> +
> +struct lamparray_quirks {
> + unsigned long flags;
> + int fixed_lamp_count;
> +};
> +
> +#define LAMPARRAY_QUIRK_LAMPCOUNT_FIXED BIT(0)
> +
> +struct lamparray_quirk_entry {
> + u16 vendor;
> + u16 product;
> + unsigned long flags;
> + int fixed_lamp_count;
> +};
> +
> +static const struct lamparray_quirk_entry lamparray_quirk_table[] = {
> + { 0xcafe, 0x4005, LAMPARRAY_QUIRK_LAMPCOUNT_FIXED, 12 },
> + {}
> +};
> +
> +static const struct lamparray_quirk_entry *
> +lamparray_lookup_quirks(struct hid_device *hdev)
> +{
> + const struct lamparray_quirk_entry *e;
> +
> + for (e = lamparray_quirk_table; e->vendor; e++) {
> + if (hdev->vendor == e->vendor && hdev->product == e->product)
> + return e;
> + }
> + return NULL;
> +}
> +
> +struct lamparray_device {
> + const struct lamparray_quirk_entry *quirks;
> +
> + struct hid_device *hdev;
> + struct hid_report *update_report;
> +
> + struct hid_field *red_field;
> + int red_index;
> + struct hid_field *green_field;
> + int green_index;
> + struct hid_field *blue_field;
> + int blue_index;
> + struct hid_field *intensity_field;
> + int intensity_index;
> +
> + struct hid_report *autonomous_report;
> + struct hid_field *autonomous_field;
> +
> + struct hid_field *range_start_field;
> + int range_start_index;
> +
> + struct hid_field *range_end_field;
> + int range_end_index;
> +
> + struct hid_field *lamp_count_field;
> + int lamp_count;
> + int lamp_count_index;
> +
> + struct led_classdev_mc mc_cdev;
> + struct mc_subled subleds[3];
> +
> + struct mutex lock; /* protects cached state and HID access */
> +
> + u8 last_r;
> + u8 last_g;
> + u8 last_b;
> + enum led_brightness last_brightness;
> +
> + bool use_leds_uapi;
> + bool led_registered;
> +};
> +
> +/*
> + * Opaque handle exposed to callers via the header.
> + * Keep the actual state in lamparray_device, but return a stable pointer.
> + */
> +struct lamparray {
> + struct lamparray_device ldev;
> +};
> +
> +static DEFINE_XARRAY(lamparray_by_hdev);
> +
> +/* HID helper functions */
> +
> +#ifdef DEBUG
> +static void lamparray_dump_reports(struct hid_device *hdev)
> +{
> + struct hid_report_enum *re;
> + struct hid_report *report;
> + int i, j, report_type;
> +
> + for (report_type = 0; report_type < HID_REPORT_TYPES; report_type++) {
> + re = &hdev->report_enum[report_type];
> + hid_dbg(hdev, "Dumping reports for report type %d",
> + report_type);
> + list_for_each_entry(report, &re->report_list, list) {
> + hid_dbg(hdev,
> + "lamparray: report id=%u type=%d maxfield=%u\n",
> + report->id, report->type, report->maxfield);
> +
> + for (i = 0; i < report->maxfield; i++) {
> + struct hid_field *field = report->field[i];
> +
> + for (j = 0; j < field->maxusage; j++) {
> + u32 usage = field->usage[j].hid;
> + u16 page = usage >> 16;
> + u16 id = usage & 0xFFFF;
> +
> + hid_dbg(hdev,
> + "lamparray: report %u field %d usage[%d]: page=0x%04x id=0x%04x\n",
> + report->id, i, j, page, id);
> + }
> + }
> + }
> + }
> +}
> +#else
> +static inline void lamparray_dump_reports(struct hid_device *hdev)
> +{}
> +#endif
> +
> +static int lamparray_read_lamp_count(struct lamparray_device *ldev)
> +{
> + struct hid_device *hdev = ldev->hdev;
> + struct hid_report *report;
> +
> + if (ldev->quirks &&
> + (ldev->quirks->flags & LAMPARRAY_QUIRK_LAMPCOUNT_FIXED)) {
> + ldev->lamp_count = ldev->quirks->fixed_lamp_count;
> + hid_dbg(hdev, "LampCount from quirk: %d\n", ldev->lamp_count);
> + return 0;
> + }
> + if (!ldev->lamp_count_field) {
> + hid_warn(hdev, "No LampCount field found\n");
> + return -ENODEV;
> + }
> +
> + report = ldev->lamp_count_field->report;
> +
> + if (!report) {
> + hid_warn(hdev, "LampCount field has no report\n");
> + return -ENODEV;
> + }
> + hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
> + ldev->lamp_count =
> + ldev->lamp_count_field->value[ldev->lamp_count_index];
> +
> + hid_dbg(hdev, "LampCount from device: %d\n", ldev->lamp_count);
> +
> + if (ldev->lamp_count <= 0) {
> + hid_warn(hdev, "LampCount is %d (invalid)\n", ldev->lamp_count);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int lamparray_parse_update_report(struct lamparray_device *ldev)
> +{
> + struct hid_device *hdev = ldev->hdev;
> + struct hid_report_enum *re;
> + struct hid_report *report;
> + struct hid_field *field;
> + int i, j;
> +
> + lamparray_dump_reports(hdev);
> +
> + re = &hdev->report_enum[HID_FEATURE_REPORT];
> +
> + list_for_each_entry(report, &re->report_list, list) {
> + for (i = 0; i < report->maxfield; i++) {
> + field = report->field[i];
> + if (!field)
> + continue;
> +
> + if (!field->usage || !field->maxusage)
> + continue;
> +
> + for (j = 0; j < field->maxusage; j++) {
> + u32 usage = field->usage[j].hid;
> + u16 page = usage >> 16;
> + u16 id = usage & 0xffff;
> +
> + if (page !=
> + HID_LIGHTING_ILLUMINATION_USAGE_PAGE)
> + continue;
> + switch (id) {
> + case HID_LAIP_LAMP_COUNT:
> + ldev->lamp_count_field = field;
> + ldev->lamp_count_index = j;
> + break;
> + case HID_LAIP_RED_UPDATE_CHANNEL:
> + ldev->update_report = report;
> + ldev->red_field = field;
> + ldev->red_index = j;
> + break;
> + case HID_LAIP_GREEN_UPDATE_CHANNEL:
> + ldev->update_report = report;
> + ldev->green_field = field;
> + ldev->green_index = j;
> + break;
> + case HID_LAIP_BLUE_UPDATE_CHANNEL:
> + ldev->update_report = report;
> + ldev->blue_field = field;
> + ldev->blue_index = j;
> + break;
> + case HID_LAIP_INTENSITY_UPDATE_CHANNEL:
> + ldev->update_report = report;
> + ldev->intensity_field = field;
> + ldev->intensity_index = j;
> + break;
> + case HID_LAIP_LAMP_ID_START:
> + ldev->range_start_field = field;
> + ldev->range_start_index = j;
> + break;
> + case HID_LAIP_LAMP_ID_END:
> + ldev->range_end_field = field;
> + ldev->range_end_index = j;
> + break;
> + case HID_LAIP_AUTONOMOUS_MODE:
> + ldev->autonomous_field = field;
> + ldev->autonomous_report = report;
> + break;
> + default:
> + break;
> + }
> + }
> + }
> + }
> +
> + if (!ldev->update_report || !ldev->red_field || !ldev->green_field ||
> + !ldev->blue_field || !ldev->autonomous_report || !ldev->autonomous_field)
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +static int lamparray_hw_set_autonomous(struct lamparray_device *ldev,
> + bool enable)
> +{
> + struct hid_device *hdev = ldev->hdev;
> + struct hid_field *field = ldev->autonomous_field;
> + struct hid_report *report = ldev->autonomous_report;
> +
> + if (!field || !report)
> + return -ENODEV;
> +
> + field->value[0] = enable ? 1 : 0;
> +
> + hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
> + return 0;
> +}
> +
> +static int lamparray_hw_set_state(struct lamparray_device *ldev, u8 r, u8 g,
> + u8 b, u8 intensity)
> +{
> + struct hid_device *hdev = ldev->hdev;
> + struct hid_report *report = ldev->update_report;
> + int i, j;
> +
> + if (!report || !ldev->red_field || !ldev->green_field ||
> + !ldev->blue_field || !ldev->intensity_field)
> + return -ENODEV;
> +
> + if (ldev->range_start_field && ldev->range_end_field) {
> + ldev->range_start_field->value[ldev->range_start_index] = 0;
> + ldev->range_end_field->value[ldev->range_end_index] = ldev->lamp_count - 1;
> + }
> +
> + for (i = 0; i < report->maxfield; i++) {
> + struct hid_field *field = report->field[i];
> +
> + if (!field || !field->usage || !field->maxusage)
> + continue;
> +
> + for (j = 0; j < field->maxusage; j++) {
> + u32 usage = field->usage[j].hid;
> + u16 page = usage >> 16;
> + u16 id = usage & 0xffff;
> +
> + if (page != HID_LIGHTING_ILLUMINATION_USAGE_PAGE)
> + continue;
> +
> + switch (id) {
> + case HID_LAIP_RED_UPDATE_CHANNEL:
> + field->value[j] = r;
> + break;
> + case HID_LAIP_GREEN_UPDATE_CHANNEL:
> + field->value[j] = g;
> + break;
> + case HID_LAIP_BLUE_UPDATE_CHANNEL:
> + field->value[j] = b;
> + break;
> + case HID_LAIP_INTENSITY_UPDATE_CHANNEL:
> + field->value[j] = intensity;
> + break;
> + default:
> + break;
> + }
> + }
> + }
> +
> + hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
> + return 0;
> +}
> +
> +static int lamparray_hw_get_state(struct lamparray_device *ldev, u8 *r, u8 *g,
> + u8 *b, enum led_brightness *brightness)
> +{
> + struct hid_device *hdev = ldev->hdev;
> + struct hid_report *report = ldev->update_report;
> +
> + if (!report || !ldev->red_field || !ldev->green_field ||
> + !ldev->blue_field || !ldev->intensity_field)
> + return -ENODEV;
> +
> + if (!r || !g || !b || !brightness)
> + return -EINVAL;
> +
> + /* Single-zone: Reading lamp 0 only suffices */
> + if (ldev->range_start_field && ldev->range_end_field) {
> + ldev->range_start_field->value[ldev->range_start_index] = 0;
> + ldev->range_end_field->value[ldev->range_end_index] = 0;
> + }
> +
> + hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
> +
> + *r = ldev->red_field->value[ldev->red_index];
> + *g = ldev->green_field->value[ldev->green_index];
> + *b = ldev->blue_field->value[ldev->blue_index];
> + *brightness = ldev->intensity_field->value[ldev->intensity_index];
> +
> + return 0;
> +}
> +
> +/* Helper functions */
> +
> +static int lamparray_restore_state(struct lamparray_device *ldev)
> +{
> + u8 r, g, b;
> + int ret;
> + enum led_brightness brightness;
> +
> + mutex_lock(&ldev->lock);
> +
> + if (!ldev->use_leds_uapi) {
> + mutex_unlock(&ldev->lock);
> + return 0;
> + }
> +
> + r = ldev->last_r;
> + g = ldev->last_g;
> + b = ldev->last_b;
> + brightness = ldev->last_brightness;
> +
> + ldev->mc_cdev.subled_info[0].brightness = r;
> + ldev->mc_cdev.subled_info[1].brightness = g;
> + ldev->mc_cdev.subled_info[2].brightness = b;
> + ldev->mc_cdev.led_cdev.brightness = brightness;
> +
> + mutex_unlock(&ldev->lock);
> +
> + ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
> + return ret;
> +}
> +
> +/* LEDs API */
> +
> +static int lamparray_led_brightness_set(struct led_classdev *cdev,
> + enum led_brightness brightness)
> +{
> + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
> + struct lamparray_device *ldev =
> + container_of(mc, struct lamparray_device, mc_cdev);
> + struct lamparray *la = container_of(ldev, struct lamparray, ldev);
> + u8 r, g, b;
> + int ret;
> +
> + if (!la)
> + return -ENODEV;
> + ldev = &la->ldev;
> +
> + ret = led_mc_calc_color_components(mc, brightness);
> + if (ret)
> + return ret;
> +
> + r = mc->subled_info[0].brightness;
> + g = mc->subled_info[1].brightness;
> + b = mc->subled_info[2].brightness;
> +
> + ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
> + if (ret)
> + hid_err(ldev->hdev, "Failed to send LampArray update: %d\n",
> + ret);
> +
> + mutex_lock(&ldev->lock);
> + ldev->last_r = r;
> + ldev->last_g = g;
> + ldev->last_b = b;
> + ldev->last_brightness = brightness;
> + mutex_unlock(&ldev->lock);
> +
> + return 0;
> +}
> +
> +static enum led_brightness
> +lamparray_led_brightness_get(struct led_classdev *cdev)
> +{
> + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
> + struct lamparray_device *ldev =
> + container_of(mc, struct lamparray_device, mc_cdev);
> + enum led_brightness brightness;
> + struct lamparray *la = container_of(ldev, struct lamparray, ldev);
> + u8 rr, gg, bb;
> + enum led_brightness br;
> + int ret;
> +
> + /* Default: cache (also used while registering LED classdev) */
> + mutex_lock(&ldev->lock);
> + brightness = ldev->last_brightness;
> + mutex_unlock(&ldev->lock);
> +
> + /* Only do HID readback after registration completed */
> + if (READ_ONCE(ldev->led_registered)) {
> + if (!la)
> + return brightness;
> + ldev = &la->ldev;
> +
> + ret = lamparray_hw_get_state(ldev, &rr, &gg, &bb, &br);
> + if (ret) {
> + hid_warn(ldev->hdev,
> + "Failed to read LampArray state (%d), using cached brightness %u\n",
> + ret, brightness);
> + return brightness;
> + }
> +
> + mutex_lock(&ldev->lock);
> + if (ldev->last_r != rr || ldev->last_g != gg ||
> + ldev->last_b != bb || ldev->last_brightness != br) {
> + ldev->last_r = rr;
> + ldev->last_g = gg;
> + ldev->last_b = bb;
> + ldev->last_brightness = br;
> +
> + if (ldev->led_registered && ldev->mc_cdev.subled_info) {
> + ldev->mc_cdev.subled_info[0].brightness = rr;
> + ldev->mc_cdev.subled_info[1].brightness = gg;
> + ldev->mc_cdev.subled_info[2].brightness = bb;
> + }
> + }
> + mutex_unlock(&ldev->lock);
> + return br;
> + }
> + return brightness;
> +}
> +
> +static int lamparray_register_led(struct lamparray_device *ldev)
> +{
> + struct device *dev = &ldev->hdev->dev;
> + struct led_classdev *cdev = &ldev->mc_cdev.led_cdev;
> + u8 r_i, g_i, b_i;
> + int ret;
> +
> + mutex_lock(&ldev->lock);
> +
> + if (ldev->led_registered) {
> + mutex_unlock(&ldev->lock);
> + return 0;
> + }
> +
> + if (!cdev->name) {
> + cdev->name =
> + devm_kasprintf(dev, GFP_KERNEL, "%s", dev_name(dev));
> + if (!cdev->name) {
> + mutex_unlock(&ldev->lock);
> + return -ENOMEM;
> + }
> + }
> +
> + cdev->max_brightness = 255;
> + cdev->brightness_set_blocking = lamparray_led_brightness_set;
> + cdev->brightness_get = lamparray_led_brightness_get;
> + cdev->brightness = ldev->last_brightness;
> +
> + ldev->subleds[0].color_index = LED_COLOR_ID_RED;
> + ldev->subleds[1].color_index = LED_COLOR_ID_GREEN;
> + ldev->subleds[2].color_index = LED_COLOR_ID_BLUE;
> +
> + /*
> + * Initialize the color mix (multi_intensity) from the last known HW/init
> + * state so that writing only /brightness scales the expected default color
> + * instead of white.
> + *
> + * If last_brightness is non-zero, treat last_r/g/b as per-channel
> + * brightness and normalize back to intensities (0..255).
> + * If last_brightness is zero, keep last_r/g/b as the intended mix.
> + */
> + if (ldev->last_brightness) {
> + r_i = (u8)min_t(unsigned int, 255,
> + (ldev->last_r * 255u) / ldev->last_brightness);
> + g_i = (u8)min_t(unsigned int, 255,
> + (ldev->last_g * 255u) / ldev->last_brightness);
> + b_i = (u8)min_t(unsigned int, 255,
> + (ldev->last_b * 255u) / ldev->last_brightness);
> + } else {
> + r_i = ldev->last_r;
> + g_i = ldev->last_g;
> + b_i = ldev->last_b;
> + }
> +
> + ldev->subleds[0].intensity = r_i;
> + ldev->subleds[1].intensity = g_i;
> + ldev->subleds[2].intensity = b_i;
> +
> + ldev->mc_cdev.subled_info = ldev->subleds;
> + ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
> +
> + /* Ensure subled_info[].brightness matches intensity + brightness */
> + led_mc_calc_color_components(&ldev->mc_cdev, cdev->brightness);
> +
> + ldev->mc_cdev.subled_info = ldev->subleds;
> + ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
> +
> + mutex_unlock(&ldev->lock);
> +
> + ret = led_classdev_multicolor_register(dev, &ldev->mc_cdev);
> + if (ret)
> + return ret;
> +
> + mutex_lock(&ldev->lock);
> + ldev->led_registered = true;
> + mutex_unlock(&ldev->lock);
> +
> + return 0;
> +}
> +
> +static void lamparray_unregister_led(struct lamparray_device *ldev)
> +{
> + bool was_registered;
> +
> + mutex_lock(&ldev->lock);
> + was_registered = ldev->led_registered;
> + ldev->led_registered = false;
> + mutex_unlock(&ldev->lock);
> +
> + if (!was_registered)
> + return;
> +
> + led_classdev_multicolor_unregister(&ldev->mc_cdev);
> +}
> +
> +/* Sysfs */
> +
> +static struct lamparray_device *
> +lamparray_ldev_from_sysfs_dev(struct device *dev)
> +{
> + struct hid_device *hdev = to_hid_device(dev);
> +
> + return xa_load(&lamparray_by_hdev, (unsigned long)hdev);
> +}
> +
> +static ssize_t use_leds_uapi_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct lamparray_device *ldev = lamparray_ldev_from_sysfs_dev(dev);
> +
> + if (!ldev)
> + return -ENODEV;
> +
> + return sysfs_emit(buf, "%d\n", ldev->use_leds_uapi);
> +}
> +
> +static ssize_t use_leds_uapi_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct lamparray_device *ldev = lamparray_ldev_from_sysfs_dev(dev);
> + int val;
> + int old_val;
> + int ret;
> +
> + if (!ldev)
> + return -ENODEV;
> +
> + ret = kstrtoint(buf, 0, &val);
> + if (ret)
> + return ret;
> +
> + if (val != 0 && val != 1)
> + return -EINVAL;
> +
> + mutex_lock(&ldev->lock);
> + old_val = ldev->use_leds_uapi;
> +
> + if (val == old_val) {
> + mutex_unlock(&ldev->lock);
> + return count;
> + }
> +
> + ldev->use_leds_uapi = val;
> + mutex_unlock(&ldev->lock);
> +
> + if (val == 1) {
> + ret = lamparray_register_led(ldev);
> + if (ret) {
> + mutex_lock(&ldev->lock);
> + ldev->use_leds_uapi = old_val;
> + mutex_unlock(&ldev->lock);
> + return ret;
> + }
> + ret = lamparray_restore_state(ldev);
> + if (ret) {
> + hid_err(ldev->hdev, "Could not restore state: %d", ret);
> + return ret;
> + }
> +
> + } else {
> + lamparray_unregister_led(ldev);
> + }
> +
> + return count;
> +}
> +static DEVICE_ATTR_RW(use_leds_uapi);
> +
> +static struct attribute *lamparray_attrs[] = {
> + &dev_attr_use_leds_uapi.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group lamparray_attr_group = {
> + .attrs = lamparray_attrs,
> +};
> +
> +static int lamparray_register_sysfs(struct lamparray_device *ldev)
> +{
> + struct device *dev = &ldev->hdev->dev;
> + int ret;
> +
> + ret = sysfs_create_group(&dev->kobj, &lamparray_attr_group);
> + if (ret)
> + hid_err(ldev->hdev,
> + "Failed to create lamparray sysfs group: %d\n", ret);
> +
> + return ret;
> +}
> +
> +static void lamparray_remove_sysfs(struct lamparray_device *ldev)
> +{
> + sysfs_remove_group(&ldev->hdev->dev.kobj, &lamparray_attr_group);
> +}
> +
> +/* Public API */
> +
> +bool lamparray_is_supported_device(struct hid_device *hdev)
> +{
> + unsigned int i;
> +
> + hid_dbg(hdev, "lamparray: walking %u collections\n",
> + hdev->maxcollection);
> +
> + for (i = 0; i < hdev->maxcollection; i++) {
> + struct hid_collection *col = &hdev->collection[i];
> + u16 page = (col->usage & HID_USAGE_PAGE) >> 16;
> + u16 code = col->usage & HID_USAGE;
> +
> + hid_dbg(hdev,
> + "lamparray: collection[%u]: type=%u level=%u usage=0x%08x page=0x%04x code=0x%04x\n",
> + i, col->type, col->level, col->usage, page, code);
> +
> + if (col->type == HID_COLLECTION_APPLICATION &&
> + page == HID_LIGHTING_ILLUMINATION_USAGE_PAGE &&
> + code == HID_APPLICATION_COLLECTION_USAGE_TYPE) {
> + return true;
> + }
> + }
> + return false;
> +}
> +EXPORT_SYMBOL_GPL(lamparray_is_supported_device);
> +
> +struct lamparray *
> +lamparray_register(struct hid_device *hdev,
> + const struct lamparray_init_state *led_init_state)
> +{
> + int ret;
> + struct lamparray *la;
> + struct lamparray_device *ldev;
> +
> + if (!hdev)
> + return ERR_PTR(-ENODEV);
> +
> + la = kzalloc(sizeof(*la), GFP_KERNEL);
> + if (!la)
> + return ERR_PTR(-ENOMEM);
> +
> + ldev = &la->ldev;
> +
> + mutex_init(&ldev->lock);
> + ldev->hdev = hdev;
> + ldev->quirks = lamparray_lookup_quirks(hdev);
> + ldev->use_leds_uapi = 1;
> + ldev->led_registered = false;
> + if (!led_init_state) {
> + ldev->last_r = 255;
> + ldev->last_g = 255;
> + ldev->last_b = 255;
> + ldev->last_brightness = LED_OFF;
> + } else {
> + ldev->last_r = led_init_state->r;
> + ldev->last_g = led_init_state->g;
> + ldev->last_b = led_init_state->b;
> + ldev->last_brightness = led_init_state->brightness;
> + }
> + ret = lamparray_parse_update_report(ldev);
> + if (ret) {
> + hid_err(hdev, "No LampArray update report found: %d\n", ret);
> + goto err_free;
> + }
> +
> + ret = lamparray_read_lamp_count(ldev);
> + if (ret) {
> + hid_err(hdev,
> + "Could not determine LampCount. This device needs a quirk for a fixed LampCount: %d\n",
> + ret);
> + goto err_unregister_led;
> + }
> +
> + ret = lamparray_register_led(ldev);
> + if (ret) {
> + hid_warn(hdev, "Failed to register LED UAPI: %d\n", ret);
> + mutex_lock(&ldev->lock);
> + ldev->use_leds_uapi = 0;
> + mutex_unlock(&ldev->lock);
> + }
> +
> + ret = xa_err(xa_store(&lamparray_by_hdev, (unsigned long)hdev, ldev,
> + GFP_KERNEL));
> + if (ret)
> + goto err_unregister_led;
> +
> + ret = lamparray_register_sysfs(ldev);
> + if (ret)
> + goto err_xa_erase;
> +
> + ret = lamparray_hw_set_autonomous(ldev, false);
> + if (ret) {
> + hid_err(hdev, "Could not disable autonomous mode: %d", ret);
> + goto err_remove_sysfs;
> + }
> +
> + hid_info(hdev, "LampArray device registered\n");
> +
> + ret = lamparray_restore_state(ldev);
> + if (ret) {
> + hid_err(hdev, "Failed to set standard state: %d", ret);
> + goto err_remove_sysfs;
> + }
> + return la;
> +
> +err_remove_sysfs:
> + lamparray_remove_sysfs(ldev);
> +err_xa_erase:
> + xa_erase(&lamparray_by_hdev, (unsigned long)hdev);
> +err_unregister_led:
> + lamparray_unregister_led(ldev);
> +err_free:
> + kfree(la);
> + return ERR_PTR(ret);
> +}
> +EXPORT_SYMBOL_GPL(lamparray_register);
> +
> +void lamparray_unregister(struct lamparray *la)
> +{
> + struct lamparray_device *ldev;
> +
> + if (!la)
> + return;
> +
> + ldev = &la->ldev;
> +
> + lamparray_unregister_led(ldev);
> + lamparray_remove_sysfs(ldev);
> + xa_erase(&lamparray_by_hdev, (unsigned long)ldev->hdev);
> +
> + kfree(la);
> +}
> +EXPORT_SYMBOL_GPL(lamparray_unregister);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("HID LampArray helper module (single-zone RGB)");
> diff --git a/drivers/hid/hid-lamparray.h b/drivers/hid/hid-lamparray.h
> new file mode 100644
> index 000000000000..ac3edd366a5b
> --- /dev/null
> +++ b/drivers/hid/hid-lamparray.h
> @@ -0,0 +1,91 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +#ifndef _HID_LAMPARRAY_H
> +#define _HID_LAMPARRAY_H
> +
> +#include <linux/types.h>
> +#include <linux/leds.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +
> +struct hid_device;
> +struct lamparray;
> +
> +/*
> + * Optional initial LED state for lamparray_register().
> + * Used to define the initial state of a LampArray's LEDs.
> + */
> +struct lamparray_init_state {
> + u8 r;
> + u8 g;
> + u8 b;
> + enum led_brightness brightness;
> +};
> +
> +#if IS_ENABLED(CONFIG_HID_LAMPARRAY)
> +
> +/**
> + * lamparray_is_supported_device() - check whether a HID device supports LampArray
> + * @hdev: HID device to inspect
> + *
> + * Check whether the given HID device exposes a Lighting/LampArray application
> + * collection as defined by the HID Lighting specification.
> + *
> + * This helper can be used by HID drivers to determine whether LampArray
> + * functionality should be enabled for a device.
> + *
> + * Return: %true if LampArray support is detected, %false otherwise.
> + */
> +bool lamparray_is_supported_device(struct hid_device *hdev);
> +
> +/**
> + * lamparray_register() - initialize LampArray support for a HID device
> + * @hdev: HID device
> + * @led_init_state: Optional LED state at init specification
> + *
> + * Allocate and initialize internal LampArray state for the given HID device.
> + * The function parses required HID reports and fields and registers the
> + * associated miscdevice and sysfs attributes.
> + *
> + * If enabled, a multicolor LED class device is also registered to expose the
> + * LampArray functionality via the LED subsystem. If specified, the desired
> + * initial LED state is applied. If led_init_state is NULL, a default state is
> + * applied.
> + *
> + * Return: pointer to a LampArray handle on success, or ERR_PTR() on failure.
> + */
> +struct lamparray *lamparray_register(struct hid_device *hdev,
> + const struct lamparray_init_state *led_init_state);
> +
> +/**
> + * lamparray_unregister() - tear down LampArray support
> + * @la: LampArray handle returned by lamparray_register()
> + *
> + * Remove all resources associated with a LampArray instance.
> + *
> + * This unregisters the LED class device (if present), removes the miscdevice
> + * and sysfs interfaces and frees all internal state associated with @la.
> + */
> +void lamparray_unregister(struct lamparray *la);
> +
> +#else /* !CONFIG_HID_LAMPARRAY */
> +
> +static inline bool lamparray_is_supported_device(struct hid_device *hdev)
> +{
> + return false;
> +}
> +
> +static inline struct lamparray *
> +lamparray_register(struct hid_device *hdev,
> + const struct lamparray_init_state *led_init_state)
> +{
> + return ERR_PTR(-EOPNOTSUPP);
> +}
> +
> +static inline void lamparray_unregister(struct lamparray *la)
> +{
> +}
> +
> +#endif /* CONFIG_HID_LAMPARRAY */
> +
> +#endif /* _HID_LAMPARRAY_H */
^ permalink raw reply
* Re: [PATCH v2 00/11] Add spi-hid transport driver
From: Krzysztof Kozlowski @ 2026-03-25 8:49 UTC (permalink / raw)
To: Jingyuan Liang
Cc: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Mark Brown,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-input, linux-doc, linux-kernel, linux-spi,
linux-trace-kernel, devicetree, hbarnor, tfiga, Jarrett Schultz,
Dmitry Antipov, Angela Czubak
In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org>
On Tue, Mar 24, 2026 at 06:39:33AM +0000, Jingyuan Liang wrote:
> This series picks up the spi-hid driver work originally started by
> Microsoft. The patch breakdown has been modified and the implementation
> has been refactored to address upstream feedback and testing issues. We
> are submitting this as a new series while keeping the original sign-off
> chain to reflect the history.
>
> Same as the original series, there is a change to HID documentation, some
> HID core changes to support a SPI device, the SPI HID transport driver,
> and HID over SPI Device Tree binding. We have added the HID over SPI ACPI
> support, power management, panel follower, and quirks for Ilitek touch
> controllers.
>
> Original authors: Jarrett Schultz <jaschultz@microsoft.com>,
> Dmitry Antipov <dmanti@microsoft.com>
> Link: https://lore.kernel.org/r/86b63b7b-afda-d7f4-7bfa-175085d5a8ef@gmail.com
>
> Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
> ---
> Changes in v2:
> - Fix style problems and remove unnecessary fields from the DT binding file
Style and removal? So other comments were skipped?
Please write detailed changelogs, otherwise it feels you just ignore
parts of the feedback.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH] Input: gf2k: clamp hat values to the lookup table
From: Dmitry Torokhov @ 2026-03-25 3:07 UTC (permalink / raw)
To: Pengpeng Hou; +Cc: linux-input, linux-kernel, kees
In-Reply-To: <20260323074541.93413-1-pengpeng@iscas.ac.cn>
On Mon, Mar 23, 2026 at 03:45:41PM +0800, Pengpeng Hou wrote:
> gf2k_read() decodes the hat position from a 4-bit field and uses it
> directly to index gf2k_hat_to_axis[]. The lookup table only has nine
> entries, so malformed packets can read past the end of the fixed table.
>
> Clamp invalid hat values to the neutral position before indexing the
> lookup table.
>
> Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
> ---
> drivers/input/joystick/gf2k.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/drivers/input/joystick/gf2k.c b/drivers/input/joystick/gf2k.c
> index 5a1cdce0bc48..78fba36285dc 100644
> --- a/drivers/input/joystick/gf2k.c
> +++ b/drivers/input/joystick/gf2k.c
> @@ -164,6 +164,8 @@ static void gf2k_read(struct gf2k *gf2k, unsigned char *data)
> input_report_abs(dev, gf2k_abs[i], GB(i*9+60,8,0) | GB(i+54,1,9));
>
> t = GB(40,4,0);
> + if (t >= ARRAY_SIZE(gf2k_hat_to_axis))
> + t = 0;
I think if "t" is too bug we should skip the loop instead of reporting
the first axis.
>
> for (i = 0; i < gf2k_hats[gf2k->id]; i++)
> input_report_abs(dev, ABS_HAT0X + i, gf2k_hat_to_axis[t][i]);
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v3] dt-bindings: input: touchscreen: edt-ft5x06: Add FocalTech FT3519
From: Dmitry Torokhov @ 2026-03-25 3:02 UTC (permalink / raw)
To: Bhushan Shah
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-input,
devicetree, linux-kernel
In-Reply-To: <20260314-edt-ft3519-v3-1-5ee91b408ed6@machinesoul.in>
On Sat, Mar 14, 2026 at 08:27:58PM +0530, Bhushan Shah wrote:
> Document FocalTech FT3519 support by adding the compatible. It's 10
> point touchscreen, which is compatible with FT3518
>
> Signed-off-by: Bhushan Shah <bhushan.shah@machinesoul.in>
Applied, thank you.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v2] Input: penmount: bound packet buffer indices in IRQ path
From: Pengpeng Hou @ 2026-03-25 1:46 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Pengpeng Hou, andriy.shevchenko, kees, linux-input, linux-kernel
In-Reply-To: <20260324131442.27632-1-pengpeng@iscas.ac.cn>
Hi Dmitry,
You are right, thanks for pointing this out.
I rechecked the control flow and my analysis was wrong here. If the
first byte does not match a supported packet header, the left side of
the && fails and ++pm->idx is not evaluated, so pm->idx stays at 0. If
the first byte does match, pm->idx only advances until packetsize and
is then reset to 0 in the handler.
So this does not provide a path for pm->idx to grow past the packet
buffer. My analyzer missed the short-circuiting behavior of && here.
I will drop this patch.
Best regards,
Pengpeng
^ permalink raw reply
* Re: [PATCH v2] Input: penmount: bound packet buffer indices in IRQ path
From: Dmitry Torokhov @ 2026-03-25 1:36 UTC (permalink / raw)
To: Pengpeng Hou; +Cc: andriy.shevchenko, kees, linux-input, linux-kernel
In-Reply-To: <20260324131442.27632-1-pengpeng@iscas.ac.cn>
Hi Pengpeng,
On Tue, Mar 24, 2026 at 09:14:42PM +0800, Pengpeng Hou wrote:
> pm_interrupt() stores each incoming byte into pm->data[] before the
> packet parser gets a chance to reset pm->idx. If the incoming serial
> stream never matches one of the expected packet headers, pm->idx can
> advance past the fixed receive buffer and the next IRQ will write beyond
> PM_MAX_LENGTH.
How will it advance? The handlers do:
if (byte_0_check() && pm->packetsize == ++pm->idx)
...
If we never match any of the protocols then pm->idx will never advance
past 0 (and we will keep overwriting the first byte of the packet
array).
Does your analyzer miss the "short-circuiting" nature of && operator?
Thanks.
--
Dmitry
^ permalink raw reply
* [PATCH] HID: Kysona: Add support for VXE Dragonfly R1 Pro
From: Lode Willems @ 2026-03-24 20:43 UTC (permalink / raw)
To: linux-input
Cc: jikos, bentiss, linux-kernel, Lode Willems, Dominykas Svetikas
Apparently this same protocol is used by more mice from different brands.
This patch adds support for the VXE Dragonfly R1 Pro.
Tested-by: Dominykas Svetikas <dominykas@svetikas.lt>
Signed-off-by: Lode Willems <me@lodewillems.com>
---
drivers/hid/hid-ids.h | 4 ++++
drivers/hid/hid-kysona.c | 2 ++
2 files changed, 6 insertions(+)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index afcee13bad61..772f24e80baa 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1471,6 +1471,10 @@
#define USB_VENDOR_ID_VTL 0x0306
#define USB_DEVICE_ID_VTL_MULTITOUCH_FF3F 0xff3f
+#define USB_VENDOR_ID_VXE 0x3554
+#define USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_DONGLE 0xf58a
+#define USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_WIRED 0xf58c
+
#define USB_VENDOR_ID_WACOM 0x056a
#define USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH 0x81
#define USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH 0x00BD
diff --git a/drivers/hid/hid-kysona.c b/drivers/hid/hid-kysona.c
index 09bfe30d02cb..ccbd8380064e 100644
--- a/drivers/hid/hid-kysona.c
+++ b/drivers/hid/hid-kysona.c
@@ -272,6 +272,8 @@ static void kysona_remove(struct hid_device *hdev)
static const struct hid_device_id kysona_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_DONGLE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_WIRED) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_VXE, USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_DONGLE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_VXE, USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_WIRED) },
{ }
};
MODULE_DEVICE_TABLE(hid, kysona_devices);
--
2.53.0
^ permalink raw reply related
* [PATCH v2] HID: ft260: validate report size and payload length in raw_event
From: Sebastian Josue Alba Vives @ 2026-03-24 20:18 UTC (permalink / raw)
To: michael.zaidman, jikos, bentiss
Cc: linux-i2c, linux-input, linux-kernel, stable,
Sebastian Josue Alba Vives
In-Reply-To: <20260324173527.11321-1-sebasjosue84@gmail.com>
ft260_raw_event() casts the raw data buffer to a
ft260_i2c_input_report struct and accesses its fields without
validating the size parameter. Since __hid_input_report() invokes
the driver's raw_event callback before hid_report_raw_event()
performs its own report-size validation, a device sending a
truncated HID report can cause out-of-bounds heap reads.
Additionally, even with a full-sized report, a corrupted
xfer->length field can cause memcpy to read beyond the report
buffer. The existing check only validates against the destination
buffer size, not the source data available in the report.
Add two checks: reject reports shorter than FT260_REPORT_MAX_LENGTH,
and verify that xfer->length does not exceed the actual data
available in the report. Log warnings to aid debugging.
Cc: stable@vger.kernel.org
Signed-off-by: Sebastian Josue Alba Vives <sebasjosue84@gmail.com>
---
drivers/hid/hid-ft260.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 333341e80..68008a423 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -1068,6 +1068,17 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
struct ft260_device *dev = hid_get_drvdata(hdev);
struct ft260_i2c_input_report *xfer = (void *)data;
+ if (size < FT260_REPORT_MAX_LENGTH) {
+ hid_warn(hdev, "short report: %d\n", size);
+ return 0;
+ }
+
+ if (xfer->length > size - offsetof(struct ft260_i2c_input_report, data)) {
+ hid_warn(hdev, "payload %d exceeds report size %d\n",
+ xfer->length, size);
+ return 0;
+ }
+
if (xfer->report >= FT260_I2C_REPORT_MIN &&
xfer->report <= FT260_I2C_REPORT_MAX) {
ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox