Linux Input/HID development
 help / color / mirror / Atom feed
* Re: [PATCH v2] Bluetooth: HIDP: cap report descriptor size in HID setup
From: Eric_Terminal @ 2026-03-22 15:37 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Bastien Nocera, marcel, johan.hedberg, luiz.dentz,
	linux-bluetooth, linux-kernel, linux-input
In-Reply-To: <abFA9nm_fBqw8mNS@beelink>

Hi all,

Just a gentle ping on this patch.

Since Benjamin reviewed it from the input side and concluded it should
be safe, I was wondering if there are any further comments from the
Bluetooth side, or if anything else is needed from me for this to be
merged?

Thanks,
Yufan

On Wed, Mar 11, 2026 at 6:19 PM Benjamin Tissoires <bentiss@kernel.org> wrote:
>
> On Mar 01 2026, Bastien Nocera wrote:
> > On Sun, 2026-03-01 at 01:26 +0800, Eric-Terminal wrote:
> > > From: Yufan Chen <ericterminal@gmail.com>
> > >
> > > hidp_setup_hid() duplicates the report descriptor from userspace
> > > based on
> > > req->rd_size. Large values can trigger oversized copies.
> > >
> > > Do not reject the connection when rd_size exceeds
> > > HID_MAX_DESCRIPTOR_SIZE. Instead, cap rd_size in hidp_setup_hid()
> > > and use the capped value for memdup_user() and session->rd_size.
> > >
> > > This keeps compatibility with existing userspace behavior while
> > > bounding memory usage in the HID setup path.
> >
> > Cross-sending this to linux-input@ for review, they would know the best
> > way to deal with oversized HID descriptors.
>
> AFAICT the hid-core code would be fine with it (it would parse it), but
> there will be some issues (hidraw will not be able to export the entire
> rdesc, so is the sysfs).
>
> For reference, usbhid just returns -EINVAL for oversize report
> descriptors.
>
> Anyway, if the report descriptor is truncated, like in this patch, the
> hid core parse will fail if the data is not correct, so I thing this
> should be safe.
>
> Cheers,
> Benjamin
>
> >
> > >
> > > Signed-off-by: Yufan Chen <ericterminal@gmail.com>
> > > ---
> > >  net/bluetooth/hidp/core.c | 7 +++++--
> > >  1 file changed, 5 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c
> > > index 6fe815241..31aeffa39 100644
> > > --- a/net/bluetooth/hidp/core.c
> > > +++ b/net/bluetooth/hidp/core.c
> > > @@ -755,13 +755,16 @@ static int hidp_setup_hid(struct hidp_session
> > > *session,
> > >                             const struct hidp_connadd_req *req)
> > >  {
> > >     struct hid_device *hid;
> > > +   unsigned int rd_size;
> > >     int err;
> > >
> > > -   session->rd_data = memdup_user(req->rd_data, req->rd_size);
> > > +   rd_size = min_t(unsigned int, req->rd_size,
> > > HID_MAX_DESCRIPTOR_SIZE);
> > > +
> > > +   session->rd_data = memdup_user(req->rd_data, rd_size);
> > >     if (IS_ERR(session->rd_data))
> > >             return PTR_ERR(session->rd_data);
> > >
> > > -   session->rd_size = req->rd_size;
> > > +   session->rd_size = rd_size;
> > >
> > >     hid = hid_allocate_device();
> > >     if (IS_ERR(hid)) {
> >

^ permalink raw reply

* Re: [PATCH v2 2/2] iio: inkern: Use namespaced exports
From: Jonathan Cameron @ 2026-03-22 12:25 UTC (permalink / raw)
  To: Romain Gantois
  Cc: MyungJoo Ham, Chanwoo Choi, Guenter Roeck, Peter Rosin,
	David Lechner, Nuno Sá, Andy Shevchenko, Lars-Peter Clausen,
	Michael Hennerich, Mariel Tinaco, Kevin Tsai, Linus Walleij,
	Dmitry Torokhov, Eugen Hristev, Vinod Koul,
	Kishon Vijay Abraham I, Sebastian Reichel, Chen-Yu Tsai,
	Hans de Goede, Support Opensource, Paul Cercueil, Iskren Chernev,
	Krzysztof Kozlowski, Marek Szyprowski, Matheus Castello,
	Saravanan Sekar, Matthias Brugger, AngeloGioacchino Del Regno,
	Casey Connolly, Pali Rohár, Orson Zhai, Baolin Wang,
	Chunyan Zhang, Amit Kucheria, Thara Gopinath, Rafael J. Wysocki,
	Daniel Lezcano, Zhang Rui, Lukasz Luba, Claudiu Beznea,
	Liam Girdwood, Mark Brown, Jaroslav Kysela, Takashi Iwai,
	Sylwester Nawrocki, Olivier Moysan, Arnaud Pouliquen,
	Maxime Coquelin, Alexandre Torgue, Thomas Petazzoni, linux-kernel,
	linux-hwmon, linux-iio, linux-input, linux-phy, linux-pm,
	linux-mips, linux-mediatek, linux-arm-msm, linux-sound,
	linux-stm32, Sebastian Reichel, Andy Shevchenko
In-Reply-To: <20260111170222.43aee69a@jic23-huawei>

On Sun, 11 Jan 2026 17:02:22 +0000
Jonathan Cameron <jic23@kernel.org> wrote:

> On Tue, 09 Dec 2025 09:25:56 +0100
> Romain Gantois <romain.gantois@bootlin.com> wrote:
> 
> > Use namespaced exports for IIO consumer API functions.
> > 
> > This will make it easier to manage the IIO export surface. Consumer drivers
> > will only be provided access to a specific set of functions, thereby
> > restricting usage of internal IIO functions by other parts of the kernel.
> > 
> > This change cannot be split into several parts without breaking
> > bisectability, thus all of the affected drivers are modified at once.
> > 
> > Acked-by: Sebastian Reichel <sebastian.reichel@collabora.com> # for power-supply
> > Acked-by: Guenter Roeck <linux@roeck-us.net>
> > Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
> > Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>  
> Ideally looking for a couple more Acks.
> 
> If any of the maintainers of other trees who haven't already replied
> have time for a quick glance that would be great.  I'll spin an
> immutable branch but I'm not really expecting any non trivial
> conflicts unless there is a new user in flight that I've forgotten
> about.

At this stage, given I'm still waiting on replies from a couple of
subsystem maintainers, I'm thinking we'll do this next cycle and I'll
provide an immutable branch based on rc1 for anyone to grab if they
run into merge conflicts in linux-next.

Thanks,

Jonathan

> 
> Jonathan
> 
> > ---
> >  drivers/extcon/extcon-adc-jack.c                |  1 +
> >  drivers/hwmon/iio_hwmon.c                       |  1 +
> >  drivers/hwmon/ntc_thermistor.c                  |  1 +
> >  drivers/iio/adc/envelope-detector.c             |  1 +
> >  drivers/iio/afe/iio-rescale.c                   |  1 +
> >  drivers/iio/buffer/industrialio-buffer-cb.c     |  1 +
> >  drivers/iio/buffer/industrialio-hw-consumer.c   |  1 +
> >  drivers/iio/dac/ad8460.c                        |  1 +
> >  drivers/iio/dac/dpot-dac.c                      |  1 +
> >  drivers/iio/inkern.c                            | 54 ++++++++++++-------------
> >  drivers/iio/light/cm3605.c                      |  1 +
> >  drivers/iio/light/gp2ap002.c                    |  1 +
> >  drivers/iio/multiplexer/iio-mux.c               |  1 +
> >  drivers/iio/potentiostat/lmp91000.c             |  1 +
> >  drivers/input/joystick/adc-joystick.c           |  1 +
> >  drivers/input/keyboard/adc-keys.c               |  1 +
> >  drivers/input/touchscreen/colibri-vf50-ts.c     |  1 +
> >  drivers/input/touchscreen/resistive-adc-touch.c |  1 +
> >  drivers/phy/motorola/phy-cpcap-usb.c            |  1 +
> >  drivers/power/supply/ab8500_btemp.c             |  1 +
> >  drivers/power/supply/ab8500_charger.c           |  1 +
> >  drivers/power/supply/ab8500_fg.c                |  1 +
> >  drivers/power/supply/axp20x_ac_power.c          |  1 +
> >  drivers/power/supply/axp20x_battery.c           |  1 +
> >  drivers/power/supply/axp20x_usb_power.c         |  1 +
> >  drivers/power/supply/axp288_fuel_gauge.c        |  1 +
> >  drivers/power/supply/cpcap-battery.c            |  1 +
> >  drivers/power/supply/cpcap-charger.c            |  1 +
> >  drivers/power/supply/da9150-charger.c           |  1 +
> >  drivers/power/supply/generic-adc-battery.c      |  1 +
> >  drivers/power/supply/ingenic-battery.c          |  1 +
> >  drivers/power/supply/intel_dc_ti_battery.c      |  1 +
> >  drivers/power/supply/lego_ev3_battery.c         |  1 +
> >  drivers/power/supply/lp8788-charger.c           |  1 +
> >  drivers/power/supply/max17040_battery.c         |  1 +
> >  drivers/power/supply/mp2629_charger.c           |  1 +
> >  drivers/power/supply/mt6370-charger.c           |  1 +
> >  drivers/power/supply/qcom_smbx.c                |  1 +
> >  drivers/power/supply/rn5t618_power.c            |  1 +
> >  drivers/power/supply/rx51_battery.c             |  1 +
> >  drivers/power/supply/sc27xx_fuel_gauge.c        |  1 +
> >  drivers/power/supply/twl4030_charger.c          |  1 +
> >  drivers/power/supply/twl4030_madc_battery.c     |  1 +
> >  drivers/power/supply/twl6030_charger.c          |  1 +
> >  drivers/thermal/qcom/qcom-spmi-adc-tm5.c        |  1 +
> >  drivers/thermal/qcom/qcom-spmi-temp-alarm.c     |  1 +
> >  drivers/thermal/renesas/rzg3s_thermal.c         |  1 +
> >  drivers/thermal/thermal-generic-adc.c           |  1 +
> >  sound/soc/codecs/audio-iio-aux.c                |  1 +
> >  sound/soc/samsung/aries_wm8994.c                |  1 +
> >  sound/soc/samsung/midas_wm1811.c                |  1 +
> >  sound/soc/stm/stm32_adfsdm.c                    |  1 +
> >  52 files changed, 78 insertions(+), 27 deletions(-)
> > 
> > diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c
> > index 7e3c9f38297b..e735f43dcdeb 100644
> > --- a/drivers/extcon/extcon-adc-jack.c
> > +++ b/drivers/extcon/extcon-adc-jack.c
> > @@ -210,3 +210,4 @@ module_platform_driver(adc_jack_driver);
> >  MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
> >  MODULE_DESCRIPTION("ADC Jack extcon driver");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
> > index e376d4cde5ad..4c7843fbcc50 100644
> > --- a/drivers/hwmon/iio_hwmon.c
> > +++ b/drivers/hwmon/iio_hwmon.c
> > @@ -222,3 +222,4 @@ module_platform_driver(iio_hwmon_driver);
> >  MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>");
> >  MODULE_DESCRIPTION("IIO to hwmon driver");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c
> > index d21f7266c411..417807fad80b 100644
> > --- a/drivers/hwmon/ntc_thermistor.c
> > +++ b/drivers/hwmon/ntc_thermistor.c
> > @@ -706,3 +706,4 @@ MODULE_DESCRIPTION("NTC Thermistor Driver");
> >  MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
> >  MODULE_LICENSE("GPL");
> >  MODULE_ALIAS("platform:ntc-thermistor");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/iio/adc/envelope-detector.c b/drivers/iio/adc/envelope-detector.c
> > index 5b16fe737659..fea20e7e6cd9 100644
> > --- a/drivers/iio/adc/envelope-detector.c
> > +++ b/drivers/iio/adc/envelope-detector.c
> > @@ -406,3 +406,4 @@ module_platform_driver(envelope_detector_driver);
> >  MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator");
> >  MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/iio/afe/iio-rescale.c b/drivers/iio/afe/iio-rescale.c
> > index ecaf59278c6f..d7f55109af3e 100644
> > --- a/drivers/iio/afe/iio-rescale.c
> > +++ b/drivers/iio/afe/iio-rescale.c
> > @@ -609,3 +609,4 @@ module_platform_driver(rescale_driver);
> >  MODULE_DESCRIPTION("IIO rescale driver");
> >  MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c
> > index 3e27385069ed..608ea9afc15a 100644
> > --- a/drivers/iio/buffer/industrialio-buffer-cb.c
> > +++ b/drivers/iio/buffer/industrialio-buffer-cb.c
> > @@ -153,3 +153,4 @@ EXPORT_SYMBOL_GPL(iio_channel_cb_get_iio_dev);
> >  MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>");
> >  MODULE_DESCRIPTION("Industrial I/O callback buffer");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/iio/buffer/industrialio-hw-consumer.c b/drivers/iio/buffer/industrialio-hw-consumer.c
> > index 526b2a8d725d..d7ff086ed783 100644
> > --- a/drivers/iio/buffer/industrialio-hw-consumer.c
> > +++ b/drivers/iio/buffer/industrialio-hw-consumer.c
> > @@ -211,3 +211,4 @@ EXPORT_SYMBOL_GPL(iio_hw_consumer_disable);
> >  MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
> >  MODULE_DESCRIPTION("Hardware consumer buffer the IIO framework");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/iio/dac/ad8460.c b/drivers/iio/dac/ad8460.c
> > index 6e45686902dd..ad654819ca22 100644
> > --- a/drivers/iio/dac/ad8460.c
> > +++ b/drivers/iio/dac/ad8460.c
> > @@ -955,3 +955,4 @@ MODULE_AUTHOR("Mariel Tinaco <mariel.tinaco@analog.com");
> >  MODULE_DESCRIPTION("AD8460 DAC driver");
> >  MODULE_LICENSE("GPL");
> >  MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/iio/dac/dpot-dac.c b/drivers/iio/dac/dpot-dac.c
> > index d1b8441051ae..49dbdb7df955 100644
> > --- a/drivers/iio/dac/dpot-dac.c
> > +++ b/drivers/iio/dac/dpot-dac.c
> > @@ -254,3 +254,4 @@ module_platform_driver(dpot_dac_driver);
> >  MODULE_DESCRIPTION("DAC emulation driver using a digital potentiometer");
> >  MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
> > index 1e5eb5a41271..c75c3a8d233f 100644
> > --- a/drivers/iio/inkern.c
> > +++ b/drivers/iio/inkern.c
> > @@ -281,7 +281,7 @@ struct iio_channel *fwnode_iio_channel_get_by_name(struct fwnode_handle *fwnode,
> >  
> >  	return ERR_PTR(-ENODEV);
> >  }
> > -EXPORT_SYMBOL_GPL(fwnode_iio_channel_get_by_name);
> > +EXPORT_SYMBOL_NS_GPL(fwnode_iio_channel_get_by_name, "IIO_CONSUMER");
> >  
> >  static struct iio_channel *fwnode_iio_channel_get_all(struct device *dev)
> >  {
> > @@ -386,7 +386,7 @@ struct iio_channel *iio_channel_get(struct device *dev,
> >  
> >  	return iio_channel_get_sys(name, channel_name);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_channel_get);
> > +EXPORT_SYMBOL_NS_GPL(iio_channel_get, "IIO_CONSUMER");
> >  
> >  void iio_channel_release(struct iio_channel *channel)
> >  {
> > @@ -395,7 +395,7 @@ void iio_channel_release(struct iio_channel *channel)
> >  	iio_device_put(channel->indio_dev);
> >  	kfree(channel);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_channel_release);
> > +EXPORT_SYMBOL_NS_GPL(iio_channel_release, "IIO_CONSUMER");
> >  
> >  static void devm_iio_channel_free(void *iio_channel)
> >  {
> > @@ -418,7 +418,7 @@ struct iio_channel *devm_iio_channel_get(struct device *dev,
> >  
> >  	return channel;
> >  }
> > -EXPORT_SYMBOL_GPL(devm_iio_channel_get);
> > +EXPORT_SYMBOL_NS_GPL(devm_iio_channel_get, "IIO_CONSUMER");
> >  
> >  struct iio_channel *devm_fwnode_iio_channel_get_by_name(struct device *dev,
> >  							struct fwnode_handle *fwnode,
> > @@ -437,7 +437,7 @@ struct iio_channel *devm_fwnode_iio_channel_get_by_name(struct device *dev,
> >  
> >  	return channel;
> >  }
> > -EXPORT_SYMBOL_GPL(devm_fwnode_iio_channel_get_by_name);
> > +EXPORT_SYMBOL_NS_GPL(devm_fwnode_iio_channel_get_by_name, "IIO_CONSUMER");
> >  
> >  struct iio_channel *iio_channel_get_all(struct device *dev)
> >  {
> > @@ -506,7 +506,7 @@ struct iio_channel *iio_channel_get_all(struct device *dev)
> >  		iio_device_put(chans[i].indio_dev);
> >  	return ERR_PTR(ret);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_channel_get_all);
> > +EXPORT_SYMBOL_NS_GPL(iio_channel_get_all, "IIO_CONSUMER");
> >  
> >  void iio_channel_release_all(struct iio_channel *channels)
> >  {
> > @@ -518,7 +518,7 @@ void iio_channel_release_all(struct iio_channel *channels)
> >  	}
> >  	kfree(channels);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_channel_release_all);
> > +EXPORT_SYMBOL_NS_GPL(iio_channel_release_all, "IIO_CONSUMER");
> >  
> >  static void devm_iio_channel_free_all(void *iio_channels)
> >  {
> > @@ -541,7 +541,7 @@ struct iio_channel *devm_iio_channel_get_all(struct device *dev)
> >  
> >  	return channels;
> >  }
> > -EXPORT_SYMBOL_GPL(devm_iio_channel_get_all);
> > +EXPORT_SYMBOL_NS_GPL(devm_iio_channel_get_all, "IIO_CONSUMER");
> >  
> >  static int iio_channel_read(struct iio_channel *chan, int *val, int *val2,
> >  			    enum iio_chan_info_enum info)
> > @@ -585,7 +585,7 @@ int iio_read_channel_raw(struct iio_channel *chan, int *val)
> >  
> >  	return iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_channel_raw);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_channel_raw, "IIO_CONSUMER");
> >  
> >  int iio_read_channel_average_raw(struct iio_channel *chan, int *val)
> >  {
> > @@ -597,7 +597,7 @@ int iio_read_channel_average_raw(struct iio_channel *chan, int *val)
> >  
> >  	return iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_AVERAGE_RAW);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_channel_average_raw);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_channel_average_raw, "IIO_CONSUMER");
> >  
> >  int iio_multiply_value(int *result, s64 multiplier,
> >  		       unsigned int type, int val, int val2)
> > @@ -701,7 +701,7 @@ int iio_convert_raw_to_processed(struct iio_channel *chan, int raw,
> >  	return iio_convert_raw_to_processed_unlocked(chan, raw, processed,
> >  						     scale);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_convert_raw_to_processed);
> > +EXPORT_SYMBOL_NS_GPL(iio_convert_raw_to_processed, "IIO_CONSUMER");
> >  
> >  int iio_read_channel_attribute(struct iio_channel *chan, int *val, int *val2,
> >  			       enum iio_chan_info_enum attribute)
> > @@ -714,13 +714,13 @@ int iio_read_channel_attribute(struct iio_channel *chan, int *val, int *val2,
> >  
> >  	return iio_channel_read(chan, val, val2, attribute);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_channel_attribute);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_channel_attribute, "IIO_CONSUMER");
> >  
> >  int iio_read_channel_offset(struct iio_channel *chan, int *val, int *val2)
> >  {
> >  	return iio_read_channel_attribute(chan, val, val2, IIO_CHAN_INFO_OFFSET);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_channel_offset);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_channel_offset, "IIO_CONSUMER");
> >  
> >  int iio_read_channel_processed_scale(struct iio_channel *chan, int *val,
> >  				     unsigned int scale)
> > @@ -748,20 +748,20 @@ int iio_read_channel_processed_scale(struct iio_channel *chan, int *val,
> >  							     scale);
> >  	}
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_channel_processed_scale);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_channel_processed_scale, "IIO_CONSUMER");
> >  
> >  int iio_read_channel_processed(struct iio_channel *chan, int *val)
> >  {
> >  	/* This is just a special case with scale factor 1 */
> >  	return iio_read_channel_processed_scale(chan, val, 1);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_channel_processed);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_channel_processed, "IIO_CONSUMER");
> >  
> >  int iio_read_channel_scale(struct iio_channel *chan, int *val, int *val2)
> >  {
> >  	return iio_read_channel_attribute(chan, val, val2, IIO_CHAN_INFO_SCALE);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_channel_scale);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_channel_scale, "IIO_CONSUMER");
> >  
> >  static int iio_channel_read_avail(struct iio_channel *chan,
> >  				  const int **vals, int *type, int *length,
> > @@ -790,7 +790,7 @@ int iio_read_avail_channel_attribute(struct iio_channel *chan,
> >  
> >  	return iio_channel_read_avail(chan, vals, type, length, attribute);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_avail_channel_attribute);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_avail_channel_attribute, "IIO_CONSUMER");
> >  
> >  int iio_read_avail_channel_raw(struct iio_channel *chan,
> >  			       const int **vals, int *length)
> > @@ -807,7 +807,7 @@ int iio_read_avail_channel_raw(struct iio_channel *chan,
> >  
> >  	return ret;
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_avail_channel_raw);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_avail_channel_raw, "IIO_CONSUMER");
> >  
> >  static int iio_channel_read_max(struct iio_channel *chan,
> >  				int *val, int *val2, int *type,
> > @@ -863,7 +863,7 @@ int iio_read_max_channel_raw(struct iio_channel *chan, int *val)
> >  
> >  	return iio_channel_read_max(chan, val, NULL, &type, IIO_CHAN_INFO_RAW);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_max_channel_raw);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_max_channel_raw, "IIO_CONSUMER");
> >  
> >  static int iio_channel_read_min(struct iio_channel *chan,
> >  				int *val, int *val2, int *type,
> > @@ -919,7 +919,7 @@ int iio_read_min_channel_raw(struct iio_channel *chan, int *val)
> >  
> >  	return iio_channel_read_min(chan, val, NULL, &type, IIO_CHAN_INFO_RAW);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_min_channel_raw);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_min_channel_raw, "IIO_CONSUMER");
> >  
> >  int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type)
> >  {
> > @@ -933,7 +933,7 @@ int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type)
> >  
> >  	return 0;
> >  }
> > -EXPORT_SYMBOL_GPL(iio_get_channel_type);
> > +EXPORT_SYMBOL_NS_GPL(iio_get_channel_type, "IIO_CONSUMER");
> >  
> >  static int iio_channel_write(struct iio_channel *chan, int val, int val2,
> >  			     enum iio_chan_info_enum info)
> > @@ -957,13 +957,13 @@ int iio_write_channel_attribute(struct iio_channel *chan, int val, int val2,
> >  
> >  	return iio_channel_write(chan, val, val2, attribute);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_write_channel_attribute);
> > +EXPORT_SYMBOL_NS_GPL(iio_write_channel_attribute, "IIO_CONSUMER");
> >  
> >  int iio_write_channel_raw(struct iio_channel *chan, int val)
> >  {
> >  	return iio_write_channel_attribute(chan, val, 0, IIO_CHAN_INFO_RAW);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_write_channel_raw);
> > +EXPORT_SYMBOL_NS_GPL(iio_write_channel_raw, "IIO_CONSUMER");
> >  
> >  unsigned int iio_get_channel_ext_info_count(struct iio_channel *chan)
> >  {
> > @@ -978,7 +978,7 @@ unsigned int iio_get_channel_ext_info_count(struct iio_channel *chan)
> >  
> >  	return i;
> >  }
> > -EXPORT_SYMBOL_GPL(iio_get_channel_ext_info_count);
> > +EXPORT_SYMBOL_NS_GPL(iio_get_channel_ext_info_count, "IIO_CONSUMER");
> >  
> >  static const struct iio_chan_spec_ext_info *
> >  iio_lookup_ext_info(const struct iio_channel *chan, const char *attr)
> > @@ -1013,7 +1013,7 @@ ssize_t iio_read_channel_ext_info(struct iio_channel *chan,
> >  	return ext_info->read(chan->indio_dev, ext_info->private,
> >  			      chan->channel, buf);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_channel_ext_info);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_channel_ext_info, "IIO_CONSUMER");
> >  
> >  ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
> >  				   const char *buf, size_t len)
> > @@ -1027,7 +1027,7 @@ ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
> >  	return ext_info->write(chan->indio_dev, ext_info->private,
> >  			       chan->channel, buf, len);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_write_channel_ext_info);
> > +EXPORT_SYMBOL_NS_GPL(iio_write_channel_ext_info, "IIO_CONSUMER");
> >  
> >  ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf)
> >  {
> > @@ -1038,4 +1038,4 @@ ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf)
> >  
> >  	return do_iio_read_channel_label(chan->indio_dev, chan->channel, buf);
> >  }
> > -EXPORT_SYMBOL_GPL(iio_read_channel_label);
> > +EXPORT_SYMBOL_NS_GPL(iio_read_channel_label, "IIO_CONSUMER");
> > diff --git a/drivers/iio/light/cm3605.c b/drivers/iio/light/cm3605.c
> > index 0c17378e27d1..1bd11292d005 100644
> > --- a/drivers/iio/light/cm3605.c
> > +++ b/drivers/iio/light/cm3605.c
> > @@ -325,3 +325,4 @@ module_platform_driver(cm3605_driver);
> >  MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
> >  MODULE_DESCRIPTION("CM3605 ambient light and proximity sensor driver");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/iio/light/gp2ap002.c b/drivers/iio/light/gp2ap002.c
> > index a0d8a58f2704..04b1f6eade0e 100644
> > --- a/drivers/iio/light/gp2ap002.c
> > +++ b/drivers/iio/light/gp2ap002.c
> > @@ -717,3 +717,4 @@ module_i2c_driver(gp2ap002_driver);
> >  MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
> >  MODULE_DESCRIPTION("GP2AP002 ambient light and proximity sensor driver");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/iio/multiplexer/iio-mux.c b/drivers/iio/multiplexer/iio-mux.c
> > index b742ca9a99d1..e193913f5af7 100644
> > --- a/drivers/iio/multiplexer/iio-mux.c
> > +++ b/drivers/iio/multiplexer/iio-mux.c
> > @@ -464,3 +464,4 @@ module_platform_driver(mux_driver);
> >  MODULE_DESCRIPTION("IIO multiplexer driver");
> >  MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/iio/potentiostat/lmp91000.c b/drivers/iio/potentiostat/lmp91000.c
> > index eccc2a34358f..7d993f2acda4 100644
> > --- a/drivers/iio/potentiostat/lmp91000.c
> > +++ b/drivers/iio/potentiostat/lmp91000.c
> > @@ -423,3 +423,4 @@ module_i2c_driver(lmp91000_driver);
> >  MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
> >  MODULE_DESCRIPTION("LMP91000 digital potentiostat");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/input/joystick/adc-joystick.c b/drivers/input/joystick/adc-joystick.c
> > index ff44f9978b71..4fa42f88bcfa 100644
> > --- a/drivers/input/joystick/adc-joystick.c
> > +++ b/drivers/input/joystick/adc-joystick.c
> > @@ -329,3 +329,4 @@ module_platform_driver(adc_joystick_driver);
> >  MODULE_DESCRIPTION("Input driver for joysticks connected over ADC");
> >  MODULE_AUTHOR("Artur Rojek <contact@artur-rojek.eu>");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/input/keyboard/adc-keys.c b/drivers/input/keyboard/adc-keys.c
> > index f1753207429d..d687459a0c80 100644
> > --- a/drivers/input/keyboard/adc-keys.c
> > +++ b/drivers/input/keyboard/adc-keys.c
> > @@ -202,3 +202,4 @@ module_platform_driver(adc_keys_driver);
> >  MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@free-electrons.com>");
> >  MODULE_DESCRIPTION("Input driver for resistor ladder connected on ADC");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/input/touchscreen/colibri-vf50-ts.c b/drivers/input/touchscreen/colibri-vf50-ts.c
> > index 98d5b2ba63fb..89c4d7b2b89e 100644
> > --- a/drivers/input/touchscreen/colibri-vf50-ts.c
> > +++ b/drivers/input/touchscreen/colibri-vf50-ts.c
> > @@ -372,3 +372,4 @@ module_platform_driver(vf50_touch_driver);
> >  MODULE_AUTHOR("Sanchayan Maity");
> >  MODULE_DESCRIPTION("Colibri VF50 Touchscreen driver");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/input/touchscreen/resistive-adc-touch.c b/drivers/input/touchscreen/resistive-adc-touch.c
> > index 7e761ec73273..2fefd652864c 100644
> > --- a/drivers/input/touchscreen/resistive-adc-touch.c
> > +++ b/drivers/input/touchscreen/resistive-adc-touch.c
> > @@ -301,3 +301,4 @@ module_platform_driver(grts_driver);
> >  MODULE_AUTHOR("Eugen Hristev <eugen.hristev@microchip.com>");
> >  MODULE_DESCRIPTION("Generic ADC Resistive Touch Driver");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/phy/motorola/phy-cpcap-usb.c b/drivers/phy/motorola/phy-cpcap-usb.c
> > index 7cb020dd3423..9591672b0511 100644
> > --- a/drivers/phy/motorola/phy-cpcap-usb.c
> > +++ b/drivers/phy/motorola/phy-cpcap-usb.c
> > @@ -717,3 +717,4 @@ MODULE_ALIAS("platform:cpcap_usb");
> >  MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
> >  MODULE_DESCRIPTION("CPCAP usb phy driver");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c
> > index e5202a7b6209..36b0c52a4b8b 100644
> > --- a/drivers/power/supply/ab8500_btemp.c
> > +++ b/drivers/power/supply/ab8500_btemp.c
> > @@ -829,3 +829,4 @@ MODULE_LICENSE("GPL v2");
> >  MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy");
> >  MODULE_ALIAS("platform:ab8500-btemp");
> >  MODULE_DESCRIPTION("AB8500 battery temperature driver");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
> > index 5f4537766e5b..6e49d1b28254 100644
> > --- a/drivers/power/supply/ab8500_charger.c
> > +++ b/drivers/power/supply/ab8500_charger.c
> > @@ -3751,3 +3751,4 @@ MODULE_LICENSE("GPL v2");
> >  MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy");
> >  MODULE_ALIAS("platform:ab8500-charger");
> >  MODULE_DESCRIPTION("AB8500 charger management driver");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
> > index 9dd99722667a..5fa559f796aa 100644
> > --- a/drivers/power/supply/ab8500_fg.c
> > +++ b/drivers/power/supply/ab8500_fg.c
> > @@ -3252,3 +3252,4 @@ MODULE_LICENSE("GPL v2");
> >  MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
> >  MODULE_ALIAS("platform:ab8500-fg");
> >  MODULE_DESCRIPTION("AB8500 Fuel Gauge driver");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c
> > index 5f6ea416fa30..e9049d6229df 100644
> > --- a/drivers/power/supply/axp20x_ac_power.c
> > +++ b/drivers/power/supply/axp20x_ac_power.c
> > @@ -421,3 +421,4 @@ module_platform_driver(axp20x_ac_power_driver);
> >  MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> >  MODULE_DESCRIPTION("AXP20X and AXP22X PMICs' AC power supply driver");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c
> > index 50ca8e110085..ee8701a6e907 100644
> > --- a/drivers/power/supply/axp20x_battery.c
> > +++ b/drivers/power/supply/axp20x_battery.c
> > @@ -1155,3 +1155,4 @@ module_platform_driver(axp20x_batt_driver);
> >  MODULE_DESCRIPTION("Battery power supply driver for AXP20X and AXP22X PMICs");
> >  MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c
> > index e75d1e377ac1..599adcf84968 100644
> > --- a/drivers/power/supply/axp20x_usb_power.c
> > +++ b/drivers/power/supply/axp20x_usb_power.c
> > @@ -1080,3 +1080,4 @@ module_platform_driver(axp20x_usb_power_driver);
> >  MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
> >  MODULE_DESCRIPTION("AXP20x PMIC USB power supply status driver");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c
> > index a3d71fc72064..c6897dd808fc 100644
> > --- a/drivers/power/supply/axp288_fuel_gauge.c
> > +++ b/drivers/power/supply/axp288_fuel_gauge.c
> > @@ -817,3 +817,4 @@ MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
> >  MODULE_AUTHOR("Todd Brandt <todd.e.brandt@linux.intel.com>");
> >  MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c
> > index 8106d1edcbc2..542c3c70e3cb 100644
> > --- a/drivers/power/supply/cpcap-battery.c
> > +++ b/drivers/power/supply/cpcap-battery.c
> > @@ -1176,3 +1176,4 @@ module_platform_driver(cpcap_battery_driver);
> >  MODULE_LICENSE("GPL v2");
> >  MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
> >  MODULE_DESCRIPTION("CPCAP PMIC Battery Driver");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c
> > index d0c3008db534..89bc0fc3c9f8 100644
> > --- a/drivers/power/supply/cpcap-charger.c
> > +++ b/drivers/power/supply/cpcap-charger.c
> > @@ -977,3 +977,4 @@ MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
> >  MODULE_DESCRIPTION("CPCAP Battery Charger Interface driver");
> >  MODULE_LICENSE("GPL v2");
> >  MODULE_ALIAS("platform:cpcap-charger");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/da9150-charger.c b/drivers/power/supply/da9150-charger.c
> > index 27f36ef5b88d..58449df6068c 100644
> > --- a/drivers/power/supply/da9150-charger.c
> > +++ b/drivers/power/supply/da9150-charger.c
> > @@ -644,3 +644,4 @@ module_platform_driver(da9150_charger_driver);
> >  MODULE_DESCRIPTION("Charger Driver for DA9150");
> >  MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c
> > index f5f2566b3a32..d18c8ee40405 100644
> > --- a/drivers/power/supply/generic-adc-battery.c
> > +++ b/drivers/power/supply/generic-adc-battery.c
> > @@ -298,3 +298,4 @@ module_platform_driver(gab_driver);
> >  MODULE_AUTHOR("anish kumar <yesanishhere@gmail.com>");
> >  MODULE_DESCRIPTION("generic battery driver using IIO");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/ingenic-battery.c b/drivers/power/supply/ingenic-battery.c
> > index b111c7ce2be3..5be269f17bff 100644
> > --- a/drivers/power/supply/ingenic-battery.c
> > +++ b/drivers/power/supply/ingenic-battery.c
> > @@ -190,3 +190,4 @@ module_platform_driver(ingenic_battery_driver);
> >  MODULE_DESCRIPTION("Battery driver for Ingenic JZ47xx SoCs");
> >  MODULE_AUTHOR("Artur Rojek <contact@artur-rojek.eu>");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/intel_dc_ti_battery.c b/drivers/power/supply/intel_dc_ti_battery.c
> > index 56b0c92e9d28..1a16ded563bc 100644
> > --- a/drivers/power/supply/intel_dc_ti_battery.c
> > +++ b/drivers/power/supply/intel_dc_ti_battery.c
> > @@ -387,3 +387,4 @@ MODULE_ALIAS("platform:" DEV_NAME);
> >  MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
> >  MODULE_DESCRIPTION("Intel Dollar Cove (TI) battery driver");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/lego_ev3_battery.c b/drivers/power/supply/lego_ev3_battery.c
> > index 28454de05761..414816662b06 100644
> > --- a/drivers/power/supply/lego_ev3_battery.c
> > +++ b/drivers/power/supply/lego_ev3_battery.c
> > @@ -231,3 +231,4 @@ module_platform_driver(lego_ev3_battery_driver);
> >  MODULE_LICENSE("GPL");
> >  MODULE_AUTHOR("David Lechner <david@lechnology.com>");
> >  MODULE_DESCRIPTION("LEGO MINDSTORMS EV3 Battery Driver");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/lp8788-charger.c b/drivers/power/supply/lp8788-charger.c
> > index f0a680c155c4..8c6ec98362d0 100644
> > --- a/drivers/power/supply/lp8788-charger.c
> > +++ b/drivers/power/supply/lp8788-charger.c
> > @@ -727,3 +727,4 @@ MODULE_DESCRIPTION("TI LP8788 Charger Driver");
> >  MODULE_AUTHOR("Milo Kim");
> >  MODULE_LICENSE("GPL");
> >  MODULE_ALIAS("platform:lp8788-charger");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c
> > index c1640bc6accd..1fe658bfecc1 100644
> > --- a/drivers/power/supply/max17040_battery.c
> > +++ b/drivers/power/supply/max17040_battery.c
> > @@ -635,3 +635,4 @@ module_i2c_driver(max17040_i2c_driver);
> >  MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>");
> >  MODULE_DESCRIPTION("MAX17040 Fuel Gauge");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/mp2629_charger.c b/drivers/power/supply/mp2629_charger.c
> > index d281c1059629..ed49f9a04c8c 100644
> > --- a/drivers/power/supply/mp2629_charger.c
> > +++ b/drivers/power/supply/mp2629_charger.c
> > @@ -660,3 +660,4 @@ module_platform_driver(mp2629_charger_driver);
> >  MODULE_AUTHOR("Saravanan Sekar <sravanhome@gmail.com>");
> >  MODULE_DESCRIPTION("MP2629 Charger driver");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/mt6370-charger.c b/drivers/power/supply/mt6370-charger.c
> > index e6db961d5818..2d02fdf37d70 100644
> > --- a/drivers/power/supply/mt6370-charger.c
> > +++ b/drivers/power/supply/mt6370-charger.c
> > @@ -941,3 +941,4 @@ module_platform_driver(mt6370_chg_driver);
> >  MODULE_AUTHOR("ChiaEn Wu <chiaen_wu@richtek.com>");
> >  MODULE_DESCRIPTION("MediaTek MT6370 Charger Driver");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c
> > index b1cb925581ec..63b88754155c 100644
> > --- a/drivers/power/supply/qcom_smbx.c
> > +++ b/drivers/power/supply/qcom_smbx.c
> > @@ -1050,3 +1050,4 @@ module_platform_driver(qcom_spmi_smb);
> >  MODULE_AUTHOR("Casey Connolly <casey.connolly@linaro.org>");
> >  MODULE_DESCRIPTION("Qualcomm SMB2 Charger Driver");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/rn5t618_power.c b/drivers/power/supply/rn5t618_power.c
> > index 40dec55a9f73..a3f30e390c11 100644
> > --- a/drivers/power/supply/rn5t618_power.c
> > +++ b/drivers/power/supply/rn5t618_power.c
> > @@ -821,3 +821,4 @@ module_platform_driver(rn5t618_power_driver);
> >  MODULE_ALIAS("platform:rn5t618-power");
> >  MODULE_DESCRIPTION("Power supply driver for RICOH RN5T618");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/rx51_battery.c b/drivers/power/supply/rx51_battery.c
> > index b0220ec2d926..57266921dc8e 100644
> > --- a/drivers/power/supply/rx51_battery.c
> > +++ b/drivers/power/supply/rx51_battery.c
> > @@ -246,3 +246,4 @@ MODULE_ALIAS("platform:rx51-battery");
> >  MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
> >  MODULE_DESCRIPTION("Nokia RX-51 battery driver");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c
> > index a7ed9de8a289..1719ec4173e6 100644
> > --- a/drivers/power/supply/sc27xx_fuel_gauge.c
> > +++ b/drivers/power/supply/sc27xx_fuel_gauge.c
> > @@ -1350,3 +1350,4 @@ module_platform_driver(sc27xx_fgu_driver);
> >  
> >  MODULE_DESCRIPTION("Spreadtrum SC27XX PMICs Fual Gauge Unit Driver");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c
> > index 04216b2bfb6c..151f7b24e9b9 100644
> > --- a/drivers/power/supply/twl4030_charger.c
> > +++ b/drivers/power/supply/twl4030_charger.c
> > @@ -1144,3 +1144,4 @@ MODULE_AUTHOR("Gražvydas Ignotas");
> >  MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver");
> >  MODULE_LICENSE("GPL");
> >  MODULE_ALIAS("platform:twl4030_bci");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/twl4030_madc_battery.c b/drivers/power/supply/twl4030_madc_battery.c
> > index 3935162e350b..9b3785d1643c 100644
> > --- a/drivers/power/supply/twl4030_madc_battery.c
> > +++ b/drivers/power/supply/twl4030_madc_battery.c
> > @@ -237,3 +237,4 @@ MODULE_LICENSE("GPL");
> >  MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");
> >  MODULE_DESCRIPTION("twl4030_madc battery driver");
> >  MODULE_ALIAS("platform:twl4030_madc_battery");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/power/supply/twl6030_charger.c b/drivers/power/supply/twl6030_charger.c
> > index b4ec26ff257c..82911a811f4e 100644
> > --- a/drivers/power/supply/twl6030_charger.c
> > +++ b/drivers/power/supply/twl6030_charger.c
> > @@ -579,3 +579,4 @@ module_platform_driver(twl6030_charger_driver);
> >  
> >  MODULE_DESCRIPTION("TWL6030 Battery Charger Interface driver");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
> > index d7f2e6ca92c2..bb6222c8cc5f 100644
> > --- a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
> > +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
> > @@ -1069,3 +1069,4 @@ module_platform_driver(adc_tm5_driver);
> >  
> >  MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
> > index f39ca0ddd17b..fb003ca96454 100644
> > --- a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
> > +++ b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
> > @@ -904,3 +904,4 @@ module_platform_driver(qpnp_tm_driver);
> >  MODULE_ALIAS("platform:spmi-temp-alarm");
> >  MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/thermal/renesas/rzg3s_thermal.c b/drivers/thermal/renesas/rzg3s_thermal.c
> > index e25e36c99a88..7ced8f76a0ec 100644
> > --- a/drivers/thermal/renesas/rzg3s_thermal.c
> > +++ b/drivers/thermal/renesas/rzg3s_thermal.c
> > @@ -270,3 +270,4 @@ module_platform_driver(rzg3s_thermal_driver);
> >  MODULE_DESCRIPTION("Renesas RZ/G3S Thermal Sensor Unit Driver");
> >  MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/drivers/thermal/thermal-generic-adc.c b/drivers/thermal/thermal-generic-adc.c
> > index 7c844589b153..cfdb8e674dd2 100644
> > --- a/drivers/thermal/thermal-generic-adc.c
> > +++ b/drivers/thermal/thermal-generic-adc.c
> > @@ -228,3 +228,4 @@ module_platform_driver(gadc_thermal_driver);
> >  MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
> >  MODULE_DESCRIPTION("Generic ADC thermal driver using IIO framework with DT");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/sound/soc/codecs/audio-iio-aux.c b/sound/soc/codecs/audio-iio-aux.c
> > index 588e48044c13..864a5a676495 100644
> > --- a/sound/soc/codecs/audio-iio-aux.c
> > +++ b/sound/soc/codecs/audio-iio-aux.c
> > @@ -312,3 +312,4 @@ module_platform_driver(audio_iio_aux_driver);
> >  MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
> >  MODULE_DESCRIPTION("IIO ALSA SoC aux driver");
> >  MODULE_LICENSE("GPL");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/sound/soc/samsung/aries_wm8994.c b/sound/soc/samsung/aries_wm8994.c
> > index 3723329b266d..b6f0f3c0d393 100644
> > --- a/sound/soc/samsung/aries_wm8994.c
> > +++ b/sound/soc/samsung/aries_wm8994.c
> > @@ -700,3 +700,4 @@ module_platform_driver(aries_audio_driver);
> >  MODULE_DESCRIPTION("ALSA SoC ARIES WM8994");
> >  MODULE_LICENSE("GPL");
> >  MODULE_ALIAS("platform:aries-audio-wm8994");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/sound/soc/samsung/midas_wm1811.c b/sound/soc/samsung/midas_wm1811.c
> > index 239e958b88d3..12c4962f901d 100644
> > --- a/sound/soc/samsung/midas_wm1811.c
> > +++ b/sound/soc/samsung/midas_wm1811.c
> > @@ -773,3 +773,4 @@ module_platform_driver(midas_driver);
> >  MODULE_AUTHOR("Simon Shields <simon@lineageos.org>");
> >  MODULE_DESCRIPTION("ASoC support for Midas");
> >  MODULE_LICENSE("GPL v2");
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> > diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c
> > index c914d1c46850..dabcd2759187 100644
> > --- a/sound/soc/stm/stm32_adfsdm.c
> > +++ b/sound/soc/stm/stm32_adfsdm.c
> > @@ -407,3 +407,4 @@ MODULE_DESCRIPTION("stm32 DFSDM DAI driver");
> >  MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
> >  MODULE_LICENSE("GPL v2");
> >  MODULE_ALIAS("platform:" STM32_ADFSDM_DRV_NAME);
> > +MODULE_IMPORT_NS("IIO_CONSUMER");
> >   
> 
> 


^ permalink raw reply

* [PATCH v2 3/3] Input: gpio-keys - add EV_REL event type support
From: Xiong Nandi @ 2026-03-22 11:35 UTC (permalink / raw)
  To: dmitry.torokhov
  Cc: linux-input, linux-kernel, Xiong Nandi, Gatien Chevallier,
	Thomas Gleixner, Fabrice Gasnier, Marco Crivellari
In-Reply-To: <ab5CXO6Fk7lhGazv@google.com>

The polled path handles EV_REL but the interrupt path silently
ignores it. Widen the type check so EV_REL goes through the same
reporting and shared-counter logic as EV_ABS.

Signed-off-by: Xiong Nandi <xndchn@gmail.com>
---
 drivers/input/keyboard/gpio_keys.c | 6 +++---
 include/linux/gpio_keys.h          | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c
index 4cbfd5a273bd..652a6932c52f 100644
--- a/drivers/input/keyboard/gpio_keys.c
+++ b/drivers/input/keyboard/gpio_keys.c
@@ -375,7 +375,7 @@ static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
 		return;
 	}
 
-	if (type == EV_ABS) {
+	if (type == EV_ABS || type == EV_REL) {
 		if (state && !bdata->axis_active) {
 			bdata->axis_active = true;
 			atomic_inc(bdata->axis_count);
@@ -960,13 +960,13 @@ static int gpio_keys_probe(struct platform_device *pdev)
 
 	fwnode_handle_put(child);
 
-	/* Allocate shared axis counters for EV_ABS buttons */
+	/* Allocate shared axis counters for EV_ABS/EV_REL buttons */
 	for (i = 0; i < pdata->nbuttons; i++) {
 		struct gpio_button_data *bdata = &ddata->data[i];
 		unsigned int type = bdata->button->type ?: EV_KEY;
 		int j;
 
-		if (type != EV_ABS)
+		if (type != EV_ABS && type != EV_REL)
 			continue;
 
 		/* Reuse counter from an earlier button with same (type, code) */
diff --git a/include/linux/gpio_keys.h b/include/linux/gpio_keys.h
index 80fa930b04c6..75a745a32fe1 100644
--- a/include/linux/gpio_keys.h
+++ b/include/linux/gpio_keys.h
@@ -13,13 +13,13 @@ struct device;
  * @active_low:		%true indicates that button is considered
  *			depressed when gpio is low
  * @desc:		label that will be attached to button's gpio
- * @type:		input event type (%EV_KEY, %EV_SW, %EV_ABS)
+ * @type:		input event type (%EV_KEY, %EV_SW, %EV_ABS, %EV_REL)
  * @wakeup:		configure the button as a wake-up source
  * @wakeup_event_action:	event action to trigger wakeup
  * @debounce_interval:	debounce ticks interval in msecs
  * @can_disable:	%true indicates that userspace is allowed to
  *			disable button via sysfs
- * @value:		axis value for %EV_ABS
+ * @value:		axis value for %EV_ABS/%EV_REL
  * @irq:		Irq number in case of interrupt keys
  * @wakeirq:		Optional dedicated wake-up interrupt
  */
-- 
2.25.1


^ permalink raw reply related

* [PATCH v2 2/3] Input: gpio-keys - use shared axis counter for EV_ABS events
From: Xiong Nandi @ 2026-03-22 11:35 UTC (permalink / raw)
  To: dmitry.torokhov
  Cc: linux-input, linux-kernel, Xiong Nandi, Gatien Chevallier,
	Marco Crivellari, Ingo Molnar, Fabrice Gasnier, Thomas Gleixner
In-Reply-To: <ab5CXO6Fk7lhGazv@google.com>

When two buttons share an EV_ABS axis code, releasing one button
zeroes the axis while the other is still held.

Add a shared atomic counter per (type, code) pair so the axis only
resets to zero when the last button on it is released.

Signed-off-by: Xiong Nandi <xndchn@gmail.com>
---
 drivers/input/keyboard/gpio_keys.c | 40 +++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c
index f97ca8dd073a..4cbfd5a273bd 100644
--- a/drivers/input/keyboard/gpio_keys.c
+++ b/drivers/input/keyboard/gpio_keys.c
@@ -51,8 +51,10 @@ struct gpio_button_data {
 	spinlock_t lock;
 	bool disabled;
 	bool key_pressed;
+	bool axis_active;
 	bool suspended;
 	bool debounce_use_hrtimer;
+	atomic_t *axis_count;
 };
 
 struct gpio_keys_drvdata {
@@ -374,8 +376,15 @@ static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
 	}
 
 	if (type == EV_ABS) {
-		if (state)
+		if (state && !bdata->axis_active) {
+			bdata->axis_active = true;
+			atomic_inc(bdata->axis_count);
 			input_event(input, type, button->code, button->value);
+		} else if (!state && bdata->axis_active) {
+			bdata->axis_active = false;
+			if (atomic_dec_and_test(bdata->axis_count))
+				input_event(input, type, button->code, 0);
+		}
 	} else {
 		input_event(input, type, *bdata->code, state);
 	}
@@ -951,6 +960,35 @@ static int gpio_keys_probe(struct platform_device *pdev)
 
 	fwnode_handle_put(child);
 
+	/* Allocate shared axis counters for EV_ABS buttons */
+	for (i = 0; i < pdata->nbuttons; i++) {
+		struct gpio_button_data *bdata = &ddata->data[i];
+		unsigned int type = bdata->button->type ?: EV_KEY;
+		int j;
+
+		if (type != EV_ABS)
+			continue;
+
+		/* Reuse counter from an earlier button with same (type, code) */
+		for (j = 0; j < i; j++) {
+			struct gpio_button_data *prev = &ddata->data[j];
+			unsigned int prev_type = prev->button->type ?: EV_KEY;
+
+			if (prev_type == type &&
+			    prev->button->code == bdata->button->code) {
+				bdata->axis_count = prev->axis_count;
+				break;
+			}
+		}
+
+		if (!bdata->axis_count) {
+			bdata->axis_count = devm_kzalloc(dev,
+					sizeof(*bdata->axis_count), GFP_KERNEL);
+			if (!bdata->axis_count)
+				return -ENOMEM;
+		}
+	}
+
 	error = input_register_device(input);
 	if (error) {
 		dev_err(dev, "Unable to register input device, error: %d\n",
-- 
2.25.1


^ permalink raw reply related

* [PATCH v2 1/3] Input: gpio-keys - set EV_ABS axis parameters at setup time
From: Xiong Nandi @ 2026-03-22 11:35 UTC (permalink / raw)
  To: dmitry.torokhov
  Cc: linux-input, linux-kernel, Xiong Nandi, Gatien Chevallier,
	Marco Crivellari, Thomas Gleixner, Fabrice Gasnier
In-Reply-To: <ab5CXO6Fk7lhGazv@google.com>

The driver calls input_set_capability() for EV_ABS axes but never
sets the actual axis parameters, so the input subsystem sees
unbounded ranges.

Add a helper that scans buttons sharing the same axis code, works
out the min/max values, and calls input_set_abs_params().

Signed-off-by: Xiong Nandi <xndchn@gmail.com>
---
 drivers/input/keyboard/gpio_keys.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c
index e19617485679..f97ca8dd073a 100644
--- a/drivers/input/keyboard/gpio_keys.c
+++ b/drivers/input/keyboard/gpio_keys.c
@@ -493,6 +493,27 @@ static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
+static void gpio_keys_set_abs_params(struct input_dev *input,
+				     struct gpio_keys_drvdata *ddata,
+				     unsigned int code)
+{
+	int i, min = 0, max = 0;
+
+	for (i = 0; i < ddata->pdata->nbuttons; i++) {
+		const struct gpio_keys_button *button = &ddata->pdata->buttons[i];
+
+		if (button->type != EV_ABS || button->code != code)
+			continue;
+
+		if (button->value < min)
+			min = button->value;
+		if (button->value > max)
+			max = button->value;
+	}
+
+	input_set_abs_params(input, code, min, max, 0, 0);
+}
+
 static int gpio_keys_setup_key(struct platform_device *pdev,
 				struct input_dev *input,
 				struct gpio_keys_drvdata *ddata,
@@ -651,6 +672,8 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
 	bdata->code = &ddata->keymap[idx];
 	*bdata->code = button->code;
 	input_set_capability(input, button->type ?: EV_KEY, *bdata->code);
+	if ((button->type ?: EV_KEY) == EV_ABS)
+		gpio_keys_set_abs_params(input, ddata, button->code);
 
 	/*
 	 * Install custom action to cancel release timer and
-- 
2.25.1


^ permalink raw reply related

* [PATCH v2 0/3] Input: gpio-keys - add full support of EV_REL and EV_ABS
From: Xiong Nandi @ 2026-03-22 11:35 UTC (permalink / raw)
  To: dmitry.torokhov
  Cc: linux-input, linux-kernel, Xiong Nandi, Fabrice Gasnier,
	Gatien Chevallier, Ingo Molnar, Marco Crivellari, Thomas Gleixner
In-Reply-To: <ab5CXO6Fk7lhGazv@google.com>

This series add full support of EV_REL and EV_ABS

v2: Split into 3 patches as requested. (In reply to ab5CXO6Fk7lhGazv@google.com)

Xiong Nandi (3):
  Input: gpio-keys - set EV_ABS axis parameters at setup time
  Input: gpio-keys - use shared axis counter for EV_ABS events
  Input: gpio-keys - add EV_REL event type support

 drivers/input/keyboard/gpio_keys.c | 65 +++++++++++++++++++++++++++++-
 include/linux/gpio_keys.h          |  4 +-
 2 files changed, 65 insertions(+), 4 deletions(-)

-- 
2.25.1


^ permalink raw reply

* Re: [PATCH WIP v2 09/11] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5
From: Krzysztof Kozlowski @ 2026-03-22 10:17 UTC (permalink / raw)
  To: david, Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
	Bjorn Andersson, Konrad Dybcio
  Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
	linux-kernel, devicetree, linux-arm-msm, phone-devel
In-Reply-To: <20260315-stmfts5-v2-9-70bc83ee9591@ixit.cz>

On 15/03/2026 19:52, David Heidelberg via B4 Relay wrote:
> From: David Heidelberg <david@ixit.cz>
> 
> Introduce more recent STM FTS5 touchscreen support.
> 
> Signed-off-by: David Heidelberg <david@ixit.cz>
> ---
>  .../devicetree/bindings/input/touchscreen/st,stmfts.yaml  | 15 ++++++++++++++-
>  1 file changed, 14 insertions(+), 1 deletion(-)
> 
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
> index 64c4f24ea3dd0..66255893a99fb 100644
> --- a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
> +++ b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
> @@ -16,10 +16,23 @@ description:
>  
>  allOf:
>    - $ref: touchscreen.yaml#
> +  - if:
> +      properties:
> +        compatible:
> +          const: st,stmfts5
> +    then:
> +      properties:
> +        switch-gpios:
> +          description: Switch between SLPI and AP mode.

Do not define properties in if:then: branches. Anyway, not a property of
this device.

Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH 08/10] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5
From: Krzysztof Kozlowski @ 2026-03-22 10:16 UTC (permalink / raw)
  To: David Heidelberg, Dmitry Baryshkov
  Cc: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
	Bjorn Andersson, Konrad Dybcio, Petr Hodina, linux-input,
	linux-stm32, linux-arm-kernel, linux-kernel, devicetree,
	linux-arm-msm, phone-devel
In-Reply-To: <4b8c8d8c-d2f3-4938-a451-e8e9524d40c3@ixit.cz>

On 15/03/2026 18:09, David Heidelberg wrote:
> On 01/03/2026 23:40, Dmitry Baryshkov wrote:
> 
> [...]
> 
>>> +    then:
>>> +      properties:
>>> +        switch-gpio:
>>> +          description: Switch between SLPI and AP mode.
>>
>> This doesn't sounds like the GPIO on the touchscreen, more like the
>> external schematic component. If it need sto be turned to one position,
>> it might be better to use GPIO hog for that.
> 
> Right now yes, but the GPIO serves to switching between SLPI and AP mode at 
> runtime, see [1]
> 
> The driver lack supports for SLPI, but at moment when SLPI support lands, we 
> should be able do something like:
> 
> -> device starts, touchscreen works
> -> screen goes to sleep, but instead of powering off touchscreen, it switches to 
> SLPI mode
> -> user taps at touchscreen, device wakes up
> 
> Thus I think we need to support this GPIO in the driver.

But that's not role of this device. You cannot just hook random GPIOs
into this device node, just because you want some use-case in the driver.

Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH 0/4] Add OneXPlayer Configuration HID Driver
From: Derek John Clark @ 2026-03-22  3:20 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, linux-input, linux-doc,
	linux-kernel
In-Reply-To: <20260322031615.1524307-1-derekjohn.clark@gmail.com>

On Sat, Mar 21, 2026 at 8:16 PM Derek J. Clark
<derekjohn.clark@gmail.com> wrote:
>
> Adds an HID driver for OneXPlayer HID configuration devices. There are
> currently 2 generations of OneXPlayer HID protocol. The first generation
> (OneXPlayer F1 series) only provides an RGB control interface over HID.
> The Second generation (X1 mini series, G1 series, AOKZOE A1X) also
> includes a hardware level button mapping interface, as well as a
> "takeover" mode that was added by the ODM for debugging the button map.
> This takeover mode can be useful for exposing the M1 and M2 accessory
> buttons as unique inputs with some userspace tools that can consume it.
>
> Signed-off-by: Derel J. Clark <derekjohn.clark@gmail.com>
>
> Derek J. Clark (4):
>   HID: hid-oxp: Add OneXPlayer configuration driver
>   HID: hid-oxp: Add Second Generation RGB Control
>   HID: hid-oxp: Add Second Generation Takeover Mode
>   HID: hid-oxp: Add Button Mapping Interface
>
>  MAINTAINERS           |    6 +
>  drivers/hid/Kconfig   |   12 +
>  drivers/hid/Makefile  |    1 +
>  drivers/hid/hid-ids.h |    6 +
>  drivers/hid/hid-oxp.c | 1340 +++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 1365 insertions(+)
>  create mode 100644 drivers/hid/hid-oxp.c
>
> --
> 2.53.0
>

Note to everyone: I forgot to rebase to a clean for-next branch before
sending this so build bots will likely fail. I'll be sure to update
the source branch to the appropriate branch for v2 after a few days to
allow time for comments on the patch substance. Sorry about the extra
churn here.

Thanks,
Derek

^ permalink raw reply

* [PATCH 4/4] HID: hid-oxp: Add Button Mapping Interface
From: Derek J. Clark @ 2026-03-22  3:16 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, Derek J . Clark,
	linux-input, linux-doc, linux-kernel
In-Reply-To: <20260322031615.1524307-1-derekjohn.clark@gmail.com>

Adds button mapping interface for second generation OneXPlayer
configuration HID interfaces. This interface allows the MCU to swap
button mappings at the hardware level. The current state cannot be
retrieved, and the mappings may have been modified in Windows prior, so
we reset the button mapping at init and expose an attribute to allow
userspace to do this again at any time.

The interface requires two pages of button mapping data to be sent
before the settings will take place. Since the MCU requires a 200ms
delay after each message (total 400ms for these attributes) use the same
debounce work queue method we used for RGB. This will allow for
userspace or udev rules to rapidly map all buttons. The values will
be cached before the final write is finally sent to the device.

Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
 drivers/hid/hid-oxp.c | 510 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 510 insertions(+)

diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index 5fed2799a2ad..915c17b97db0 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -33,10 +33,126 @@ enum oxp_function_index {
 	OXP_FID_GEN1_RGB_SET =		0x07,
 	OXP_FID_GEN1_RGB_REPLY =	0x0f,
 	OXP_FID_GEN2_TOGGLE_MODE =	0xb2,
+	OXP_FID_GEN2_KEY_STATE =	0xb4,
 	OXP_FID_GEN2_RGB_EVENT =	0xb8,
 };
 
+enum oxp_joybutton_index {
+	BUTTON_A = 0x01,
+	BUTTON_B,
+	BUTTON_X,
+	BUTTON_Y,
+	BUTTON_LB,
+	BUTTON_RB,
+	BUTTON_LT,
+	BUTTON_RT,
+	BUTTON_START,
+	BUTTON_SELECT,
+	BUTTON_L3,
+	BUTTON_R3,
+	BUTTON_DUP,
+	BUTTON_DDOWN,
+	BUTTON_DLEFT,
+	BUTTON_DRIGHT,
+	JOY_L_UP,
+	JOY_L_UP_RIGHT,
+	JOY_L_RIGHT,
+	JOY_L_DOWN_RIGHT,
+	JOY_L_DOWN,
+	JOY_L_DOWN_LEFT,
+	JOY_L_LEFT,
+	JOY_L_UP_LEFT,
+	JOY_R_UP,
+	JOY_R_UP_RIGHT,
+	JOY_R_RIGHT,
+	JOY_R_DOWN_RIGHT,
+	JOY_R_DOWN,
+	JOY_R_DOWN_LEFT,
+	JOY_R_LEFT,
+	JOY_R_UP_LEFT,
+	BUTTON_GUIDE = 0x22,
+};
+
+static const char *const oxp_joybutton_text[] = {
+	[BUTTON_A] = "a",
+	[BUTTON_B] = "b",
+	[BUTTON_X] = "x",
+	[BUTTON_Y] = "y",
+	[BUTTON_LB] = "lb",
+	[BUTTON_RB] = "rb",
+	[BUTTON_LT] = "lt",
+	[BUTTON_RT] = "rt",
+	[BUTTON_START] = "start",
+	[BUTTON_SELECT] = "select",
+	[BUTTON_L3] = "l3",
+	[BUTTON_R3] = "r3",
+	[BUTTON_DUP] = "d_up",
+	[BUTTON_DDOWN] = "d_down",
+	[BUTTON_DLEFT] = "d_left",
+	[BUTTON_DRIGHT] = "d_right",
+	[JOY_L_UP] = "joy_l_up",
+	[JOY_L_UP_RIGHT] = "joy_l_up_right",
+	[JOY_L_RIGHT] = "joy_l_right",
+	[JOY_L_DOWN_RIGHT] = "joy_l_down_right",
+	[JOY_L_DOWN] = "joy_l_down",
+	[JOY_L_DOWN_LEFT] = "joy_l_down_left",
+	[JOY_L_LEFT] = "joy_l_left",
+	[JOY_L_UP_LEFT] = "joy_l_up_left",
+	[JOY_R_UP] = "joy_r_up",
+	[JOY_R_UP_RIGHT] = "joy_r_up_right",
+	[JOY_R_RIGHT] = "joy_r_right",
+	[JOY_R_DOWN_RIGHT] = "joy_r_down_right",
+	[JOY_R_DOWN] = "joy_r_down",
+	[JOY_R_DOWN_LEFT] = "joy_r_down_left",
+	[JOY_R_LEFT] = "joy_r_left",
+	[JOY_R_UP_LEFT] = "joy_r_up_left",
+	[BUTTON_GUIDE] = "guide",
+};
+
+enum oxp_custom_button_index {
+	BUTTON_M1 = 0x22,
+	BUTTON_M2,
+	/* These are unused currently, reserved for future devices */
+	BUTTON_M3,
+	BUTTON_M4,
+	BUTTON_M5,
+	BUTTON_M6,
+};
+
+struct oxp_button {
+	u8 index;
+	u8 mode;
+	u8 mapping;
+	u8 padding[3];
+} __packed;
+
+struct oxp_bmap_page_1 {
+	struct oxp_button btn_a;
+	struct oxp_button btn_b;
+	struct oxp_button btn_x;
+	struct oxp_button btn_y;
+	struct oxp_button btn_lb;
+	struct oxp_button btn_rb;
+	struct oxp_button btn_lt;
+	struct oxp_button btn_rt;
+	struct oxp_button btn_start;
+} __packed;
+
+struct oxp_bmap_page_2 {
+	struct oxp_button btn_select;
+	struct oxp_button btn_l3;
+	struct oxp_button btn_r3;
+	struct oxp_button btn_dup;
+	struct oxp_button btn_ddown;
+	struct oxp_button btn_dleft;
+	struct oxp_button btn_dright;
+	struct oxp_button btn_m1;
+	struct oxp_button btn_m2;
+} __packed;
+
 static struct oxp_hid_cfg {
+	struct oxp_bmap_page_1 *bmap_1;
+	struct oxp_bmap_page_2 *bmap_2;
 	struct led_classdev_mc *led_mc;
 	struct hid_device *hdev;
 	struct mutex cfg_mutex; /*ensure single synchronous output report*/
@@ -144,6 +260,10 @@ struct oxp_gen_2_rgb_report {
 	u8 effect;
 } __packed;
 
+struct oxp_button_attr {
+	u8 index;
+};
+
 static u16 get_usage_page(struct hid_device *hdev)
 {
 	return hdev->collection[0].usage >> 16;
@@ -351,9 +471,380 @@ static ssize_t button_takeover_index_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(button_takeover_index);
 
+static void oxp_set_defaults_bmap_1(struct oxp_bmap_page_1 *bmap)
+{
+	bmap->btn_a.index = BUTTON_A;
+	bmap->btn_a.mode = 0x01;
+	bmap->btn_a.mapping = BUTTON_A;
+	bmap->btn_b.index = BUTTON_B;
+	bmap->btn_b.mode = 0x01;
+	bmap->btn_b.mapping = BUTTON_B;
+	bmap->btn_x.index = BUTTON_X;
+	bmap->btn_x.mode = 0x01;
+	bmap->btn_x.mapping = BUTTON_X;
+	bmap->btn_y.index = BUTTON_Y;
+	bmap->btn_y.mode = 0x01;
+	bmap->btn_y.mapping = BUTTON_Y;
+	bmap->btn_lb.index = BUTTON_LB;
+	bmap->btn_lb.mode = 0x01;
+	bmap->btn_lb.mapping = BUTTON_LB;
+	bmap->btn_rb.index = BUTTON_RB;
+	bmap->btn_rb.mode = 0x01;
+	bmap->btn_rb.mapping = BUTTON_RB;
+	bmap->btn_lt.index = BUTTON_LT;
+	bmap->btn_lt.mode = 0x01;
+	bmap->btn_lt.mapping = BUTTON_LT;
+	bmap->btn_rt.index = BUTTON_RT;
+	bmap->btn_rt.mode = 0x01;
+	bmap->btn_rt.mapping = BUTTON_RT;
+	bmap->btn_start.index = BUTTON_START;
+	bmap->btn_start.mode = 0x01;
+	bmap->btn_start.mapping = BUTTON_START;
+}
+
+static void oxp_set_defaults_bmap_2(struct oxp_bmap_page_2 *bmap)
+{
+	bmap->btn_select.index = BUTTON_SELECT;
+	bmap->btn_select.mode = 0x01;
+	bmap->btn_select.mapping = BUTTON_SELECT;
+	bmap->btn_l3.index = BUTTON_L3;
+	bmap->btn_l3.mode = 0x01;
+	bmap->btn_l3.mapping = BUTTON_L3;
+	bmap->btn_r3.index = BUTTON_R3;
+	bmap->btn_r3.mode = 0x01;
+	bmap->btn_r3.mapping = BUTTON_R3;
+	bmap->btn_dup.index = BUTTON_DUP;
+	bmap->btn_dup.mode = 0x01;
+	bmap->btn_dup.mapping = BUTTON_DUP;
+	bmap->btn_ddown.index = BUTTON_DDOWN;
+	bmap->btn_ddown.mode = 0x01;
+	bmap->btn_ddown.mapping = BUTTON_DDOWN;
+	bmap->btn_dleft.index = BUTTON_DLEFT;
+	bmap->btn_dleft.mode = 0x01;
+	bmap->btn_dleft.mapping = BUTTON_DLEFT;
+	bmap->btn_dright.index = BUTTON_DRIGHT;
+	bmap->btn_dright.mode = 0x01;
+	bmap->btn_dright.mapping = BUTTON_DRIGHT;
+	bmap->btn_m1.index = BUTTON_M1;
+	bmap->btn_m1.mode = 0x01;
+	bmap->btn_m1.mapping = BUTTON_LT;
+	bmap->btn_m2.index = BUTTON_M2;
+	bmap->btn_m2.mode = 0x01;
+	bmap->btn_m2.mapping = BUTTON_RT;
+}
+
+static int oxp_set_buttons(void)
+{
+	u8 data[59] = { 0x02, 0x00, 0x00, 0x00, 0x01 };
+	u16 up = get_usage_page(drvdata.hdev);
+	int ret;
+
+	if (up != GEN2_USAGE_PAGE)
+		return -EINVAL;
+
+	memcpy(data + 5, drvdata.bmap_1, sizeof(struct oxp_bmap_page_1));
+	ret = oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, data, ARRAY_SIZE(data));
+	if (ret)
+		return ret;
+
+	memcpy(data + 5, drvdata.bmap_2, sizeof(struct oxp_bmap_page_2));
+	return oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, data, ARRAY_SIZE(data));
+}
+
+static int oxp_reset_buttons(void)
+{
+	oxp_set_defaults_bmap_1(drvdata.bmap_1);
+	oxp_set_defaults_bmap_2(drvdata.bmap_2);
+	return oxp_set_buttons();
+}
+
+static ssize_t reset_buttons_store(struct device *dev,
+				   struct device_attribute *attr, const char *buf,
+				   size_t count)
+{
+	int val, ret;
+
+	ret = kstrtoint(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val != 1)
+		return -EINVAL;
+
+	ret = oxp_reset_buttons();
+	if (ret)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_WO(reset_buttons);
+
+static void oxp_btn_queue_fn(struct work_struct *work)
+{
+	int ret;
+
+	ret = oxp_set_buttons();
+	if (ret)
+		dev_err(&drvdata.hdev->dev,
+			"Error: Failed to write button mapping: %i\n", ret);
+}
+
+static DECLARE_DELAYED_WORK(oxp_btn_queue, oxp_btn_queue_fn);
+
+static ssize_t map_button_store(struct device *dev,
+				struct device_attribute *attr, const char *buf,
+				size_t count, u8 index)
+{
+	int ret;
+	u8 val;
+
+	ret = sysfs_match_string(oxp_joybutton_text, buf);
+	if (ret < 0)
+		return ret;
+
+	val = ret;
+
+	switch (index) {
+	case BUTTON_A:
+		drvdata.bmap_1->btn_a.mapping = val;
+		break;
+	case BUTTON_B:
+		drvdata.bmap_1->btn_b.mapping = val;
+		break;
+	case BUTTON_X:
+		drvdata.bmap_1->btn_x.mapping = val;
+		break;
+	case BUTTON_Y:
+		drvdata.bmap_1->btn_y.mapping = val;
+		break;
+	case BUTTON_LB:
+		drvdata.bmap_1->btn_lb.mapping = val;
+		break;
+	case BUTTON_RB:
+		drvdata.bmap_1->btn_rb.mapping = val;
+		break;
+	case BUTTON_LT:
+		drvdata.bmap_1->btn_lt.mapping = val;
+		break;
+	case BUTTON_RT:
+		drvdata.bmap_1->btn_rt.mapping = val;
+		break;
+	case BUTTON_START:
+		drvdata.bmap_1->btn_start.mapping = val;
+		break;
+	case BUTTON_SELECT:
+		drvdata.bmap_2->btn_select.mapping = val;
+		break;
+	case BUTTON_L3:
+		drvdata.bmap_2->btn_l3.mapping = val;
+		break;
+	case BUTTON_R3:
+		drvdata.bmap_2->btn_r3.mapping = val;
+		break;
+	case BUTTON_DUP:
+		drvdata.bmap_2->btn_dup.mapping = val;
+		break;
+	case BUTTON_DDOWN:
+		drvdata.bmap_2->btn_ddown.mapping = val;
+		break;
+	case BUTTON_DLEFT:
+		drvdata.bmap_2->btn_dleft.mapping = val;
+		break;
+	case BUTTON_DRIGHT:
+		drvdata.bmap_2->btn_dright.mapping = val;
+		break;
+	case BUTTON_M1:
+		drvdata.bmap_2->btn_m1.mapping = val;
+		break;
+	case BUTTON_M2:
+		drvdata.bmap_2->btn_m2.mapping = val;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mod_delayed_work(system_wq, &oxp_btn_queue, msecs_to_jiffies(50));
+	return count;
+}
+
+static ssize_t map_button_show(struct device *dev,
+			       struct device_attribute *attr, char *buf,
+			       u8 index)
+{
+	u8 i;
+
+	switch (index) {
+	case BUTTON_A:
+		i = drvdata.bmap_1->btn_a.mapping;
+		break;
+	case BUTTON_B:
+		i = drvdata.bmap_1->btn_b.mapping;
+		break;
+	case BUTTON_X:
+		i = drvdata.bmap_1->btn_x.mapping;
+		break;
+	case BUTTON_Y:
+		i = drvdata.bmap_1->btn_y.mapping;
+		break;
+	case BUTTON_LB:
+		i = drvdata.bmap_1->btn_lb.mapping;
+		break;
+	case BUTTON_RB:
+		i = drvdata.bmap_1->btn_rb.mapping;
+		break;
+	case BUTTON_LT:
+		i = drvdata.bmap_1->btn_lt.mapping;
+		break;
+	case BUTTON_RT:
+		i = drvdata.bmap_1->btn_rt.mapping;
+		break;
+	case BUTTON_START:
+		i = drvdata.bmap_1->btn_start.mapping;
+		break;
+	case BUTTON_SELECT:
+		i = drvdata.bmap_2->btn_select.mapping;
+		break;
+	case BUTTON_L3:
+		i = drvdata.bmap_2->btn_l3.mapping;
+		break;
+	case BUTTON_R3:
+		i = drvdata.bmap_2->btn_r3.mapping;
+		break;
+	case BUTTON_DUP:
+		i = drvdata.bmap_2->btn_dup.mapping;
+		break;
+	case BUTTON_DDOWN:
+		i = drvdata.bmap_2->btn_ddown.mapping;
+		break;
+	case BUTTON_DLEFT:
+		i = drvdata.bmap_2->btn_dleft.mapping;
+		break;
+	case BUTTON_DRIGHT:
+		i = drvdata.bmap_2->btn_dright.mapping;
+		break;
+	case BUTTON_M1:
+		i = drvdata.bmap_2->btn_m1.mapping;
+		break;
+	case BUTTON_M2:
+		i = drvdata.bmap_2->btn_m2.mapping;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (i >= ARRAY_SIZE(oxp_joybutton_text))
+		return -EINVAL;
+
+	return sysfs_emit(buf, "%s\n", oxp_joybutton_text[i]);
+}
+
+static ssize_t button_mapping_options_show(struct device *dev,
+					   struct device_attribute *attr, char *buf)
+{
+	ssize_t count = 0;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(oxp_joybutton_text); i++)
+		count += sysfs_emit_at(buf, count, "%s ", oxp_joybutton_text[i]);
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+static DEVICE_ATTR_RO(button_mapping_options);
+
+#define OXP_DEVICE_ATTR_RW(_name, _group)                                     \
+	static ssize_t _name##_store(struct device *dev,                      \
+				     struct device_attribute *attr,           \
+				     const char *buf, size_t count)           \
+	{                                                                     \
+		return _group##_store(dev, attr, buf, count, _name.index);    \
+	}                                                                     \
+	static ssize_t _name##_show(struct device *dev,                       \
+				    struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return _group##_show(dev, attr, buf, _name.index);            \
+	}                                                                     \
+	static DEVICE_ATTR_RW(_name)
+
+static struct oxp_button_attr button_a = { BUTTON_A };
+OXP_DEVICE_ATTR_RW(button_a, map_button);
+
+static struct oxp_button_attr button_b = { BUTTON_B };
+OXP_DEVICE_ATTR_RW(button_b, map_button);
+
+static struct oxp_button_attr button_x = { BUTTON_X };
+OXP_DEVICE_ATTR_RW(button_x, map_button);
+
+static struct oxp_button_attr button_y = { BUTTON_Y };
+OXP_DEVICE_ATTR_RW(button_y, map_button);
+
+static struct oxp_button_attr button_lb = { BUTTON_LB };
+OXP_DEVICE_ATTR_RW(button_lb, map_button);
+
+static struct oxp_button_attr button_rb = { BUTTON_RB };
+OXP_DEVICE_ATTR_RW(button_rb, map_button);
+
+static struct oxp_button_attr button_lt = { BUTTON_LT };
+OXP_DEVICE_ATTR_RW(button_lt, map_button);
+
+static struct oxp_button_attr button_rt = { BUTTON_RT };
+OXP_DEVICE_ATTR_RW(button_rt, map_button);
+
+static struct oxp_button_attr button_start = { BUTTON_START };
+OXP_DEVICE_ATTR_RW(button_start, map_button);
+
+static struct oxp_button_attr button_select = { BUTTON_SELECT };
+OXP_DEVICE_ATTR_RW(button_select, map_button);
+
+static struct oxp_button_attr button_l3 = { BUTTON_L3 };
+OXP_DEVICE_ATTR_RW(button_l3, map_button);
+
+static struct oxp_button_attr button_r3 = { BUTTON_R3 };
+OXP_DEVICE_ATTR_RW(button_r3, map_button);
+
+static struct oxp_button_attr button_d_up = { BUTTON_DUP };
+OXP_DEVICE_ATTR_RW(button_d_up, map_button);
+
+static struct oxp_button_attr button_d_down = { BUTTON_DDOWN };
+OXP_DEVICE_ATTR_RW(button_d_down, map_button);
+
+static struct oxp_button_attr button_d_left = { BUTTON_DLEFT };
+OXP_DEVICE_ATTR_RW(button_d_left, map_button);
+
+static struct oxp_button_attr button_d_right = { BUTTON_DRIGHT };
+OXP_DEVICE_ATTR_RW(button_d_right, map_button);
+
+static struct oxp_button_attr button_m1 = { BUTTON_M1 };
+OXP_DEVICE_ATTR_RW(button_m1, map_button);
+
+static struct oxp_button_attr button_m2 = { BUTTON_M2 };
+OXP_DEVICE_ATTR_RW(button_m2, map_button);
+
 static struct attribute *oxp_cfg_attrs[] = {
+	&dev_attr_button_a.attr,
+	&dev_attr_button_b.attr,
+	&dev_attr_button_d_down.attr,
+	&dev_attr_button_d_left.attr,
+	&dev_attr_button_d_right.attr,
+	&dev_attr_button_d_up.attr,
+	&dev_attr_button_l3.attr,
+	&dev_attr_button_lb.attr,
+	&dev_attr_button_lt.attr,
+	&dev_attr_button_m1.attr,
+	&dev_attr_button_m2.attr,
+	&dev_attr_button_mapping_options.attr,
+	&dev_attr_button_r3.attr,
+	&dev_attr_button_rb.attr,
+	&dev_attr_button_rt.attr,
+	&dev_attr_button_select.attr,
+	&dev_attr_button_start.attr,
 	&dev_attr_button_takeover.attr,
 	&dev_attr_button_takeover_index.attr,
+	&dev_attr_button_x.attr,
+	&dev_attr_button_y.attr,
+	&dev_attr_reset_buttons.attr,
 	NULL,
 };
 
@@ -729,6 +1220,8 @@ static struct led_classdev_mc oxp_cdev_rgb = {
 
 static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
 {
+	struct oxp_bmap_page_1 *bmap_1;
+	struct oxp_bmap_page_2 *bmap_2;
 	int ret;
 
 	hid_set_drvdata(hdev, &drvdata);
@@ -756,6 +1249,23 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
 	if (up != GEN2_USAGE_PAGE)
 		return 0;
 
+	bmap_1 = devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_1), GFP_KERNEL);
+	if (!bmap_1)
+		return dev_err_probe(&hdev->dev, -ENOMEM,
+				     "Unable to allocate button map page 1\n");
+
+	bmap_2 = devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_2), GFP_KERNEL);
+	if (!bmap_2)
+		return dev_err_probe(&hdev->dev, -ENOMEM,
+				     "Unable to allocate button map page 2\n");
+
+	drvdata.bmap_1 = bmap_1;
+	drvdata.bmap_2 = bmap_2;
+	ret = oxp_reset_buttons();
+	if (ret)
+		return dev_err_probe(&hdev->dev, ret,
+				     "Failed to reset button mapping\n");
+
 	ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group);
 	if (ret)
 		return dev_err_probe(&hdev->dev, ret,
-- 
2.53.0


^ permalink raw reply related

* [PATCH 3/4] HID: hid-oxp: Add Second Generation Takeover Mode
From: Derek J. Clark @ 2026-03-22  3:16 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, Derek J . Clark,
	linux-input, linux-doc, linux-kernel
In-Reply-To: <20260322031615.1524307-1-derekjohn.clark@gmail.com>

Adds "takeover_enabled" attribute to second generation OneXPlayer
configuration HID devices. This attribute initiates a mode shift in the
device MCU that puts it into a state where all events are routed to an
hidraw interface instead of the xpad evdev interface. This allows for
debugging the hardware input mapping, and allows some userspace tools to
consume the interface to add support for features that are unable to be
exposed through the evdev, such as treating the M1 and M2 accessory
buttons as unique inputs.

Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
 drivers/hid/hid-oxp.c | 81 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index 587e0d57c85f..5fed2799a2ad 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -32,6 +32,7 @@
 enum oxp_function_index {
 	OXP_FID_GEN1_RGB_SET =		0x07,
 	OXP_FID_GEN1_RGB_REPLY =	0x0f,
+	OXP_FID_GEN2_TOGGLE_MODE =	0xb2,
 	OXP_FID_GEN2_RGB_EVENT =	0xb8,
 };
 
@@ -39,12 +40,15 @@ static struct oxp_hid_cfg {
 	struct led_classdev_mc *led_mc;
 	struct hid_device *hdev;
 	struct mutex cfg_mutex; /*ensure single synchronous output report*/
+	u8 takeover_enabled;
 	u8 rgb_brightness;
 	u8 rgb_effect;
 	u8 rgb_speed;
 	u8 rgb_en;
 } drvdata;
 
+#define OXP_TAKEOVER_ENABLED_TRUE 0x03
+
 enum oxp_feature_en_index {
 	OXP_FEAT_DISABLED,
 	OXP_FEAT_ENABLED,
@@ -289,6 +293,74 @@ static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data,
 				footer_size);
 }
 
+static ssize_t button_takeover_store(struct device *dev,
+				     struct device_attribute *attr, const char *buf,
+				     size_t count)
+{
+	u16 up = get_usage_page(drvdata.hdev);
+	u8 data[3] = { 0x00, 0x01, 0x02 };
+	u8 val = 0;
+	int ret;
+
+	if (up != GEN2_USAGE_PAGE)
+		return -EINVAL;
+
+	ret = sysfs_match_string(oxp_feature_en_text, buf);
+	if (ret < 0)
+		return ret;
+	val = ret;
+
+	switch (val) {
+	case OXP_FEAT_DISABLED:
+		break;
+	case OXP_FEAT_ENABLED:
+		data[0] = OXP_TAKEOVER_ENABLED_TRUE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, data, 3);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t button_takeover_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%s\n", oxp_feature_en_text[drvdata.takeover_enabled]);
+}
+static DEVICE_ATTR_RW(button_takeover);
+
+static ssize_t button_takeover_index_show(struct device *dev,
+					  struct device_attribute *attr,
+					  char *buf)
+{
+	ssize_t count = 0;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(oxp_feature_en_text); i++)
+		count += sysfs_emit_at(buf, count, "%s ", oxp_feature_en_text[i]);
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+static DEVICE_ATTR_RO(button_takeover_index);
+
+static struct attribute *oxp_cfg_attrs[] = {
+	&dev_attr_button_takeover.attr,
+	&dev_attr_button_takeover_index.attr,
+	NULL,
+};
+
+static const struct attribute_group oxp_cfg_attrs_group = {
+	.attrs = oxp_cfg_attrs,
+};
+
 static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
 {
 	u16 up = get_usage_page(drvdata.hdev);
@@ -680,6 +752,15 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
 		dev_warn(drvdata.led_mc->led_cdev.dev,
 			 "Failed to query RGB initial state: %i\n", ret);
 
+	/* Below features are only implemented in gen 2 */
+	if (up != GEN2_USAGE_PAGE)
+		return 0;
+
+	ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group);
+	if (ret)
+		return dev_err_probe(&hdev->dev, ret,
+				     "Failed to attach configuration attributes\n");
+
 	return 0;
 }
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH 1/4] HID: hid-oxp: Add OneXPlayer configuration driver
From: Derek J. Clark @ 2026-03-22  3:16 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, Derek J . Clark,
	linux-input, linux-doc, linux-kernel
In-Reply-To: <20260322031615.1524307-1-derekjohn.clark@gmail.com>

Adds OneXPlayer HID configuration driver. In this initial driver patch,
add the RGB interface for the first generation of HID based RGB control.

This interface provides the following attributes:
- brightness: provided by the LED core, this works in a fairly unique
  way on this device. The hardware accepts 5 brightness values (0-4),
  which affects the brightness of the multicolor and animated effects
  built into the MCU firmware. For monocolor settings, the device
  expects the hardware brightness value to be pushed to maximum, then we
  apply brightness adjustments mathematically based on % (0-100). This
  leads to some odd conversion as we need the brightness slider to reach
  the full range, but it has no affect when incrementing between the
  division points for other effects.
- multi-intensity: provided by the LED core for red, green, and blue.
- effect: Allows the MCU to set 19 individual effects.
- effect_index: Lists the 19 valid effect names for the interface.
- enabled: Allows the MCU to toggle the RGB interface on/off.
- enabled_index: Lists the valid states for enabled.
- speed: Allows the MCU to set the animation rate for the various
  effects.
- speed_range: Lists the valid range of speed (0-9).

The MCU also has a few odd quirks that make sending multiple synchronous
events challenging. It will essentially freeze if it receives another
message before it has finished processing the last command. It also will
not reply if you wait on it using a completion. To get around this, we
do a 200ms sleep inside a work queue thread and debounce all but the most
recent message using a 50ms mod_delayed_work. This will cache the last
write, queue the work, then return so userspace can release its write
thread. The work queue is only used for brightness/multi-intensity as
that is the path likely to receive rapid successive writes.

Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
 MAINTAINERS           |   6 +
 drivers/hid/Kconfig   |  12 +
 drivers/hid/Makefile  |   1 +
 drivers/hid/hid-ids.h |   3 +
 drivers/hid/hid-oxp.c | 652 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 674 insertions(+)
 create mode 100644 drivers/hid/hid-oxp.c

diff --git a/MAINTAINERS b/MAINTAINERS
index eeb8fcfa32eb..ba44ab2452be 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19228,6 +19228,12 @@ S:	Maintained
 F:	drivers/mtd/nand/onenand/
 F:	include/linux/mtd/onenand*.h
 
+ONEXPLAYER HID DRIVER
+M:	Derek J. Clark <derekjohn.clark@gmail.com>
+L:	linux-input@vger.kernel.org
+S:	Maintained
+F:	drivers/hid/hid-oxp.c
+
 ONEXPLAYER PLATFORM EC DRIVER
 M:	Antheas Kapenekakis <lkml@antheas.dev>
 M:	Derek John Clark <derekjohn.clark@gmail.com>
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index a797549b580e..42af8fc15476 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -920,6 +920,18 @@ config HID_ORTEK
 	   - Ortek WKB-2000
 	   - Skycable wireless presenter
 
+config HID_OXP
+	tristate "OneXPlayer handheld controller configuration support"
+	depends on USB_HID
+	depends on LEDS_CLASS
+	depends on LEDS_CLASS_MULTICOLOR
+	help
+	  Say Y here if you would like to enable support for OneXPlayer handheld
+	  devices that come with RGB LED rings around the joysticks and macro buttons.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called hid-oxp.
+
 config HID_PANTHERLORD
 	tristate "Pantherlord/GreenAsia game controller"
 	help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 95fac34e8499..52e26a1d9df7 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -99,6 +99,7 @@ obj-$(CONFIG_HID_NTI)			+= hid-nti.o
 obj-$(CONFIG_HID_NTRIG)		+= hid-ntrig.o
 obj-$(CONFIG_HID_NVIDIA_SHIELD)	+= hid-nvidia-shield.o
 obj-$(CONFIG_HID_ORTEK)		+= hid-ortek.o
+obj-$(CONFIG_HID_OXP)		+= hid-oxp.o
 obj-$(CONFIG_HID_PRODIKEYS)	+= hid-prodikeys.o
 obj-$(CONFIG_HID_PANTHERLORD)	+= hid-pl.o
 obj-$(CONFIG_HID_PENMOUNT)	+= hid-penmount.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index f01020569dea..8b272d1ab9ba 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1102,6 +1102,9 @@
 #define USB_VENDOR_ID_NVIDIA				0x0955
 #define USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER	0x7214
 
+#define USB_VENDOR_ID_CRSC			0x1a2c
+#define USB_DEVICE_ID_ONEXPLAYER_GEN1		0xb001
+
 #define USB_VENDOR_ID_ONTRAK		0x0a07
 #define USB_DEVICE_ID_ONTRAK_ADU100	0x0064
 
diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
new file mode 100644
index 000000000000..391de2798320
--- /dev/null
+++ b/drivers/hid/hid-oxp.c
@@ -0,0 +1,652 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  HID driver for OneXPlayer gamepad configuration devices.
+ *
+ *  Copyright (c) 2026 Valve Corporation
+ */
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/jiffies.h>
+#include <linux/kstrtox.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include "hid-ids.h"
+
+#define OXP_PACKET_SIZE 64
+
+#define GEN1_MESSAGE_ID	0xff
+
+#define GEN1_USAGE_PAGE	0xff01
+
+enum oxp_function_index {
+	OXP_FID_GEN1_RGB_SET =		0x07,
+	OXP_FID_GEN1_RGB_REPLY =	0x0f,
+};
+
+static struct oxp_hid_cfg {
+	struct led_classdev_mc *led_mc;
+	struct hid_device *hdev;
+	struct mutex cfg_mutex; /*ensure single synchronous output report*/
+	u8 rgb_brightness;
+	u8 rgb_effect;
+	u8 rgb_speed;
+	u8 rgb_en;
+} drvdata;
+
+enum oxp_feature_en_index {
+	OXP_FEAT_DISABLED,
+	OXP_FEAT_ENABLED,
+};
+
+static const char *const oxp_feature_en_text[] = {
+	[OXP_FEAT_DISABLED] = "false",
+	[OXP_FEAT_ENABLED] = "true",
+};
+
+enum oxp_rgb_effect_index {
+	OXP_UNKNOWN,
+	OXP_EFFECT_AURORA,
+	OXP_EFFECT_BIRTHDAY,
+	OXP_EFFECT_FLOWING,
+	OXP_EFFECT_CHROMA_1,
+	OXP_EFFECT_NEON,
+	OXP_EFFECT_CHROMA_2,
+	OXP_EFFECT_DREAMY,
+	OXP_EFFECT_WARM,
+	OXP_EFFECT_CYBERPUNK,
+	OXP_EFFECT_SEA,
+	OXP_EFFECT_SUNSET,
+	OXP_EFFECT_COLORFUL,
+	OXP_EFFECT_MONSTER,
+	OXP_EFFECT_GREEN,
+	OXP_EFFECT_BLUE,
+	OXP_EFFECT_YELLOW,
+	OXP_EFFECT_TEAL,
+	OXP_EFFECT_PURPLE,
+	OXP_EFFECT_FOGGY,
+	OXP_EFFECT_MONO_LIST, /* placeholder for effect_index_show */
+};
+
+/* These belong to rgb_effect_index, but we want to hide them from
+ * rgb_effect_text
+ */
+
+#define OXP_GET_PROPERTY 0xfc
+#define OXP_SET_PROPERTY 0xfd
+#define OXP_EFFECT_MONO_TRUE 0xfe /* actual index for monocolor */
+
+static const char *const oxp_rgb_effect_text[] = {
+	[OXP_UNKNOWN] = "unknown",
+	[OXP_EFFECT_AURORA] = "aurora",
+	[OXP_EFFECT_BIRTHDAY] = "birthday_cake",
+	[OXP_EFFECT_FLOWING] = "flowing_light",
+	[OXP_EFFECT_CHROMA_1] = "chroma_popping",
+	[OXP_EFFECT_NEON] = "neon",
+	[OXP_EFFECT_CHROMA_2] = "chroma_breathing",
+	[OXP_EFFECT_DREAMY] = "dreamy",
+	[OXP_EFFECT_WARM] = "warm_sun",
+	[OXP_EFFECT_CYBERPUNK] = "cyberpunk",
+	[OXP_EFFECT_SEA] = "sea_foam",
+	[OXP_EFFECT_SUNSET] = "sunset_afterglow",
+	[OXP_EFFECT_COLORFUL] = "colorful",
+	[OXP_EFFECT_MONSTER] = "monster_woke",
+	[OXP_EFFECT_GREEN] = "green_breathing",
+	[OXP_EFFECT_BLUE] = "blue_breathing",
+	[OXP_EFFECT_YELLOW] = "yellow_breathing",
+	[OXP_EFFECT_TEAL] = "teal_breathing",
+	[OXP_EFFECT_PURPLE] = "purple_breathing",
+	[OXP_EFFECT_FOGGY] = "foggy_haze",
+	[OXP_EFFECT_MONO_LIST] = "monocolor",
+};
+
+struct oxp_gen_1_rgb_report {
+	u8 report_id;
+	u8 message_id;
+	u8 padding_2[2];
+	u8 effect;
+	u8 enabled;
+	u8 speed;
+	u8 brightness;
+	u8 red;
+	u8 green;
+	u8 blue;
+} __packed;
+
+static u16 get_usage_page(struct hid_device *hdev)
+{
+	return hdev->collection[0].usage >> 16;
+}
+
+static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
+				   struct hid_report *report, u8 *data,
+				   int size)
+{
+	struct led_classdev_mc *led_mc = drvdata.led_mc;
+	struct oxp_gen_1_rgb_report *rgb_rep;
+
+	if (data[1] != OXP_FID_GEN1_RGB_REPLY)
+		return 0;
+
+	rgb_rep = (struct oxp_gen_1_rgb_report *)data;
+	/* Ensure we save monocolor as the list value */
+	drvdata.rgb_effect =
+		rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
+				   OXP_EFFECT_MONO_LIST :
+				   rgb_rep->effect;
+	drvdata.rgb_speed = rgb_rep->speed;
+	drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
+						 OXP_FEAT_ENABLED;
+	drvdata.rgb_brightness = rgb_rep->brightness;
+	led_mc->led_cdev.brightness = rgb_rep->brightness / 4 *
+				      led_mc->led_cdev.max_brightness;
+	/* If monocolor had less than 100% brightness on the previous boot,
+	 * there will be no reliable way to determine the real intensity.
+	 * Since intensity scaling is used with a hardware brightness set at max,
+	 * our brightness will always look like 100%. Use the last set value to
+	 * prevent successive boots from lowering the brightness further.
+	 * Brightness will be "wrong" but the effect will remain the same visually.
+	 */
+	led_mc->subled_info[0].intensity = rgb_rep->red;
+	led_mc->subled_info[1].intensity = rgb_rep->green;
+	led_mc->subled_info[2].intensity = rgb_rep->blue;
+
+	return 0;
+}
+
+static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report,
+			     u8 *data, int size)
+{
+	u16 up = get_usage_page(hdev);
+
+	dev_dbg(&hdev->dev, "raw event data: [%*ph]\n", OXP_PACKET_SIZE, data);
+
+	switch (up) {
+	case GEN1_USAGE_PAGE:
+		return oxp_hid_raw_event_gen_1(hdev, report, data, size);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int mcu_property_out(u8 *header, size_t header_size, u8 *data,
+			    size_t data_size, u8 *footer, size_t footer_size)
+{
+	unsigned char *dmabuf __free(kfree) = kzalloc(OXP_PACKET_SIZE, GFP_KERNEL);
+	int ret;
+
+	if (!dmabuf)
+		return -ENOMEM;
+
+	if (header_size + data_size + footer_size > OXP_PACKET_SIZE)
+		return -EINVAL;
+
+	guard(mutex)(&drvdata.cfg_mutex);
+	memcpy(dmabuf, header, header_size);
+	memcpy(dmabuf + header_size, data, data_size);
+	if (footer_size)
+		memcpy(dmabuf + OXP_PACKET_SIZE - footer_size, footer, footer_size);
+
+	dev_dbg(&drvdata.hdev->dev, "raw data: [%*ph]\n", OXP_PACKET_SIZE, dmabuf);
+
+	ret = hid_hw_output_report(drvdata.hdev, dmabuf, OXP_PACKET_SIZE);
+	if (ret < 0)
+		return ret;
+
+	/* MCU takes 200ms to be ready for another command. */
+	msleep(200);
+	return ret == OXP_PACKET_SIZE ? 0 : -EIO;
+}
+
+static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data,
+				  u8 data_size)
+{
+	u8 header[] = { fid, GEN1_MESSAGE_ID };
+	size_t header_size = ARRAY_SIZE(header);
+
+	return mcu_property_out(header, header_size, data, data_size, NULL, 0);
+}
+
+static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
+{
+	u16 up = get_usage_page(drvdata.hdev);
+	u8 *data;
+
+	/* Always default to max brightness and use intensity scaling when in
+	 * monocolor mode.
+	 */
+	switch (up) {
+	case GEN1_USAGE_PAGE:
+		data = (u8[4]) { OXP_SET_PROPERTY, enabled, speed, brightness };
+		if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
+			data[3] = 0x04;
+		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4);
+	default:
+		return -ENODEV;
+	}
+}
+
+static ssize_t oxp_rgb_status_show(void)
+{
+	u16 up = get_usage_page(drvdata.hdev);
+	u8 *data;
+
+	switch (up) {
+	case GEN1_USAGE_PAGE:
+		data = (u8[1]) { OXP_GET_PROPERTY };
+		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
+	default:
+		return -ENODEV;
+	}
+}
+
+static int oxp_rgb_color_set(void)
+{
+	u8 max_br = drvdata.led_mc->led_cdev.max_brightness;
+	u8 br = drvdata.led_mc->led_cdev.brightness;
+	u16 up = get_usage_page(drvdata.hdev);
+	u8 green, red, blue;
+	size_t size;
+	u8 *data;
+	int i;
+
+	red = br * drvdata.led_mc->subled_info[0].intensity / max_br;
+	green = br * drvdata.led_mc->subled_info[1].intensity / max_br;
+	blue = br * drvdata.led_mc->subled_info[2].intensity / max_br;
+
+	switch (up) {
+	case GEN1_USAGE_PAGE:
+		size = 55;
+		data = (u8[55]) { OXP_EFFECT_MONO_TRUE };
+
+		for (i = 0; i < (size - 1) / 3; i++) {
+			data[3 * i + 1] = red;
+			data[3 * i + 2] = green;
+			data[3 * i + 3] = blue;
+		}
+		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size);
+	default:
+		return -ENODEV;
+	}
+}
+
+static int oxp_rgb_effect_set(u8 effect)
+{
+	u16 up = get_usage_page(drvdata.hdev);
+	u8 *data;
+	int ret;
+
+	switch (effect) {
+	case OXP_EFFECT_AURORA:
+	case OXP_EFFECT_BIRTHDAY:
+	case OXP_EFFECT_FLOWING:
+	case OXP_EFFECT_CHROMA_1:
+	case OXP_EFFECT_NEON:
+	case OXP_EFFECT_CHROMA_2:
+	case OXP_EFFECT_DREAMY:
+	case OXP_EFFECT_WARM:
+	case OXP_EFFECT_CYBERPUNK:
+	case OXP_EFFECT_SEA:
+	case OXP_EFFECT_SUNSET:
+	case OXP_EFFECT_COLORFUL:
+	case OXP_EFFECT_MONSTER:
+	case OXP_EFFECT_GREEN:
+	case OXP_EFFECT_BLUE:
+	case OXP_EFFECT_YELLOW:
+	case OXP_EFFECT_TEAL:
+	case OXP_EFFECT_PURPLE:
+	case OXP_EFFECT_FOGGY:
+		switch (up) {
+		case GEN1_USAGE_PAGE:
+			data = (u8[1]) { effect };
+			ret = oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
+			break;
+		default:
+			ret = -ENODEV;
+		}
+		break;
+	case OXP_EFFECT_MONO_LIST:
+		ret = oxp_rgb_color_set();
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (ret)
+		return ret;
+
+	drvdata.rgb_effect = effect;
+
+	return 0;
+}
+
+static ssize_t enabled_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	int ret;
+	u8 val;
+
+	ret = sysfs_match_string(oxp_feature_en_text, buf);
+	if (ret < 0)
+		return ret;
+	val = ret;
+
+	ret = oxp_rgb_status_store(val, drvdata.rgb_speed,
+				   drvdata.rgb_brightness);
+	if (ret)
+		return ret;
+
+	drvdata.rgb_en = val;
+	return count;
+}
+
+static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	int ret;
+
+	ret = oxp_rgb_status_show();
+	if (ret)
+		return ret;
+
+	if (drvdata.rgb_en >= ARRAY_SIZE(oxp_feature_en_text))
+		return -EINVAL;
+
+	return sysfs_emit(buf, "%s\n", oxp_feature_en_text[drvdata.rgb_en]);
+}
+static DEVICE_ATTR_RW(enabled);
+
+static ssize_t enabled_index_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	size_t count = 0;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(oxp_feature_en_text); i++)
+		count += sysfs_emit_at(buf, count, "%s ", oxp_feature_en_text[i]);
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+static DEVICE_ATTR_RO(enabled_index);
+
+static ssize_t effect_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	int ret;
+	u8 val;
+
+	ret = sysfs_match_string(oxp_rgb_effect_text, buf);
+	if (ret < 0)
+		return ret;
+
+	val = ret;
+
+	ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed,
+				   drvdata.rgb_brightness);
+	if (ret)
+		return ret;
+
+	ret = oxp_rgb_effect_set(val);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t effect_show(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	int ret;
+
+	ret = oxp_rgb_status_show();
+	if (ret)
+		return ret;
+
+	if (drvdata.rgb_effect >= ARRAY_SIZE(oxp_rgb_effect_text))
+		return -EINVAL;
+
+	return sysfs_emit(buf, "%s\n", oxp_rgb_effect_text[drvdata.rgb_effect]);
+}
+
+static DEVICE_ATTR_RW(effect);
+
+static ssize_t effect_index_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	size_t count = 0;
+	unsigned int i;
+
+	for (i = 1; i < ARRAY_SIZE(oxp_rgb_effect_text); i++)
+		count += sysfs_emit_at(buf, count, "%s ", oxp_rgb_effect_text[i]);
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+static DEVICE_ATTR_RO(effect_index);
+
+static ssize_t speed_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	int ret;
+	u8 val;
+
+	ret = kstrtou8(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val > 9)
+		return -EINVAL;
+
+	ret = oxp_rgb_status_store(drvdata.rgb_en, val, drvdata.rgb_brightness);
+	if (ret)
+		return ret;
+
+	drvdata.rgb_speed = val;
+	return count;
+}
+
+static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	int ret;
+
+	ret = oxp_rgb_status_show();
+	if (ret)
+		return ret;
+
+	if (drvdata.rgb_speed > 9)
+		return -EINVAL;
+
+	return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed);
+}
+static DEVICE_ATTR_RW(speed);
+
+static ssize_t speed_range_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "0-9\n");
+}
+static DEVICE_ATTR_RO(speed_range);
+
+static void oxp_rgb_queue_fn(struct work_struct *work)
+{
+	unsigned int max_brightness = drvdata.led_mc->led_cdev.max_brightness;
+	unsigned int brightness = drvdata.led_mc->led_cdev.brightness;
+	u8 val = 4 * brightness / max_brightness;
+	int ret;
+
+	if (drvdata.rgb_brightness != val) {
+		ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, val);
+		if (ret)
+			dev_err(drvdata.led_mc->led_cdev.dev,
+				"Error: Failed to write RGB Status: %i\n", ret);
+
+		drvdata.rgb_brightness = val;
+	}
+
+	if (drvdata.rgb_effect != OXP_EFFECT_MONO_LIST)
+		return;
+
+	ret = oxp_rgb_effect_set(drvdata.rgb_effect);
+	if (ret)
+		dev_err(drvdata.led_mc->led_cdev.dev, "Error: Failed to write RGB color: %i\n",
+			ret);
+}
+
+static DECLARE_DELAYED_WORK(oxp_rgb_queue, oxp_rgb_queue_fn);
+
+static void oxp_rgb_brightness_set(struct led_classdev *led_cdev,
+				   enum led_brightness brightness)
+{
+	led_cdev->brightness = brightness;
+	mod_delayed_work(system_wq, &oxp_rgb_queue, msecs_to_jiffies(50));
+}
+
+static struct attribute *oxp_rgb_attrs[] = {
+	&dev_attr_effect.attr,
+	&dev_attr_effect_index.attr,
+	&dev_attr_enabled.attr,
+	&dev_attr_enabled_index.attr,
+	&dev_attr_speed.attr,
+	&dev_attr_speed_range.attr,
+	NULL,
+};
+
+static const struct attribute_group oxp_rgb_attr_group = {
+	.attrs = oxp_rgb_attrs,
+};
+
+static struct mc_subled oxp_rgb_subled_info[] = {
+	{
+		.color_index = LED_COLOR_ID_RED,
+		.intensity = 0x24,
+		.channel = 0x1,
+	},
+	{
+		.color_index = LED_COLOR_ID_GREEN,
+		.intensity = 0x22,
+		.channel = 0x2,
+	},
+	{
+		.color_index = LED_COLOR_ID_BLUE,
+		.intensity = 0x99,
+		.channel = 0x3,
+	},
+};
+
+static struct led_classdev_mc oxp_cdev_rgb = {
+	.led_cdev = {
+		.name = "oxp:rgb:joystick_rings",
+		.color = LED_COLOR_ID_RGB,
+		.brightness = 0x64,
+		.max_brightness = 0x64,
+		.brightness_set = oxp_rgb_brightness_set,
+	},
+	.num_colors = ARRAY_SIZE(oxp_rgb_subled_info),
+	.subled_info = oxp_rgb_subled_info,
+};
+
+static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
+{
+	int ret;
+
+	hid_set_drvdata(hdev, &drvdata);
+	drvdata.hdev = hdev;
+	drvdata.led_mc = &oxp_cdev_rgb;
+	mutex_init(&drvdata.cfg_mutex);
+
+	ret = devm_led_classdev_multicolor_register(&hdev->dev, &oxp_cdev_rgb);
+	if (ret)
+		return dev_err_probe(&hdev->dev, ret,
+				     "Failed to create RGB device\n");
+
+	ret = devm_device_add_group(drvdata.led_mc->led_cdev.dev,
+				    &oxp_rgb_attr_group);
+	if (ret)
+		return dev_err_probe(drvdata.led_mc->led_cdev.dev, ret,
+				     "Failed to create RGB configuration attributes\n");
+
+	ret = oxp_rgb_status_show();
+	if (ret)
+		dev_warn(drvdata.led_mc->led_cdev.dev,
+			 "Failed to query RGB initial state: %i\n", ret);
+
+	return 0;
+}
+
+static int oxp_hid_probe(struct hid_device *hdev,
+			 const struct hid_device_id *id)
+{
+	int ret;
+	u16 up;
+
+	ret = hid_parse(hdev);
+	if (ret)
+		return dev_err_probe(&hdev->dev, ret, "Failed to parse HID device\n");
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret)
+		return dev_err_probe(&hdev->dev, ret, "Failed to start HID device\n");
+
+	ret = hid_hw_open(hdev);
+	if (ret) {
+		hid_hw_stop(hdev);
+		return dev_err_probe(&hdev->dev, ret, "Failed to open HID device\n");
+	}
+
+	up = get_usage_page(hdev);
+	dev_dbg(&hdev->dev, "Got usage page %04x\n", up);
+
+	switch (up) {
+	case GEN1_USAGE_PAGE:
+		ret = oxp_cfg_probe(hdev, up);
+		if (ret) {
+			hid_hw_close(hdev);
+			hid_hw_stop(hdev);
+		}
+
+		return ret;
+	default:
+		return 0;
+	}
+}
+
+static void oxp_hid_remove(struct hid_device *hdev)
+{
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id oxp_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) },
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, oxp_devices);
+static struct hid_driver hid_oxp = {
+	.name = "hid-oxp",
+	.id_table = oxp_devices,
+	.probe = oxp_hid_probe,
+	.remove = oxp_hid_remove,
+	.raw_event = oxp_hid_raw_event,
+};
+module_hid_driver(hid_oxp);
+
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Driver for OneXPlayer HID Interfaces");
+MODULE_LICENSE("GPL");
-- 
2.53.0


^ permalink raw reply related

* [PATCH 2/4] HID: hid-oxp: Add Second Generation RGB Control
From: Derek J. Clark @ 2026-03-22  3:16 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, Derek J . Clark,
	linux-input, linux-doc, linux-kernel
In-Reply-To: <20260322031615.1524307-1-derekjohn.clark@gmail.com>

Adds support for the second generation of RGB Control for OneXPlayer
devices. The interface mirrors the first generation, with some
differences to how messages are formatted.

Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
 drivers/hid/hid-ids.h |  3 ++
 drivers/hid/hid-oxp.c | 96 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 99 insertions(+)

diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 8b272d1ab9ba..b33782ba6556 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1105,6 +1105,9 @@
 #define USB_VENDOR_ID_CRSC			0x1a2c
 #define USB_DEVICE_ID_ONEXPLAYER_GEN1		0xb001
 
+#define USB_VENDOR_ID_WCH			0x1a86
+#define USB_DEVICE_ID_ONEXPLAYER_GEN2		0xfe00
+
 #define USB_VENDOR_ID_ONTRAK		0x0a07
 #define USB_DEVICE_ID_ONTRAK_ADU100	0x0064
 
diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index 391de2798320..587e0d57c85f 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -24,12 +24,15 @@
 #define OXP_PACKET_SIZE 64
 
 #define GEN1_MESSAGE_ID	0xff
+#define GEN2_MESSAGE_ID	0x3f
 
 #define GEN1_USAGE_PAGE	0xff01
+#define GEN2_USAGE_PAGE	0xff00
 
 enum oxp_function_index {
 	OXP_FID_GEN1_RGB_SET =		0x07,
 	OXP_FID_GEN1_RGB_REPLY =	0x0f,
+	OXP_FID_GEN2_RGB_EVENT =	0xb8,
 };
 
 static struct oxp_hid_cfg {
@@ -121,6 +124,22 @@ struct oxp_gen_1_rgb_report {
 	u8 blue;
 } __packed;
 
+struct oxp_gen_2_rgb_report {
+	u8 report_id;
+	u8 header_id;
+	u8 padding_2;
+	u8 message_id;
+	u8 padding_4[2];
+	u8 enabled;
+	u8 speed;
+	u8 brightness;
+	u8 red;
+	u8 green;
+	u8 blue;
+	u8 padding_12[3];
+	u8 effect;
+} __packed;
+
 static u16 get_usage_page(struct hid_device *hdev)
 {
 	return hdev->collection[0].usage >> 16;
@@ -162,6 +181,45 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
 	return 0;
 }
 
+static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
+				   struct hid_report *report, u8 *data,
+				   int size)
+{
+	struct led_classdev_mc *led_mc = drvdata.led_mc;
+	struct oxp_gen_2_rgb_report *rgb_rep;
+
+	if (data[0] != OXP_FID_GEN2_RGB_EVENT)
+		return 0;
+
+	if (data[3] != OXP_GET_PROPERTY)
+		return 0;
+
+	rgb_rep = (struct oxp_gen_2_rgb_report *)data;
+	/* Ensure we save monocolor as the list value */
+	drvdata.rgb_effect =
+		rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
+				   OXP_EFFECT_MONO_LIST :
+				   rgb_rep->effect;
+	drvdata.rgb_speed = rgb_rep->speed;
+	drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
+						 OXP_FEAT_ENABLED;
+	drvdata.rgb_brightness = rgb_rep->brightness;
+	led_mc->led_cdev.brightness = rgb_rep->brightness / 4 *
+				      led_mc->led_cdev.max_brightness;
+	/* If monocolor had less than 100% brightness on the previous boot,
+	 * there will be no reliable way to determine the real intensity.
+	 * Since intensity scaling is used with a hardware brightness set at max,
+	 * our brightness will always look like 100%. Use the last set value to
+	 * prevent successive boots from lowering the brightness further.
+	 * Brightness will be "wrong" but the effect will remain the same visually.
+	 */
+	led_mc->subled_info[0].intensity = rgb_rep->red;
+	led_mc->subled_info[1].intensity = rgb_rep->green;
+	led_mc->subled_info[2].intensity = rgb_rep->blue;
+
+	return 0;
+}
+
 static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report,
 			     u8 *data, int size)
 {
@@ -172,6 +230,8 @@ static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report,
 	switch (up) {
 	case GEN1_USAGE_PAGE:
 		return oxp_hid_raw_event_gen_1(hdev, report, data, size);
+	case GEN2_USAGE_PAGE:
+		return oxp_hid_raw_event_gen_2(hdev, report, data, size);
 	default:
 		break;
 	}
@@ -217,6 +277,18 @@ static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data,
 	return mcu_property_out(header, header_size, data, data_size, NULL, 0);
 }
 
+static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data,
+				  u8 data_size)
+{
+	u8 header[] = { fid, GEN2_MESSAGE_ID, 0x01 };
+	u8 footer[] = { GEN2_MESSAGE_ID, fid };
+	size_t header_size = ARRAY_SIZE(header);
+	size_t footer_size = ARRAY_SIZE(footer);
+
+	return mcu_property_out(header, header_size, data, data_size, footer,
+				footer_size);
+}
+
 static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
 {
 	u16 up = get_usage_page(drvdata.hdev);
@@ -231,6 +303,11 @@ static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
 		if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
 			data[3] = 0x04;
 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4);
+	case GEN2_USAGE_PAGE:
+		data = (u8[6]) { OXP_SET_PROPERTY, 0x00, 0x02, enabled, speed, brightness };
+		if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
+			data[5] = 0x04;
+		return oxp_gen_2_property_out(OXP_FID_GEN2_RGB_EVENT, data, 6);
 	default:
 		return -ENODEV;
 	}
@@ -245,6 +322,9 @@ static ssize_t oxp_rgb_status_show(void)
 	case GEN1_USAGE_PAGE:
 		data = (u8[1]) { OXP_GET_PROPERTY };
 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
+	case GEN2_USAGE_PAGE:
+		data = (u8[3]) { OXP_GET_PROPERTY, 0x00, 0x02 };
+		return oxp_gen_2_property_out(OXP_FID_GEN2_RGB_EVENT, data, 3);
 	default:
 		return -ENODEV;
 	}
@@ -275,6 +355,16 @@ static int oxp_rgb_color_set(void)
 			data[3 * i + 3] = blue;
 		}
 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size);
+	case GEN2_USAGE_PAGE:
+		size = 57;
+		data = (u8[57]) { OXP_EFFECT_MONO_TRUE, 0x00, 0x02 };
+
+		for (i = 1; i < size / 3; i++) {
+			data[3 * i] = red;
+			data[3 * i + 1] = green;
+			data[3 * i + 2] = blue;
+		}
+		return oxp_gen_2_property_out(OXP_FID_GEN2_RGB_EVENT, data, size);
 	default:
 		return -ENODEV;
 	}
@@ -311,6 +401,10 @@ static int oxp_rgb_effect_set(u8 effect)
 			data = (u8[1]) { effect };
 			ret = oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
 			break;
+		case GEN2_USAGE_PAGE:
+			data = (u8[3]) { effect, 0x00, 0x02 };
+			ret = oxp_gen_2_property_out(OXP_FID_GEN2_RGB_EVENT, data, 3);
+			break;
 		default:
 			ret = -ENODEV;
 		}
@@ -614,6 +708,7 @@ static int oxp_hid_probe(struct hid_device *hdev,
 
 	switch (up) {
 	case GEN1_USAGE_PAGE:
+	case GEN2_USAGE_PAGE:
 		ret = oxp_cfg_probe(hdev, up);
 		if (ret) {
 			hid_hw_close(hdev);
@@ -634,6 +729,7 @@ static void oxp_hid_remove(struct hid_device *hdev)
 
 static const struct hid_device_id oxp_devices[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WCH, USB_DEVICE_ID_ONEXPLAYER_GEN2) },
 	{}
 };
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH 0/4] Add OneXPlayer Configuration HID Driver
From: Derek J. Clark @ 2026-03-22  3:16 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, Derek J . Clark,
	linux-input, linux-doc, linux-kernel

Adds an HID driver for OneXPlayer HID configuration devices. There are
currently 2 generations of OneXPlayer HID protocol. The first generation
(OneXPlayer F1 series) only provides an RGB control interface over HID.
The Second generation (X1 mini series, G1 series, AOKZOE A1X) also
includes a hardware level button mapping interface, as well as a
"takeover" mode that was added by the ODM for debugging the button map.
This takeover mode can be useful for exposing the M1 and M2 accessory
buttons as unique inputs with some userspace tools that can consume it.

Signed-off-by: Derel J. Clark <derekjohn.clark@gmail.com>

Derek J. Clark (4):
  HID: hid-oxp: Add OneXPlayer configuration driver
  HID: hid-oxp: Add Second Generation RGB Control
  HID: hid-oxp: Add Second Generation Takeover Mode
  HID: hid-oxp: Add Button Mapping Interface

 MAINTAINERS           |    6 +
 drivers/hid/Kconfig   |   12 +
 drivers/hid/Makefile  |    1 +
 drivers/hid/hid-ids.h |    6 +
 drivers/hid/hid-oxp.c | 1340 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1365 insertions(+)
 create mode 100644 drivers/hid/hid-oxp.c

-- 
2.53.0


^ permalink raw reply

* [PATCH] Docs: hid: intel-ish-hid: make long URL usable
From: Randy Dunlap @ 2026-03-21 23:09 UTC (permalink / raw)
  To: linux-kernel
  Cc: Randy Dunlap, Jiri Kosina, Benjamin Tissoires,
	Srinivas Pandruvada, linux-input, Jonathan Corbet, Shuah Khan,
	linux-doc

The '\' line continuation character in this long URL
doesn't help anything. There is no documentation tooling that
handles the line continuation character to join the 2 lines
to make a usable URL. Web browsers terminate the URL just
before the '\' character so that the second line of the URL
is lost. See:
  https://docs.kernel.org/hid/intel-ish-hid.html

Join the 2 lines together so that the URL is usable.

Signed-off-by: Randy Dunlap <rdunlap@infradead.org>
---
Cc: Jiri Kosina <jikos@kernel.org>
Cc: Benjamin Tissoires <bentiss@kernel.org>
Cc: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Cc: linux-input@vger.kernel.org
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Shuah Khan <skhan@linuxfoundation.org>
Cc: linux-doc@vger.kernel.org

 Documentation/hid/intel-ish-hid.rst |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

--- linux-next-20260320.orig/Documentation/hid/intel-ish-hid.rst
+++ linux-next-20260320/Documentation/hid/intel-ish-hid.rst
@@ -163,8 +163,8 @@ The transport layer is a bi-directional
 - A flow control mechanism to avoid buffer overflows
 
 This protocol resembles bus messages described in the following document:
-http://www.intel.com/content/dam/www/public/us/en/documents/technical-\
-specifications/dcmi-hi-1-0-spec.pdf "Chapter 7: Bus Message Layer"
+http://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/dcmi-hi-1-0-spec.pdf
+"Chapter 7: Bus Message Layer".
 
 Connection and Flow Control Mechanism
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

^ permalink raw reply

* [PATCH 3/3] dt-bindings: input: adafruit-seesaw-gamepad: fix interrupt polarity
From: charles.embedded @ 2026-03-21 20:24 UTC (permalink / raw)
  To: Anshul Dalal, Dmitry Torokhov
  Cc: Shuah Khan, Brigham Campbell, linux-input, linux-kernel,
	Charles Dias
In-Reply-To: <20260321202446.724277-1-charles.embedded@gmail.com>

From: Charles Dias <charlesdias.cd@outlook.com>

The INT line is open-drain and asserts low on button GPIO changes, so
the binding should describe a falling-edge trigger rather than rising
edge. Also update the example to use IRQ_TYPE_EDGE_FALLING and add
interrupt-parent, and clarify that the driver can fall back to polling
when no IRQ is wired.

Signed-off-by: Charles Dias <charlesdias.cd@outlook.com>
---
 .../bindings/input/adafruit,seesaw-gamepad.yaml      | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/input/adafruit,seesaw-gamepad.yaml b/Documentation/devicetree/bindings/input/adafruit,seesaw-gamepad.yaml
index 5e86f6de6978..f0ebb326bf74 100644
--- a/Documentation/devicetree/bindings/input/adafruit,seesaw-gamepad.yaml
+++ b/Documentation/devicetree/bindings/input/adafruit,seesaw-gamepad.yaml
@@ -25,6 +25,11 @@ description: |
   SE -> Select
   X, A, B, Y -> Digital action buttons
 
+  The gamepad exposes button events through the seesaw GPIO block and joystick
+  axes through the seesaw ADC block. If the optional IRQ pin is wired, button
+  presses can be interrupt-driven while joystick axes remain polled. Without an
+  IRQ, the driver falls back to fully polled operation.
+
   Datasheet: https://cdn-learn.adafruit.com/downloads/pdf/gamepad-qt.pdf
   Product page: https://www.adafruit.com/product/5743
   Arduino Driver: https://github.com/adafruit/Adafruit_Seesaw
@@ -39,7 +44,9 @@ properties:
   interrupts:
     maxItems: 1
     description:
-      The gamepad's IRQ pin triggers a rising edge if interrupts are enabled.
+      Optional interrupt from the gamepad's open-drain INT pin. The device
+      asserts INT low on button GPIO changes when interrupts are enabled in the
+      seesaw firmware, so the host should typically use a falling-edge trigger.
 
 required:
   - compatible
@@ -57,7 +64,8 @@ examples:
 
         joystick@50 {
             compatible = "adafruit,seesaw-gamepad";
-            interrupts = <18 IRQ_TYPE_EDGE_RISING>;
+            interrupt-parent = <&gpio1>;
+            interrupts = <18 IRQ_TYPE_EDGE_FALLING>;
             reg = <0x50>;
         };
     };

^ permalink raw reply related

* [PATCH 2/3] Input: adafruit-seesaw - add interrupt support
From: charles.embedded @ 2026-03-21 20:24 UTC (permalink / raw)
  To: Anshul Dalal, Dmitry Torokhov
  Cc: Shuah Khan, Brigham Campbell, linux-input, linux-kernel,
	Charles Dias
In-Reply-To: <20260321202446.724277-1-charles.embedded@gmail.com>

From: Charles Dias <charlesdias.cd@outlook.com>

Use IRQ-driven reporting for button events when an interrupt is
described in DTS. Joystick axis values still have no interrupt source,
so axis reporting continues to use polling.

When the DTS specifies an interrupt, the IRQ thread calls only
seesaw_report_buttons(), avoiding unnecessary ADC reads on each button
event. The polling path always calls seesaw_report_axes(), and calls
seesaw_report_buttons() only when no IRQ is configured. This avoids
concurrent access to button_state between the IRQ thread and the poll
timer.

When no interrupt is available, the driver falls back to fully polled
operation.

Also remove the now-unused seesaw_data structure. Axis values are read
directly in seesaw_report_axes(), and button_state already lives in the
driver data, where it persists across calls for state comparison.

Signed-off-by: Charles Dias <charlesdias.cd@outlook.com>
---
 drivers/input/joystick/adafruit-seesaw.c | 141 +++++++++++++++++------
 1 file changed, 103 insertions(+), 38 deletions(-)

diff --git a/drivers/input/joystick/adafruit-seesaw.c b/drivers/input/joystick/adafruit-seesaw.c
index 177b42446e9b..ed8896c639ce 100644
--- a/drivers/input/joystick/adafruit-seesaw.c
+++ b/drivers/input/joystick/adafruit-seesaw.c
@@ -11,8 +11,10 @@
  * Product page: https://www.adafruit.com/product/5743
  * Firmware and hardware sources: https://github.com/adafruit/Adafruit_Seesaw
  *
- * TODO:
- *	- Add interrupt support
+ * Interrupt support is available when the DTS defines an interrupt for the
+ * device. Button events are then driven by the seesaw INT line, while joystick
+ * axes are always polled since the seesaw ADC has no interrupt source.
+ * Without an interrupt, the driver falls back to fully polling mode.
  */
 
 #include <linux/unaligned.h>
@@ -21,6 +23,7 @@
 #include <linux/i2c.h>
 #include <linux/input.h>
 #include <linux/input/sparse-keymap.h>
+#include <linux/interrupt.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 
@@ -31,12 +34,14 @@
 #define SEESAW_GPIO_DIRCLR_BULK		0x0103
 #define SEESAW_GPIO_BULK		0x0104
 #define SEESAW_GPIO_BULK_SET		0x0105
+#define SEESAW_GPIO_INTENSET		0x0108
 #define SEESAW_GPIO_PULLENSET		0x010b
 
 #define SEESAW_STATUS_HW_ID		0x0001
 #define SEESAW_STATUS_SWRST		0x007f
 
 #define SEESAW_ADC_OFFSET		0x07
+#define SEESAW_ADC_REG(ch)		(SEESAW_ADC_BASE | (SEESAW_ADC_OFFSET + (ch)))
 
 #define SEESAW_BUTTON_A			0x05
 #define SEESAW_BUTTON_B			0x01
@@ -67,12 +72,6 @@ struct seesaw_gamepad {
 	u32 button_state;
 };
 
-struct seesaw_data {
-	u16 x;
-	u16 y;
-	u32 button_state;
-};
-
 static const struct key_entry seesaw_buttons_new[] = {
 	{ KE_KEY, SEESAW_BUTTON_A, .keycode = BTN_SOUTH },
 	{ KE_KEY, SEESAW_BUTTON_B, .keycode = BTN_EAST },
@@ -142,39 +141,61 @@ static int seesaw_register_write_u32(struct i2c_client *client, u16 reg,
 	return 0;
 }
 
-static int seesaw_read_data(struct i2c_client *client, struct seesaw_data *data)
+static int seesaw_report_buttons(struct seesaw_gamepad *private)
 {
-	__be16 adc_data;
+	struct input_dev *input = private->input_dev;
+	unsigned long changed;
+	u32 button_state;
 	__be32 read_buf;
-	int err;
+	int err, i;
 
-	err = seesaw_register_read(client, SEESAW_GPIO_BULK,
+	err = seesaw_register_read(private->i2c_client, SEESAW_GPIO_BULK,
 				   &read_buf, sizeof(read_buf));
 	if (err)
 		return err;
 
-	data->button_state = ~be32_to_cpu(read_buf);
+	button_state = ~be32_to_cpu(read_buf) & SEESAW_BUTTON_MASK;
+	changed = private->button_state ^ button_state;
+	private->button_state = button_state;
+
+	for_each_set_bit(i, &changed, fls(SEESAW_BUTTON_MASK)) {
+		if (!sparse_keymap_report_event(input, i,
+						button_state & BIT(i), false))
+			dev_err_ratelimited(&input->dev,
+						"failed to report keymap event");
+	}
+
+	input_sync(input);
+	return 0;
+}
+
+static int seesaw_report_axes(struct seesaw_gamepad *private)
+{
+	struct input_dev *input = private->input_dev;
+	__be16 adc_data;
+	int err;
 
-	err = seesaw_register_read(client,
-				   SEESAW_ADC_BASE |
-					(SEESAW_ADC_OFFSET + SEESAW_ANALOG_X),
+	err = seesaw_register_read(private->i2c_client,
+				   SEESAW_ADC_REG(SEESAW_ANALOG_X),
 				   &adc_data, sizeof(adc_data));
 	if (err)
 		return err;
+
 	/*
 	 * ADC reads left as max and right as 0, must be reversed since kernel
 	 * expects reports in opposite order.
 	 */
-	data->x = SEESAW_JOYSTICK_MAX_AXIS - be16_to_cpu(adc_data);
+	input_report_abs(input, ABS_X,
+			 SEESAW_JOYSTICK_MAX_AXIS - be16_to_cpu(adc_data));
 
-	err = seesaw_register_read(client,
-				   SEESAW_ADC_BASE |
-					(SEESAW_ADC_OFFSET + SEESAW_ANALOG_Y),
+	err = seesaw_register_read(private->i2c_client,
+				   SEESAW_ADC_REG(SEESAW_ANALOG_Y),
 				   &adc_data, sizeof(adc_data));
 	if (err)
 		return err;
 
-	data->y = be16_to_cpu(adc_data);
+	input_report_abs(input, ABS_Y, be16_to_cpu(adc_data));
+	input_sync(input);
 
 	return 0;
 }
@@ -182,42 +203,72 @@ static int seesaw_read_data(struct i2c_client *client, struct seesaw_data *data)
 static int seesaw_open(struct input_dev *input)
 {
 	struct seesaw_gamepad *private = input_get_drvdata(input);
+	int err;
 
 	private->button_state = 0;
 
+	if (private->i2c_client->irq) {
+		/*
+		 * Read and report current button state before enabling the
+		 * edge-triggered IRQ. This deasserts any pending INT already
+		 * latched by the chip since probe(), preventing the IRQ line
+		 * from being stuck low on the first open.
+		 */
+		err = seesaw_report_buttons(private);
+		if (err)
+			return err;
+
+		enable_irq(private->i2c_client->irq);
+	}
+
 	return 0;
 }
 
+static void seesaw_close(struct input_dev *input)
+{
+	struct seesaw_gamepad *private = input_get_drvdata(input);
+
+	if (private->i2c_client->irq)
+		disable_irq(private->i2c_client->irq);
+}
+
 static void seesaw_poll(struct input_dev *input)
 {
 	struct seesaw_gamepad *private = input_get_drvdata(input);
-	struct seesaw_data data;
-	unsigned long changed;
-	int err, i;
+	int err;
 
-	err = seesaw_read_data(private->i2c_client, &data);
+	err = seesaw_report_axes(private);
 	if (err) {
 		dev_err_ratelimited(&input->dev,
-				    "failed to read joystick state: %d\n", err);
+					"failed to read joystick axes: %d\n", err);
 		return;
 	}
 
-	input_report_abs(input, ABS_X, data.x);
-	input_report_abs(input, ABS_Y, data.y);
+	/*
+	 * In interrupt mode, buttons are reported exclusively by
+	 * seesaw_irq_thread() to avoid concurrent access to button_state.
+	 */
+	if (!private->i2c_client->irq) {
+		err = seesaw_report_buttons(private);
+		if (err)
+			dev_err_ratelimited(&input->dev,
+					"failed to read button state: %d\n", err);
+	}
+}
 
-	data.button_state &= SEESAW_BUTTON_MASK;
-	changed = private->button_state ^ data.button_state;
-	private->button_state = data.button_state;
+static irqreturn_t seesaw_irq_thread(int irq, void *dev_id)
+{
+	struct seesaw_gamepad *private = dev_id;
+	int err;
 
-	for_each_set_bit(i, &changed, fls(SEESAW_BUTTON_MASK)) {
-		if (!sparse_keymap_report_event(input, i,
-						data.button_state & BIT(i),
-						false))
-			dev_err_ratelimited(&input->dev,
-					    "failed to report keymap event");
+	err = seesaw_report_buttons(private);
+	if (err) {
+		dev_err_ratelimited(&private->input_dev->dev,
+					"failed to read button state: %d\n", err);
+		return IRQ_NONE;
 	}
 
-	input_sync(input);
+	return IRQ_HANDLED;
 }
 
 static int seesaw_probe(struct i2c_client *client)
@@ -268,6 +319,7 @@ static int seesaw_probe(struct i2c_client *client)
 	seesaw->input_dev->name = "Adafruit Seesaw Gamepad";
 	seesaw->input_dev->phys = "i2c/" SEESAW_DEVICE_NAME;
 	seesaw->input_dev->open = seesaw_open;
+	seesaw->input_dev->close = seesaw_close;
 	input_set_drvdata(seesaw->input_dev, seesaw);
 	input_set_abs_params(seesaw->input_dev, ABS_X,
 			     0, SEESAW_JOYSTICK_MAX_AXIS,
@@ -289,6 +341,19 @@ static int seesaw_probe(struct i2c_client *client)
 	input_set_max_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MAX);
 	input_set_min_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MIN);
 
+	if (client->irq) {
+		err = seesaw_register_write_u32(client, SEESAW_GPIO_INTENSET, SEESAW_BUTTON_MASK);
+		if (err)
+			return dev_err_probe(&client->dev, err,
+						"failed to enable hardware interrupts\n");
+
+		err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+						seesaw_irq_thread, IRQF_ONESHOT | IRQF_NO_AUTOEN,
+						SEESAW_DEVICE_NAME, seesaw);
+		if (err)
+			return dev_err_probe(&client->dev, err, "failed to request IRQ\n");
+	}
+
 	err = input_register_device(seesaw->input_dev);
 	if (err)
 		return dev_err_probe(&client->dev, err, "failed to register joystick\n");

^ permalink raw reply related

* [PATCH 1/3] Input: adafruit-seesaw - switch to using dev_err_probe()
From: charles.embedded @ 2026-03-21 20:24 UTC (permalink / raw)
  To: Anshul Dalal, Dmitry Torokhov
  Cc: Shuah Khan, Brigham Campbell, linux-input, linux-kernel,
	Charles Dias
In-Reply-To: <20260321202446.724277-1-charles.embedded@gmail.com>

From: Charles Dias <charlesdias.cd@outlook.com>

Use dev_err_probe() instead of dev_err() in seesaw_probe function
to improve error handling.

Signed-off-by: Charles Dias <charlesdias.cd@outlook.com>
---
 drivers/input/joystick/adafruit-seesaw.c | 19 ++++++-------------
 1 file changed, 6 insertions(+), 13 deletions(-)

diff --git a/drivers/input/joystick/adafruit-seesaw.c b/drivers/input/joystick/adafruit-seesaw.c
index c248c15b849d..177b42446e9b 100644
--- a/drivers/input/joystick/adafruit-seesaw.c
+++ b/drivers/input/joystick/adafruit-seesaw.c
@@ -277,17 +277,12 @@ static int seesaw_probe(struct i2c_client *client)
 			     SEESAW_JOYSTICK_FUZZ, SEESAW_JOYSTICK_FLAT);
 
 	err = sparse_keymap_setup(seesaw->input_dev, seesaw_buttons_new, NULL);
-	if (err) {
-		dev_err(&client->dev,
-			"failed to set up input device keymap: %d\n", err);
-		return err;
-	}
+	if (err)
+		return dev_err_probe(&client->dev, err, "failed to set up input device keymap\n");
 
 	err = input_setup_polling(seesaw->input_dev, seesaw_poll);
-	if (err) {
-		dev_err(&client->dev, "failed to set up polling: %d\n", err);
-		return err;
-	}
+	if (err)
+		return dev_err_probe(&client->dev, err, "failed to set up polling\n");
 
 	input_set_poll_interval(seesaw->input_dev,
 				SEESAW_GAMEPAD_POLL_INTERVAL_MS);
@@ -295,10 +290,8 @@ static int seesaw_probe(struct i2c_client *client)
 	input_set_min_poll_interval(seesaw->input_dev, SEESAW_GAMEPAD_POLL_MIN);
 
 	err = input_register_device(seesaw->input_dev);
-	if (err) {
-		dev_err(&client->dev, "failed to register joystick: %d\n", err);
-		return err;
-	}
+	if (err)
+		return dev_err_probe(&client->dev, err, "failed to register joystick\n");
 
 	return 0;
 }

^ permalink raw reply related

* [PATCH 0/3] Input: adafruit-seesaw: use dev_err_probe and add IRQ support
From: charles.embedded @ 2026-03-21 20:24 UTC (permalink / raw)
  To: Anshul Dalal, Dmitry Torokhov
  Cc: Shuah Khan, Brigham Campbell, linux-input, linux-kernel,
	Charles Dias

From: Charles Dias <charlesdias.cd@outlook.com>

This series improves the Adafruit seesaw gamepad driver in two steps.

The first patch switches to using dev_err_probe() in seesaw_probe()
to improve error handling.

The second patch adds optional interrupt support for button events when
an IRQ is described in DTS. Joystick axes remain polled because the
default Adafruit seesaw gamepad firmware exposes button interrupts
through the GPIO module, while the joystick positions are read from ADC
channels.

When no IRQ is described in DTS, the driver continues to operate in
pure polling mode.

This series was validated on a BeaglePlay board with the Adafruit
Seesaw Gamepad, both with the interrupt enabled in DTS and without it.

Charles Dias (3):
  Input: adafruit-seesaw - switch to using dev_err_probe()
  Input: adafruit-seesaw - add interrupt support
  dt-bindings: input: adafruit-seesaw-gamepad: fix interrupt polarity

 .../input/adafruit,seesaw-gamepad.yaml        |  12 +-
 drivers/input/joystick/adafruit-seesaw.c      | 160 ++++++++++++------
 2 files changed, 119 insertions(+), 53 deletions(-)


^ permalink raw reply

* [PATCH] HID: drop 'default !EXPERT' from tristate symbols
From: Thomas Weißschuh @ 2026-03-21 13:15 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: linux-input, linux-kernel, Thomas Weißschuh

There is no reason to build random drivers for obscure hardware into the
core kernel by default.

The usages of 'default !EXPERT' for the HID_PICOLCD suboptions are kept,
as these make some sense, although they probably should use 'default y'.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
 drivers/hid/Kconfig | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 10c12d8e6557..7b1d8d81bd65 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -107,7 +107,6 @@ menu "Special HID drivers"
 
 config HID_A4TECH
 	tristate "A4TECH mice"
-	default !EXPERT
 	help
 	Support for some A4TECH mice with two scroll wheels.
 
@@ -140,7 +139,6 @@ config HID_APPLE
 	tristate "Apple {i,Power,Mac}Books"
 	depends on LEDS_CLASS
 	depends on NEW_LEDS
-	default !EXPERT
 	help
 	Support for some Apple devices which less or more break
 	HID specification.
@@ -209,7 +207,6 @@ config HID_AUREAL
 
 config HID_BELKIN
 	tristate "Belkin Flip KVM and Wireless keyboard"
-	default !EXPERT
 	help
 	Support for Belkin Flip KVM and Wireless keyboard.
 
@@ -237,14 +234,12 @@ config HID_BIGBEN_FF
 
 config HID_CHERRY
 	tristate "Cherry Cymotion keyboard"
-	default !EXPERT
 	help
 	Support for Cherry Cymotion keyboard.
 
 config HID_CHICONY
 	tristate "Chicony devices"
 	depends on USB_HID
-	default !EXPERT
 	help
 	Support for Chicony Tactical pad and special keys on Chicony keyboards.
 
@@ -322,7 +317,6 @@ config HID_CREATIVE_SB0540
 
 config HID_CYPRESS
 	tristate "Cypress mouse and barcode readers"
-	default !EXPERT
 	help
 	Support for cypress mouse and barcode readers.
 
@@ -388,7 +382,6 @@ config HID_EVISION
 
 config HID_EZKEY
 	tristate "Ezkey BTC 8193 keyboard"
-	default !EXPERT
 	help
 	Support for Ezkey BTC 8193 keyboard.
 
@@ -564,7 +557,6 @@ config HID_ICADE
 
 config HID_ITE
 	tristate "ITE devices"
-	default !EXPERT
 	help
 	Support for ITE devices not fully compliant with HID standard.
 
@@ -585,7 +577,6 @@ config HID_TWINHAN
 
 config HID_KENSINGTON
 	tristate "Kensington Slimblade Trackball"
-	default !EXPERT
 	help
 	Support for Kensington Slimblade Trackball.
 
@@ -666,7 +657,6 @@ config HID_LOGITECH
 	depends on USB_HID
 	depends on LEDS_CLASS
 	depends on LEDS_CLASS_MULTICOLOR
-	default !EXPERT
 	help
 	Support for Logitech devices that are not fully compliant with HID standard.
 
@@ -781,20 +771,17 @@ config HID_MEGAWORLD_FF
 
 config HID_REDRAGON
 	tristate "Redragon keyboards"
-	default !EXPERT
 	help
     Support for Redragon keyboards that need fix-ups to work properly.
 
 config HID_MICROSOFT
 	tristate "Microsoft non-fully HID-compliant devices"
-	default !EXPERT
 	select INPUT_FF_MEMLESS
 	help
 	Support for Microsoft devices that are not fully compliant with HID standard.
 
 config HID_MONTEREY
 	tristate "Monterey Genius KB29E keyboard"
-	default !EXPERT
 	help
 	Support for Monterey Genius KB29E.
 

---
base-commit: 2bbe306cc6f970474cb2ad7d5332220e81119bbd
change-id: 20260321-hid-expert-87fcf609230a

Best regards,
-- 
Thomas Weißschuh <linux@weissschuh.net>


^ permalink raw reply related

* [PATCH v2] HID: rakk: add support for Rakk Dasig X side buttons
From: Karl Cayme @ 2026-03-21 12:42 UTC (permalink / raw)
  To: jikos, bentiss; +Cc: linux-input, linux-kernel, linuxhid, Karl Cayme
In-Reply-To: <3284f00a-bea0-4141-a009-b367f527c466@cosmicgizmosystems.com>

The Rakk Dasig X gaming mouse has a faulty HID report descriptor that
declares USAGE_MAXIMUM=3 (buttons 1-3) while actually sending 5 button
bits (REPORT_COUNT=5). This causes the kernel to ignore side buttons
(buttons 4 and 5).

Fix by patching the descriptor to set USAGE_MAXIMUM=5 in the
report_fixup callback.

The mouse uses Telink vendor ID 0x248a with three product IDs for USB
direct (0xfb01), wireless dongle (0xfa02), and Bluetooth (0x8266)
connection modes. All three variants have the same bug at byte offset 17.

Suggested-by: Terry Junge <linuxhid@cosmicgizmosystems.com> 
Signed-off-by: Karl Cayme <kcayme@gmail.com>
---
Hi,

Thanks for the feedback. I updated the patch with your suggestion to
check PIDs.

Best,
Karl

v2 Changes:
  - included PID checking alongside descriptor size in report_fixup

 drivers/hid/Kconfig    |  9 +++++
 drivers/hid/Makefile   |  1 +
 drivers/hid/hid-ids.h  |  5 +++
 drivers/hid/hid-rakk.c | 75 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 90 insertions(+)
 create mode 100644 drivers/hid/hid-rakk.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index c1d9f7c6a5f2..11c48cb1c6a6 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -755,6 +755,15 @@ config HID_MEGAWORLD_FF
 	Say Y here if you have a Mega World based game controller and want
 	to have force feedback support for it.
 
+config HID_RAKK
+	tristate "Rakk support"
+	help
+	  Support for Rakk gaming peripherals.
+
+	  Fixes the HID report descriptor of the Rakk Dasig X mouse,
+	  which declares USAGE_MAXIMUM=3 (buttons 1-3) while actually
+	  sending 5 button bits. This causes side buttons to be ignored.
+
 config HID_REDRAGON
 	tristate "Redragon keyboards"
 	default !EXPERT
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index e01838239ae6..7800613f5b2b 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -113,6 +113,7 @@ obj-$(CONFIG_HID_PLANTRONICS)	+= hid-plantronics.o
 obj-$(CONFIG_HID_PLAYSTATION)	+= hid-playstation.o
 obj-$(CONFIG_HID_PRIMAX)	+= hid-primax.o
 obj-$(CONFIG_HID_PXRC)		+= hid-pxrc.o
