Linux Input/HID development
 help / color / mirror / Atom feed
* 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


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox