Linux Input/HID development
 help / color / mirror / Atom feed
* Re: [PATCH RESEND 2] HID: Add force feedback support for Speedlink Cougar Vibration Flightstick
From: Harald Judt @ 2026-05-13 12:00 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input
In-Reply-To: <1q46pn22-177p-9no9-q4p7-4qq8n38po504@xreary.bet>

Hi,

Thanks for reviewing this.

Am 12.05.26 um 18:11 schrieb Jiri Kosina:
> On Fri, 8 May 2026, Harald Judt wrote:
[...]

>> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
>> index 04420a713be0..b4e2c8f67728 100644
>> --- a/drivers/hid/Kconfig
>> +++ b/drivers/hid/Kconfig
>> @@ -406,6 +406,14 @@ config HID_GEMBIRD
>>  	help
>>  	Support for Gembird JPD-DualForce 2.
>>  
>> +config HID_GEMBIRD_JOY_FF
>> +	tristate "Gembird Joysticks force feedback support"
>> +	depends on USB_HID
>> +	select INPUT_FF_MEMLESS
>> +	help
>> +	Force feedback support for Gembird (Vendor ID 0x12bd) based devices:
>> +	  - Speed Link Cougar Vibration Flightstick (SL-6630)
>> +
>>  config HID_GFRM
>>  	tristate "Google Fiber TV Box remote control support"
>>  	help
>> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
>> index 361a7daedeb8..593a429661ed 100644
>> --- a/drivers/hid/Makefile
>> +++ b/drivers/hid/Makefile
>> @@ -54,6 +54,7 @@ obj-$(CONFIG_HID_EVISION)	+= hid-evision.o
>>  obj-$(CONFIG_HID_EZKEY)		+= hid-ezkey.o
>>  obj-$(CONFIG_HID_FT260)		+= hid-ft260.o
>>  obj-$(CONFIG_HID_GEMBIRD)	+= hid-gembird.o
>> +obj-$(CONFIG_HID_GEMBIRD_JOY_FF)	+= hid-gembird-joy.o
> 
> Would it be possible to link this support to hid-gembird if enabled?

The problem I encountered is that gembird != gembird, that is,
the USB Vendor IDs differ. These are defined:

#define USB_VENDOR_ID_GEMBIRD			0x11ff
#define USB_DEVICE_ID_GEMBIRD_JPD_DUALFORCE2	0x3331

#define USB_VENDOR_ID_GEMBIRD_JOY		0x12bd
#define USB_DEVICE_ID_GEMBIRD_JOY_SL_6630	0xa02f

What would be the best way to solve this? Can my implementation
reside in separate files or should it be integrated into the
existing hid-gembird implementation? I am pretty new to
writing kernel modules, and doing it separately simply seemed
more suited for an easier start...

Regards,
Harald

-- 
`Experience is the best teacher.'

PGP Key ID: 4FFFAB21B8580ABD
Fingerprint: E073 6DD8 FF40 9CF2 0665 11D4 4FFF AB21 B858 0ABD

^ permalink raw reply

* Re: [PATCH v2] HID: multitouch: Fix Yoga Book 9 14IAH10 touchscreen misclassification
From: Dave Carey @ 2026-05-13 12:57 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: linux-input, bentiss
In-Reply-To: <364r776o-6771-o29n-8ss3-857n32071op7@xreary.bet>

It's pretty rad.  Have one more patch coming in a bit to fix a trailing 
ghost touch issue.

On 5/12/26 11:39 AM, Jiri Kosina wrote:
> On Mon, 13 Apr 2026, Dave Carey wrote:
>
>> The Lenovo Yoga Book 9 14IAH10 (83KJ) (17EF:6161) firmware includes a
>> HID_DG_TOUCHPAD application collection designed for the Windows inbox HID
>> driver's Win8 PTP touchpad mode.  On Linux the HID_DG_TOUCHSCREEN
>> collections provide the correct direct-touch interface.  The presence of
>> the touchpad collection causes hid-multitouch to misclassify the
>> touchscreen nodes as indirect buttonpads, leaving them non-functional.
>>
>> Within the touchpad collection:
>> - HID_UP_BUTTON usages trigger the touchscreen-with-buttons heuristic
>>    that sets INPUT_MT_POINTER on the touchscreen applications.
>> - The HID_DG_TOUCHPAD application itself sets INPUT_MT_POINTER via
>>    mt_allocate_application(), propagating to all touchscreen nodes.
>> - A HID_DG_BUTTONTYPE feature (report 0x51) returns MT_BUTTONTYPE_CLICKPAD,
>>    setting td->is_buttonpad = true for the entire device.
>>
>> Additionally, the firmware resets if any USB control request arrives while
>> the CDC-ACM interface is initialising (~1.18 s after enumeration).
>> The Win8 compliance blob (0xff00:0xc5) and Contact Count Max feature
>> reports in the touchscreen collections trigger GET_REPORT calls at probe
>> that hit this window.  Surface Switch (0x57) and Button Switch (0x58)
>> feature reports are sent by mt_set_modes() on every input-device open and
>> close, repeatedly hitting this window throughout device lifetime.
>>
>> The firmware also leaves a persistent ghost contact in its contact buffer
>> (contact ID 2, fixed coordinates, tip always asserted) on every enumeration.
>> This ghost occupies a multitouch slot and prevents KWin from seeing a clean
>> finger-lift, causing stuck touch state.  The ghost is cleared when Input
>> Mode is set via HID_REQ_SET_REPORT at probe.
> Oh man, what a device.
> Applied, thanks.
>

^ permalink raw reply

* Re: [PATCH RESEND 2] HID: Add force feedback support for Speedlink Cougar Vibration Flightstick
From: Harald Judt @ 2026-05-13 13:12 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-input
In-Reply-To: <af5tJlRJr_GFTjVH@google.com>

Hi,

Am 09.05.26 um 01:14 schrieb Dmitry Torokhov:

[ ... ]

>>> +	strong = (strong / 0xff) * 0x1f / 0xff;
>>> +	weak = (weak / 0xff) * 0x1f / 0xff;
>>> +
>>> +	/* ... and to support the notions of strong vs weak rumble effects,
>>> +	 * increase the magnitude for the strong rumble effect if it is below the
>>> +	 * half of the maximum value, as the strong motor has the same strength as
>>> +	 * the weak one. Likewise, decrease the magnitude for the weak effect.
>>> +	 */
>>> +	if (strong < 0x10 && !weak)         /* fftest effect 4 strong rumble */
>>> +		strong *= 2;
>>> +	else if (!strong && weak >= 0x10)   /* fftest effect 5 weak rumble */
>>> +		weak /= 2;
>>
>> Will this cause issues with non-monotonic force feedback behavior and
>> unintended truncation?

I thought about this too. I have not experienced any negative behavior with
this solution, the stick seems to work fine with it. But honestly, there
are not many tests available. fftest and SuperTuxKart have been the only
applications I found supporting force feedback, it worked fine there,
but the stick is also a bit limited in capabilities, it does not really
support "force feedback", only some degrees of rumble.

Since I wrote this patch some time passed and I have gained a bit more
experience with force feedback in general and implementing support for it.

>> Since the math uses integer division, any input value below 255 for strong
>> or weak will evaluate to zero during the first division by 0xff.

I am aware of this but IIRC this does not have any practical impact for
this stick. It seems to correspond to the values I got from the SET_REPORTs
in Windows and the vibrating behaviour.

>> Additionally, could the conditional scaling cause abrupt changes in rumble
>> strength? For instance, if a game requests a strong magnitude that scales
>> down to 15, it gets doubled to 30 because of the strong < 0x10 check.
>> However, a slightly stronger requested magnitude that scales to 16 will
>> remain 16, meaning an increase in requested strength results in a drop in
>> physical rumble.

True. I have tested this only with fftest and observed the behaviour in
SuperTuxKart, where it worked fine, as it uses both strong and weak
attributes. Existing implementations for other devices in the kernel
source I found sometimes use a "left" motor and a "right" motor,
other times a "strong" motor and a "weak" motor. But the games do a
variation of strong vs weak. So I finally decided to somehow get it
to work with the game I could test it with. I will do more checks
with other parameters/gain and try to improve this or think about
another, better solution. I might probably need to write some
ff tests myself for this, but why not...
>> [ ... ]
>>
>>> +static int gembird_joy_probe(struct hid_device *hdev, const struct hid_device_id *id)
>>> +{
>>> +	int ret;
>>> +
>>> +	ret = hid_parse(hdev);
>>> +	if (ret) {
>>> +		hid_err(hdev, "parse failed\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
>>> +	if (ret) {
>>> +		hid_err(hdev, "hw start failed\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	gembird_joy_init(hdev);
>>> +
>>> +	return 0;
>>> +}
>>
>> What happens if gembird_joy_init fails?
>>
>> If input_ff_create_memless returns an error, gembird_joy_init
>> propagates it, but the return value is ignored here and probe succeeds.
>>
>> Because set_bit(FF_RUMBLE, dev->ffbit) was already called, the device
>> remains registered and advertises rumble support. If userspace then issues
>> an EVIOCSFF ioctl, the kernel will allow it, which could lead to a panic
>> when it tries to dereference the missing force feedback structures.
> 
> Need to have error handling.

I have looked at some other implementations, and if I have not missed
anything, then they do it similarly (hid-gaff.c, hid-sjoy.c, hid-emsff.c,
hid-megaworld.c). I will take a closer look again and try to fix it.

> Thanks.
Thank you for your suggestions and spending your time on this review.
I will try to improve it and send a new patch when ready.

Regards,
Harald

-- 
`Experience is the best teacher.'

PGP Key ID: 4FFFAB21B8580ABD
Fingerprint: E073 6DD8 FF40 9CF2 0665 11D4 4FFF AB21 B858 0ABD

^ permalink raw reply

* Re: [PATCH v4 5/6 RESEND] mfd: motorola-cpcap: diverge configuration per-board
From: Lee Jones @ 2026-05-13 14:04 UTC (permalink / raw)
  To: Svyatoslav Ryhel
  Cc: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Pavel Machek, David Lechner, Tony Lindgren, linux-input,
	devicetree, linux-kernel, linux-leds
In-Reply-To: <CAPVz0n24U3=hwda5BTXUhHiiFHKmWUfLy_nxJp_CFHBHEYDWsA@mail.gmail.com>

On Thu, 07 May 2026, Svyatoslav Ryhel wrote:

> чт, 7 трав. 2026 р. о 17:05 Lee Jones <lee@kernel.org> пише:
> >
> > On Tue, 28 Apr 2026, Svyatoslav Ryhel wrote:
> >
> > > MFD have rigid subdevice structure which does not allow flexible dynamic
> > > subdevice linking. Address this by diverging CPCAP subdevice composition
> > > to take into account board specific configuration.
> > >
> > > Create a common default subdevice composition, rename existing subdevice
> > > composition into cpcap_mapphone_mfd_devices since it targets mainly
> > > Mapphone board.
> > >
> > > Removed st,6556002 as it is no longer applicable to all cases and
> > > duplicates motorola,cpcap, which is used as the default composition.
> > >
> > > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > ---
> >
> > Changelog?
> >
> > >  drivers/mfd/motorola-cpcap.c | 101 ++++++++++++++++++++++++++++-------
> > >  1 file changed, 83 insertions(+), 18 deletions(-)
> > >
> > > diff --git a/drivers/mfd/motorola-cpcap.c b/drivers/mfd/motorola-cpcap.c
> > > index d8243b956f87..516d1e33affa 100644
> > > --- a/drivers/mfd/motorola-cpcap.c
> > > +++ b/drivers/mfd/motorola-cpcap.c
> > > @@ -12,6 +12,7 @@
> > >  #include <linux/kernel.h>
> > >  #include <linux/module.h>
> > >  #include <linux/mod_devicetable.h>
> > > +#include <linux/property.h>
> > >  #include <linux/regmap.h>
> > >  #include <linux/sysfs.h>
> > >
> > > @@ -24,10 +25,16 @@
> > >  #define CPCAP_REGISTER_SIZE  4
> > >  #define CPCAP_REGISTER_BITS  16
> > >
> > > +struct cpcap_chip_data {
> > > +     const struct mfd_cell *mfd_devices;
> > > +     unsigned int num_devices;
> > > +};
> >
> > This is a red flag.
> >
> > >  struct cpcap_ddata {
> > >       struct spi_device *spi;
> > >       struct regmap_irq *irqs;
> > >       struct regmap_irq_chip_data *irqdata[CPCAP_NR_IRQ_CHIPS];
> > > +     const struct cpcap_chip_data *cdata;
> > >       const struct regmap_config *regmap_conf;
> > >       struct regmap *regmap;
> > >  };
> > > @@ -195,20 +202,6 @@ static int cpcap_init_irq(struct cpcap_ddata *cpcap)
> > >       return 0;
> > >  }
> > >
> > > -static const struct of_device_id cpcap_of_match[] = {
> > > -     { .compatible = "motorola,cpcap", },
> > > -     { .compatible = "st,6556002", },
> > > -     {},
> > > -};
> > > -MODULE_DEVICE_TABLE(of, cpcap_of_match);
> > > -
> > > -static const struct spi_device_id cpcap_spi_ids[] = {
> > > -     { .name = "cpcap", },
> > > -     { .name = "6556002", },
> > > -     {},
> > > -};
> > > -MODULE_DEVICE_TABLE(spi, cpcap_spi_ids);
> > > -
> > >  static const struct regmap_config cpcap_regmap_config = {
> > >       .reg_bits = 16,
> > >       .reg_stride = 4,
> > > @@ -241,7 +234,56 @@ static int cpcap_resume(struct device *dev)
> > >
> > >  static DEFINE_SIMPLE_DEV_PM_OPS(cpcap_pm, cpcap_suspend, cpcap_resume);
> > >
> > > -static const struct mfd_cell cpcap_mfd_devices[] = {
> > > +static const struct mfd_cell cpcap_default_mfd_devices[] = {
> > > +     {
> > > +             .name          = "cpcap_adc",
> > > +             .of_compatible = "motorola,cpcap-adc",
> > > +     }, {
> > > +             .name          = "cpcap_battery",
> > > +             .of_compatible = "motorola,cpcap-battery",
> > > +     }, {
> > > +             .name          = "cpcap-regulator",
> > > +             .of_compatible = "motorola,cpcap-regulator",
> > > +     }, {
> > > +             .name          = "cpcap-rtc",
> > > +             .of_compatible = "motorola,cpcap-rtc",
> > > +     }, {
> > > +             .name          = "cpcap-pwrbutton",
> > > +             .of_compatible = "motorola,cpcap-pwrbutton",
> > > +     }, {
> > > +             .name          = "cpcap-usb-phy",
> > > +             .of_compatible = "motorola,cpcap-usb-phy",
> > > +     }, {
> > > +             .name          = "cpcap-led",
> > > +             .id            = 0,
> > > +             .of_compatible = "motorola,cpcap-led-red",
> > > +     }, {
> > > +             .name          = "cpcap-led",
> > > +             .id            = 1,
> > > +             .of_compatible = "motorola,cpcap-led-green",
> > > +     }, {
> > > +             .name          = "cpcap-led",
> > > +             .id            = 2,
> > > +             .of_compatible = "motorola,cpcap-led-blue",
> > > +     }, {
> > > +             .name          = "cpcap-led",
> > > +             .id            = 3,
> > > +             .of_compatible = "motorola,cpcap-led-adl",
> > > +     }, {
> > > +             .name          = "cpcap-led",
> > > +             .id            = 4,
> > > +             .of_compatible = "motorola,cpcap-led-cp",
> > > +     }, {
> > > +             .name          = "cpcap-codec",
> > > +     },
> > > +};
> 
> Lee, would you mind if I convert these mfd_cell structures to use
> macros in this commit since I am refactoring them anyway?

Sure.

-- 
Lee Jones

^ permalink raw reply

* Re: [PATCH v4 5/6 RESEND] mfd: motorola-cpcap: diverge configuration per-board
From: Lee Jones @ 2026-05-13 14:05 UTC (permalink / raw)
  To: Svyatoslav Ryhel
  Cc: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Pavel Machek, David Lechner, Tony Lindgren, linux-input,
	devicetree, linux-kernel, linux-leds
In-Reply-To: <CAPVz0n1Ubvj9MHHMcM2BpxAcTCCheMihr3aJUqcDVoi_V0OQ5g@mail.gmail.com>

On Thu, 07 May 2026, Svyatoslav Ryhel wrote:

> чт, 7 трав. 2026 р. о 17:05 Lee Jones <lee@kernel.org> пише:
> >
> > On Tue, 28 Apr 2026, Svyatoslav Ryhel wrote:
> >
> > > MFD have rigid subdevice structure which does not allow flexible dynamic
> > > subdevice linking. Address this by diverging CPCAP subdevice composition
> > > to take into account board specific configuration.
> > >
> > > Create a common default subdevice composition, rename existing subdevice
> > > composition into cpcap_mapphone_mfd_devices since it targets mainly
> > > Mapphone board.
> > >
> > > Removed st,6556002 as it is no longer applicable to all cases and
> > > duplicates motorola,cpcap, which is used as the default composition.
> > >
> > > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > ---
> >
> > Changelog?
> >
> 
> Changelog is in the cover.
> 
> > >  drivers/mfd/motorola-cpcap.c | 101 ++++++++++++++++++++++++++++-------
> > >  1 file changed, 83 insertions(+), 18 deletions(-)
> > >
> > > diff --git a/drivers/mfd/motorola-cpcap.c b/drivers/mfd/motorola-cpcap.c
> > > index d8243b956f87..516d1e33affa 100644
> > > --- a/drivers/mfd/motorola-cpcap.c
> > > +++ b/drivers/mfd/motorola-cpcap.c
> > > @@ -12,6 +12,7 @@
> > >  #include <linux/kernel.h>
> > >  #include <linux/module.h>
> > >  #include <linux/mod_devicetable.h>
> > > +#include <linux/property.h>
> > >  #include <linux/regmap.h>
> > >  #include <linux/sysfs.h>
> > >
> > > @@ -24,10 +25,16 @@
> > >  #define CPCAP_REGISTER_SIZE  4
> > >  #define CPCAP_REGISTER_BITS  16
> > >
> > > +struct cpcap_chip_data {
> > > +     const struct mfd_cell *mfd_devices;
> > > +     unsigned int num_devices;
> > > +};
> >
> > This is a red flag.
> >
> > >  struct cpcap_ddata {
> > >       struct spi_device *spi;
> > >       struct regmap_irq *irqs;
> > >       struct regmap_irq_chip_data *irqdata[CPCAP_NR_IRQ_CHIPS];
> > > +     const struct cpcap_chip_data *cdata;
> > >       const struct regmap_config *regmap_conf;
> > >       struct regmap *regmap;
> > >  };
> > > @@ -195,20 +202,6 @@ static int cpcap_init_irq(struct cpcap_ddata *cpcap)
> > >       return 0;
> > >  }
> > >
> > > -static const struct of_device_id cpcap_of_match[] = {
> > > -     { .compatible = "motorola,cpcap", },
> > > -     { .compatible = "st,6556002", },
> > > -     {},
> > > -};
> > > -MODULE_DEVICE_TABLE(of, cpcap_of_match);
> > > -
> > > -static const struct spi_device_id cpcap_spi_ids[] = {
> > > -     { .name = "cpcap", },
> > > -     { .name = "6556002", },
> > > -     {},
> > > -};
> > > -MODULE_DEVICE_TABLE(spi, cpcap_spi_ids);
> > > -
> > >  static const struct regmap_config cpcap_regmap_config = {
> > >       .reg_bits = 16,
> > >       .reg_stride = 4,
> > > @@ -241,7 +234,56 @@ static int cpcap_resume(struct device *dev)
> > >
> > >  static DEFINE_SIMPLE_DEV_PM_OPS(cpcap_pm, cpcap_suspend, cpcap_resume);
> > >
> > > -static const struct mfd_cell cpcap_mfd_devices[] = {
> > > +static const struct mfd_cell cpcap_default_mfd_devices[] = {
> > > +     {
> > > +             .name          = "cpcap_adc",
> > > +             .of_compatible = "motorola,cpcap-adc",
> > > +     }, {
> > > +             .name          = "cpcap_battery",
> > > +             .of_compatible = "motorola,cpcap-battery",
> > > +     }, {
> > > +             .name          = "cpcap-regulator",
> > > +             .of_compatible = "motorola,cpcap-regulator",
> > > +     }, {
> > > +             .name          = "cpcap-rtc",
> > > +             .of_compatible = "motorola,cpcap-rtc",
> > > +     }, {
> > > +             .name          = "cpcap-pwrbutton",
> > > +             .of_compatible = "motorola,cpcap-pwrbutton",
> > > +     }, {
> > > +             .name          = "cpcap-usb-phy",
> > > +             .of_compatible = "motorola,cpcap-usb-phy",
> > > +     }, {
> > > +             .name          = "cpcap-led",
> > > +             .id            = 0,
> > > +             .of_compatible = "motorola,cpcap-led-red",
> > > +     }, {
> > > +             .name          = "cpcap-led",
> > > +             .id            = 1,
> > > +             .of_compatible = "motorola,cpcap-led-green",
> > > +     }, {
> > > +             .name          = "cpcap-led",
> > > +             .id            = 2,
> > > +             .of_compatible = "motorola,cpcap-led-blue",
> > > +     }, {
> > > +             .name          = "cpcap-led",
> > > +             .id            = 3,
> > > +             .of_compatible = "motorola,cpcap-led-adl",
> > > +     }, {
> > > +             .name          = "cpcap-led",
> > > +             .id            = 4,
> > > +             .of_compatible = "motorola,cpcap-led-cp",
> > > +     }, {
> > > +             .name          = "cpcap-codec",
> > > +     },
> > > +};
> > > +
> > > +static const struct cpcap_chip_data cpcap_default_data = {
> > > +     .mfd_devices = cpcap_default_mfd_devices,
> > > +     .num_devices = ARRAY_SIZE(cpcap_default_mfd_devices),
> > > +};
> > > +
> > > +static const struct mfd_cell cpcap_mapphone_mfd_devices[] = {
> > >       {
> > >               .name          = "cpcap_adc",
> > >               .of_compatible = "motorola,mapphone-cpcap-adc",
> > > @@ -285,7 +327,12 @@ static const struct mfd_cell cpcap_mfd_devices[] = {
> > >               .of_compatible = "motorola,cpcap-led-cp",
> > >       }, {
> > >               .name          = "cpcap-codec",
> > > -     }
> > > +     },
> > > +};
> > > +
> > > +static const struct cpcap_chip_data cpcap_mapphone_data = {
> > > +     .mfd_devices = cpcap_mapphone_mfd_devices,
> > > +     .num_devices = ARRAY_SIZE(cpcap_mapphone_mfd_devices),
> > >  };
> > >
> > >  static int cpcap_probe(struct spi_device *spi)
> > > @@ -297,9 +344,17 @@ static int cpcap_probe(struct spi_device *spi)
> > >       if (!cpcap)
> > >               return -ENOMEM;
> > >
> > > +     cpcap->cdata = device_get_match_data(&spi->dev);
> > > +     if (!cpcap->cdata)
> > > +             return -ENODEV;
> > > +
> > >       cpcap->spi = spi;
> > >       spi_set_drvdata(spi, cpcap);
> > >
> > > @@ -331,16 +382,24 @@ static int cpcap_probe(struct spi_device *spi)
> > >       spi->dev.coherent_dma_mask = 0;
> > >       spi->dev.dma_mask = &spi->dev.coherent_dma_mask;
> > >
> > > -     return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,
> > > -                                 ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);
> > > +     return devm_mfd_add_devices(&spi->dev, 0, cpcap->cdata->mfd_devices,
> > > +                                 cpcap->cdata->num_devices, NULL, 0, NULL);
> > >  }
> > >
> > > +static const struct of_device_id cpcap_of_match[] = {
> > > +     { .compatible = "motorola,cpcap", .data = &cpcap_default_data },
> > > +     { .compatible = "motorola,mapphone-cpcap", .data = &cpcap_mapphone_data },
> >
> > We don't allow data from one device registration API (MFD) to be passed
> > through another (OF) because it tends to lead to all sorts of "creative
> > solutions".  Pass a value instead and match on that in a switch()
> > statement like all of the other MFD drivers do.
> >
> 
> You don't allow this. I have not seen this enforced anywhere in the
> kernel except the mfd subsystem. Fine, does not matter, if this makes
> you happy I will adjust.

Where else would this rule be applicable?  I can't think of anywhere.

-- 
Lee Jones

^ permalink raw reply

* [dtor-input:next] BUILD SUCCESS b7710233c16c1f42e0b58a1f0485658fc4fa61c1
From: kernel test robot @ 2026-05-13 14:37 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: b7710233c16c1f42e0b58a1f0485658fc4fa61c1  Input: atmel_mxt_ts - use __free() for obuf in mxt_object_show

elapsed time: 1225m

configs tested: 244
configs skipped: 14

The following configs have been built successfully.
More configs may be tested in the coming days.

tested configs:
alpha                             allnoconfig    gcc-15.2.0
alpha                            allyesconfig    gcc-15.2.0
alpha                               defconfig    gcc-15.2.0
arc                              allmodconfig    clang-16
arc                              allmodconfig    gcc-15.2.0
arc                               allnoconfig    gcc-15.2.0
arc                              allyesconfig    clang-23
arc                              allyesconfig    gcc-15.2.0
arc                                 defconfig    gcc-15.2.0
arc                        nsimosci_defconfig    gcc-15.2.0
arc                   randconfig-001-20260513    gcc-14.3.0
arc                   randconfig-002-20260513    gcc-14.3.0
arm                               allnoconfig    clang-23
arm                               allnoconfig    gcc-15.2.0
arm                              allyesconfig    clang-16
arm                              allyesconfig    gcc-15.2.0
arm                                 defconfig    gcc-15.2.0
arm                          gemini_defconfig    clang-20
arm                          ixp4xx_defconfig    gcc-15.2.0
arm                   randconfig-001-20260513    gcc-14.3.0
arm                   randconfig-002-20260513    gcc-14.3.0
arm                   randconfig-003-20260513    gcc-14.3.0
arm                   randconfig-004-20260513    gcc-14.3.0
arm64                            allmodconfig    clang-19
arm64                            allmodconfig    clang-23
arm64                             allnoconfig    gcc-15.2.0
arm64                               defconfig    gcc-15.2.0
arm64                          randconfig-001    clang-23
arm64                 randconfig-001-20260513    clang-23
arm64                 randconfig-001-20260513    gcc-12.5.0
arm64                          randconfig-002    clang-23
arm64                 randconfig-002-20260513    clang-23
arm64                 randconfig-002-20260513    gcc-12.5.0
arm64                          randconfig-003    clang-23
arm64                 randconfig-003-20260513    clang-23
arm64                 randconfig-003-20260513    gcc-12.5.0
arm64                          randconfig-004    clang-23
arm64                 randconfig-004-20260513    clang-23
arm64                 randconfig-004-20260513    gcc-12.5.0
csky                             allmodconfig    gcc-15.2.0
csky                              allnoconfig    gcc-15.2.0
csky                                defconfig    gcc-15.2.0
csky                           randconfig-001    clang-23
csky                  randconfig-001-20260513    clang-23
csky                  randconfig-001-20260513    gcc-12.5.0
csky                           randconfig-002    clang-23
csky                  randconfig-002-20260513    clang-23
csky                  randconfig-002-20260513    gcc-12.5.0
hexagon                          allmodconfig    clang-17
hexagon                          allmodconfig    gcc-15.2.0
hexagon                           allnoconfig    clang-23
hexagon                           allnoconfig    gcc-15.2.0
hexagon                             defconfig    gcc-15.2.0
hexagon                        randconfig-001    gcc-11.5.0
hexagon               randconfig-001-20260513    gcc-11.5.0
hexagon               randconfig-001-20260513    gcc-8.5.0
hexagon                        randconfig-002    gcc-11.5.0
hexagon               randconfig-002-20260513    gcc-11.5.0
hexagon               randconfig-002-20260513    gcc-8.5.0
i386                             allmodconfig    clang-20
i386                              allnoconfig    gcc-14
i386                              allnoconfig    gcc-15.2.0
i386                             allyesconfig    clang-20
i386                             allyesconfig    gcc-14
i386        buildonly-randconfig-001-20260513    clang-20
i386        buildonly-randconfig-002-20260513    clang-20
i386        buildonly-randconfig-003-20260513    clang-20
i386        buildonly-randconfig-004-20260513    clang-20
i386        buildonly-randconfig-005-20260513    clang-20
i386        buildonly-randconfig-006-20260513    clang-20
i386                                defconfig    gcc-15.2.0
i386                  randconfig-001-20260513    clang-20
i386                  randconfig-002-20260513    clang-20
i386                  randconfig-003-20260513    clang-20
i386                  randconfig-004-20260513    clang-20
i386                  randconfig-005-20260513    clang-20
i386                  randconfig-006-20260513    clang-20
i386                  randconfig-007-20260513    clang-20
i386                  randconfig-011-20260513    clang-20
i386                  randconfig-012-20260513    clang-20
i386                  randconfig-012-20260513    gcc-14
i386                  randconfig-013-20260513    clang-20
i386                  randconfig-014-20260513    clang-20
i386                  randconfig-015-20260513    clang-20
i386                  randconfig-016-20260513    clang-20
i386                  randconfig-017-20260513    clang-20
loongarch                        allmodconfig    clang-19
loongarch                        allmodconfig    clang-23
loongarch                         allnoconfig    clang-23
loongarch                         allnoconfig    gcc-15.2.0
loongarch                           defconfig    clang-19
loongarch                      randconfig-001    gcc-11.5.0
loongarch             randconfig-001-20260513    gcc-11.5.0
loongarch             randconfig-001-20260513    gcc-8.5.0
loongarch                      randconfig-002    gcc-11.5.0
loongarch             randconfig-002-20260513    gcc-11.5.0
loongarch             randconfig-002-20260513    gcc-8.5.0
m68k                             allmodconfig    gcc-15.2.0
m68k                              allnoconfig    gcc-15.2.0
m68k                             allyesconfig    clang-16
m68k                             allyesconfig    gcc-15.2.0
m68k                          atari_defconfig    gcc-15.2.0
m68k                                defconfig    clang-19
microblaze                        allnoconfig    gcc-15.2.0
microblaze                       allyesconfig    gcc-15.2.0
microblaze                          defconfig    clang-19
mips                             allmodconfig    gcc-15.2.0
mips                              allnoconfig    gcc-15.2.0
mips                             allyesconfig    gcc-15.2.0
mips                      loongson3_defconfig    gcc-15.2.0
mips                    maltaup_xpa_defconfig    gcc-15.2.0
nios2                            allmodconfig    clang-23
nios2                            allmodconfig    gcc-11.5.0
nios2                             allnoconfig    clang-23
nios2                             allnoconfig    gcc-11.5.0
nios2                               defconfig    clang-19
nios2                          randconfig-001    gcc-11.5.0
nios2                 randconfig-001-20260513    gcc-11.5.0
nios2                 randconfig-001-20260513    gcc-8.5.0
nios2                          randconfig-002    gcc-11.5.0
nios2                 randconfig-002-20260513    gcc-11.5.0
nios2                 randconfig-002-20260513    gcc-8.5.0
openrisc                         allmodconfig    clang-23
openrisc                         allmodconfig    gcc-15.2.0
openrisc                          allnoconfig    clang-23
openrisc                          allnoconfig    gcc-15.2.0
openrisc                            defconfig    gcc-15.2.0
parisc                           allmodconfig    gcc-15.2.0
parisc                            allnoconfig    clang-23
parisc                            allnoconfig    gcc-15.2.0
parisc                           allyesconfig    clang-19
parisc                           allyesconfig    gcc-15.2.0
parisc                              defconfig    gcc-15.2.0
parisc                randconfig-001-20260513    gcc-8.5.0
parisc                randconfig-002-20260513    gcc-8.5.0
parisc64                            defconfig    clang-19
powerpc                          allmodconfig    gcc-15.2.0
powerpc                           allnoconfig    clang-23
powerpc                           allnoconfig    gcc-15.2.0
powerpc                 mpc832x_rdb_defconfig    gcc-15.2.0
powerpc                 mpc834x_itx_defconfig    clang-16
powerpc                     rainier_defconfig    gcc-15.2.0
powerpc               randconfig-001-20260513    gcc-8.5.0
powerpc               randconfig-002-20260513    gcc-8.5.0
powerpc64             randconfig-001-20260513    gcc-8.5.0
powerpc64             randconfig-002-20260513    gcc-8.5.0
riscv                            allmodconfig    clang-23
riscv                             allnoconfig    clang-23
riscv                             allnoconfig    gcc-15.2.0
riscv                            allyesconfig    clang-16
riscv                               defconfig    gcc-15.2.0
riscv                 randconfig-001-20260513    gcc-15.2.0
riscv                 randconfig-002-20260513    gcc-15.2.0
s390                             allmodconfig    clang-18
s390                             allmodconfig    clang-19
s390                              allnoconfig    clang-23
s390                             allyesconfig    gcc-15.2.0
s390                                defconfig    gcc-15.2.0
s390                  randconfig-001-20260513    gcc-15.2.0
s390                  randconfig-002-20260513    gcc-15.2.0
sh                               allmodconfig    gcc-15.2.0
sh                                allnoconfig    clang-23
sh                                allnoconfig    gcc-15.2.0
sh                               allyesconfig    clang-19
sh                               allyesconfig    gcc-15.2.0
sh                                  defconfig    gcc-14
sh                 kfr2r09-romimage_defconfig    gcc-15.2.0
sh                    randconfig-001-20260513    gcc-15.2.0
sh                    randconfig-002-20260513    gcc-15.2.0
sparc                             allnoconfig    clang-23
sparc                             allnoconfig    gcc-15.2.0
sparc                               defconfig    gcc-15.2.0
sparc                          randconfig-001    gcc-11.5.0
sparc                 randconfig-001-20260513    gcc-11.5.0
sparc                          randconfig-002    gcc-11.5.0
sparc                 randconfig-002-20260513    gcc-11.5.0
sparc64                          allmodconfig    clang-23
sparc64                             defconfig    gcc-14
sparc64                        randconfig-001    gcc-11.5.0
sparc64               randconfig-001-20260513    gcc-11.5.0
sparc64                        randconfig-002    gcc-11.5.0
sparc64               randconfig-002-20260513    gcc-11.5.0
um                               allmodconfig    clang-19
um                                allnoconfig    clang-23
um                               allyesconfig    gcc-14
um                               allyesconfig    gcc-15.2.0
um                                  defconfig    gcc-14
um                             i386_defconfig    gcc-14
um                             randconfig-001    gcc-11.5.0
um                    randconfig-001-20260513    gcc-11.5.0
um                             randconfig-002    gcc-11.5.0
um                    randconfig-002-20260513    gcc-11.5.0
um                           x86_64_defconfig    gcc-14
x86_64                           allmodconfig    clang-20
x86_64                            allnoconfig    clang-20
x86_64                            allnoconfig    clang-23
x86_64                           allyesconfig    clang-20
x86_64               buildonly-randconfig-001    gcc-12
x86_64      buildonly-randconfig-001-20260513    gcc-12
x86_64               buildonly-randconfig-002    gcc-12
x86_64      buildonly-randconfig-002-20260513    gcc-12
x86_64               buildonly-randconfig-003    gcc-12
x86_64      buildonly-randconfig-003-20260513    gcc-12
x86_64               buildonly-randconfig-004    gcc-12
x86_64      buildonly-randconfig-004-20260513    gcc-12
x86_64               buildonly-randconfig-005    gcc-12
x86_64      buildonly-randconfig-005-20260513    gcc-12
x86_64               buildonly-randconfig-006    gcc-12
x86_64      buildonly-randconfig-006-20260513    gcc-12
x86_64                              defconfig    gcc-14
x86_64                                  kexec    clang-20
x86_64                randconfig-001-20260513    clang-20
x86_64                randconfig-002-20260513    clang-20
x86_64                randconfig-003-20260513    clang-20
x86_64                randconfig-004-20260513    clang-20
x86_64                randconfig-005-20260513    clang-20
x86_64                randconfig-006-20260513    clang-20
x86_64                randconfig-011-20260513    gcc-14
x86_64                randconfig-012-20260513    gcc-14
x86_64                randconfig-013-20260513    gcc-14
x86_64                randconfig-014-20260513    gcc-14
x86_64                randconfig-015-20260513    gcc-14
x86_64                randconfig-016-20260513    gcc-14
x86_64                randconfig-071-20260513    gcc-14
x86_64                randconfig-072-20260513    gcc-14
x86_64                randconfig-073-20260513    gcc-14
x86_64                randconfig-074-20260513    gcc-14
x86_64                randconfig-075-20260513    gcc-14
x86_64                randconfig-076-20260513    gcc-14
x86_64                               rhel-9.4    clang-20
x86_64                           rhel-9.4-bpf    gcc-14
x86_64                          rhel-9.4-func    clang-20
x86_64                    rhel-9.4-kselftests    clang-20
x86_64                         rhel-9.4-kunit    gcc-14
x86_64                           rhel-9.4-ltp    gcc-14
x86_64                          rhel-9.4-rust    clang-20
xtensa                            allnoconfig    clang-23
xtensa                            allnoconfig    gcc-15.2.0
xtensa                           allyesconfig    clang-23
xtensa                           allyesconfig    gcc-15.2.0
xtensa                         randconfig-001    gcc-11.5.0
xtensa                randconfig-001-20260513    gcc-11.5.0
xtensa                         randconfig-002    gcc-11.5.0
xtensa                randconfig-002-20260513    gcc-11.5.0

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH 1/1] HID: wacom: Fix OOB write in wacom_hid_set_device_mode()
From: Ping Cheng @ 2026-05-13 15:47 UTC (permalink / raw)
  To: Lee Jones
  Cc: Ping Cheng, Jason Gerecke, Jiri Kosina, Benjamin Tissoires,
	linux-input, linux-kernel, stable
In-Reply-To: <20260513075935.1715836-1-lee@kernel.org>

On Wed, May 13, 2026 at 1:05 AM Lee Jones <lee@kernel.org> wrote:
>
> wacom_hid_set_device_mode() currently assumes that the HID_DG_INPUTMODE
> usage is always located in the first field (field[0]) of the feature report.
> However, a device can specify HID_DG_INPUTMODE in a different field.
>
> If HID_DG_INPUTMODE is in a field other than the first one and the first
> field has a report_count smaller than the usage_index of HID_DG_INPUTMODE,
> this leads to an out-of-bounds write to r->field[0]->value.
>
> Fix this by storing the field index of HID_DG_INPUTMODE in 'struct
> hid_data' during feature mapping.  In wacom_hid_set_device_mode(), use
> this stored field index to access the correct field and add bounds
> checks to ensure both the field index and the value index are within
> valid ranges before writing.
>
> Cc: stable@vger.kernel.org
> Fixes: 5ae6e89f7409 ("HID: wacom: implement the finger part of the HID generic handling")
> Signed-off-by: Lee Jones <lee@kernel.org>

Patch looks sensible to me. Thank you for your effort, Lee!

Tested-by: Ping Cheng <ping.cheng@wacom.com>
Reviewed-by: Ping Cheng <ping.cheng@wacom.com>

Cheers,
Ping

> ---
>  drivers/hid/wacom_sys.c | 13 ++++++++++---
>  drivers/hid/wacom_wac.h |  1 +
>  2 files changed, 11 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
> index 1b1112772777..a6c5281afa06 100644
> --- a/drivers/hid/wacom_sys.c
> +++ b/drivers/hid/wacom_sys.c
> @@ -341,6 +341,7 @@ static void wacom_feature_mapping(struct hid_device *hdev,
>
>                 hid_data->inputmode = field->report->id;
>                 hid_data->inputmode_index = usage->usage_index;
> +               hid_data->inputmode_field_index = field->index;
>                 break;
>
>         case HID_UP_DIGITIZER:
> @@ -556,9 +557,14 @@ static int wacom_hid_set_device_mode(struct hid_device *hdev)
>
>         re = &(hdev->report_enum[HID_FEATURE_REPORT]);
>         r = re->report_id_hash[hid_data->inputmode];
> -       if (r) {
> -               r->field[0]->value[hid_data->inputmode_index] = 2;
> -               hid_hw_request(hdev, r, HID_REQ_SET_REPORT);
> +       if (r && hid_data->inputmode_field_index >= 0 &&
> +           hid_data->inputmode_field_index < r->maxfield) {
> +               struct hid_field *field = r->field[hid_data->inputmode_field_index];
> +
> +               if (field && hid_data->inputmode_index < field->report_count) {
> +                       field->value[hid_data->inputmode_index] = 2;
> +                       hid_hw_request(hdev, r, HID_REQ_SET_REPORT);
> +               }
>         }
>         return 0;
>  }
> @@ -2819,6 +2825,7 @@ static int wacom_probe(struct hid_device *hdev,
>                 return error;
>
>         wacom_wac->hid_data.inputmode = -1;
> +       wacom_wac->hid_data.inputmode_field_index = -1;
>         wacom_wac->mode_report = -1;
>
>         if (hid_is_usb(hdev)) {
> diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h
> index c8803d5c6a71..b2e74d7ab3c4 100644
> --- a/drivers/hid/wacom_wac.h
> +++ b/drivers/hid/wacom_wac.h
> @@ -298,6 +298,7 @@ struct wacom_shared {
>  struct hid_data {
>         __s16 inputmode;        /* InputMode HID feature, -1 if non-existent */
>         __s16 inputmode_index;  /* InputMode HID feature index in the report */
> +       __s16 inputmode_field_index; /* InputMode HID feature field index in the report */
>         bool sense_state;
>         bool inrange_state;
>         bool invert_state;
> --
> 2.54.0.563.g4f69b47b94-goog
>
>

^ permalink raw reply

* [PATCH] input/evdev: move kill_fasync() outside buffer_lock to fix SOFTIRQ deadlock
From: Rik van Riel @ 2026-05-13 15:50 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, kernel-team

buffer_lock is a SOFTIRQ-safe spinlock. kill_fasync() acquires fa_lock
(SOFTIRQ-unsafe), creating a potential SOFTIRQ-safe->SOFTIRQ-unsafe lock
ordering violation that lockdep flags as a deadlock.

Fix by moving the kill_fasync() call to evdev_pass_values() after
buffer_lock is released, alongside the existing wake_up_interruptible_poll().

The wakeup condition check is the same in __pass_event() and
evdev_pass_values()

Found by syzkaller

Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Rik van Riel <riel@surriel.com>
---
 drivers/input/evdev.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index c7325226cb86..bda63f7a507a 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -235,10 +235,8 @@ static void __pass_event(struct evdev_client *client,
 		client->packet_head = client->tail;
 	}
 
-	if (event->type == EV_SYN && event->code == SYN_REPORT) {
+	if (event->type == EV_SYN && event->code == SYN_REPORT)
 		client->packet_head = client->head;
-		kill_fasync(&client->fasync, SIGIO, POLL_IN);
-	}
 }
 
 static void evdev_pass_values(struct evdev_client *client,
@@ -280,9 +278,11 @@ static void evdev_pass_values(struct evdev_client *client,
 
 	spin_unlock(&client->buffer_lock);
 
-	if (wakeup)
+	if (wakeup) {
+		kill_fasync(&client->fasync, SIGIO, POLL_IN);
 		wake_up_interruptible_poll(&client->wait,
 			EPOLLIN | EPOLLOUT | EPOLLRDNORM | EPOLLWRNORM);
+	}
 }
 
 /*
-- 
2.53.0-Meta



^ permalink raw reply related

* [PATCH] HID: Expose LattePanda IOTA UPS as a power_supply device
From: Andrew Maney @ 2026-05-13 15:57 UTC (permalink / raw)
  To: jikos; +Cc: bentiss, linux-kernel, linux-input, Andrew Maney

This driver exposes the DFRobot LattePanda IOTA UPS board as a standard
power_supply device, allowing desktop environments and power management
tools such as UPower and systemd-logind to display battery status,
remaining capacity, and charging status without any special
configuration. It also enables automatic suspend or shutdown on low
battery and power profile configuration via any tool that supports the
standard power_supply interface.

The UPS presents itself as an Arduino Leonardo HID device running custom
firmware (VID 0x2341, PID 0x8036). It reports status and capacity via
HID reports 0x07 and 0x0C respectively.

The charge limit (80% or 100%) is configured via a physical DIP switch
on the UPS board and cannot be detected automatically. Userspace can
inform the driver of the configured limit via
charge_control_end_threshold.

Known issue: the driver occasionally reports 0% capacity briefly on
initial load before the first valid HID report is received. I am
investigating the cause.

Signed-off-by: Andrew Maney <andrewmaney05@gmail.com>

---

Changes in v2:
- Rebased on top of the current tree
- Moved vendor and device IDs to drivers/hid/hid-ids.h
- Added Kconfig entry under HID bus support -> Special HID drivers
- Added build rule to drivers/hid/Makefile
---
 MAINTAINERS                           |   6 +
 drivers/hid/Kconfig                   |  10 +
 drivers/hid/Makefile                  |   1 +
 drivers/hid/hid-ids.h                 |   3 +
 drivers/hid/hid-lattepanda-iota-ups.c | 354 ++++++++++++++++++++++++++
 5 files changed, 374 insertions(+)
 create mode 100644 drivers/hid/hid-lattepanda-iota-ups.c

diff --git a/MAINTAINERS b/MAINTAINERS
index b2040011a..fd2947a80 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11410,6 +11410,12 @@ F:	include/uapi/linux/hid*
 F:	samples/hid/
 F:	tools/testing/selftests/hid/
 
+HID LATTEPANDA IOTA UPS DRIVER
+M:	Andrew Maney <andrewmaney05@gmail.com>
+L:	linux-input@vger.kernel.org
+S:	Maintained
+F:	drivers/hid/hid-lattepanda-iota-ups.c
+
 HID LOGITECH DRIVERS
 R:	Filipe Laíns <lains@riseup.net>
 L:	linux-input@vger.kernel.org
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index ff2f580b6..3c1efef3d 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -105,6 +105,16 @@ config HID_HAPTIC
 
 menu "Special HID drivers"
 
+config HID_LATTEPANDA_IOTA_UPS
+	tristate "LattePanda IOTA UPS"
+	depends on USB_HID && X86
+	help
+	Support for the LattePanda IOTA UPS (DFRobot, VID 0x2341 PID 0x8036).
+	Exposes the battery status and capacity via the power_supply interface.
+
+	To compile as a module, choose M here: the module will be
+	called hid-lattepanda-iota-ups.
+
 config HID_A4TECH
 	tristate "A4TECH mice"
 	help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 0597fd6a4..7ba44223c 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -24,6 +24,7 @@ hid-logitech-$(CONFIG_LOGIWHEELS_FF)	+= hid-lg4ff.o
 hid-wiimote-y		:= hid-wiimote-core.o hid-wiimote-modules.o
 hid-wiimote-$(CONFIG_DEBUG_FS)	+= hid-wiimote-debug.o
 
+obj-$(CONFIG_HID_LATTEPANDA_IOTA_UPS)	+= hid-lattepanda-iota-ups.o
 obj-$(CONFIG_HID_A4TECH)	+= hid-a4tech.o
 obj-$(CONFIG_HID_ACCUTOUCH)	+= hid-accutouch.o
 obj-$(CONFIG_HID_ALPS)		+= hid-alps.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 0cf637423..3b69b072c 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -25,6 +25,9 @@
 #define USB_VENDOR_ID_8BITDO		0x2dc8
 #define USB_DEVICE_ID_8BITDO_PRO_3	0x6009
 
+#define USB_VENDOR_ID_LATTEPANDA_IOTA	0x2341
+#define USB_DEVICE_ID_LATTEPANDA_IOTA_UPS	0x8036
+
 #define USB_VENDOR_ID_A4TECH		0x09da
 #define USB_DEVICE_ID_A4TECH_WCP32PU	0x0006
 #define USB_DEVICE_ID_A4TECH_X5_005D	0x000a
diff --git a/drivers/hid/hid-lattepanda-iota-ups.c b/drivers/hid/hid-lattepanda-iota-ups.c
new file mode 100644
index 000000000..cb0c2a6f3
--- /dev/null
+++ b/drivers/hid/hid-lattepanda-iota-ups.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/completion.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include "hid-ids.h"
+
+#define REPORT_ID_STATUS	0x07
+#define REPORT_ID_CAPACITY	0x0C
+
+#define STATUS_PLUGGED_IN	BIT(0)
+#define STATUS_DISCHARGING	BIT(1)
+#define STATUS_CHARGING		BIT(2)
+
+MODULE_AUTHOR("Andrew Maney");
+MODULE_DESCRIPTION("LattePanda IOTA UPS power supply driver");
+MODULE_LICENSE("GPL");
+
+struct iota_ups {
+	struct hid_device		*hiddev;
+	struct power_supply		*psu;
+	struct power_supply_desc	 psu_desc;
+	spinlock_t			 lock; /* Protects all cached HID report values */
+
+	/* Cached values updated from HID reports */
+	char	serial[64];
+	bool	plugged_in;
+	int	psu_status;
+	int	capacity;
+	int	charge_limit;
+
+	/*
+	 * Wait for both status and capacity reports before registering
+	 * with the power_supply core, so initial values are correct.
+	 */
+	struct completion	got_initial_data;
+	bool			data_ready;
+	bool			got_status;
+	bool			got_capacity;
+};
+
+static enum power_supply_property iota_ups_properties[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_SCOPE,
+	POWER_SUPPLY_PROP_TECHNOLOGY,
+	POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_MANUFACTURER,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_SERIAL_NUMBER,
+};
+
+static const struct hid_device_id iota_ups_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LATTEPANDA_IOTA,
+			USB_DEVICE_ID_LATTEPANDA_IOTA_UPS) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, iota_ups_devices);
+
+static int iota_ups_get_property(struct power_supply *supply,
+				 enum power_supply_property psp,
+				 union power_supply_propval *val)
+{
+	struct iota_ups *ups = power_supply_get_drvdata(supply);
+	unsigned long flags;
+
+	spin_lock_irqsave(&ups->lock, flags);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		val->intval = ups->psu_status;
+		break;
+	/* Remaining capacity as a percentage, 0-100 */
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = ups->capacity;
+		break;
+	/* Always present if the driver is loaded */
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = 1;
+		break;
+	/* Whether mains power is connected */
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = ups->plugged_in ? 1 : 0;
+		break;
+	/* System-level UPS, not a laptop battery */
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+		break;
+	/* V1.0 only accepts 18650 Li-ion cells */
+	case POWER_SUPPLY_PROP_TECHNOLOGY:
+		val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+		break;
+	/* 80% or 100%, configured via DIP switch on the board */
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+		val->intval = ups->charge_limit;
+		break;
+	/* V1.0 does not report capacity level via HID */
+	case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+		val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+		break;
+	/* V1.0 does not report time remaining */
+	case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+	case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+		val->intval = 0;
+		break;
+	/* V1.0 does not report health; assume good */
+	case POWER_SUPPLY_PROP_HEALTH:
+		val->intval = POWER_SUPPLY_HEALTH_GOOD;
+		break;
+	/* 3.7V in microvolts, typical Li-ion resting voltage */
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		val->intval = 3700000;
+		break;
+	case POWER_SUPPLY_PROP_MANUFACTURER:
+		val->strval = "DFRobot";
+		break;
+	case POWER_SUPPLY_PROP_MODEL_NAME:
+		val->strval = "LattePanda IOTA UPS";
+		break;
+	/* Retrieved from the USB descriptor */
+	case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+		val->strval = ups->serial;
+		break;
+	default:
+		spin_unlock_irqrestore(&ups->lock, flags);
+		return -EINVAL;
+	}
+
+	spin_unlock_irqrestore(&ups->lock, flags);
+	return 0;
+}
+
+static int iota_ups_set_property(struct power_supply *supply,
+				 enum power_supply_property psp,
+				 const union power_supply_propval *val)
+{
+	struct iota_ups *ups = power_supply_get_drvdata(supply);
+
+	if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) {
+		/*
+		 * V1.0 supports 80% and 100% charge limits only, set via
+		 * DIP switch on the board. This property allows userspace
+		 * to inform the driver which limit is configured.
+		 */
+		if (val->intval != 80 && val->intval != 100)
+			return -EINVAL;
+		ups->charge_limit = val->intval;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int iota_ups_property_is_writable(struct power_supply *supply,
+					 enum power_supply_property psp)
+{
+	return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD;
+}
+
+static int iota_ups_raw_event(struct hid_device *hdev,
+			      struct hid_report *report,
+			      u8 *data, int size)
+{
+	struct iota_ups *ups = hid_get_drvdata(hdev);
+	unsigned long flags;
+	bool changed = false;
+
+	/* All UPS reports are at least 2 bytes */
+	if (size < 2)
+		return 0;
+
+	spin_lock_irqsave(&ups->lock, flags);
+
+	switch (data[0]) {
+	case REPORT_ID_STATUS: {
+		u8 status = data[1];
+		int new_status;
+		bool plugged_in = !!(status & STATUS_PLUGGED_IN);
+
+		if (status & STATUS_CHARGING) {
+			if (ups->capacity >= ups->charge_limit)
+				new_status = POWER_SUPPLY_STATUS_FULL;
+			else
+				new_status = POWER_SUPPLY_STATUS_CHARGING;
+		} else if (status & STATUS_DISCHARGING) {
+			new_status = POWER_SUPPLY_STATUS_DISCHARGING;
+		} else if (plugged_in) {
+			new_status = POWER_SUPPLY_STATUS_FULL;
+		} else {
+			new_status = POWER_SUPPLY_STATUS_UNKNOWN;
+		}
+
+		if (new_status != ups->psu_status ||
+		    plugged_in != ups->plugged_in) {
+			ups->plugged_in = plugged_in;
+			ups->psu_status = new_status;
+			changed = true;
+		}
+
+		ups->got_status = true;
+		break;
+	}
+	case REPORT_ID_CAPACITY: {
+		int new_cap = clamp((int)data[1], 0, 100);
+
+		if (new_cap != ups->capacity) {
+			ups->capacity = new_cap;
+			changed = true;
+		}
+
+		ups->got_capacity = true;
+		break;
+	}
+	}
+
+	/*
+	 * Signal ready only once both status and capacity have been
+	 * received, so the power_supply is registered with valid data.
+	 */
+	if (!ups->data_ready && ups->got_status && ups->got_capacity) {
+		ups->data_ready = true;
+		complete(&ups->got_initial_data);
+	}
+
+	spin_unlock_irqrestore(&ups->lock, flags);
+
+	/*
+	 * Notify the power_supply core outside the spinlock. This
+	 * triggers UPower's PropertiesChanged signal with the new values.
+	 */
+	if (changed && ups->psu)
+		power_supply_changed(ups->psu);
+
+	return 0;
+}
+
+static int iota_ups_probe(struct hid_device *hdev,
+			  const struct hid_device_id *id)
+{
+	struct power_supply_config psu_config = {};
+	struct usb_device *udev;
+	struct iota_ups *ups;
+	int ret;
+
+	ups = devm_kzalloc(&hdev->dev, sizeof(*ups), GFP_KERNEL);
+	if (!ups)
+		return -ENOMEM;
+
+	ups->hiddev		= hdev;
+	ups->psu_status		= POWER_SUPPLY_STATUS_UNKNOWN;
+	ups->capacity		= 50;
+	/*
+	 * Default to 100% — the DIP switch may be set to 80% but there
+	 * is no way to detect this automatically from HID reports.
+	 * Userspace can update this via charge_control_end_threshold.
+	 */
+	ups->charge_limit	= 100;
+	ups->data_ready		= false;
+	ups->got_status		= false;
+	ups->got_capacity	= false;
+
+	init_completion(&ups->got_initial_data);
+	spin_lock_init(&ups->lock);
+	hid_set_drvdata(hdev, ups);
+
+	/* Retrieve serial number from the USB descriptor */
+	udev = to_usb_device(hdev->dev.parent->parent);
+	if (udev->serial)
+		strscpy(ups->serial, udev->serial, sizeof(ups->serial));
+	else
+		strscpy(ups->serial, "Unknown", sizeof(ups->serial));
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "HID parse failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+	if (ret) {
+		hid_err(hdev, "HID hw start failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = hid_hw_open(hdev);
+	if (ret) {
+		hid_err(hdev, "HID hw open failed: %d\n", ret);
+		goto err_stop;
+	}
+
+	/*
+	 * Wait for both status and capacity reports before registering.
+	 * The device sends reports every ~1 second; 3 seconds is safe.
+	 */
+	wait_for_completion_timeout(&ups->got_initial_data,
+				    msecs_to_jiffies(3000));
+
+	ups->psu_desc.name			= "lattepanda-iota-ups";
+	ups->psu_desc.type			= POWER_SUPPLY_TYPE_BATTERY;
+	ups->psu_desc.properties		= iota_ups_properties;
+	ups->psu_desc.num_properties		= ARRAY_SIZE(iota_ups_properties);
+	ups->psu_desc.get_property		= iota_ups_get_property;
+	ups->psu_desc.set_property		= iota_ups_set_property;
+	ups->psu_desc.property_is_writeable	= iota_ups_property_is_writable;
+	psu_config.drv_data = ups;
+
+	ups->psu = devm_power_supply_register(&hdev->dev,
+					      &ups->psu_desc,
+					      &psu_config);
+	if (IS_ERR(ups->psu)) {
+		ret = PTR_ERR(ups->psu);
+		hid_err(hdev, "power_supply register failed: %d\n", ret);
+		goto err_close;
+	}
+
+	/*
+	 * Force an immediate notification so UPower reads the correct
+	 * initial state right after registration.
+	 */
+	power_supply_changed(ups->psu);
+
+	hid_info(hdev, "LattePanda IOTA UPS registered as power supply\n");
+	return 0;
+
+err_close:
+	hid_hw_close(hdev);
+err_stop:
+	hid_hw_stop(hdev);
+	return ret;
+}
+
+static void iota_ups_remove(struct hid_device *hdev)
+{
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+}
+
+static struct hid_driver iota_ups_driver = {
+	.name		= "lattepanda-iota-ups",
+	.id_table	= iota_ups_devices,
+	.probe		= iota_ups_probe,
+	.remove		= iota_ups_remove,
+	.raw_event	= iota_ups_raw_event,
+};
+module_hid_driver(iota_ups_driver);
-- 
2.54.0


^ permalink raw reply related

* [PATCH 0/7] ASUS Zenbook Duo keyboard support
From: Paolo Pisati @ 2026-05-13 16:32 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input

Add support for the ASUS Zenbook Duo line of usb/BT wireless convertible keyboards.

This patchset is a collective effort, gathered here:

https://github.com/NeroReflex/asusctl/issues/25

Tested by many on all Zenbook Duo SKUs: UX8406MA, UX8406CA and UX8407AA.

Joshua Leivenzon (6):
  hid-asus: Fix up Zenbook Duo report descriptors
  hid-asus: Add missing Zenbook Duo hotkeys
  hid-asus: Add report descriptor fixup offsets for UX8406MA USB
    keyboard
  hid-asus: Remove more bogus zero bytes from some report descriptors
  hid-asus: Fix input mapping on dedicated vendor HID interfaces
  hid-asus: Allow adding custom hotkey handler logic

Luke Jones (1):
  hid-asus: add prod-id, quirk for Zenbook Duo keyboard

 drivers/hid/hid-asus.c | 126 +++++++++++++++++++++++++++++++++++++----
 drivers/hid/hid-ids.h  |   6 ++
 2 files changed, 121 insertions(+), 11 deletions(-)

-- 
2.53.0


^ permalink raw reply

* [PATCH 1/7] hid-asus: Fix up Zenbook Duo report descriptors
From: Paolo Pisati @ 2026-05-13 16:32 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260513163248.16483-1-p.pisati@gmail.com>

From: Joshua Leivenzon <hacker1024@users.sourceforge.net>

This is similar to the T100CHI/T90CHI keyboard dock fix.
Without it, all reports log:

Unmapped Asus vendor usagepage code 0x76

Signed-off-by: Joshua Leivenzon <hacker1024@users.sourceforge.net>
---
 drivers/hid/hid-asus.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 3f5e96900b67a..ce246efba74d3 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -99,6 +99,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define QUIRK_ROG_CLAYMORE_II_KEYBOARD	BIT(12)
 #define QUIRK_ROG_ALLY_XPAD		BIT(13)
 #define QUIRK_HID_FN_LOCK		BIT(14)
+#define QUIRK_ZENBOOK_DUO_KEYBOARD	BIT(15)
 
 #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
 						 QUIRK_NO_INIT_REPORTS | \
@@ -1384,17 +1385,20 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 		hid_info(hdev, "Fixing up Asus T100 keyb report descriptor\n");
 		rdesc[74] &= ~HID_MAIN_ITEM_CONSTANT;
 	}
-	/* For the T100CHI/T90CHI keyboard dock */
-	if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
+	/* For the T100CHI/T90CHI keyboard dock and Zenbook Duo 2024+ keyboards */
+	if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI | QUIRK_ZENBOOK_DUO_KEYBOARD)) {
 		int rsize_orig;
 		int offs;
 
 		if (drvdata->quirks & QUIRK_T100CHI) {
 			rsize_orig = 403;
 			offs = 388;
-		} else {
+		} else if (drvdata->quirks & QUIRK_T90CHI) {
 			rsize_orig = 306;
 			offs = 291;
+		} else if (drvdata->quirks & QUIRK_ZENBOOK_DUO_KEYBOARD) {
+			rsize_orig = 257;
+			offs = 176;
 		}
 
 		/*
@@ -1414,7 +1418,8 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 
 			hid_info(hdev, "Fixing up %s keyb report descriptor\n",
 				drvdata->quirks & QUIRK_T100CHI ?
-				"T100CHI" : "T90CHI");
+				"T100CHI" : drvdata->quirks & QUIRK_T90CHI ?
+				"T90CHI" : "ZENBOOK DUO");
 
 			memcpy(new_rdesc, rdesc, rsize_orig);
 			*rsize = rsize_orig + 1;
-- 
2.53.0


^ permalink raw reply related

* [PATCH 2/7] hid-asus: Add missing Zenbook Duo hotkeys
From: Paolo Pisati @ 2026-05-13 16:32 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260513163248.16483-1-p.pisati@gmail.com>

From: Joshua Leivenzon <hacker1024@users.sourceforge.net>

Signed-off-by: Joshua Leivenzon <hacker1024@users.sourceforge.net>
---
 drivers/hid/hid-asus.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index ce246efba74d3..cba638b19465f 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -1046,6 +1046,7 @@ static int asus_input_mapping(struct hid_device *hdev,
 		case 0x6c: asus_map_key_clear(KEY_SLEEP);		break;
 		case 0x7c: asus_map_key_clear(KEY_MICMUTE);		break;
 		case 0x82: asus_map_key_clear(KEY_CAMERA);		break;
+		case 0x86: asus_map_key_clear(KEY_CONTROLPANEL);	break; /* MyASUS */
 		case 0x88: asus_map_key_clear(KEY_RFKILL);			break;
 		case 0xb5: asus_map_key_clear(KEY_CALC);			break;
 		case 0xc4: asus_map_key_clear(KEY_KBDILLUMUP);		break;
@@ -1066,6 +1067,18 @@ static int asus_input_mapping(struct hid_device *hdev,
 		case 0xb3: asus_map_key_clear(KEY_PROG3);	break; /* Fn+Left next aura */
 		case 0x6a: asus_map_key_clear(KEY_F13);		break; /* Screenpad toggle */
 		case 0x4b: asus_map_key_clear(KEY_F14);		break; /* Arrows/Pg-Up/Dn toggle */
+		case 0x9c: asus_map_key_clear(KEY_F19);		break; /* Screen swap */
+
+		/* Mutually exclusive section: Special keys from different devices can
+		 * bind to the same keycodes without concern, as they will never
+		 * conflict.
+		 *
+		 * Note that devices with removable keyboards that connect over a
+		 * standard interface (e.g. Zenbook Duo UX8406 with USB/Bluetooth) do
+		 * not belong in this section, as such keyboards can be connected to
+		 * another ASUS device that also uses this driver for its own keyboard.
+		 */
+		/* ROG Ally */
 		case 0xa5: asus_map_key_clear(KEY_F15);		break; /* ROG Ally left back */
 		case 0xa6: asus_map_key_clear(KEY_F16);		break; /* ROG Ally QAM button */
 		case 0xa7: asus_map_key_clear(KEY_F17);		break; /* ROG Ally ROG long-press */
-- 
2.53.0


^ permalink raw reply related

* [PATCH 3/7] hid-asus: Add report descriptor fixup offsets for UX8406MA USB keyboard
From: Paolo Pisati @ 2026-05-13 16:32 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260513163248.16483-1-p.pisati@gmail.com>

From: Joshua Leivenzon <hacker1024@users.sourceforge.net>

Signed-off-by: Joshua Leivenzon <hacker1024@users.sourceforge.net>
---
 drivers/hid/hid-asus.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index cba638b19465f..2a88ce695489d 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -1410,8 +1410,13 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 			rsize_orig = 306;
 			offs = 291;
 		} else if (drvdata->quirks & QUIRK_ZENBOOK_DUO_KEYBOARD) {
-			rsize_orig = 257;
-			offs = 176;
+			if (hid_is_usb(hdev)) {
+				rsize_orig = 90;
+				offs = 66;
+			} else /* Bluetooth */ {
+				rsize_orig = 257;
+				offs = 176;
+			}
 		}
 
 		/*
-- 
2.53.0


^ permalink raw reply related

* [PATCH 4/7] hid-asus: Remove more bogus zero bytes from some report descriptors
From: Paolo Pisati @ 2026-05-13 16:32 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260513163248.16483-1-p.pisati@gmail.com>

From: Joshua Leivenzon <hacker1024@users.sourceforge.net>

Signed-off-by: Joshua Leivenzon <hacker1024@users.sourceforge.net>
---
 drivers/hid/hid-asus.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 2a88ce695489d..cf9184ed2d3ec 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -1422,14 +1422,19 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 		/*
 		 * Change Usage (76h) to Usage Minimum (00h), Usage Maximum
 		 * (FFh) and clear the flags in the Input() byte.
-		 * Note the descriptor has a bogus 0 byte at the end so we
-		 * only need 1 extra byte.
 		 */
 		if (*rsize == rsize_orig &&
 			rdesc[offs] == 0x09 && rdesc[offs + 1] == 0x76) {
 			__u8 *new_rdesc;
+            unsigned int new_rsize = rsize_orig;
 
-			new_rdesc = devm_kzalloc(&hdev->dev, rsize_orig + 1,
+			/* Clear bogus trailing zero bytes. */
+			while (rdesc[new_rsize + 1] == 0)
+				--new_rsize;
+            /* Make room for the added bytes. */
+			new_rsize += 2;
+
+            new_rdesc = devm_kzalloc(&hdev->dev, new_rsize,
 						 GFP_KERNEL);
 			if (!new_rdesc)
 				return rdesc;
@@ -1439,8 +1444,8 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 				"T100CHI" : drvdata->quirks & QUIRK_T90CHI ?
 				"T90CHI" : "ZENBOOK DUO");
 
-			memcpy(new_rdesc, rdesc, rsize_orig);
-			*rsize = rsize_orig + 1;
+			memcpy(new_rdesc, rdesc, new_rsize);
+			*rsize = new_rsize;
 			rdesc = new_rdesc;
 
 			memmove(rdesc + offs + 4, rdesc + offs + 2, 12);
-- 
2.53.0


^ permalink raw reply related

* [PATCH 5/7] hid-asus: Fix input mapping on dedicated vendor HID interfaces
From: Paolo Pisati @ 2026-05-13 16:32 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260513163248.16483-1-p.pisati@gmail.com>

From: Joshua Leivenzon <hacker1024@users.sourceforge.net>

Signed-off-by: Joshua Leivenzon <hacker1024@users.sourceforge.net>
---
 drivers/hid/hid-asus.c | 44 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index cf9184ed2d3ec..fe52fd63d61c6 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -1376,6 +1376,30 @@ static void asus_remove(struct hid_device *hdev)
 	hid_hw_stop(hdev);
 }
 
+/*
+ * Some USB keyboards, like the Zenbook Duo UX8406MA keyboard, have a dedicated
+ * USB interface for vendor-specific reports, separate to the generic HID
+ * keyboard or consumer control interfaces.
+ *
+ * The kernel does not register these vendor-specific interfaces as keyboards,
+ * or perform input mapping on them at all.
+ *
+ * To work around this, a fake keyboard input can be added to the
+ * vendor-specific interface's report descriptor. The kernel then combines it
+ * with the vendor-specific collections, and allows the interface to be used as
+ * a regular keyboard with our custom mappings.
+ */
+static const __u8 asus_fake_keyboard_rdesc[] = {
+	0x05, 0x01, /* Usage Page (Generic Desktop) */
+	0x09, 0x06, /* Usage (Keyboard) */
+	0xa1, 0x01, /* Collection (Application) */
+	0x85, 0x01, /*   Report ID (1) */
+	0x75, 0x08, /*   Report Size (8) */
+	0x95, 0x01, /*   Report Count (1) */
+	0x81, 0x00, /*   Input (Data,Arr,Abs) */
+	0xc0,       /* End Collection */
+};
+
 static const __u8 asus_g752_fixed_rdesc[] = {
         0x19, 0x00,			/*   Usage Minimum (0x00)       */
         0x2A, 0xFF, 0x00,		/*   Usage Maximum (0xFF)       */
@@ -1457,6 +1481,26 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 		}
 	}
 
+	if ((drvdata->quirks & QUIRK_ZENBOOK_DUO_KEYBOARD) &&
+	    hid_is_usb(hdev) &&
+	    to_usb_interface(hdev->dev.parent)->altsetting->desc.bInterfaceNumber == 4) {
+
+		__u8 *new_rdesc;
+		size_t new_size = *rsize + sizeof(asus_fake_keyboard_rdesc);
+
+		new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);
+		if (new_rdesc == NULL)
+			return rdesc;
+
+		hid_info(hdev, "Injecting virtual Zenbook Duo keyboard usage page\n");
+
+		memcpy(new_rdesc, asus_fake_keyboard_rdesc, sizeof(asus_fake_keyboard_rdesc));
+		memcpy(new_rdesc + sizeof(asus_fake_keyboard_rdesc), rdesc, *rsize);
+
+		*rsize = new_size;
+		rdesc = new_rdesc;
+	}
+
 	if (drvdata->quirks & QUIRK_G752_KEYBOARD &&
 		 *rsize == 75 && rdesc[61] == 0x15 && rdesc[62] == 0x00) {
 		/* report is missing usage minimum and maximum */
-- 
2.53.0


^ permalink raw reply related

* [PATCH 6/7] hid-asus: Allow adding custom hotkey handler logic
From: Paolo Pisati @ 2026-05-13 16:32 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260513163248.16483-1-p.pisati@gmail.com>

From: Joshua Leivenzon <hacker1024@users.sourceforge.net>

Respond to platform profile cycle key:
Fn + F on UX8406MA.

May vary across devices.

Signed-off-by: Joshua Leivenzon <hacker1024@users.sourceforge.net>
---
 drivers/hid/hid-asus.c | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index fe52fd63d61c6..37e3aec612b6d 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -29,6 +29,7 @@
 #include <linux/input/mt.h>
 #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
 #include <linux/power_supply.h>
+#include <linux/platform_profile.h>
 #include <linux/leds.h>
 
 #include "hid-ids.h"
@@ -362,8 +363,21 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
 	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR &&
 	    (usage->hid & HID_USAGE) != 0x00 &&
 	    (usage->hid & HID_USAGE) != 0xff && !usage->type) {
-		hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
-			 usage->hid & HID_USAGE);
+
+		/*
+		 * Some reports do not map directly to standard keys, and need special
+		 * handling.
+		 */
+		switch (usage->hid & HID_USAGE) {
+			case 0x9d:
+				if (!value)
+					break;
+				return platform_profile_cycle();
+				break;
+			default:
+				hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
+					 usage->hid & HID_USAGE);
+		}
 	}
 
 	if (usage->type == EV_KEY && value) {
-- 
2.53.0


^ permalink raw reply related

* [PATCH 7/7] hid-asus: add prod-id, quirk for Zenbook Duo keyboard
From: Paolo Pisati @ 2026-05-13 16:32 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260513163248.16483-1-p.pisati@gmail.com>

From: Luke Jones <luke@ljones.dev>

The 2024/2025/2026 ASUS Zenbook Duo has a similar keyboard setup as many of the ROG
range of laptops, with the same init sequence and control for backlight.
Enable the keyboard control and backlight by:
- adding the product ID for 2024/2025/2026 Zenbook Duo keyboard MCU
- adding the Bluetooth Zenbook Duo keyboard IDs
- use Fn-lock on Zenbook Duo keyboards

Intended for the UX8406-style product line with the fully removable keyboard.

Signed-off-by: Luke Jones <luke@ljones.dev>
Signed-off-by: Joshua Leivenzon <hacker1024@users.sourceforge.net>
Signed-off-by: Joshua Leivenzon <hacker1024@users.sourceforge.net>
Signed-off-by: Joshua Leivenzon <hacker1024@users.sourceforge.net>
Signed-off-by: Paolo Pisati <p.pisati@gmail.com>
Signed-off-by: Ivan Levchenko <me@livan.pro>
---
 drivers/hid/hid-asus.c | 18 ++++++++++++++++++
 drivers/hid/hid-ids.h  |  6 ++++++
 2 files changed, 24 insertions(+)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 37e3aec612b6d..ba093f11f802f 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -1583,6 +1583,24 @@ static const struct hid_device_id asus_devices[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
 	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+	    USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406MA_KEYBOARD),
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_HID_FN_LOCK | QUIRK_ZENBOOK_DUO_KEYBOARD },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ASUSTEK,
+	    BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406MA_KEYBOARD),
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_HID_FN_LOCK | QUIRK_ZENBOOK_DUO_KEYBOARD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+	    USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406CA_KEYBOARD),
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_HID_FN_LOCK | QUIRK_ZENBOOK_DUO_KEYBOARD },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ASUSTEK,
+	    BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406CA_KEYBOARD),
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_HID_FN_LOCK | QUIRK_ZENBOOK_DUO_KEYBOARD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+	    USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8407AA_KEYBOARD),
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_HID_FN_LOCK | QUIRK_ZENBOOK_DUO_KEYBOARD },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ASUSTEK,
+	    BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8407AA_KEYBOARD),
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_HID_FN_LOCK | QUIRK_ZENBOOK_DUO_KEYBOARD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
 	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD},
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 0cf63742315bf..97dc451adac5a 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -227,6 +227,12 @@
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD	0x1866
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2	0x19b6
 #define USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO		0x1a30
+#define USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406MA_KEYBOARD	0x1b2c
+#define BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406MA_KEYBOARD	0x1b2d
+#define USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406CA_KEYBOARD	0x1bf2
+#define BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406CA_KEYBOARD	0x1bf3
+#define USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8407AA_KEYBOARD	0x1cd7
+#define BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8407AA_KEYBOARD	0x1cd8
 #define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR		0x18c6
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY		0x1abe
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X		0x1b4c
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH v4 5/6 RESEND] mfd: motorola-cpcap: diverge configuration per-board
From: Svyatoslav Ryhel @ 2026-05-13 16:47 UTC (permalink / raw)
  To: Lee Jones
  Cc: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Pavel Machek, David Lechner, Tony Lindgren, linux-input,
	devicetree, linux-kernel, linux-leds
In-Reply-To: <20260513140550.GD305027@google.com>

ср, 13 трав. 2026 р. о 17:05 Lee Jones <lee@kernel.org> пише:
>
> On Thu, 07 May 2026, Svyatoslav Ryhel wrote:
>
> > чт, 7 трав. 2026 р. о 17:05 Lee Jones <lee@kernel.org> пише:
> > >
> > > On Tue, 28 Apr 2026, Svyatoslav Ryhel wrote:
> > >
> > > > MFD have rigid subdevice structure which does not allow flexible dynamic
> > > > subdevice linking. Address this by diverging CPCAP subdevice composition
> > > > to take into account board specific configuration.
> > > >
> > > > Create a common default subdevice composition, rename existing subdevice
> > > > composition into cpcap_mapphone_mfd_devices since it targets mainly
> > > > Mapphone board.
> > > >
> > > > Removed st,6556002 as it is no longer applicable to all cases and
> > > > duplicates motorola,cpcap, which is used as the default composition.
> > > >
> > > > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > > ---
> > >
> > > Changelog?
> > >
> >
> > Changelog is in the cover.
> >
> > > >  drivers/mfd/motorola-cpcap.c | 101 ++++++++++++++++++++++++++++-------
> > > >  1 file changed, 83 insertions(+), 18 deletions(-)
> > > >
> > > > diff --git a/drivers/mfd/motorola-cpcap.c b/drivers/mfd/motorola-cpcap.c
> > > > index d8243b956f87..516d1e33affa 100644
> > > > --- a/drivers/mfd/motorola-cpcap.c
> > > > +++ b/drivers/mfd/motorola-cpcap.c
> > > > @@ -12,6 +12,7 @@
> > > >  #include <linux/kernel.h>
> > > >  #include <linux/module.h>
> > > >  #include <linux/mod_devicetable.h>
> > > > +#include <linux/property.h>
> > > >  #include <linux/regmap.h>
> > > >  #include <linux/sysfs.h>
> > > >
> > > > @@ -24,10 +25,16 @@
> > > >  #define CPCAP_REGISTER_SIZE  4
> > > >  #define CPCAP_REGISTER_BITS  16
> > > >
> > > > +struct cpcap_chip_data {
> > > > +     const struct mfd_cell *mfd_devices;
> > > > +     unsigned int num_devices;
> > > > +};
> > >
> > > This is a red flag.
> > >
> > > >  struct cpcap_ddata {
> > > >       struct spi_device *spi;
> > > >       struct regmap_irq *irqs;
> > > >       struct regmap_irq_chip_data *irqdata[CPCAP_NR_IRQ_CHIPS];
> > > > +     const struct cpcap_chip_data *cdata;
> > > >       const struct regmap_config *regmap_conf;
> > > >       struct regmap *regmap;
> > > >  };
> > > > @@ -195,20 +202,6 @@ static int cpcap_init_irq(struct cpcap_ddata *cpcap)
> > > >       return 0;
> > > >  }
> > > >
> > > > -static const struct of_device_id cpcap_of_match[] = {
> > > > -     { .compatible = "motorola,cpcap", },
> > > > -     { .compatible = "st,6556002", },
> > > > -     {},
> > > > -};
> > > > -MODULE_DEVICE_TABLE(of, cpcap_of_match);
> > > > -
> > > > -static const struct spi_device_id cpcap_spi_ids[] = {
> > > > -     { .name = "cpcap", },
> > > > -     { .name = "6556002", },
> > > > -     {},
> > > > -};
> > > > -MODULE_DEVICE_TABLE(spi, cpcap_spi_ids);
> > > > -
> > > >  static const struct regmap_config cpcap_regmap_config = {
> > > >       .reg_bits = 16,
> > > >       .reg_stride = 4,
> > > > @@ -241,7 +234,56 @@ static int cpcap_resume(struct device *dev)
> > > >
> > > >  static DEFINE_SIMPLE_DEV_PM_OPS(cpcap_pm, cpcap_suspend, cpcap_resume);
> > > >
> > > > -static const struct mfd_cell cpcap_mfd_devices[] = {
> > > > +static const struct mfd_cell cpcap_default_mfd_devices[] = {
> > > > +     {
> > > > +             .name          = "cpcap_adc",
> > > > +             .of_compatible = "motorola,cpcap-adc",
> > > > +     }, {
> > > > +             .name          = "cpcap_battery",
> > > > +             .of_compatible = "motorola,cpcap-battery",
> > > > +     }, {
> > > > +             .name          = "cpcap-regulator",
> > > > +             .of_compatible = "motorola,cpcap-regulator",
> > > > +     }, {
> > > > +             .name          = "cpcap-rtc",
> > > > +             .of_compatible = "motorola,cpcap-rtc",
> > > > +     }, {
> > > > +             .name          = "cpcap-pwrbutton",
> > > > +             .of_compatible = "motorola,cpcap-pwrbutton",
> > > > +     }, {
> > > > +             .name          = "cpcap-usb-phy",
> > > > +             .of_compatible = "motorola,cpcap-usb-phy",
> > > > +     }, {
> > > > +             .name          = "cpcap-led",
> > > > +             .id            = 0,
> > > > +             .of_compatible = "motorola,cpcap-led-red",
> > > > +     }, {
> > > > +             .name          = "cpcap-led",
> > > > +             .id            = 1,
> > > > +             .of_compatible = "motorola,cpcap-led-green",
> > > > +     }, {
> > > > +             .name          = "cpcap-led",
> > > > +             .id            = 2,
> > > > +             .of_compatible = "motorola,cpcap-led-blue",
> > > > +     }, {
> > > > +             .name          = "cpcap-led",
> > > > +             .id            = 3,
> > > > +             .of_compatible = "motorola,cpcap-led-adl",
> > > > +     }, {
> > > > +             .name          = "cpcap-led",
> > > > +             .id            = 4,
> > > > +             .of_compatible = "motorola,cpcap-led-cp",
> > > > +     }, {
> > > > +             .name          = "cpcap-codec",
> > > > +     },
> > > > +};
> > > > +
> > > > +static const struct cpcap_chip_data cpcap_default_data = {
> > > > +     .mfd_devices = cpcap_default_mfd_devices,
> > > > +     .num_devices = ARRAY_SIZE(cpcap_default_mfd_devices),
> > > > +};
> > > > +
> > > > +static const struct mfd_cell cpcap_mapphone_mfd_devices[] = {
> > > >       {
> > > >               .name          = "cpcap_adc",
> > > >               .of_compatible = "motorola,mapphone-cpcap-adc",
> > > > @@ -285,7 +327,12 @@ static const struct mfd_cell cpcap_mfd_devices[] = {
> > > >               .of_compatible = "motorola,cpcap-led-cp",
> > > >       }, {
> > > >               .name          = "cpcap-codec",
> > > > -     }
> > > > +     },
> > > > +};
> > > > +
> > > > +static const struct cpcap_chip_data cpcap_mapphone_data = {
> > > > +     .mfd_devices = cpcap_mapphone_mfd_devices,
> > > > +     .num_devices = ARRAY_SIZE(cpcap_mapphone_mfd_devices),
> > > >  };
> > > >
> > > >  static int cpcap_probe(struct spi_device *spi)
> > > > @@ -297,9 +344,17 @@ static int cpcap_probe(struct spi_device *spi)
> > > >       if (!cpcap)
> > > >               return -ENOMEM;
> > > >
> > > > +     cpcap->cdata = device_get_match_data(&spi->dev);
> > > > +     if (!cpcap->cdata)
> > > > +             return -ENODEV;
> > > > +
> > > >       cpcap->spi = spi;
> > > >       spi_set_drvdata(spi, cpcap);
> > > >
> > > > @@ -331,16 +382,24 @@ static int cpcap_probe(struct spi_device *spi)
> > > >       spi->dev.coherent_dma_mask = 0;
> > > >       spi->dev.dma_mask = &spi->dev.coherent_dma_mask;
> > > >
> > > > -     return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,
> > > > -                                 ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);
> > > > +     return devm_mfd_add_devices(&spi->dev, 0, cpcap->cdata->mfd_devices,
> > > > +                                 cpcap->cdata->num_devices, NULL, 0, NULL);
> > > >  }
> > > >
> > > > +static const struct of_device_id cpcap_of_match[] = {
> > > > +     { .compatible = "motorola,cpcap", .data = &cpcap_default_data },
> > > > +     { .compatible = "motorola,mapphone-cpcap", .data = &cpcap_mapphone_data },
> > >
> > > We don't allow data from one device registration API (MFD) to be passed
> > > through another (OF) because it tends to lead to all sorts of "creative
> > > solutions".  Pass a value instead and match on that in a switch()
> > > statement like all of the other MFD drivers do.
> > >
> >
> > You don't allow this. I have not seen this enforced anywhere in the
> > kernel except the mfd subsystem. Fine, does not matter, if this makes
> > you happy I will adjust.
>
> Where else would this rule be applicable?  I can't think of anywhere.
>

I have adjusted as you requested and already sent v5.

> --
> Lee Jones

^ permalink raw reply

* Re: [PATCH] input/evdev: move kill_fasync() outside buffer_lock to fix SOFTIRQ deadlock
From: Dmitry Torokhov @ 2026-05-13 20:01 UTC (permalink / raw)
  To: Rik van Riel; +Cc: linux-input, linux-kernel, kernel-team
In-Reply-To: <20260513115000.77ec5975@fangorn>

Hi Rik,

On Wed, May 13, 2026 at 11:50:00AM -0400, Rik van Riel wrote:
> buffer_lock is a SOFTIRQ-safe spinlock. kill_fasync() acquires fa_lock
> (SOFTIRQ-unsafe), creating a potential SOFTIRQ-safe->SOFTIRQ-unsafe lock
> ordering violation that lockdep flags as a deadlock.
> 
> Fix by moving the kill_fasync() call to evdev_pass_values() after
> buffer_lock is released, alongside the existing wake_up_interruptible_poll().
> 
> The wakeup condition check is the same in __pass_event() and
> evdev_pass_values()

Does this really fix anything? This code is running holding
input->event_lock with IRQs off...

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH v5 1/2] dt-bindings: input: Document Imagis ISA1200 haptic motor driver
From: sashiko-bot @ 2026-05-13 20:15 UTC (permalink / raw)
  To: Svyatoslav Ryhel
  Cc: robh, linux-input, conor+dt, dmitry.torokhov, krzk+dt, devicetree
In-Reply-To: <20260512102445.55372-2-clamor95@gmail.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Missing scalar type definitions for `imagis,period-ns` and `imagis,duty-cycle-ns` cause schema bounds validation to be silently bypassed.
--

commit c2a7ef2ca3d62317c69bc5b6a450107172eeeac7
Author: Svyatoslav Ryhel <clamor95@gmail.com>

dt-bindings: input: Document Imagis ISA1200 haptic motor driver

This commit documents the Imagis ISA1200 haptic motor driver. It introduces
a new YAML binding schema for the driver, specifying properties like
control-gpios, clocks, and internal PWM configurations.

> diff --git a/Documentation/devicetree/bindings/input/imagis,isa1200.yaml b/Documentation/devicetree/bindings/input/imagis,isa1200.yaml
> new file mode 100644
> index 0000000000000..bbe6f99d39c19
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/imagis,isa1200.yaml
[ ... ]
> @@ -0,0 +1,140 @@
[ ... ]
> +  imagis,mode:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: |
> +      Defines the motor type isa1200 drives
> +      0 - LRA (Linear Resonant Actuator)
> +      1 - ERM (Eccentric Rotating Mass)
> +    enum: [0, 1]
> +    default: 0
> +
> +  imagis,period-ns:
> +    description:
> +      Period of the internal PWM channel in nanoseconds.
> +    minimum: 10000
> +    maximum: 30000
> +
> +  imagis,duty-cycle-ns:
> +    description:
> +      Duty cycle of the external/internal PWM channel in nanoseconds,
> +      defaults to 50% of the channel's period

Will the minimum and maximum bounds on imagis,period-ns be silently ignored
during validation?

The core property-units.yaml schema automatically assigns an array type to
properties ending in -ns. Without an explicit reference to
/schemas/types.yaml#/definitions/uint32, these properties are evaluated as
arrays rather than scalar integers. 

In standard JSON Schema, numeric constraints like minimum and maximum are
ignored when applied to an array instance. Adding the uint32 type reference
for both imagis,period-ns and imagis,duty-cycle-ns would ensure the
constraints are properly enforced on a single scalar value.

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260512102445.55372-1-clamor95@gmail.com?part=1

^ permalink raw reply

* Re: [PATCH v5 2/2] Input: isa1200 - new driver for Imagis ISA1200
From: sashiko-bot @ 2026-05-13 20:44 UTC (permalink / raw)
  To: Svyatoslav Ryhel
  Cc: linux-input, robh, krzk+dt, dmitry.torokhov, devicetree, conor+dt
In-Reply-To: <20260512102445.55372-3-clamor95@gmail.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Flawed interaction between isa1200_stop, suspend, and isa->level leads to lost haptic state on resume and power leaks.
- [High] Missing workqueue cancellation and timer cleanup on driver unbind causes Use-After-Free.
- [High] Data races around hardware state transitions lead to resource leaks and I2C corruption.
- [Medium] Stack out-of-bounds read due to unvalidated GPIO descriptor count.
--

commit 87a2f648deb0c974007e802b082c1ec62d4fb097
Author: Linus Walleij <linusw@kernel.org>

Input: isa1200 - new driver for Imagis ISA1200

This commit introduces a new input driver for the Imagis ISA1200 haptic
feedback unit, adding support for controlling its motors over I2C and
PWM interfaces.

> diff --git a/drivers/input/misc/isa1200.c b/drivers/input/misc/isa1200.c
> new file mode 100644
> index 0000000000000..ff82252a08e1b
> --- /dev/null
> +++ b/drivers/input/misc/isa1200.c
[ ... ]
> +static void isa1200_start(struct isa1200 *isa)
> +{
> +	struct isa1200_config *config = &isa->config;
> +	struct device *dev = &isa->input->dev;
> +	struct pwm_state state;
> +	u8 hctrl0 = 0, hctrl1 = 0;
> +	DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX);
> +	int err;
> +
> +	if (!isa->active) {
> +		err = regulator_bulk_enable(ARRAY_SIZE(isa1200_supplies),
> +					    isa->supplies);

Are isa1200_start() and isa1200_stop() vulnerable to data races on the
isa->active state?

The active flag is read and modified here without holding any locks.
If the workqueue and PM resume callbacks execute concurrently, could both
threads observe isa->active as false? This might lead to double
initialization of the clocks and regulators, leaking their reference
counts when isa1200_stop() only disables them once.

[ ... ]
> +static void isa1200_stop(struct isa1200 *isa)
> +{
[ ... ]
> +	isa->active = false;
> +	isa->level = 0;
> +}

[ ... ]
> +static int isa1200_of_probe(struct i2c_client *client)
> +{
[ ... ]
> +	isa->enable_gpios = devm_gpiod_get_array_optional(dev, "control",
> +							  GPIOD_OUT_LOW);

Should this validate the number of returned GPIO descriptors against
ISA1200_EN_PINS_MAX?

In isa1200_start() and isa1200_stop(), a local bitmap is declared on the
stack with DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX). If a malformed
device tree provides more GPIOs than expected, could
gpiod_multi_set_value_cansleep() read past the stack bounds of the values
bitmap?

[ ... ]
> +static int isa1200_probe(struct i2c_client *client)
> +{
[ ... ]
> +	INIT_WORK(&isa->play_work, isa1200_play_work);

The isa structure is allocated via devm_kzalloc() and will be freed
automatically when the device is unbound. Because there is no remove
callback or devm action to cancel isa->play_work, could the workqueue
execute after unbind and cause a use-after-free if an effect is playing
during unbind?

Similarly, the ff_memless timer is tied to the input_dev, which can
outlive the I2C device if userspace holds an open file descriptor. Does
the timer also need to be cleaned up on driver unbind to prevent it from
accessing freed memory?

[ ... ]
> +static int isa1200_suspend(struct device *dev)
> +{
> +	struct isa1200 *isa = dev_get_drvdata(dev);
> +
> +	guard(mutex)(&isa->input->mutex);
> +
> +	if (input_device_enabled(isa->input)) {
> +		cancel_work_sync(&isa->play_work);
> +		if (isa->level)
> +			isa1200_stop(isa);
> +	}
> +
> +	return 0;
> +}

If the user has already requested the motor to stop (level is 0), this
skips calling isa1200_stop(). If the workqueue is cancelled before it
gets a chance to shut down the hardware, would this leave the device
active and leak power throughout suspend?

Additionally, looking at the resume function:

[ ... ]
> +static int isa1200_resume(struct device *dev)
> +{
> +	struct isa1200 *isa = dev_get_drvdata(dev);
> +
> +	guard(mutex)(&isa->input->mutex);
> +
> +	if (input_device_enabled(isa->input))
> +		if (isa->level)
> +			isa1200_start(isa);
> +
> +	return 0;
> +}

Since isa1200_stop() unconditionally sets isa->level to 0 during suspend,
will this check always evaluate to false, causing the driver to permanently
lose its haptic state after resuming?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260512102445.55372-1-clamor95@gmail.com?part=2

^ permalink raw reply

* Re: [PATCH] Input: elan_i2c - prevent division by zero on invalid device parameters
From: Dmitry Torokhov @ 2026-05-13 21:00 UTC (permalink / raw)
  To: Ranjan Kumar; +Cc: bleung, dusonlin, bentiss, linux-input, linux-kernel
In-Reply-To: <20260513073908.3326178-1-kumarranja@chromium.org>

Hi Ranjan,

On Wed, May 13, 2026 at 07:39:08AM +0000, Ranjan Kumar wrote:
> The Elan I2C touchpad driver queries the device for its physical
> dimensions and trace counts to calculate the device resolution and width.
> However, if the device firmware or device tree provides invalid zero
> values for x_traces, y_traces, x_mm, or y_mm, it results in a fatal
> division-by-zero exception leading to a kernel panic during device probe.
> 

Neither crashing nor aborting probe with -EINVAL is great as we will not
be able to flash another firmware.

How do we get into this state? Is this something actually observed or
just theoretical?

> Add sanity checks to ensure these physical parameters are non-zero
> before performing the division. If invalid values are detected, log an
> error and return -EINVAL to gracefully abort the initialization and
> maintain system stability.

That is hardly "stability" for a laptop when its pointer is not working.

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH] Input: synaptics - add LEN2058 to SMBus passlist for ThinkPad E490
From: Dmitry Torokhov @ 2026-05-13 21:27 UTC (permalink / raw)
  To: contacto; +Cc: linux-input, linux-kernel
In-Reply-To: <20260513045749.185969-1-contacto@bazaes.cl>

On Wed, May 13, 2026 at 12:57:44AM -0400, contacto@bazaes.cl wrote:
> From: Nicolás Bazaes <contacto@bazaes.cl>
> 
> The Lenovo ThinkPad E490 (PNP ID: LEN2058) has a Synaptics TM3471-020
> touchpad that supports SMBus/RMI4 mode but is not listed in
> smbus_pnp_ids[]. Without this entry, psmouse ignores the
> synaptics_intertouch parameter and falls back to PS/2 mode even when
> explicitly requested.
> 
> Adding LEN2058 to the passlist enables RMI4 over SMBus, which provides
> better multitouch support and matches the behavior of similar ThinkPad
> models already in the list (E480/LEN2054, E580/LEN2055).
> 
> Tested on ThinkPad E490 with kernel 7.0.5-zen1 and Arch Linux.
> 
> Signed-off-by: Nicolás Bazaes <contacto@bazaes.cl>
> Assisted-by: Claude <claude.ai>

Thank you for the patch. From checkpatch.pl:

WARNING: Assisted-by expects 'AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2]' format

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH] HID: lenovo: Restore Fn-lock LED state on resume for tp10ubkbd devices
From: sashiko-bot @ 2026-05-13 22:27 UTC (permalink / raw)
  To: Kean; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260512142229.4098386-1-rh_king@163.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Unprotected state modifications / TOCTOU Race leading to Deadlock
--

commit 7c663fefe9bb0e01bc7e377e27b713f9cd82c9d8
Author: Kean <rh_king@163.com>

HID: lenovo: Restore Fn-lock LED state on resume for tp10ubkbd devices

This commit updates the reset_resume callback to restore the Fn-lock LED
state for several ThinkPad tablet keyboards. It does this by reading the
driver's cached state and calling lenovo_led_set_tp10ubkbd to update the
hardware.

> diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
> --- a/drivers/hid/hid-lenovo.c
> +++ b/drivers/hid/hid-lenovo.c

[ ... ]

> @@ -1424,6 +1424,8 @@ static int lenovo_probe(struct hid_device *hdev,
>  
>  static int lenovo_reset_resume(struct hid_device *hdev)
>  {
> +	struct lenovo_drvdata *data;
> +
>  	switch (hdev->product) {
>  	case USB_DEVICE_ID_LENOVO_CUSBKBD:
>  	case USB_DEVICE_ID_LENOVO_TPIIUSBKBD:
> @@ -1431,6 +1433,17 @@ static int lenovo_reset_resume(struct hid_device *hdev)
>  			lenovo_features_set_cptkbd(hdev);
>  
>  		break;
> +	case USB_DEVICE_ID_LENOVO_X12_TAB:
> +	case USB_DEVICE_ID_LENOVO_X12_TAB2:
> +	case USB_DEVICE_ID_LENOVO_TP10UBKBD:
> +	case USB_DEVICE_ID_LENOVO_X1_TAB:
> +	case USB_DEVICE_ID_LENOVO_X1_TAB2:
> +	case USB_DEVICE_ID_LENOVO_X1_TAB3:
> +		data = hid_get_drvdata(hdev);
> +		if (data)
> +			lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED,
> +						 data->fn_lock ? LED_ON : LED_OFF);

Could this new call to lenovo_led_set_tp10ubkbd() cause a deadlock during
runtime resume?

When userspace writes to the fn_lock sysfs attribute, it eventually calls
lenovo_led_set_tp10ubkbd() which acquires led_report_mutex before issuing
the raw HID request:

lenovo_led_set_tp10ubkbd()
    mutex_lock(&data->led_report_mutex);
    ...
    hid_hw_raw_request(...)

If the device is runtime-suspended and requires a reset-resume,
hid_hw_raw_request() will trigger usb_autopm_get_interface() to
synchronously wake the device. This causes the USB core to execute
lenovo_reset_resume() in the same thread:

lenovo_reset_resume()
    lenovo_led_set_tp10ubkbd()
        mutex_lock(&data->led_report_mutex);

Since led_report_mutex is already held by the initial sysfs write call,
will this result in an immediate self-deadlock?

Additionally, if a resume is initiated by a remote wakeup via a PM worker
thread while a sysfs write is concurrently holding led_report_mutex, could
this lead to an ABBA deadlock between the sysfs thread and the PM worker
thread?

> +		break;
>  	default:
>  		break;
>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260512142229.4098386-1-rh_king@163.com?part=1

^ permalink raw reply

* Re: [PATCH v1] Input: atlas - Check ACPI_COMPANION() against NULL
From: sashiko-bot @ 2026-05-13 22:58 UTC (permalink / raw)
  To: Rafael J. Wysocki; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <8696590.T7Z3S40VBb@rafael.j.wysocki>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Incomplete Fix for driver_override Abuse (Global State Corruption)
--

commit ef51cbfbb2dd379fb06abd9c22db66e86bb70eb3
Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

Input: atlas - Check ACPI_COMPANION() against NULL

This commit adds a check to ensure the ACPI companion device is present in
the atlas_btns driver to prevent issues when a device is forced to match
via device_match_driver_override().

> diff --git a/drivers/input/misc/atlas_btns.c b/drivers/input/misc/atlas_btns.c
> index 47b31725e8507..835ad45a9d65c 100644
> --- a/drivers/input/misc/atlas_btns.c
> +++ b/drivers/input/misc/atlas_btns.c
> @@ -60,11 +60,15 @@ static acpi_status acpi_atlas_button_handler(u32 function,
>  
>  static int atlas_acpi_button_probe(struct platform_device *pdev)
>  {
> -	struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
> +	struct acpi_device *device;
>  	acpi_status status;
>  	int i;
>  	int err;
>  
> +	device = ACPI_COMPANION(&pdev->dev);
> +	if (!device)
> +		return -ENODEV;
> +
>  	input_dev = input_allocate_device();

The commit message mentions this check is to protect against
device_match_driver_override() forcing a match with unexpected devices.

If driver_override is used to force-bind this driver to multiple platform
devices that do have ACPI companions, could this cause issues with the global
input_dev variable?

Since input_dev is overwritten on each successful probe, it looks like earlier
allocated input devices would be leaked if multiple bindings succeed.

Additionally, if these devices are later unbound, or if a subsequent probe
fails after modifying input_dev, could this lead to a double-free or null
pointer dereference when the shared input_dev pointer is cleaned up?

Would it make sense to either reject multiple bindings or store the input
device in the platform device's private data using platform_set_drvdata()?

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/8696590.T7Z3S40VBb@rafael.j.wysocki?part=1

^ permalink raw reply


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