+obj-$(CONFIG_HID_RAKK)		+= hid-rakk.o
 obj-$(CONFIG_HID_RAPOO) += hid-rapoo.o
 obj-$(CONFIG_HID_RAZER)	+= hid-razer.o
 obj-$(CONFIG_HID_REDRAGON)	+= hid-redragon.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 3e299a30dcde..68fab837e8b3 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1369,6 +1369,11 @@
 #define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5_017	0x73f6
 #define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5	0x81a7
 
+#define USB_VENDOR_ID_TELINK				0x248a
+#define USB_DEVICE_ID_TELINK_RAKK_DASIG_X		0xfb01
+#define USB_DEVICE_ID_TELINK_RAKK_DASIG_X_DONGLE	0xfa02
+#define USB_DEVICE_ID_TELINK_RAKK_DASIG_X_BT		0x8266
+
 #define USB_VENDOR_ID_TEXAS_INSTRUMENTS	0x2047
 #define USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA	0x0855
 
diff --git a/drivers/hid/hid-rakk.c b/drivers/hid/hid-rakk.c
new file mode 100644
index 000000000000..c59ea47b8996
--- /dev/null
+++ b/drivers/hid/hid-rakk.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  HID driver for Rakk devices
+ *
+ *  Copyright (c) 2026 Karl Cayme
+ *
+ *  The Rakk Dasig X gaming mouse has a faulty HID report descriptor that
+ *  declares USAGE_MAXIMUM = 3 (buttons 1-3) while actually sending 5 button
+ *  bits (REPORT_COUNT = 5). This causes the kernel to ignore side buttons
+ *  (buttons 4 and 5). This driver fixes the descriptor so all 5 buttons
+ *  are properly recognized across 3 modes (wired, dongle, and Bluetooth).
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include "hid-ids.h"
+
+/*
+ * The faulty byte is at offset 17 in the report descriptor for all three
+ * connection modes (USB direct, wireless dongle, and Bluetooth).
+ *
+ * Bytes 16-17 are: 0x29 0x03 (USAGE_MAXIMUM = 3)
+ * The fix changes byte 17 to 0x05 (USAGE_MAXIMUM = 5).
+ *
+ * Original descriptor bytes 0-17:
+ *   05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03
+ *                                                       ^^
+ *   Should be 0x05 to declare 5 buttons instead of 3.
+ */
+#define RAKK_RDESC_USAGE_MAX_OFFSET	17
+#define RAKK_RDESC_USAGE_MAX_ORIG	0x03
+#define RAKK_RDESC_USAGE_MAX_FIXED	0x05
+#define RAKK_RDESC_USB_SIZE		193
+#define RAKK_RDESC_DONGLE_SIZE		150
+#define RAKK_RDESC_BT_SIZE		89
+
+static const __u8 *rakk_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+				     unsigned int *rsize)
+{
+	if (((*rsize == RAKK_RDESC_USB_SIZE &&
+	      hdev->product == USB_DEVICE_ID_TELINK_RAKK_DASIG_X) ||
+	     (*rsize == RAKK_RDESC_DONGLE_SIZE &&
+	      hdev->product == USB_DEVICE_ID_TELINK_RAKK_DASIG_X_DONGLE) ||
+	     (*rsize == RAKK_RDESC_BT_SIZE &&
+	      hdev->product == USB_DEVICE_ID_TELINK_RAKK_DASIG_X_BT)) &&
+	    rdesc[RAKK_RDESC_USAGE_MAX_OFFSET] == RAKK_RDESC_USAGE_MAX_ORIG) {
+		hid_info(hdev, "fixing Rakk Dasig X button count (3 -> 5)\n");
+		rdesc[RAKK_RDESC_USAGE_MAX_OFFSET] = RAKK_RDESC_USAGE_MAX_FIXED;
+	}
+
+	return rdesc;
+}
+
+static const struct hid_device_id rakk_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TELINK,
+			 USB_DEVICE_ID_TELINK_RAKK_DASIG_X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TELINK,
+			 USB_DEVICE_ID_TELINK_RAKK_DASIG_X_DONGLE) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_TELINK,
+			       USB_DEVICE_ID_TELINK_RAKK_DASIG_X_BT) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, rakk_devices);
+
+static struct hid_driver rakk_driver = {
+	.name = "rakk",
+	.id_table = rakk_devices,
+	.report_fixup = rakk_report_fixup,
+};
+module_hid_driver(rakk_driver);
+
+MODULE_DESCRIPTION("HID driver for Rakk Dasig X mouse - fix side button support");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Karl Cayme");
-- 
2.53.0


^ permalink raw reply related

* [PATCH] Input: goodix-berlin: report a resolution of 10 units/mm
From: Val Packett @ 2026-03-21  7:30 UTC (permalink / raw)
  To: Hans de Goede, Dmitry Torokhov, Henrik Rydberg
  Cc: Val Packett, Stanislav Zaikin, linux-input, phone-devel,
	~postmarketos/upstreaming, linux-kernel

Without a reported resolution, userspace was assuming 1 unit/mm which
is wildly wrong: a regular smartphone is clearly not 2.4 meters tall.
Most applications do not care much for this kind of raw mm value,
but Phosh's on-screen keyboard would accidentally trigger swipe-to-close
gestures due to misinterpreting small movements as huge ones.

Do what the older goodix.c driver does and set the resolution to 10
units/mm to make sure the numbers calculated by userspace are reasonable.

Signed-off-by: Val Packett <val@packett.cool>
---
 drivers/input/touchscreen/goodix_berlin_core.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/input/touchscreen/goodix_berlin_core.c b/drivers/input/touchscreen/goodix_berlin_core.c
index 83f28b870531..b0938a4f3fec 100644
--- a/drivers/input/touchscreen/goodix_berlin_core.c
+++ b/drivers/input/touchscreen/goodix_berlin_core.c
@@ -628,6 +628,14 @@ static int goodix_berlin_input_dev_config(struct goodix_berlin_core *cd,
 
 	touchscreen_parse_properties(cd->input_dev, true, &cd->props);
 
+	/*
+	 * The resolution of these touchscreens is about 10 units/mm, the actual
+	 * resolution does not matter much since we set INPUT_PROP_DIRECT.
+	 * Set it to 10 to ensure userspace isn't off by an order of magnitude.
+	 */
+	input_abs_set_res(cd->input_dev, ABS_MT_POSITION_X, 10);
+	input_abs_set_res(cd->input_dev, ABS_MT_POSITION_Y, 10);
+
 	error = input_mt_init_slots(cd->input_dev, GOODIX_BERLIN_MAX_TOUCH,
 				    INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
 	if (error)
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH] Input: gpio-keys - add full support of EV_REL and EV_ABS
From: Dmitry Torokhov @ 2026-03-21  7:06 UTC (permalink / raw)
  To: Xiong Nandi
  Cc: linux-input, linux-kernel, Gatien Chevallier, Ingo Molnar,
	Thomas Gleixner, Marco Crivellari, Fabrice Gasnier
In-Reply-To: <20260320145217.9088-1-xndchn@gmail.com>

Hi Xiong,

On Fri, Mar 20, 2026 at 10:52:14PM +0800, Xiong Nandi wrote:
> gpio_keys_gpio_report_event() handled EV_ABS but silently ignored EV_REL,
> while the polled driver supports both.  Extend the interrupt-driven driver
> to fire an EV_REL event on button press.
> 
> For EV_ABS, use a shared atomic counter per (type, code) pair so that
> a zero-value reset is sent only when the last active button on an axis
> is released, avoiding premature axis resets when multiple buttons share
> the same axis code.
> 
> Add gpio_keys_set_abs_params() to call input_set_abs_params() at setup
> time, deriving the axis min/max from the configured button values.
> Without this the input subsystem reports unbounded axis ranges.

Could you please split this out? It looks like there are 2 or 3
logically separate changes.

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH v7 2/7] Input: synaptics-rmi4 - handle duplicate/unknown PDT entries
From: David Heidelberg @ 2026-03-20 17:19 UTC (permalink / raw)
  To: Casey Connolly, Kaustabh Chakraborty, Dmitry Torokhov,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jason A. Donenfeld, Matthias Schiffer, Vincent Huang
  Cc: linux-input, devicetree, linux-kernel, phone-devel
In-Reply-To: <f31d10ce-a3dc-4599-8201-8a78aa2fc65f@ixit.cz>

Pushed changes to

https://codeberg.org/sdm845/linux/commits/branch/b4/synaptics-rmi4

On 20/03/2026 18:12, David Heidelberg wrote:
> On 20/03/2026 18:03, Casey Connolly wrote:
>>
>>
>> On 20/03/2026 17:54, David Heidelberg wrote:
>>> On 20/03/2026 17:49, Casey Connolly wrote:
>>>> Hi David,
>>>>
>>>> Nice timing with the series, I hit an OOB access (found it when I
>>>> enabled UBSAN) with this patch the other day.
>>>>
>>>> The pdt_scan_state->pdts array should actually be of size
>>>> (RMI_PDT_MAX+1).
>>>>
>>>> Additionally, I think rmi_pdt_entry_is_valid() is missing a bounds check.
>>>>
>>>> Kind regards,
>>>
>>>
>>> Thanks a lot for catching this and for the detailed notes — that’s very
>>> helpful.
>>>
>>> Since you’re the original author of the commit, I’m completely fine with
>>> you taking over the b4 series if you’d prefer. Alternatively, if it’s
>>> easier, feel free to just send me a fixed patch and I can incorporate it.
>>>
>>
>> Uh sure, not sure this will apply cleanly I just edited inline it's a
>> 3-line delta. Also figured we can drop pdt_count since it's unused.
> 
> The pdt_count is used in
> 
> Input: synaptics-rmi4 - support fallback values for PDT descriptor bytes
> 
> thus should be moved there I assume, but can be dropped here.
> 
> David
> 
>>
>> ---
>>
>> diff --git a/drivers/input/rmi4/rmi_driver.c
>> b/drivers/input/rmi4/rmi_driver.c
>> index ccd9338a44dbe..c7d2f68e65487 100644
>> --- a/drivers/input/rmi4/rmi_driver.c
>> +++ b/drivers/input/rmi4/rmi_driver.c
>> @@ -494,12 +494,39 @@ static void rmi_driver_copy_pdt_to_fd(const struct
>> pdt_entry *pdt,
>>       fd->function_version = pdt->function_version;
>>   }
>>
>> +static bool rmi_pdt_entry_is_valid(struct rmi_device *rmi_dev,
>> +                   struct pdt_scan_state *state, u8 fn)
>> +{
>> +    if (fn > RMI_PDT_MAX)
>> +        return false;
>> +
>> +    switch (fn) {
>> +    case 0x01:
>> +    case 0x03:
>> +    case 0x11:
>> +    case 0x12:
>> +    case 0x30:
>> +    case 0x34:
>> +    case 0x3a:
>> +    case 0x54:
>> +    case 0x55:
>> +        if (state->pdts[fn] == true)
>> +            return false;
>> +        break;
>> +    default:
>> +        rmi_dbg(RMI_DEBUG_CORE, &rmi_dev->dev,
>> +            "PDT has unknown function number %#02x\n", fn);
>> +        return false;
>> +    }
>> +
>> +    state->pdts[fn] = true;
>> +    return true;
>> +}
>> +
>>   #define RMI_SCAN_CONTINUE    0
>>   #define RMI_SCAN_DONE        1
>>
>>   static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
>>                    int page,
>> -                 int *empty_pages,
>> +                 struct pdt_scan_state *state,
>>                    void *ctx,
>>                    int (*callback)(struct rmi_device *rmi_dev,
>>                            void *ctx,
>> @@ -522,6 +549,9 @@ static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
>>           if (RMI4_END_OF_PDT(pdt_entry.function_number))
>>               break;
>>
>> +        if (!rmi_pdt_entry_is_valid(rmi_dev, state, pdt_entry.function_number))
>> +            continue;
>> +
>>           retval = callback(rmi_dev, ctx, &pdt_entry);
>>           if (retval != RMI_SCAN_CONTINUE)
>>               return retval;
>> @@ -532,11 +562,11 @@ static int rmi_scan_pdt_page(struct rmi_device
>> *rmi_dev,
>>        * or more is found, stop scanning.
>>        */
>>       if (addr == pdt_start)
>> -        ++*empty_pages;
>> +        ++state->empty_pages;
>>       else
>> -        *empty_pages = 0;
>> +        state->empty_pages = 0;
>>
>> -    return (data->bootloader_mode || *empty_pages >= 2) ?
>> +    return (data->bootloader_mode || state->empty_pages >= 2) ?
>>                       RMI_SCAN_DONE : RMI_SCAN_CONTINUE;
>>   }
>>
>> @@ -545,11 +575,11 @@ int rmi_scan_pdt(struct rmi_device *rmi_dev, void
>> *ctx,
>>            void *ctx, const struct pdt_entry *entry))
>>   {
>>       int page;
>> -    int empty_pages = 0;
>> +    struct pdt_scan_state state = {0, {0}};
>>       int retval = RMI_SCAN_DONE;
>>
>>       for (page = 0; page <= RMI4_MAX_PAGE; page++) {
>> -        retval = rmi_scan_pdt_page(rmi_dev, page, &empty_pages,
>> +        retval = rmi_scan_pdt_page(rmi_dev, page, &state,
>>                          ctx, callback);
>>           if (retval != RMI_SCAN_CONTINUE)
>>               break;
>> diff --git a/drivers/input/rmi4/rmi_driver.h
>> b/drivers/input/rmi4/rmi_driver.h
>> index e84495caab151..a4ae2af93ce3a 100644
>> --- a/drivers/input/rmi4/rmi_driver.h
>> +++ b/drivers/input/rmi4/rmi_driver.h
>> @@ -46,6 +46,14 @@ struct pdt_entry {
>>       u8 function_number;
>>   };
>>
>> +#define RMI_PDT_MAX 0x55
>> +
>> +struct pdt_scan_state {
>> +    u8 empty_pages;
>> +    bool pdts[RMI_PDT_MAX + 1];
>> +};
>> +
>>   #define RMI_REG_DESC_PRESENSE_BITS    (32 * BITS_PER_BYTE)
>>   #define RMI_REG_DESC_SUBPACKET_BITS    (37 * BITS_PER_BYTE)
>>
>>
> 

-- 
David Heidelberg


^ permalink raw reply

* Re: [PATCH v7 2/7] Input: synaptics-rmi4 - handle duplicate/unknown PDT entries
From: David Heidelberg @ 2026-03-20 17:12 UTC (permalink / raw)
  To: Casey Connolly, Kaustabh Chakraborty, Dmitry Torokhov,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jason A. Donenfeld, Matthias Schiffer, Vincent Huang
  Cc: linux-input, devicetree, linux-kernel, phone-devel
In-Reply-To: <55dab1d8-87af-4285-9ab1-924bf392c78d@postmarketos.org>

On 20/03/2026 18:03, Casey Connolly wrote:
> 
> 
> On 20/03/2026 17:54, David Heidelberg wrote:
>> On 20/03/2026 17:49, Casey Connolly wrote:
>>> Hi David,
>>>
>>> Nice timing with the series, I hit an OOB access (found it when I
>>> enabled UBSAN) with this patch the other day.
>>>
>>> The pdt_scan_state->pdts array should actually be of size
>>> (RMI_PDT_MAX+1).
>>>
>>> Additionally, I think rmi_pdt_entry_is_valid() is missing a bounds check.
>>>
>>> Kind regards,
>>
>>
>> Thanks a lot for catching this and for the detailed notes — that’s very
>> helpful.
>>
>> Since you’re the original author of the commit, I’m completely fine with
>> you taking over the b4 series if you’d prefer. Alternatively, if it’s
>> easier, feel free to just send me a fixed patch and I can incorporate it.
>>
> 
> Uh sure, not sure this will apply cleanly I just edited inline it's a
> 3-line delta. Also figured we can drop pdt_count since it's unused.

The pdt_count is used in

Input: synaptics-rmi4 - support fallback values for PDT descriptor bytes

thus should be moved there I assume, but can be dropped here.

David

> 
> ---
> 
> diff --git a/drivers/input/rmi4/rmi_driver.c
> b/drivers/input/rmi4/rmi_driver.c
> index ccd9338a44dbe..c7d2f68e65487 100644
> --- a/drivers/input/rmi4/rmi_driver.c
> +++ b/drivers/input/rmi4/rmi_driver.c
> @@ -494,12 +494,39 @@ static void rmi_driver_copy_pdt_to_fd(const struct
> pdt_entry *pdt,
>   	fd->function_version = pdt->function_version;
>   }
> 
> +static bool rmi_pdt_entry_is_valid(struct rmi_device *rmi_dev,
> +				   struct pdt_scan_state *state, u8 fn)
> +{
> +	if (fn > RMI_PDT_MAX)
> +		return false;
> +
> +	switch (fn) {
> +	case 0x01:
> +	case 0x03:
> +	case 0x11:
> +	case 0x12:
> +	case 0x30:
> +	case 0x34:
> +	case 0x3a:
> +	case 0x54:
> +	case 0x55:
> +		if (state->pdts[fn] == true)
> +			return false;
> +		break;
> +	default:
> +		rmi_dbg(RMI_DEBUG_CORE, &rmi_dev->dev,
> +			"PDT has unknown function number %#02x\n", fn);
> +		return false;
> +	}
> +
> +	state->pdts[fn] = true;
> +	return true;
> +}
> +
>   #define RMI_SCAN_CONTINUE	0
>   #define RMI_SCAN_DONE		1
> 
>   static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
>   			     int page,
> -			     int *empty_pages,
> +			     struct pdt_scan_state *state,
>   			     void *ctx,
>   			     int (*callback)(struct rmi_device *rmi_dev,
>   					     void *ctx,
> @@ -522,6 +549,9 @@ static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
>   		if (RMI4_END_OF_PDT(pdt_entry.function_number))
>   			break;
> 
> +		if (!rmi_pdt_entry_is_valid(rmi_dev, state, pdt_entry.function_number))
> +			continue;
> +
>   		retval = callback(rmi_dev, ctx, &pdt_entry);
>   		if (retval != RMI_SCAN_CONTINUE)
>   			return retval;
> @@ -532,11 +562,11 @@ static int rmi_scan_pdt_page(struct rmi_device
> *rmi_dev,
>   	 * or more is found, stop scanning.
>   	 */
>   	if (addr == pdt_start)
> -		++*empty_pages;
> +		++state->empty_pages;
>   	else
> -		*empty_pages = 0;
> +		state->empty_pages = 0;
> 
> -	return (data->bootloader_mode || *empty_pages >= 2) ?
> +	return (data->bootloader_mode || state->empty_pages >= 2) ?
>   					RMI_SCAN_DONE : RMI_SCAN_CONTINUE;
>   }
> 
> @@ -545,11 +575,11 @@ int rmi_scan_pdt(struct rmi_device *rmi_dev, void
> *ctx,
>   		 void *ctx, const struct pdt_entry *entry))
>   {
>   	int page;
> -	int empty_pages = 0;
> +	struct pdt_scan_state state = {0, {0}};
>   	int retval = RMI_SCAN_DONE;
> 
>   	for (page = 0; page <= RMI4_MAX_PAGE; page++) {
> -		retval = rmi_scan_pdt_page(rmi_dev, page, &empty_pages,
> +		retval = rmi_scan_pdt_page(rmi_dev, page, &state,
>   					   ctx, callback);
>   		if (retval != RMI_SCAN_CONTINUE)
>   			break;
> diff --git a/drivers/input/rmi4/rmi_driver.h
> b/drivers/input/rmi4/rmi_driver.h
> index e84495caab151..a4ae2af93ce3a 100644
> --- a/drivers/input/rmi4/rmi_driver.h
> +++ b/drivers/input/rmi4/rmi_driver.h
> @@ -46,6 +46,14 @@ struct pdt_entry {
>   	u8 function_number;
>   };
> 
> +#define RMI_PDT_MAX 0x55
> +
> +struct pdt_scan_state {
> +	u8 empty_pages;
> +	bool pdts[RMI_PDT_MAX + 1];
> +};
> +
>   #define RMI_REG_DESC_PRESENSE_BITS	(32 * BITS_PER_BYTE)
>   #define RMI_REG_DESC_SUBPACKET_BITS	(37 * BITS_PER_BYTE)
> 
> 

-- 
David Heidelberg


^ 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