Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH] Input: atkbd - skip cleanup when used as a wakeup source
From: Henry Barnor via B4 Relay @ 2026-03-11  7:08 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, Henry Barnor

From: Henry Barnor <hbarnor@chromium.org>

In systems using suspend-to-idle, the serio core calls atkbd_cleanup()
during the suspend transition. Currently, this function unconditionally
calls atkbd_disable(), setting atkbd->enabled to false.

This creates a race condition during wakeup: when a key is pressed to
wake the system, atkbd_receive_byte() is triggered. It correctly signals
a wakeup event to the PM core, but then checks atkbd->enabled. Because
the driver was disabled during suspend, the scancode is discarded.
The system wakes up, but the initial keystroke is lost.

This is particularly problematic for platforms like Android that rely on
the input event itself to complete the wakeup transition and turn on the
screen. On these systems, the device wakes up but remains in a dim state
because the initial interaction was lost.

This patch modifies atkbd_cleanup() to skip disabling and resetting
the keyboard if the device is configured as a wakeup source. This
preserves atkbd->enabled = true through the sleep duration, ensuring
the wake-up scancode is reported to the input subsystem.

Note that this also affects the shutdown path. However, if a device is
configured for wakeup, skipping the reset to "default" state is
consistent with the goal of allowing the hardware to trigger a
system-state transition. Modern BIOS/UEFI implementations perform their
own keyboard initialization, so skipping the legacy reset on reboot
for these devices poses minimal risk.

Signed-off-by: Henry Barnor <hbarnor@chromium.org>
---
 drivers/input/keyboard/atkbd.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c
index 4560d3964eee..1fba1932412e 100644
--- a/drivers/input/keyboard/atkbd.c
+++ b/drivers/input/keyboard/atkbd.c
@@ -958,6 +958,9 @@ static void atkbd_cleanup(struct serio *serio)
 {
 	struct atkbd *atkbd = atkbd_from_serio(serio);
 
+	if (device_may_wakeup(&serio->dev))
+		return;
+
 	atkbd_disable(atkbd);
 	ps2_command(&atkbd->ps2dev, NULL, ATKBD_CMD_RESET_DEF);
 }

---
base-commit: 6d4b67a2a76a4ff2393fe88119ae4332821b82b4
change-id: 20260310-wakeup-ec57a88a0162

Best regards,
-- 
Henry Barnor <hbarnor@chromium.org>



^ permalink raw reply related

* Re: [PATCH v5 7/7] power: supply: Add charger driver for Asus Transformers
From: Sebastian Reichel @ 2026-03-11  6:47 UTC (permalink / raw)
  To: Svyatoslav Ryhel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Lee Jones, Pavel Machek, Ion Agorria, Michał Mirosław,
	devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260304185751.83494-8-clamor95@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 8925 bytes --]

Hi,

On Wed, Mar 04, 2026 at 08:57:51PM +0200, Svyatoslav Ryhel wrote:
> From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> 
> Add support for charger detection capabilities found in the embedded
> controller of ASUS Transformer devices.
> 
> Suggested-by: Maxim Schwalm <maxim.schwalm@gmail.com>
> Suggested-by: Svyatoslav Ryhel <clamor95@gmail.com>
> Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> ---

Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>

-- Sebastian

>  drivers/power/supply/Kconfig                  |  11 +
>  drivers/power/supply/Makefile                 |   1 +
>  .../supply/asus-transformer-ec-charger.c      | 193 ++++++++++++++++++
>  3 files changed, 205 insertions(+)
>  create mode 100644 drivers/power/supply/asus-transformer-ec-charger.c
> 
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index 3c46b412632d..56800aab82f9 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -497,6 +497,17 @@ config CHARGER_88PM860X
>  	help
>  	  Say Y here to enable charger for Marvell 88PM860x chip.
>  
> +config CHARGER_ASUS_TRANSFORMER_EC
> +	tristate "Asus Transformer's charger driver"
> +	depends on MFD_ASUS_TRANSFORMER_EC
> +	help
> +	  Say Y here to enable support AC plug detection on Asus Transformer
> +	  Dock.
> +
> +	  This sub-driver supports charger detection mechanism found in Asus
> +	  Transformer tablets and mobile docks and controlled by special
> +	  embedded controller.
> +
>  config CHARGER_PF1550
>  	tristate "NXP PF1550 battery charger driver"
>  	depends on MFD_PF1550
> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
> index aa5e6b05b018..24679f09bb61 100644
> --- a/drivers/power/supply/Makefile
> +++ b/drivers/power/supply/Makefile
> @@ -68,6 +68,7 @@ obj-$(CONFIG_CHARGER_RT9471)	+= rt9471.o
>  obj-$(CONFIG_CHARGER_RT9756)	+= rt9756.o
>  obj-$(CONFIG_BATTERY_TWL4030_MADC)	+= twl4030_madc_battery.o
>  obj-$(CONFIG_CHARGER_88PM860X)	+= 88pm860x_charger.o
> +obj-$(CONFIG_CHARGER_ASUS_TRANSFORMER_EC)	+= asus-transformer-ec-charger.o
>  obj-$(CONFIG_CHARGER_PF1550)	+= pf1550-charger.o
>  obj-$(CONFIG_BATTERY_RX51)	+= rx51_battery.o
>  obj-$(CONFIG_AB8500_BM)		+= ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o ab8500_chargalg.o
> diff --git a/drivers/power/supply/asus-transformer-ec-charger.c b/drivers/power/supply/asus-transformer-ec-charger.c
> new file mode 100644
> index 000000000000..de01f0bf2fd7
> --- /dev/null
> +++ b/drivers/power/supply/asus-transformer-ec-charger.c
> @@ -0,0 +1,193 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +#include <linux/err.h>
> +#include <linux/mfd/asus-transformer-ec.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/power_supply.h>
> +#include <linux/property.h>
> +
> +struct asus_ec_charger_data {
> +	struct notifier_block nb;
> +	const struct asusec_info *ec;
> +	struct power_supply *psy;
> +	struct power_supply_desc psy_desc;
> +};
> +
> +static enum power_supply_property asus_ec_charger_properties[] = {
> +	POWER_SUPPLY_PROP_USB_TYPE,
> +	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
> +	POWER_SUPPLY_PROP_ONLINE,
> +	POWER_SUPPLY_PROP_MODEL_NAME,
> +};
> +
> +static int asus_ec_charger_get_property(struct power_supply *psy,
> +					enum power_supply_property psp,
> +					union power_supply_propval *val)
> +{
> +	struct asus_ec_charger_data *priv = power_supply_get_drvdata(psy);
> +	enum power_supply_usb_type psu;
> +	int ret;
> +	u64 ctl;
> +
> +	ret = asus_ec_get_ctl(priv->ec, &ctl);
> +	if (ret)
> +		return ret;
> +
> +	switch (ctl & (ASUSEC_CTL_FULL_POWER_SOURCE | ASUSEC_CTL_DIRECT_POWER_SOURCE)) {
> +	case ASUSEC_CTL_FULL_POWER_SOURCE:
> +		psu = POWER_SUPPLY_USB_TYPE_CDP;	/* DOCK */
> +		break;
> +	case ASUSEC_CTL_DIRECT_POWER_SOURCE:
> +		psu = POWER_SUPPLY_USB_TYPE_SDP;	/* USB */
> +		break;
> +	case 0:
> +		psu = POWER_SUPPLY_USB_TYPE_UNKNOWN;	/* no power source connected */
> +		break;
> +	default:
> +		psu = POWER_SUPPLY_USB_TYPE_ACA;	/* power adapter */
> +		break;
> +	}
> +
> +	switch (psp) {
> +	case POWER_SUPPLY_PROP_ONLINE:
> +		val->intval = psu != POWER_SUPPLY_USB_TYPE_UNKNOWN;
> +		return 0;
> +
> +	case POWER_SUPPLY_PROP_USB_TYPE:
> +		val->intval = psu;
> +		return 0;
> +
> +	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
> +		if (ctl & ASUSEC_CTL_TEST_DISCHARGE)
> +			val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
> +		else if (ctl & ASUSEC_CTL_USB_CHARGE)
> +			val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
> +		else
> +			val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
> +		return 0;
> +
> +	case POWER_SUPPLY_PROP_MODEL_NAME:
> +		val->strval = priv->ec->model;
> +		return 0;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int asus_ec_charger_set_property(struct power_supply *psy,
> +					enum power_supply_property psp,
> +					const union power_supply_propval *val)
> +{
> +	struct asus_ec_charger_data *priv = power_supply_get_drvdata(psy);
> +
> +	switch (psp) {
> +	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
> +		switch ((enum power_supply_charge_behaviour)val->intval) {
> +		case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
> +			return asus_ec_update_ctl(priv->ec,
> +				ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE,
> +				ASUSEC_CTL_USB_CHARGE);
> +
> +		case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
> +			return asus_ec_clear_ctl_bits(priv->ec,
> +				ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE);
> +
> +		case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
> +			return asus_ec_update_ctl(priv->ec,
> +				ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE,
> +				ASUSEC_CTL_TEST_DISCHARGE);
> +		default:
> +			return -EINVAL;
> +		}
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int asus_ec_charger_property_is_writeable(struct power_supply *psy,
> +						 enum power_supply_property psp)
> +{
> +	switch (psp) {
> +	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +static const struct power_supply_desc asus_ec_charger_desc = {
> +	.name = "asus-ec-charger",
> +	.type = POWER_SUPPLY_TYPE_USB,
> +	.charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) |
> +			     BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) |
> +			     BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE),
> +	.usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) |
> +		     BIT(POWER_SUPPLY_USB_TYPE_SDP) |
> +		     BIT(POWER_SUPPLY_USB_TYPE_CDP) |
> +		     BIT(POWER_SUPPLY_USB_TYPE_ACA),
> +	.properties = asus_ec_charger_properties,
> +	.num_properties = ARRAY_SIZE(asus_ec_charger_properties),
> +	.get_property = asus_ec_charger_get_property,
> +	.set_property = asus_ec_charger_set_property,
> +	.property_is_writeable = asus_ec_charger_property_is_writeable,
> +	.no_thermal = true,
> +};
> +
> +static int asus_ec_charger_notify(struct notifier_block *nb,
> +				  unsigned long action, void *data)
> +{
> +	struct asus_ec_charger_data *priv =
> +		container_of(nb, struct asus_ec_charger_data, nb);
> +
> +	switch (action) {
> +	case ASUSEC_SMI_ACTION(POWER_NOTIFY):
> +	case ASUSEC_SMI_ACTION(ADAPTER_EVENT):
> +		power_supply_changed(priv->psy);
> +		break;
> +	}
> +
> +	return NOTIFY_DONE;
> +}
> +
> +static int asus_ec_charger_probe(struct platform_device *pdev)
> +{
> +	struct asus_ec_charger_data *priv;
> +	struct device *dev = &pdev->dev;
> +	struct power_supply_config cfg = { };
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, priv);
> +	priv->ec = cell_to_ec(pdev);
> +
> +	cfg.fwnode = dev_fwnode(dev->parent);
> +	cfg.drv_data = priv;
> +
> +	memcpy(&priv->psy_desc, &asus_ec_charger_desc, sizeof(priv->psy_desc));
> +	priv->psy_desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s-charger",
> +					     priv->ec->name);
> +
> +	priv->psy = devm_power_supply_register(dev, &priv->psy_desc, &cfg);
> +	if (IS_ERR(priv->psy))
> +		return dev_err_probe(dev, PTR_ERR(priv->psy),
> +				     "Failed to register power supply\n");
> +
> +	priv->nb.notifier_call = asus_ec_charger_notify;
> +
> +	return devm_asus_ec_register_notifier(pdev, &priv->nb);
> +}
> +
> +static struct platform_driver asus_ec_charger_driver = {
> +	.driver.name = "asus-transformer-ec-charger",
> +	.probe = asus_ec_charger_probe,
> +};
> +module_platform_driver(asus_ec_charger_driver);
> +
> +MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
> +MODULE_DESCRIPTION("ASUS Transformer Pad battery charger driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.51.0
> 
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply

* Re: [PATCH v5 6/7] power: supply: Add driver for ASUS Transformer battery
From: Sebastian Reichel @ 2026-03-11  6:30 UTC (permalink / raw)
  To: Svyatoslav Ryhel
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Lee Jones, Pavel Machek, Ion Agorria, Michał Mirosław,
	devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260304185751.83494-7-clamor95@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 10929 bytes --]

Hi,

On Wed, Mar 04, 2026 at 08:57:50PM +0200, Svyatoslav Ryhel wrote:
> From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> 
> Driver implements one battery cell per EC controller and supports reading
> of battery status for ASUS Transformer's pad and mobile dock.
> 
> Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
> Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> ---
>  drivers/power/supply/Kconfig                  |  11 +
>  drivers/power/supply/Makefile                 |   1 +
>  .../supply/asus-transformer-ec-battery.c      | 272 ++++++++++++++++++
>  3 files changed, 284 insertions(+)
>  create mode 100644 drivers/power/supply/asus-transformer-ec-battery.c
> 
> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
> index 81fadb0695a9..3c46b412632d 100644
> --- a/drivers/power/supply/Kconfig
> +++ b/drivers/power/supply/Kconfig
> @@ -122,6 +122,17 @@ config BATTERY_CHAGALL
>  	  This driver can also be built as a module. If so, the module will be
>  	  called chagall-battery.
>  
> +config BATTERY_ASUS_TRANSFORMER_EC
> +	tristate "Asus Transformer's battery driver"
> +	depends on MFD_ASUS_TRANSFORMER_EC
> +	help
> +	  Say Y here to enable support APM status emulation using
> +	  battery class devices.

^^^

You forgot to drop that when you used the APM_POWER config entry as
template. Otherwise the driver LGTM:

Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>

Greetings,

-- Sebastian

> +	  This sub-driver supports battery cells found in Asus Transformer
> +	  tablets and mobile docks and controlled by special embedded
> +	  controller.
> +
>  config BATTERY_CPCAP
>  	tristate "Motorola CPCAP PMIC battery driver"
>  	depends on MFD_CPCAP && IIO
> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
> index 41c400bbf022..aa5e6b05b018 100644
> --- a/drivers/power/supply/Makefile
> +++ b/drivers/power/supply/Makefile
> @@ -22,6 +22,7 @@ obj-$(CONFIG_TEST_POWER)	+= test_power.o
>  obj-$(CONFIG_BATTERY_88PM860X)	+= 88pm860x_battery.o
>  obj-$(CONFIG_CHARGER_ADP5061)	+= adp5061.o
>  obj-$(CONFIG_BATTERY_ACT8945A)	+= act8945a_charger.o
> +obj-$(CONFIG_BATTERY_ASUS_TRANSFORMER_EC)	+= asus-transformer-ec-battery.o
>  obj-$(CONFIG_BATTERY_AXP20X)	+= axp20x_battery.o
>  obj-$(CONFIG_CHARGER_AXP20X)	+= axp20x_ac_power.o
>  obj-$(CONFIG_BATTERY_CHAGALL)	+= chagall-battery.o
> diff --git a/drivers/power/supply/asus-transformer-ec-battery.c b/drivers/power/supply/asus-transformer-ec-battery.c
> new file mode 100644
> index 000000000000..aefcd3fed6fe
> --- /dev/null
> +++ b/drivers/power/supply/asus-transformer-ec-battery.c
> @@ -0,0 +1,272 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +#include <linux/array_size.h>
> +#include <linux/devm-helpers.h>
> +#include <linux/err.h>
> +#include <linux/mfd/asus-transformer-ec.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/power_supply.h>
> +#include <linux/property.h>
> +#include <linux/unaligned.h>
> +
> +#define ASUSEC_BATTERY_DATA_FRESH_MSEC		5000
> +
> +#define ASUSEC_BATTERY_DISCHARGING		0x40
> +#define ASUSEC_BATTERY_FULL_CHARGED		0x20
> +#define ASUSEC_BATTERY_NOT_CHARGING		0x10
> +
> +#define TEMP_CELSIUS_OFFSET			2731
> +
> +struct asus_ec_battery_data {
> +	const struct asusec_info *ec;
> +	struct power_supply *battery;
> +	struct power_supply_desc psy_desc;
> +	struct delayed_work poll_work;
> +	struct mutex battery_lock; /* for data refresh */
> +	unsigned long batt_data_ts;
> +	int last_state;
> +	u8 batt_data[DOCKRAM_ENTRY_BUFSIZE];
> +};
> +
> +static int asus_ec_battery_refresh(struct asus_ec_battery_data *priv)
> +{
> +	int ret = 0;
> +
> +	guard(mutex)(&priv->battery_lock);
> +
> +	if (time_before(jiffies, priv->batt_data_ts))
> +		return ret;
> +
> +	ret = asus_dockram_read(priv->ec->dockram, ASUSEC_DOCKRAM_BATT_CTL,
> +				priv->batt_data);
> +	if (ret < 0)
> +		return ret;
> +
> +	priv->batt_data_ts = jiffies +
> +		msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC);
> +
> +	return ret;
> +}
> +
> +static enum power_supply_property asus_ec_battery_properties[] = {
> +	POWER_SUPPLY_PROP_STATUS,
> +	POWER_SUPPLY_PROP_VOLTAGE_MAX,
> +	POWER_SUPPLY_PROP_CURRENT_MAX,
> +	POWER_SUPPLY_PROP_TEMP,
> +	POWER_SUPPLY_PROP_VOLTAGE_NOW,
> +	POWER_SUPPLY_PROP_CURRENT_NOW,
> +	POWER_SUPPLY_PROP_CAPACITY,
> +	POWER_SUPPLY_PROP_CHARGE_NOW,
> +	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
> +	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
> +	POWER_SUPPLY_PROP_PRESENT,
> +};
> +
> +static const unsigned int asus_ec_battery_prop_offs[] = {
> +	[POWER_SUPPLY_PROP_STATUS] = 1,
> +	[POWER_SUPPLY_PROP_VOLTAGE_MAX] = 3,
> +	[POWER_SUPPLY_PROP_CURRENT_MAX] = 5,
> +	[POWER_SUPPLY_PROP_TEMP] = 7,
> +	[POWER_SUPPLY_PROP_VOLTAGE_NOW] = 9,
> +	[POWER_SUPPLY_PROP_CURRENT_NOW] = 11,
> +	[POWER_SUPPLY_PROP_CAPACITY] = 13,
> +	[POWER_SUPPLY_PROP_CHARGE_NOW] = 15,
> +	[POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW] = 17,
> +	[POWER_SUPPLY_PROP_TIME_TO_FULL_NOW] = 19,
> +};
> +
> +static int asus_ec_battery_get_value(struct asus_ec_battery_data *priv,
> +				     enum power_supply_property psp)
> +{
> +	int ret, offs;
> +
> +	if (psp >= ARRAY_SIZE(asus_ec_battery_prop_offs))
> +		return -EINVAL;
> +
> +	offs = asus_ec_battery_prop_offs[psp];
> +	if (!offs)
> +		return -EINVAL;
> +
> +	ret = asus_ec_battery_refresh(priv);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (offs >= priv->batt_data[0])
> +		return -ENODATA;
> +
> +	return get_unaligned_le16(priv->batt_data + offs);
> +}
> +
> +static int asus_ec_battery_get_property(struct power_supply *psy,
> +					enum power_supply_property psp,
> +					union power_supply_propval *val)
> +{
> +	struct asus_ec_battery_data *priv = power_supply_get_drvdata(psy);
> +	int ret;
> +
> +	switch (psp) {
> +	case POWER_SUPPLY_PROP_PRESENT:
> +		val->intval = 1;
> +		break;
> +
> +	default:
> +		ret = asus_ec_battery_get_value(priv, psp);
> +		if (ret < 0)
> +			return ret;
> +
> +		val->intval = (s16)ret;
> +
> +		switch (psp) {
> +		case POWER_SUPPLY_PROP_STATUS:
> +			if (ret & ASUSEC_BATTERY_FULL_CHARGED)
> +				val->intval = POWER_SUPPLY_STATUS_FULL;
> +			else if (ret & ASUSEC_BATTERY_NOT_CHARGING)
> +				val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
> +			else if (ret & ASUSEC_BATTERY_DISCHARGING)
> +				val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
> +			else
> +				val->intval = POWER_SUPPLY_STATUS_CHARGING;
> +			break;
> +
> +		case POWER_SUPPLY_PROP_TEMP:
> +			val->intval -= TEMP_CELSIUS_OFFSET;
> +			break;
> +
> +		case POWER_SUPPLY_PROP_CHARGE_NOW:
> +		case POWER_SUPPLY_PROP_CURRENT_NOW:
> +		case POWER_SUPPLY_PROP_CURRENT_MAX:
> +		case POWER_SUPPLY_PROP_VOLTAGE_NOW:
> +		case POWER_SUPPLY_PROP_VOLTAGE_MAX:
> +			val->intval *= 1000;
> +			break;
> +
> +		case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
> +		case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
> +			val->intval *= 60;
> +			break;
> +
> +		default:
> +			break;
> +		}
> +
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static void asus_ec_battery_poll_work(struct work_struct *work)
> +{
> +	struct asus_ec_battery_data *priv =
> +		container_of(work, struct asus_ec_battery_data, poll_work.work);
> +	int state;
> +
> +	state = asus_ec_battery_get_value(priv, POWER_SUPPLY_PROP_STATUS);
> +	if (state < 0)
> +		return;
> +
> +	if (state & ASUSEC_BATTERY_FULL_CHARGED)
> +		state = POWER_SUPPLY_STATUS_FULL;
> +	else if (state & ASUSEC_BATTERY_DISCHARGING)
> +		state = POWER_SUPPLY_STATUS_DISCHARGING;
> +	else
> +		state = POWER_SUPPLY_STATUS_CHARGING;
> +
> +	if (priv->last_state != state) {
> +		priv->last_state = state;
> +		power_supply_changed(priv->battery);
> +	}
> +
> +	/* continuously send uevent notification */
> +	schedule_delayed_work(&priv->poll_work,
> +			      msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
> +}
> +
> +static const struct power_supply_desc asus_ec_battery_desc = {
> +	.name = "asus-ec-battery",
> +	.type = POWER_SUPPLY_TYPE_BATTERY,
> +	.properties = asus_ec_battery_properties,
> +	.num_properties = ARRAY_SIZE(asus_ec_battery_properties),
> +	.get_property = asus_ec_battery_get_property,
> +	.external_power_changed = power_supply_changed,
> +};
> +
> +static int asus_ec_battery_probe(struct platform_device *pdev)
> +{
> +	struct asus_ec_battery_data *priv;
> +	struct device *dev = &pdev->dev;
> +	struct power_supply_config cfg = { };
> +	int ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, priv);
> +
> +	mutex_init(&priv->battery_lock);
> +
> +	priv->ec = cell_to_ec(pdev);
> +	priv->batt_data_ts = jiffies - 1;
> +	priv->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
> +
> +	cfg.fwnode = dev_fwnode(dev->parent);
> +	cfg.drv_data = priv;
> +
> +	memcpy(&priv->psy_desc, &asus_ec_battery_desc, sizeof(priv->psy_desc));
> +	priv->psy_desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s-battery",
> +					     priv->ec->name);
> +
> +	priv->battery = devm_power_supply_register(dev, &priv->psy_desc, &cfg);
> +	if (IS_ERR(priv->battery))
> +		return dev_err_probe(dev, PTR_ERR(priv->battery),
> +				     "Failed to register power supply\n");
> +
> +	ret = devm_delayed_work_autocancel(dev, &priv->poll_work,
> +					   asus_ec_battery_poll_work);
> +	if (ret)
> +		return ret;
> +
> +	schedule_delayed_work(&priv->poll_work,
> +			      msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused asus_ec_battery_suspend(struct device *dev)
> +{
> +	struct asus_ec_battery_data *priv = dev_get_drvdata(dev);
> +
> +	cancel_delayed_work_sync(&priv->poll_work);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused asus_ec_battery_resume(struct device *dev)
> +{
> +	struct asus_ec_battery_data *priv = dev_get_drvdata(dev);
> +
> +	schedule_delayed_work(&priv->poll_work,
> +			      msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
> +
> +	return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(asus_ec_battery_pm_ops,
> +			 asus_ec_battery_suspend, asus_ec_battery_resume);
> +
> +static struct platform_driver asus_ec_battery_driver = {
> +	.driver = {
> +		.name = "asus-transformer-ec-battery",
> +		.pm = &asus_ec_battery_pm_ops,
> +	},
> +	.probe = asus_ec_battery_probe,
> +};
> +module_platform_driver(asus_ec_battery_driver);
> +
> +MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
> +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
> +MODULE_DESCRIPTION("ASUS Transformer's battery driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.51.0
> 
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply

* Re: [PATCH] xpad: expose xinput capabilities via sysattr
From: Sanjay Govind @ 2026-03-11  5:47 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Vicki Pfau, Mario Limonciello, Nilton Perim Neto,
	Pierre-Loup A. Griffais, linux-input, linux-kernel
In-Reply-To: <CALQgdA1jJUzpSvva-YM3UOvK2Cwtyb3sK7uQO8T5NxZ-OB4Y_Q@mail.gmail.com>

On Wed, Mar 4, 2026 at 2:34 PM Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
> You need to start with enumerating what data is currently not available
> through other means and why it is needed. Is this something that other
> gamepad-like devices also lack?

Thinking about this some more, I think what I'm really after is just a
generic way for an app to request a vendor-defined report from a
controller, so that libraries like SDL can better request the
capabilities of a device when trying to handle devices properly. As it
stands, PS4, PS5, OG Xbox, Xbox 360 and Xbox One all have vendor
defined reports that can be fetched for describing controller
information, but only PS4 and PS5 actually even give something like
SDL the ability to fetch that data right now because they can just do
hid feature report reads to get it, while the Xbox controllers don't
use HID and thus we need to expose that from the input layer.

^ permalink raw reply

* Re: [PATCH 07/12] HID: spi_hid: add ACPI support for SPI over HID
From: Dmitry Torokhov @ 2026-03-11  5:27 UTC (permalink / raw)
  To: Jingyuan Liang
  Cc: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Mark Brown,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-input, linux-doc,
	linux-kernel, linux-spi, linux-trace-kernel, devicetree, hbarnor,
	Angela Czubak
In-Reply-To: <20260303-send-upstream-v1-7-1515ba218f3d@chromium.org>

On Tue, Mar 03, 2026 at 06:12:59AM +0000, Jingyuan Liang wrote:
> From: Angela Czubak <acz@semihalf.com>
> 
> Detect SPI HID devices described in ACPI.
> 
> Signed-off-by: Angela Czubak <acz@semihalf.com>
> Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
> ---
>  drivers/hid/spi-hid/Kconfig        |  15 +++
>  drivers/hid/spi-hid/Makefile       |   1 +
>  drivers/hid/spi-hid/spi-hid-acpi.c | 253 +++++++++++++++++++++++++++++++++++++
>  drivers/hid/spi-hid/spi-hid-core.c |  27 +---
>  drivers/hid/spi-hid/spi-hid.h      |  44 +++++++
>  5 files changed, 316 insertions(+), 24 deletions(-)
> 
> diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig
> index 836fdefe8345..114b1e00da39 100644
> --- a/drivers/hid/spi-hid/Kconfig
> +++ b/drivers/hid/spi-hid/Kconfig
> @@ -10,6 +10,21 @@ menuconfig SPI_HID
>  
>  if SPI_HID
>  
> +config SPI_HID_ACPI
> +	tristate "HID over SPI transport layer ACPI driver"
> +	depends on ACPI
> +	select SPI_HID_CORE
> +	help
> +	  Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
> +	  other HID based devices which are connected to your computer via SPI.
> +	  This driver supports ACPI-based systems.
> +
> +	  If unsure, say N.
> +
> +	  This support is also available as a module.  If so, the module
> +	  will be called spi-hid-acpi. It will also build/depend on the
> +	  module spi-hid.
> +
>  config SPI_HID_CORE
>  	tristate
>  endif
> diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile
> index 92e24cddbfc2..753c7b7a7844 100644
> --- a/drivers/hid/spi-hid/Makefile
> +++ b/drivers/hid/spi-hid/Makefile
> @@ -7,3 +7,4 @@
>  
>  obj-$(CONFIG_SPI_HID_CORE)	+= spi-hid.o
>  spi-hid-objs 			= spi-hid-core.o
> +obj-$(CONFIG_SPI_HID_ACPI)	+= spi-hid-acpi.o
> diff --git a/drivers/hid/spi-hid/spi-hid-acpi.c b/drivers/hid/spi-hid/spi-hid-acpi.c
> new file mode 100644
> index 000000000000..612e74fe72f9
> --- /dev/null
> +++ b/drivers/hid/spi-hid/spi-hid-acpi.c
> @@ -0,0 +1,253 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * HID over SPI protocol, ACPI related code
> + *
> + * Copyright (c) 2021 Microsoft Corporation
> + * Copyright (c) 2026 Google LLC
> + *
> + * This code was forked out of the HID over SPI core code, which is partially
> + * based on "HID over I2C protocol implementation:
> + *
> + * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
> + * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
> + * Copyright (c) 2012 Red Hat, Inc
> + *
> + * which in turn is partially based on "USB HID support for Linux":
> + *
> + * Copyright (c) 1999 Andreas Gal
> + * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
> + * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
> + * Copyright (c) 2007-2008 Oliver Neukum
> + * Copyright (c) 2006-2010 Jiri Kosina
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/reset.h>
> +#include <linux/uuid.h>
> +
> +#include "spi-hid.h"
> +
> +/* Config structure is filled with data from ACPI */
> +struct spi_hid_acpi_config {
> +	struct spihid_ops ops;
> +
> +	struct spi_hid_conf property_conf;
> +	u32 post_power_on_delay_ms;
> +	u32 minimal_reset_delay_ms;
> +	struct acpi_device *adev;
> +};
> +
> +/* HID SPI Device: 6e2ac436-0fcf41af-a265-b32a220dcfab */
> +static guid_t spi_hid_guid =
> +	GUID_INIT(0x6E2AC436, 0x0FCF, 0x41AF,
> +		  0xA2, 0x65, 0xB3, 0x2A, 0x22, 0x0D, 0xCF, 0xAB);
> +
> +static int spi_hid_acpi_populate_config(struct spi_hid_acpi_config *conf,
> +					struct acpi_device *adev)
> +{
> +	acpi_handle handle = acpi_device_handle(adev);
> +	union acpi_object *obj;
> +
> +	conf->adev = adev;
> +
> +	/* Revision 3 for HID over SPI V1, see specification. */
> +	obj = acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 1, NULL,
> +				      ACPI_TYPE_INTEGER);
> +	if (!obj) {
> +		acpi_handle_err(handle,
> +				"Error _DSM call to get HID input report header address failed");
> +		return -ENODEV;
> +	}
> +	conf->property_conf.input_report_header_address = obj->integer.value;
> +	ACPI_FREE(obj);
> +
> +	obj = acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 2, NULL,
> +				      ACPI_TYPE_INTEGER);
> +	if (!obj) {
> +		acpi_handle_err(handle,
> +				"Error _DSM call to get HID input report body address failed");
> +		return -ENODEV;
> +	}
> +	conf->property_conf.input_report_body_address = obj->integer.value;
> +	ACPI_FREE(obj);
> +
> +	obj = acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 3, NULL,
> +				      ACPI_TYPE_INTEGER);
> +	if (!obj) {
> +		acpi_handle_err(handle,
> +				"Error _DSM call to get HID output report header address failed");
> +		return -ENODEV;
> +	}
> +	conf->property_conf.output_report_address = obj->integer.value;
> +	ACPI_FREE(obj);
> +
> +	obj = acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 4, NULL,
> +				      ACPI_TYPE_BUFFER);
> +	if (!obj) {
> +		acpi_handle_err(handle,
> +				"Error _DSM call to get HID read opcode failed");
> +		return -ENODEV;
> +	}
> +	if (obj->buffer.length == 1) {
> +		conf->property_conf.read_opcode = obj->buffer.pointer[0];
> +	} else {
> +		acpi_handle_err(handle,
> +				"Error _DSM call to get HID read opcode, too long buffer");
> +		ACPI_FREE(obj);
> +		return -ENODEV;
> +	}
> +	ACPI_FREE(obj);
> +
> +	obj = acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 5, NULL,
> +				      ACPI_TYPE_BUFFER);
> +	if (!obj) {
> +		acpi_handle_err(handle,
> +				"Error _DSM call to get HID write opcode failed");
> +		return -ENODEV;
> +	}
> +	if (obj->buffer.length == 1) {
> +		conf->property_conf.write_opcode = obj->buffer.pointer[0];
> +	} else {
> +		acpi_handle_err(handle,
> +				"Error _DSM call to get HID write opcode, too long buffer");
> +		ACPI_FREE(obj);
> +		return -ENODEV;
> +	}
> +	ACPI_FREE(obj);
> +
> +	/* Value not provided in ACPI,*/
> +	conf->post_power_on_delay_ms = 5;
> +	conf->minimal_reset_delay_ms = 150;
> +
> +	if (!acpi_has_method(handle, "_RST")) {
> +		acpi_handle_err(handle, "No reset method for acpi handle");
> +		return -ENODEV;

I would return -EINVAL as we have the device with right _DSM but without
mandated by the spec _RST.

> +	}
> +
> +	/* FIXME: not reading hid-over-spi-flags, multi-SPI not supported */
> +
> +	return 0;
> +}
> +
> +static int spi_hid_acpi_power_none(struct spihid_ops *ops)
> +{
> +	return 0;
> +}
> +
> +static int spi_hid_acpi_power_down(struct spihid_ops *ops)
> +{
> +	struct spi_hid_acpi_config *conf = container_of(ops,
> +							struct spi_hid_acpi_config,
> +							ops);
> +
> +	return acpi_device_set_power(conf->adev, ACPI_STATE_D3);
> +}
> +
> +static int spi_hid_acpi_power_up(struct spihid_ops *ops)
> +{
> +	struct spi_hid_acpi_config *conf = container_of(ops,
> +							struct spi_hid_acpi_config,
> +							ops);
> +	int error;
> +
> +	error = acpi_device_set_power(conf->adev, ACPI_STATE_D0);
> +	if (error) {
> +		dev_err(&conf->adev->dev, "Error could not power up ACPI device: %d.", error);
> +		return error;
> +	}
> +
> +	if (conf->post_power_on_delay_ms)
> +		msleep(conf->post_power_on_delay_ms);
> +
> +	return 0;
> +}
> +
> +static int spi_hid_acpi_assert_reset(struct spihid_ops *ops)
> +{
> +	return 0;
> +}
> +
> +static int spi_hid_acpi_deassert_reset(struct spihid_ops *ops)
> +{
> +	struct spi_hid_acpi_config *conf = container_of(ops,
> +							struct spi_hid_acpi_config,
> +							ops);
> +
> +	return device_reset(&conf->adev->dev);
> +}
> +
> +static void spi_hid_acpi_sleep_minimal_reset_delay(struct spihid_ops *ops)
> +{
> +	struct spi_hid_acpi_config *conf = container_of(ops,
> +							struct spi_hid_acpi_config,
> +							ops);
> +	usleep_range(1000 * conf->minimal_reset_delay_ms,
> +		     1000 * (conf->minimal_reset_delay_ms + 1));

I'd probably use "fsleep(conf->minimal_reset_delay_ms * 1000)".

> +}
> +
> +static int spi_hid_acpi_probe(struct spi_device *spi)
> +{
> +	struct device *dev = &spi->dev;
> +	struct acpi_device *adev;
> +	struct spi_hid_acpi_config *config;
> +	int error;
> +
> +	adev = ACPI_COMPANION(dev);
> +	if (!adev) {
> +		dev_err(dev, "Error could not get ACPI device.");
> +		return -ENODEV;
> +	}
> +
> +	config = devm_kzalloc(dev, sizeof(struct spi_hid_acpi_config),
> +			      GFP_KERNEL);
> +	if (!config)
> +		return -ENOMEM;
> +
> +	if (acpi_device_power_manageable(adev)) {
> +		config->ops.power_up = spi_hid_acpi_power_up;
> +		config->ops.power_down = spi_hid_acpi_power_down;
> +	} else {
> +		config->ops.power_up = spi_hid_acpi_power_none;
> +		config->ops.power_down = spi_hid_acpi_power_none;
> +	}
> +	config->ops.assert_reset = spi_hid_acpi_assert_reset;
> +	config->ops.deassert_reset = spi_hid_acpi_deassert_reset;
> +	config->ops.sleep_minimal_reset_delay =
> +		spi_hid_acpi_sleep_minimal_reset_delay;
> +
> +	error = spi_hid_acpi_populate_config(config, adev);
> +	if (error) {
> +		dev_err(dev, "%s: unable to populate config data.", __func__);
> +		return error;
> +	}

I would add a blank line.

> +	return spi_hid_core_probe(spi, &config->ops, &config->property_conf);
> +}
> +
> +static const struct acpi_device_id spi_hid_acpi_match[] = {
> +	{ "ACPI0C51", 0 },
> +	{ "PNP0C51", 0 },
> +	{ },

No comma on sentinels.

> +};
> +MODULE_DEVICE_TABLE(acpi, spi_hid_acpi_match);
> +
> +static struct spi_driver spi_hid_acpi_driver = {
> +	.driver = {
> +		.name	= "spi_hid_acpi",
> +		.owner	= THIS_MODULE,
> +		.acpi_match_table = ACPI_PTR(spi_hid_acpi_match),

This is dependent on ACPI, so no need to sue ACPI_PTR().

> +		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +		.dev_groups = spi_hid_groups,
> +	},
> +	.probe		= spi_hid_acpi_probe,
> +	.remove		= spi_hid_core_remove,
> +};
> +
> +module_spi_driver(spi_hid_acpi_driver);
> +
> +MODULE_DESCRIPTION("HID over SPI ACPI transport driver");
> +MODULE_AUTHOR("Angela Czubak <aczubak@google.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-hid-core.c
> index e3273846267e..02beb209a92d 100644
> --- a/drivers/hid/spi-hid/spi-hid-core.c
> +++ b/drivers/hid/spi-hid/spi-hid-core.c
> @@ -43,6 +43,9 @@
>  #include <linux/wait.h>
>  #include <linux/workqueue.h>
>  
> +#include "spi-hid.h"
> +#include "spi-hid-core.h"
> +
>  /* Protocol constants */
>  #define SPI_HID_READ_APPROVAL_CONSTANT		0xff
>  #define SPI_HID_INPUT_HEADER_SYNC_BYTE		0x5a
> @@ -105,30 +108,6 @@ struct spi_hid_output_report {
>  	u8 *content;
>  };
>  
> -/* struct spi_hid_conf - Conf provided to the core */
> -struct spi_hid_conf {
> -	u32 input_report_header_address;
> -	u32 input_report_body_address;
> -	u32 output_report_address;
> -	u8 read_opcode;
> -	u8 write_opcode;
> -};
> -
> -/**
> - * struct spihid_ops - Ops provided to the core
> - * @power_up: do sequencing to power up the device
> - * @power_down: do sequencing to power down the device
> - * @assert_reset: do sequencing to assert the reset line
> - * @deassert_reset: do sequencing to deassert the reset line
> - */
> -struct spihid_ops {
> -	int (*power_up)(struct spihid_ops *ops);
> -	int (*power_down)(struct spihid_ops *ops);
> -	int (*assert_reset)(struct spihid_ops *ops);
> -	int (*deassert_reset)(struct spihid_ops *ops);
> -	void (*sleep_minimal_reset_delay)(struct spihid_ops *ops);
> -};
> -
>  static struct hid_ll_driver spi_hid_ll_driver;
>  
>  static void spi_hid_populate_read_approvals(const struct spi_hid_conf *conf,
> diff --git a/drivers/hid/spi-hid/spi-hid.h b/drivers/hid/spi-hid/spi-hid.h
> new file mode 100644
> index 000000000000..1fdd45262647
> --- /dev/null
> +++ b/drivers/hid/spi-hid/spi-hid.h
> @@ -0,0 +1,44 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2021 Microsoft Corporation
> + * Copyright (c) 2026 Google LLC
> + */
> +
> +#ifndef SPI_HID_H
> +#define SPI_HID_H
> +
> +#include <linux/spi/spi.h>
> +#include <linux/sysfs.h>
> +
> +/* struct spi_hid_conf - Conf provided to the core */
> +struct spi_hid_conf {
> +	u32 input_report_header_address;
> +	u32 input_report_body_address;
> +	u32 output_report_address;
> +	u8 read_opcode;
> +	u8 write_opcode;
> +};
> +
> +/**
> + * struct spihid_ops - Ops provided to the core
> + * @power_up: do sequencing to power up the device
> + * @power_down: do sequencing to power down the device
> + * @assert_reset: do sequencing to assert the reset line
> + * @deassert_reset: do sequencing to deassert the reset line
> + */
> +struct spihid_ops {
> +	int (*power_up)(struct spihid_ops *ops);
> +	int (*power_down)(struct spihid_ops *ops);
> +	int (*assert_reset)(struct spihid_ops *ops);
> +	int (*deassert_reset)(struct spihid_ops *ops);
> +	void (*sleep_minimal_reset_delay)(struct spihid_ops *ops);
> +};
> +
> +int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
> +		       struct spi_hid_conf *conf);
> +
> +void spi_hid_core_remove(struct spi_device *spi);
> +
> +extern const struct attribute_group *spi_hid_groups[];
> +
> +#endif /* SPI_HID_H */

I am not sure if this belongs to this patch or if it should be better in
the patch introducing the main driver from the beginning.

For the ACPI part:

Reviewed-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH 15/61] trace: Prefer IS_ERR_OR_NULL over manual NULL check
From: Masami Hiramatsu @ 2026-03-11  5:13 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Philipp Hahn, amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel,
	dri-devel, gfs2, intel-gfx, intel-wired-lan, iommu, kvm,
	linux-arm-kernel, linux-block, linux-bluetooth, linux-btrfs,
	linux-cifs, linux-clk, linux-erofs, linux-ext4, linux-fsdevel,
	linux-gpio, linux-hyperv, linux-input, linux-kernel, linux-leds,
	linux-media, linux-mips, linux-mm, linux-modules, linux-mtd,
	linux-nfs, linux-omap, linux-phy, linux-pm, linux-rockchip,
	linux-s390, linux-scsi, linux-sctp, linux-security-module,
	linux-sh, linux-sound, linux-stm32, linux-trace-kernel, linux-usb,
	linux-wireless, netdev, ntfs3, samba-technical, sched-ext,
	target-devel, tipc-discussion, v9fs, Masami Hiramatsu,
	Mathieu Desnoyers
In-Reply-To: <20260310100750.303af303@gandalf.local.home>

On Tue, 10 Mar 2026 10:07:50 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:

> On Tue, 10 Mar 2026 12:48:41 +0100
> Philipp Hahn <phahn-oss@avm.de> wrote:
> 
> > Prefer using IS_ERR_OR_NULL() over using IS_ERR() and a manual NULL
> > check.
> 
> Why?
> 
> > 
> > Change generated with coccinelle.
> > 
> > To: Steven Rostedt <rostedt@goodmis.org>
> > To: Masami Hiramatsu <mhiramat@kernel.org>
> > To: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
> > Cc: linux-kernel@vger.kernel.org
> > Cc: linux-trace-kernel@vger.kernel.org
> > Signed-off-by: Philipp Hahn <phahn-oss@avm.de>
> > ---
> >  kernel/trace/fprobe.c                | 2 +-
> >  kernel/trace/kprobe_event_gen_test.c | 2 +-
> >  kernel/trace/trace_events_hist.c     | 2 +-
> >  3 files changed, 3 insertions(+), 3 deletions(-)
> > 
> > diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c
> > index dcadf1d23b8a31f571392d0c49cbd22df1716b4f..a94ce810d83b90f55d1178a9bd29c78fd068df4c 100644
> > --- a/kernel/trace/fprobe.c
> > +++ b/kernel/trace/fprobe.c
> > @@ -607,7 +607,7 @@ static int fprobe_module_callback(struct notifier_block *nb,
> >  	do {
> >  		rhashtable_walk_start(&iter);
> >  
> > -		while ((node = rhashtable_walk_next(&iter)) && !IS_ERR(node))
> > +		while (!IS_ERR_OR_NULL((node = rhashtable_walk_next(&iter))))
> 
> Ug, No!
> 
> That looks so much worse than the original.

Hmm, now IS_ERR_OR_NULL() is an inline function, so it is safe.
But if you want to use IS_ERR_OR_NULL() here, it will be better something like

node = rhashtable_walk_next(&iter);
while (!IS_ERR_OR_NULL(node)) {
	fprobe_remove_node_in_module(mod, node, &alist);
	node = rhashtable_walk_next(&iter);
}

Thanks,

> 
> -- Steve
> 
> >  			fprobe_remove_node_in_module(mod, node, &alist);
> >  
> >  		rhashtable_walk_stop(&iter);
> > diff --git a/kernel/trace/kprobe_event_gen_test.c b/kernel/trace/kprobe_event_gen_test.c
> > index 5a4b722b50451bfdee42769a6d3be39c055690d1..a1735ca273f0b756aa1fcfcdab30ddad9bc51c5f 100644
> > --- a/kernel/trace/kprobe_event_gen_test.c
> > +++ b/kernel/trace/kprobe_event_gen_test.c
> > @@ -75,7 +75,7 @@ static struct trace_event_file *gen_kretprobe_test;
> >  
> >  static bool trace_event_file_is_valid(struct trace_event_file *input)
> >  {
> > -	return input && !IS_ERR(input);
> > +	return !IS_ERR_OR_NULL(input);
> >  }
> >  
> >  /*
> > diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
> > index 73ea180cad555898693e92ee397a1c9493c7c167..59df215e1dfd9349eca1c0823ed709ec7285f766 100644
> > --- a/kernel/trace/trace_events_hist.c
> > +++ b/kernel/trace/trace_events_hist.c
> > @@ -3973,7 +3973,7 @@ trace_action_create_field_var(struct hist_trigger_data *hist_data,
> >  	 */
> >  	field_var = create_target_field_var(hist_data, system, event, var);
> >  
> > -	if (field_var && !IS_ERR(field_var)) {
> > +	if (!IS_ERR_OR_NULL(field_var)) {
> >  		save_field_var(hist_data, field_var);
> >  		hist_field = field_var->var;
> >  	} else {
> > 
> 


-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>

^ permalink raw reply

* Re: [PATCH 02/12] HID: Add BUS_SPI support and define HID_SPI_DEVICE macro
From: Dmitry Torokhov @ 2026-03-11  5:11 UTC (permalink / raw)
  To: Jingyuan Liang
  Cc: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Mark Brown,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-input, linux-doc,
	linux-kernel, linux-spi, linux-trace-kernel, devicetree, hbarnor,
	Jarrett Schultz, Dmitry Antipov
In-Reply-To: <20260303-send-upstream-v1-2-1515ba218f3d@chromium.org>

On Tue, Mar 03, 2026 at 06:12:54AM +0000, Jingyuan Liang wrote:
> From: Jarrett Schultz <jaschultz@microsoft.com>
> 
> If connecting a hid_device with bus field indicating BUS_SPI print out
> "SPI" in the debug print.
> 
> Macro sets the bus field to BUS_SPI and uses arguments to set vendor
> product fields.
> 
> Signed-off-by: Dmitry Antipov <dmanti@microsoft.com>
> Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
> ---
>  drivers/hid/hid-core.c | 3 +++
>  include/linux/hid.h    | 2 ++
>  2 files changed, 5 insertions(+)
> 
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index a5b3a8ca2fcb..813c9c743ccd 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -2316,6 +2316,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
>  	case BUS_I2C:
>  		bus = "I2C";
>  		break;
> +	case BUS_SPI:
> +		bus = "SPI";
> +		break;
>  	case BUS_SDW:
>  		bus = "SOUNDWIRE";
>  		break;
> diff --git a/include/linux/hid.h b/include/linux/hid.h
> index dce862cafbbd..957f322a0ebd 100644
> --- a/include/linux/hid.h
> +++ b/include/linux/hid.h
> @@ -786,6 +786,8 @@ struct hid_descriptor {
>  	.bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod)
>  #define HID_I2C_DEVICE(ven, prod)				\
>  	.bus = BUS_I2C, .vendor = (ven), .product = (prod)
> +#define HID_SPI_DEVICE(ven, prod)				\
> +	.bus = BUS_SPI, .vendor = (ven), .product = (prod)
>  
>  #define HID_REPORT_ID(rep) \
>  	.report_type = (rep)

Reviewed-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH 01/12] Documentation: Correction in HID output_report callback description.
From: Dmitry Torokhov @ 2026-03-11  5:10 UTC (permalink / raw)
  To: Jingyuan Liang
  Cc: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Mark Brown,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-input, linux-doc,
	linux-kernel, linux-spi, linux-trace-kernel, devicetree, hbarnor,
	Jarrett Schultz, Dmitry Antipov
In-Reply-To: <20260303-send-upstream-v1-1-1515ba218f3d@chromium.org>

On Tue, Mar 03, 2026 at 06:12:53AM +0000, Jingyuan Liang wrote:
> From: Jarrett Schultz <jaschultz@microsoft.com>
> 
> Originally output_report callback was described as must-be asynchronous,
> but that is not the case in some implementations, namely i2c-hid.
> Correct the documentation to say that it may be asynchronous.
> 
> Signed-off-by: Dmitry Antipov <dmanti@microsoft.com>
> Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
> ---
>  Documentation/hid/hid-transport.rst | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/Documentation/hid/hid-transport.rst b/Documentation/hid/hid-transport.rst
> index 6f1692da296c..2008cf432af1 100644
> --- a/Documentation/hid/hid-transport.rst
> +++ b/Documentation/hid/hid-transport.rst
> @@ -327,8 +327,8 @@ The available HID callbacks are:
>  
>     Send raw output report via intr channel. Used by some HID device drivers
>     which require high throughput for outgoing requests on the intr channel. This
> -   must not cause SET_REPORT calls! This must be implemented as asynchronous
> -   output report on the intr channel!
> +   must not cause SET_REPORT calls! This call might be asynchronous, so the
> +   caller should not expect an immediate response!

Reviewed-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH 57/61] reset: Prefer IS_ERR_OR_NULL over manual NULL check
From: Masami Hiramatsu @ 2026-03-11  4:59 UTC (permalink / raw)
  To: Philipp Hahn
  Cc: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
	gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
	linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
	linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
	linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
	linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
	linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
	linux-sctp, linux-security-module, linux-sh, linux-sound,
	linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
	netdev, ntfs3, samba-technical, sched-ext, target-devel,
	tipc-discussion, v9fs, Philipp Zabel
In-Reply-To: <20260310-b4-is_err_or_null-v1-57-bd63b656022d@avm.de>

On Tue, 10 Mar 2026 12:49:23 +0100
Philipp Hahn <phahn-oss@avm.de> wrote:

> Prefer using IS_ERR_OR_NULL() over using IS_ERR() and a manual NULL
> check.
> 
> Semantich change: Previously the code only printed the warning on error,
> but not when the pointer was NULL. Now the warning is printed in both
> cases!
> 
> Change found with coccinelle.
> 
> To: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Philipp Hahn <phahn-oss@avm.de>
> ---
>  drivers/reset/core.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/reset/core.c b/drivers/reset/core.c
> index fceec45c8afc1e74fe46311bdc023ff257e8d770..649bb4ebabb20a09349ccbfc62f8280621df450e 100644
> --- a/drivers/reset/core.c
> +++ b/drivers/reset/core.c
> @@ -715,7 +715,7 @@ EXPORT_SYMBOL_GPL(reset_control_bulk_acquire);
>   */
>  void reset_control_release(struct reset_control *rstc)
>  {
> -	if (!rstc || WARN_ON(IS_ERR(rstc)))
> +	if (WARN_ON(IS_ERR_OR_NULL(rstc)))

This changes the behavior when rstc == NULL.
WARN_ON does not hit when rstc == NULL in the original code.

Thanks,

>  		return;
>  
>  	if (reset_control_is_array(rstc))
> 
> -- 
> 2.43.0
> 
> 


-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>

^ permalink raw reply

* Re: [PATCH v4 1/2] dt-bindings: input: touchscreen:
From: phucduc.bui @ 2026-03-11  4:28 UTC (permalink / raw)
  To: dmitry.torokhov; +Cc: linux-input, linux-kernel, phucduc.bui
In-Reply-To: <abCx6kZkHPd3lYh9@google.com>

Hi Dmitry,

Thank you for applying the patch.

Best regards,
Phuc

^ permalink raw reply

* Re: [PATCH v4 1/2] dt-bindings: input: touchscreen:
From: phucduc.bui @ 2026-03-11  4:25 UTC (permalink / raw)
  To: dmitry.torokhov
  Cc: conor+dt, devicetree, geert+renesas, hechtb, javier.carrasco,
	jeff, krzk+dt, krzk, krzysztof.kozlowski, linux-input,
	linux-kernel, linux-renesas-soc, magnus.damm, phucduc.bui, robh,
	wsa+renesas
In-Reply-To: <abCqyU2-iQgcghzy@google.com>

Hi Dmitry,

Thank you for applying the patch.

Best regards,
Phuc

^ permalink raw reply

* Re: [PATCH v4 2/2] arm: dts: renesas: r8a7740-armadillo800eva:
From: phucduc.bui @ 2026-03-11  4:22 UTC (permalink / raw)
  To: wsa+renesas
  Cc: conor+dt, devicetree, dmitry.torokhov, geert+renesas, hechtb,
	javier.carrasco, jeff, krzk+dt, krzk, krzysztof.kozlowski,
	linux-input, linux-kernel, linux-renesas-soc, magnus.damm,
	phucduc.bui, robh
In-Reply-To: <abCkX_oyqaa9jM5F@shikoro>

Hi Wolfram,

Thank you for your review.

Best regards,
Phuc

^ permalink raw reply

* [PATCH 2/2] Input: Touchscreen: tsc200x - delegate wakeup IRQ
From: phucduc.bui @ 2026-03-11  3:17 UTC (permalink / raw)
  To: dmitry.torokhov
  Cc: conor+dt, devicetree, krzk+dt, linux-input, linux-kernel, marex,
	mingo, mwelling, phucduc.bui, robh, tglx
In-Reply-To: <abC1NfUoHCHEeZpr@google.com>

Hi Dmitry,

> Sorry, but this just makes it all worse. There is no downside from
> letting the driver to control wakeup if it wants to, so I'd rather leave
> it as it was, at least for now.

Thanks you for your reply

I was thinking that the code might be simplified by removing
ts->wake_irq_enabled.

In resume(), we could just check device_may_wakeup(dev) before calling
disable_irq_wake(ts->irq). From what I can see, wake_irq_enabled is only
used there, so it seems redundant.

I don't have the hardware to test this right now, so I didn't try the
change myself.

Do you think it would make sense to remove this field?

Best regards,
Phuc

^ permalink raw reply

* [PATCH] Remove unused headers in x86/tools, scripts, pps, input
From: Oli @ 2026-03-11  3:01 UTC (permalink / raw)
  To: Thomas Gleixner, Ingo Molnar, Steven Rostedt
  Cc: Mathieu Desnoyers, Masami Hiramatsu, Rodolfo Giometti,
	Henrik Rydberg, Dmitry Torokhov, Nathan Chancellor,
	Nicolas Schier, linux-kernel, linux-trace-kernel, linux-kbuild,
	linux-input, x86


[-- Attachment #1.1: Type: text/plain, Size: 2304 bytes --]

From c78a0572f5ec2b927f9b723af687e6ef913561a4 Mon Sep 17 00:00:00 2001
From: Eddie Hudgins <Oochiolio@gmail.com>
Date: Tue, 10 Mar 2026 21:53:07 -0500
Subject: [PATCH] Signed-off-by: Eddie Hudgins <Oochiolio@gmail.com>
 arch/x86/tools: Removed headers in relocs_32.c scripts/basic: Removed
headers
 in fixdep.c drivers/pps: Removed headers in pps.c drivers/input: Removed
 headers in input-mt.c

These changes compile for x86, x86_64, and powerpc (Those were the only
ones fairly tested) under defconfig. This aims to clean up code and
simplify the files for developers. This will also contribute to start of
decluttering the environment.
---
 arch/x86/tools/relocs_32.c | 1 -
 drivers/input/input-mt.c   | 1 -
 drivers/pps/pps.c          | 3 ---
 scripts/basic/fixdep.c     | 1 -
 4 files changed, 6 deletions(-)

diff --git a/arch/x86/tools/relocs_32.c b/arch/x86/tools/relocs_32.c
index 9442ff78be83..9e4668e74993 100644
--- a/arch/x86/tools/relocs_32.c
+++ b/arch/x86/tools/relocs_32.c
@@ -1,6 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0
 #include "relocs.h"
-
 #define ELF_BITS 32

 #define ELF_MACHINE            EM_386
diff --git a/drivers/input/input-mt.c b/drivers/input/input-mt.c
index c06e98fbd77c..b553b7f2313a 100644
--- a/drivers/input/input-mt.c
+++ b/drivers/input/input-mt.c
@@ -7,7 +7,6 @@

 #include <linux/input/mt.h>
 #include <linux/export.h>
-#include <linux/slab.h>
 #include "input-core-private.h"

 #define TRKID_SGN      ((TRKID_MAX + 1) >> 1)
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c
index c6b8b6478276..a9a8802c2399 100644
--- a/drivers/pps/pps.c
+++ b/drivers/pps/pps.c
@@ -7,14 +7,11 @@

 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

-#include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/init.h>
-#include <linux/sched.h>
 #include <linux/uaccess.h>
 #include <linux/idr.h>
 #include <linux/mutex.h>
-#include <linux/cdev.h>
 #include <linux/poll.h>
 #include <linux/pps_kernel.h>
 #include <linux/slab.h>
diff --git a/scripts/basic/fixdep.c b/scripts/basic/fixdep.c
index cdd5da7e009b..feb9e7d8984d 100644
--- a/scripts/basic/fixdep.c
+++ b/scripts/basic/fixdep.c
@@ -89,7 +89,6 @@
  *  but I don't think the added complexity is worth it)
  */

-#include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <fcntl.h>
--
2.43.0

[-- Attachment #1.2: Type: text/html, Size: 2829 bytes --]

[-- Attachment #2: 0001-Signed-off-by-Eddie-Hudgins-Oochiolio-gmail.com.patch --]
[-- Type: application/octet-stream, Size: 2297 bytes --]

From c78a0572f5ec2b927f9b723af687e6ef913561a4 Mon Sep 17 00:00:00 2001
From: Eddie Hudgins <Oochiolio@gmail.com>
Date: Tue, 10 Mar 2026 21:53:07 -0500
Subject: [PATCH] Signed-off-by: Eddie Hudgins <Oochiolio@gmail.com>
 arch/x86/tools: Removed headers in relocs_32.c scripts/basic: Removed headers
 in fixdep.c drivers/pps: Removed headers in pps.c drivers/input: Removed
 headers in input-mt.c

These changes compile for x86, x86_64, and powerpc (Those were the only ones fairly tested) under defconfig. This aims to clean up code and simplify the files for developers. This will also contribute to start of decluttering the environment.
---
 arch/x86/tools/relocs_32.c | 1 -
 drivers/input/input-mt.c   | 1 -
 drivers/pps/pps.c          | 3 ---
 scripts/basic/fixdep.c     | 1 -
 4 files changed, 6 deletions(-)

diff --git a/arch/x86/tools/relocs_32.c b/arch/x86/tools/relocs_32.c
index 9442ff78be83..9e4668e74993 100644
--- a/arch/x86/tools/relocs_32.c
+++ b/arch/x86/tools/relocs_32.c
@@ -1,6 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0
 #include "relocs.h"
-
 #define ELF_BITS 32
 
 #define ELF_MACHINE		EM_386
diff --git a/drivers/input/input-mt.c b/drivers/input/input-mt.c
index c06e98fbd77c..b553b7f2313a 100644
--- a/drivers/input/input-mt.c
+++ b/drivers/input/input-mt.c
@@ -7,7 +7,6 @@
 
 #include <linux/input/mt.h>
 #include <linux/export.h>
-#include <linux/slab.h>
 #include "input-core-private.h"
 
 #define TRKID_SGN	((TRKID_MAX + 1) >> 1)
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c
index c6b8b6478276..a9a8802c2399 100644
--- a/drivers/pps/pps.c
+++ b/drivers/pps/pps.c
@@ -7,14 +7,11 @@
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
-#include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/init.h>
-#include <linux/sched.h>
 #include <linux/uaccess.h>
 #include <linux/idr.h>
 #include <linux/mutex.h>
-#include <linux/cdev.h>
 #include <linux/poll.h>
 #include <linux/pps_kernel.h>
 #include <linux/slab.h>
diff --git a/scripts/basic/fixdep.c b/scripts/basic/fixdep.c
index cdd5da7e009b..feb9e7d8984d 100644
--- a/scripts/basic/fixdep.c
+++ b/scripts/basic/fixdep.c
@@ -89,7 +89,6 @@
  *  but I don't think the added complexity is worth it)
  */
 
-#include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <fcntl.h>
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH 56/61] clk: Prefer IS_ERR_OR_NULL over manual NULL check
From: Chen-Yu Tsai @ 2026-03-11  2:07 UTC (permalink / raw)
  To: Philipp Hahn
  Cc: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
	gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
	linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
	linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
	linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
	linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
	linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
	linux-sctp, linux-security-module, linux-sh, linux-sound,
	linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
	netdev, ntfs3, samba-technical, sched-ext, target-devel,
	tipc-discussion, v9fs, Michael Turquette, Stephen Boyd,
	Daniel Lezcano, Thomas Gleixner
In-Reply-To: <20260310-b4-is_err_or_null-v1-56-bd63b656022d@avm.de>

On Tue, Mar 10, 2026 at 9:57 PM Philipp Hahn <phahn-oss@avm.de> wrote:
>
> Prefer using IS_ERR_OR_NULL() over using IS_ERR() and a manual NULL
> check.
>
> Semantich change: Previously the code only printed the warning on error,
> but not when the pointer was NULL. Now the warning is printed in both
> cases!
>
> Change found with coccinelle.
>
> To: Michael Turquette <mturquette@baylibre.com>
> To: Stephen Boyd <sboyd@kernel.org>
> To: Daniel Lezcano <daniel.lezcano@kernel.org>
> To: Thomas Gleixner <tglx@kernel.org>
> Cc: linux-clk@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Philipp Hahn <phahn-oss@avm.de>
> ---
>  drivers/clk/clk.c               | 4 ++--
>  drivers/clocksource/timer-pxa.c | 2 +-
>  2 files changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index 47093cda9df32223c1120c3710261296027c4cd3..35146e3869a7dd93741d10b7223d4488a9216ed1 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
> @@ -4558,7 +4558,7 @@ void clk_unregister(struct clk *clk)
>         unsigned long flags;
>         const struct clk_ops *ops;
>
> -       if (!clk || WARN_ON_ONCE(IS_ERR(clk)))
> +       if (WARN_ON_ONCE(IS_ERR_OR_NULL(clk)))
>                 return;
>
>         clk_debug_unregister(clk->core);
> @@ -4744,7 +4744,7 @@ void __clk_put(struct clk *clk)
>  {
>         struct module *owner;
>
> -       if (!clk || WARN_ON_ONCE(IS_ERR(clk)))
> +       if (WARN_ON_ONCE(IS_ERR_OR_NULL(clk)))

clk_get_optional() returns NULL if the clk isn't present.

Drivers would just pass this to clk_put(). Your change here would cause
this pattern to emit a very big warning.

I don't think this change should be landed.


ChenYu

>                 return;
>
>         clk_prepare_lock();
> diff --git a/drivers/clocksource/timer-pxa.c b/drivers/clocksource/timer-pxa.c
> index 7ad0e5adb2ffac4125c34710fc67f4b45f30331d..f65fb0b7fc318b766227e5e7a4c0fb08ba11c8f9 100644
> --- a/drivers/clocksource/timer-pxa.c
> +++ b/drivers/clocksource/timer-pxa.c
> @@ -218,7 +218,7 @@ void __init pxa_timer_nodt_init(int irq, void __iomem *base)
>
>         timer_base = base;
>         clk = clk_get(NULL, "OSTIMER0");
> -       if (clk && !IS_ERR(clk)) {
> +       if (!IS_ERR_OR_NULL(clk)) {
>                 clk_prepare_enable(clk);
>                 pxa_timer_common_init(irq, clk_get_rate(clk));
>         } else {
>
> --
> 2.43.0
>
>

^ permalink raw reply

* Re: [PATCH] Input: hgpk - remove protocol support
From: Andres Salomon @ 2026-03-11  1:04 UTC (permalink / raw)
  To: Dmitry Torokhov, linux-kernel; +Cc: linux-input, Daniel Drake
In-Reply-To: <abC5U_JigA9TrGYu@google.com>

That seems fair. I haven't had access to OLPC hardware in just about as 
long. If you need it:

Acked-by: Andres Salomon <dilinger@queued.net>


On 3/10/26 20:43, Dmitry Torokhov wrote:
> The protocol flavor for ALPS touchpads found in OLPC laptops has been
> broken since 2015 commit c378b5119eb0 ("Input: psmouse - factor out
> common protocol probing code") that forgot to add hgpk_init() to HGPK
> entry in psmouse_protocols array.
> 
> Since nobody complained for 10 years let's remove it.
> 
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> ---
>   drivers/input/mouse/Kconfig        |   10 -
>   drivers/input/mouse/Makefile       |    1 -
>   drivers/input/mouse/hgpk.c         | 1063 ----------------------------
>   drivers/input/mouse/hgpk.h         |   61 --
>   drivers/input/mouse/psmouse-base.c |   21 +-
>   drivers/input/mouse/psmouse.h      |    2 +-
>   6 files changed, 2 insertions(+), 1156 deletions(-)
> 
> diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
> index 833b643f0616..30d119d4634b 100644
> --- a/drivers/input/mouse/Kconfig
> +++ b/drivers/input/mouse/Kconfig
> @@ -164,16 +164,6 @@ config MOUSE_PS2_TOUCHKIT
>   
>   	  If unsure, say N.
>   
> -config MOUSE_PS2_OLPC
> -	bool "OLPC PS/2 mouse protocol extension"
> -	depends on MOUSE_PS2 && OLPC
> -	help
> -	  Say Y here if you have an OLPC XO-1 laptop (with built-in
> -	  PS/2 touchpad/tablet device).  The manufacturer calls the
> -	  touchpad an HGPK.
> -
> -	  If unsure, say N.
> -
>   config MOUSE_PS2_FOCALTECH
>   	bool "FocalTech PS/2 mouse protocol extension" if EXPERT
>   	default y
> diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
> index a1336d5bee6f..137ed0c32d90 100644

^ permalink raw reply

* [PATCH] Input: hgpk - remove protocol support
From: Dmitry Torokhov @ 2026-03-11  0:43 UTC (permalink / raw)
  To: linux-kernel; +Cc: linux-input, Andres Salomon, Daniel Drake

The protocol flavor for ALPS touchpads found in OLPC laptops has been
broken since 2015 commit c378b5119eb0 ("Input: psmouse - factor out
common protocol probing code") that forgot to add hgpk_init() to HGPK
entry in psmouse_protocols array.

Since nobody complained for 10 years let's remove it.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/mouse/Kconfig        |   10 -
 drivers/input/mouse/Makefile       |    1 -
 drivers/input/mouse/hgpk.c         | 1063 ----------------------------
 drivers/input/mouse/hgpk.h         |   61 --
 drivers/input/mouse/psmouse-base.c |   21 +-
 drivers/input/mouse/psmouse.h      |    2 +-
 6 files changed, 2 insertions(+), 1156 deletions(-)

diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
index 833b643f0616..30d119d4634b 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -164,16 +164,6 @@ config MOUSE_PS2_TOUCHKIT
 
 	  If unsure, say N.
 
-config MOUSE_PS2_OLPC
-	bool "OLPC PS/2 mouse protocol extension"
-	depends on MOUSE_PS2 && OLPC
-	help
-	  Say Y here if you have an OLPC XO-1 laptop (with built-in
-	  PS/2 touchpad/tablet device).  The manufacturer calls the
-	  touchpad an HGPK.
-
-	  If unsure, say N.
-
 config MOUSE_PS2_FOCALTECH
 	bool "FocalTech PS/2 mouse protocol extension" if EXPERT
 	default y
diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
index a1336d5bee6f..137ed0c32d90 100644
--- a/drivers/input/mouse/Makefile
+++ b/drivers/input/mouse/Makefile
@@ -29,7 +29,6 @@ psmouse-objs := psmouse-base.o synaptics.o focaltech.o
 psmouse-$(CONFIG_MOUSE_PS2_ALPS)	+= alps.o
 psmouse-$(CONFIG_MOUSE_PS2_BYD)		+= byd.o
 psmouse-$(CONFIG_MOUSE_PS2_ELANTECH)	+= elantech.o
-psmouse-$(CONFIG_MOUSE_PS2_OLPC)	+= hgpk.o
 psmouse-$(CONFIG_MOUSE_PS2_LOGIPS2PP)	+= logips2pp.o
 psmouse-$(CONFIG_MOUSE_PS2_LIFEBOOK)	+= lifebook.o
 psmouse-$(CONFIG_MOUSE_PS2_SENTELIC)	+= sentelic.o
diff --git a/drivers/input/mouse/hgpk.c b/drivers/input/mouse/hgpk.c
deleted file mode 100644
index 6125652e5ad8..000000000000
--- a/drivers/input/mouse/hgpk.c
+++ /dev/null
@@ -1,1063 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * OLPC HGPK (XO-1) touchpad PS/2 mouse driver
- *
- * Copyright (c) 2006-2008 One Laptop Per Child
- * Authors:
- *   Zephaniah E. Hull
- *   Andres Salomon <dilinger@debian.org>
- *
- * This driver is partly based on the ALPS driver, which is:
- *
- * Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au>
- * Copyright (c) 2003-2005 Peter Osterlund <petero2@telia.com>
- * Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru>
- * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz>
- */
-
-/*
- * The spec from ALPS is available from
- * <http://wiki.laptop.org/go/Touch_Pad/Tablet>.  It refers to this
- * device as HGPK (Hybrid GS, PT, and Keymatrix).
- *
- * The earliest versions of the device had simultaneous reporting; that
- * was removed.  After that, the device used the Advanced Mode GS/PT streaming
- * stuff.  That turned out to be too buggy to support, so we've finally
- * switched to Mouse Mode (which utilizes only the center 1/3 of the touchpad).
- */
-
-#define DEBUG
-#include <linux/slab.h>
-#include <linux/input.h>
-#include <linux/module.h>
-#include <linux/serio.h>
-#include <linux/libps2.h>
-#include <linux/delay.h>
-#include <asm/olpc.h>
-
-#include "psmouse.h"
-#include "hgpk.h"
-
-#define ILLEGAL_XY 999999
-
-static bool tpdebug;
-module_param(tpdebug, bool, 0644);
-MODULE_PARM_DESC(tpdebug, "enable debugging, dumping packets to KERN_DEBUG.");
-
-static int recalib_delta = 100;
-module_param(recalib_delta, int, 0644);
-MODULE_PARM_DESC(recalib_delta,
-	"packets containing a delta this large will be discarded, and a "
-	"recalibration may be scheduled.");
-
-static int jumpy_delay = 20;
-module_param(jumpy_delay, int, 0644);
-MODULE_PARM_DESC(jumpy_delay,
-	"delay (ms) before recal after jumpiness detected");
-
-static int spew_delay = 1;
-module_param(spew_delay, int, 0644);
-MODULE_PARM_DESC(spew_delay,
-	"delay (ms) before recal after packet spew detected");
-
-static int recal_guard_time;
-module_param(recal_guard_time, int, 0644);
-MODULE_PARM_DESC(recal_guard_time,
-	"interval (ms) during which recal will be restarted if packet received");
-
-static int post_interrupt_delay = 40;
-module_param(post_interrupt_delay, int, 0644);
-MODULE_PARM_DESC(post_interrupt_delay,
-	"delay (ms) before recal after recal interrupt detected");
-
-static bool autorecal = true;
-module_param(autorecal, bool, 0644);
-MODULE_PARM_DESC(autorecal, "enable recalibration in the driver");
-
-static char hgpk_mode_name[16];
-module_param_string(hgpk_mode, hgpk_mode_name, sizeof(hgpk_mode_name), 0644);
-MODULE_PARM_DESC(hgpk_mode,
-	"default hgpk mode: mouse, glidesensor or pentablet");
-
-static int hgpk_default_mode = HGPK_MODE_MOUSE;
-
-static const char * const hgpk_mode_names[] = {
-	[HGPK_MODE_MOUSE] = "Mouse",
-	[HGPK_MODE_GLIDESENSOR] = "GlideSensor",
-	[HGPK_MODE_PENTABLET] = "PenTablet",
-};
-
-static int hgpk_mode_from_name(const char *buf, int len)
-{
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(hgpk_mode_names); i++) {
-		const char *name = hgpk_mode_names[i];
-		if (strlen(name) == len && !strncasecmp(name, buf, len))
-			return i;
-	}
-
-	return HGPK_MODE_INVALID;
-}
-
-/*
- * see if new value is within 20% of half of old value
- */
-static int approx_half(int curr, int prev)
-{
-	int belowhalf, abovehalf;
-
-	if (curr < 5 || prev < 5)
-		return 0;
-
-	belowhalf = (prev * 8) / 20;
-	abovehalf = (prev * 12) / 20;
-
-	return belowhalf < curr && curr <= abovehalf;
-}
-
-/*
- * Throw out oddly large delta packets, and any that immediately follow whose
- * values are each approximately half of the previous.  It seems that the ALPS
- * firmware emits errant packets, and they get averaged out slowly.
- */
-static int hgpk_discard_decay_hack(struct psmouse *psmouse, int x, int y)
-{
-	struct hgpk_data *priv = psmouse->private;
-	int avx, avy;
-	bool do_recal = false;
-
-	avx = abs(x);
-	avy = abs(y);
-
-	/* discard if too big, or half that but > 4 times the prev delta */
-	if (avx > recalib_delta ||
-		(avx > recalib_delta / 2 && ((avx / 4) > priv->xlast))) {
-		psmouse_warn(psmouse, "detected %dpx jump in x\n", x);
-		priv->xbigj = avx;
-	} else if (approx_half(avx, priv->xbigj)) {
-		psmouse_warn(psmouse, "detected secondary %dpx jump in x\n", x);
-		priv->xbigj = avx;
-		priv->xsaw_secondary++;
-	} else {
-		if (priv->xbigj && priv->xsaw_secondary > 1)
-			do_recal = true;
-		priv->xbigj = 0;
-		priv->xsaw_secondary = 0;
-	}
-
-	if (avy > recalib_delta ||
-		(avy > recalib_delta / 2 && ((avy / 4) > priv->ylast))) {
-		psmouse_warn(psmouse, "detected %dpx jump in y\n", y);
-		priv->ybigj = avy;
-	} else if (approx_half(avy, priv->ybigj)) {
-		psmouse_warn(psmouse, "detected secondary %dpx jump in y\n", y);
-		priv->ybigj = avy;
-		priv->ysaw_secondary++;
-	} else {
-		if (priv->ybigj && priv->ysaw_secondary > 1)
-			do_recal = true;
-		priv->ybigj = 0;
-		priv->ysaw_secondary = 0;
-	}
-
-	priv->xlast = avx;
-	priv->ylast = avy;
-
-	if (do_recal && jumpy_delay) {
-		psmouse_warn(psmouse, "scheduling recalibration\n");
-		psmouse_queue_work(psmouse, &priv->recalib_wq,
-				msecs_to_jiffies(jumpy_delay));
-	}
-
-	return priv->xbigj || priv->ybigj;
-}
-
-static void hgpk_reset_spew_detection(struct hgpk_data *priv)
-{
-	priv->spew_count = 0;
-	priv->dupe_count = 0;
-	priv->x_tally = 0;
-	priv->y_tally = 0;
-	priv->spew_flag = NO_SPEW;
-}
-
-static void hgpk_reset_hack_state(struct psmouse *psmouse)
-{
-	struct hgpk_data *priv = psmouse->private;
-
-	priv->abs_x = priv->abs_y = -1;
-	priv->xlast = priv->ylast = ILLEGAL_XY;
-	priv->xbigj = priv->ybigj = 0;
-	priv->xsaw_secondary = priv->ysaw_secondary = 0;
-	hgpk_reset_spew_detection(priv);
-}
-
-/*
- * We have no idea why this particular hardware bug occurs.  The touchpad
- * will randomly start spewing packets without anything touching the
- * pad.  This wouldn't necessarily be bad, but it's indicative of a
- * severely miscalibrated pad; attempting to use the touchpad while it's
- * spewing means the cursor will jump all over the place, and act "drunk".
- *
- * The packets that are spewed tend to all have deltas between -2 and 2, and
- * the cursor will move around without really going very far.  It will
- * tend to end up in the same location; if we tally up the changes over
- * 100 packets, we end up w/ a final delta of close to 0.  This happens
- * pretty regularly when the touchpad is spewing, and is pretty hard to
- * manually trigger (at least for *my* fingers).  So, it makes a perfect
- * scheme for detecting spews.
- */
-static void hgpk_spewing_hack(struct psmouse *psmouse,
-			      int l, int r, int x, int y)
-{
-	struct hgpk_data *priv = psmouse->private;
-
-	/* ignore button press packets; many in a row could trigger
-	 * a false-positive! */
-	if (l || r)
-		return;
-
-	/* don't track spew if the workaround feature has been turned off */
-	if (!spew_delay)
-		return;
-
-	if (abs(x) > 3 || abs(y) > 3) {
-		/* no spew, or spew ended */
-		hgpk_reset_spew_detection(priv);
-		return;
-	}
-
-	/* Keep a tally of the overall delta to the cursor position caused by
-	 * the spew */
-	priv->x_tally += x;
-	priv->y_tally += y;
-
-	switch (priv->spew_flag) {
-	case NO_SPEW:
-		/* we're not spewing, but this packet might be the start */
-		priv->spew_flag = MAYBE_SPEWING;
-
-		fallthrough;
-
-	case MAYBE_SPEWING:
-		priv->spew_count++;
-
-		if (priv->spew_count < SPEW_WATCH_COUNT)
-			break;
-
-		/* excessive spew detected, request recalibration */
-		priv->spew_flag = SPEW_DETECTED;
-
-		fallthrough;
-
-	case SPEW_DETECTED:
-		/* only recalibrate when the overall delta to the cursor
-		 * is really small. if the spew is causing significant cursor
-		 * movement, it is probably a case of the user moving the
-		 * cursor very slowly across the screen. */
-		if (abs(priv->x_tally) < 3 && abs(priv->y_tally) < 3) {
-			psmouse_warn(psmouse, "packet spew detected (%d,%d)\n",
-				     priv->x_tally, priv->y_tally);
-			priv->spew_flag = RECALIBRATING;
-			psmouse_queue_work(psmouse, &priv->recalib_wq,
-					   msecs_to_jiffies(spew_delay));
-		}
-
-		break;
-	case RECALIBRATING:
-		/* we already detected a spew and requested a recalibration,
-		 * just wait for the queue to kick into action. */
-		break;
-	}
-}
-
-/*
- * HGPK Mouse Mode format (standard mouse format, sans middle button)
- *
- * byte 0:	y-over	x-over	y-neg	x-neg	1	0	swr	swl
- * byte 1:	x7	x6	x5	x4	x3	x2	x1	x0
- * byte 2:	y7	y6	y5	y4	y3	y2	y1	y0
- *
- * swr/swl are the left/right buttons.
- * x-neg/y-neg are the x and y delta negative bits
- * x-over/y-over are the x and y overflow bits
- *
- * ---
- *
- * HGPK Advanced Mode - single-mode format
- *
- * byte 0(PT):  1    1    0    0    1    1     1     1
- * byte 0(GS):  1    1    1    1    1    1     1     1
- * byte 1:      0   x6   x5   x4   x3   x2    x1    x0
- * byte 2(PT):  0    0   x9   x8   x7    ? pt-dsw    0
- * byte 2(GS):  0  x10   x9   x8   x7    ? gs-dsw pt-dsw
- * byte 3:      0   y9   y8   y7    1    0   swr   swl
- * byte 4:      0   y6   y5   y4   y3   y2    y1    y0
- * byte 5:      0   z6   z5   z4   z3   z2    z1    z0
- *
- * ?'s are not defined in the protocol spec, may vary between models.
- *
- * swr/swl are the left/right buttons.
- *
- * pt-dsw/gs-dsw indicate that the pt/gs sensor is detecting a
- * pen/finger
- */
-static bool hgpk_is_byte_valid(struct psmouse *psmouse, unsigned char *packet)
-{
-	struct hgpk_data *priv = psmouse->private;
-	int pktcnt = psmouse->pktcnt;
-	bool valid;
-
-	switch (priv->mode) {
-	case HGPK_MODE_MOUSE:
-		valid = (packet[0] & 0x0C) == 0x08;
-		break;
-
-	case HGPK_MODE_GLIDESENSOR:
-		valid = pktcnt == 1 ?
-			packet[0] == HGPK_GS : !(packet[pktcnt - 1] & 0x80);
-		break;
-
-	case HGPK_MODE_PENTABLET:
-		valid = pktcnt == 1 ?
-			packet[0] == HGPK_PT : !(packet[pktcnt - 1] & 0x80);
-		break;
-
-	default:
-		valid = false;
-		break;
-	}
-
-	if (!valid)
-		psmouse_dbg(psmouse,
-			    "bad data, mode %d (%d) %*ph\n",
-			    priv->mode, pktcnt, 6, psmouse->packet);
-
-	return valid;
-}
-
-static void hgpk_process_advanced_packet(struct psmouse *psmouse)
-{
-	struct hgpk_data *priv = psmouse->private;
-	struct input_dev *idev = psmouse->dev;
-	unsigned char *packet = psmouse->packet;
-	int down = !!(packet[2] & 2);
-	int left = !!(packet[3] & 1);
-	int right = !!(packet[3] & 2);
-	int x = packet[1] | ((packet[2] & 0x78) << 4);
-	int y = packet[4] | ((packet[3] & 0x70) << 3);
-
-	if (priv->mode == HGPK_MODE_GLIDESENSOR) {
-		int pt_down = !!(packet[2] & 1);
-		int finger_down = !!(packet[2] & 2);
-		int z = packet[5];
-
-		input_report_abs(idev, ABS_PRESSURE, z);
-		if (tpdebug)
-			psmouse_dbg(psmouse, "pd=%d fd=%d z=%d",
-				    pt_down, finger_down, z);
-	} else {
-		/*
-		 * PenTablet mode does not report pressure, so we don't
-		 * report it here
-		 */
-		if (tpdebug)
-			psmouse_dbg(psmouse, "pd=%d ", down);
-	}
-
-	if (tpdebug)
-		psmouse_dbg(psmouse, "l=%d r=%d x=%d y=%d\n",
-			    left, right, x, y);
-
-	input_report_key(idev, BTN_TOUCH, down);
-	input_report_key(idev, BTN_LEFT, left);
-	input_report_key(idev, BTN_RIGHT, right);
-
-	/*
-	 * If this packet says that the finger was removed, reset our position
-	 * tracking so that we don't erroneously detect a jump on next press.
-	 */
-	if (!down) {
-		hgpk_reset_hack_state(psmouse);
-		goto done;
-	}
-
-	/*
-	 * Weed out duplicate packets (we get quite a few, and they mess up
-	 * our jump detection)
-	 */
-	if (x == priv->abs_x && y == priv->abs_y) {
-		if (++priv->dupe_count > SPEW_WATCH_COUNT) {
-			if (tpdebug)
-				psmouse_dbg(psmouse, "hard spew detected\n");
-			priv->spew_flag = RECALIBRATING;
-			psmouse_queue_work(psmouse, &priv->recalib_wq,
-					   msecs_to_jiffies(spew_delay));
-		}
-		goto done;
-	}
-
-	/* not a duplicate, continue with position reporting */
-	priv->dupe_count = 0;
-
-	/* Don't apply hacks in PT mode, it seems reliable */
-	if (priv->mode != HGPK_MODE_PENTABLET && priv->abs_x != -1) {
-		int x_diff = priv->abs_x - x;
-		int y_diff = priv->abs_y - y;
-		if (hgpk_discard_decay_hack(psmouse, x_diff, y_diff)) {
-			if (tpdebug)
-				psmouse_dbg(psmouse, "discarding\n");
-			goto done;
-		}
-		hgpk_spewing_hack(psmouse, left, right, x_diff, y_diff);
-	}
-
-	input_report_abs(idev, ABS_X, x);
-	input_report_abs(idev, ABS_Y, y);
-	priv->abs_x = x;
-	priv->abs_y = y;
-
-done:
-	input_sync(idev);
-}
-
-static void hgpk_process_simple_packet(struct psmouse *psmouse)
-{
-	struct input_dev *dev = psmouse->dev;
-	unsigned char *packet = psmouse->packet;
-	int left = packet[0] & 1;
-	int right = (packet[0] >> 1) & 1;
-	int x = packet[1] - ((packet[0] << 4) & 0x100);
-	int y = ((packet[0] << 3) & 0x100) - packet[2];
-
-	if (packet[0] & 0xc0)
-		psmouse_dbg(psmouse,
-			    "overflow -- 0x%02x 0x%02x 0x%02x\n",
-			    packet[0], packet[1], packet[2]);
-
-	if (hgpk_discard_decay_hack(psmouse, x, y)) {
-		if (tpdebug)
-			psmouse_dbg(psmouse, "discarding\n");
-		return;
-	}
-
-	hgpk_spewing_hack(psmouse, left, right, x, y);
-
-	if (tpdebug)
-		psmouse_dbg(psmouse, "l=%d r=%d x=%d y=%d\n",
-			    left, right, x, y);
-
-	input_report_key(dev, BTN_LEFT, left);
-	input_report_key(dev, BTN_RIGHT, right);
-
-	input_report_rel(dev, REL_X, x);
-	input_report_rel(dev, REL_Y, y);
-
-	input_sync(dev);
-}
-
-static psmouse_ret_t hgpk_process_byte(struct psmouse *psmouse)
-{
-	struct hgpk_data *priv = psmouse->private;
-
-	if (!hgpk_is_byte_valid(psmouse, psmouse->packet))
-		return PSMOUSE_BAD_DATA;
-
-	if (psmouse->pktcnt >= psmouse->pktsize) {
-		if (priv->mode == HGPK_MODE_MOUSE)
-			hgpk_process_simple_packet(psmouse);
-		else
-			hgpk_process_advanced_packet(psmouse);
-		return PSMOUSE_FULL_PACKET;
-	}
-
-	if (priv->recalib_window) {
-		if (time_before(jiffies, priv->recalib_window)) {
-			/*
-			 * ugh, got a packet inside our recalibration
-			 * window, schedule another recalibration.
-			 */
-			psmouse_dbg(psmouse,
-				    "packet inside calibration window, queueing another recalibration\n");
-			psmouse_queue_work(psmouse, &priv->recalib_wq,
-					msecs_to_jiffies(post_interrupt_delay));
-		}
-		priv->recalib_window = 0;
-	}
-
-	return PSMOUSE_GOOD_DATA;
-}
-
-static int hgpk_select_mode(struct psmouse *psmouse)
-{
-	struct ps2dev *ps2dev = &psmouse->ps2dev;
-	struct hgpk_data *priv = psmouse->private;
-	int i;
-	int cmd;
-
-	/*
-	 * 4 disables to enable advanced mode
-	 * then 3 0xf2 bytes as the preamble for GS/PT selection
-	 */
-	const int advanced_init[] = {
-		PSMOUSE_CMD_DISABLE, PSMOUSE_CMD_DISABLE,
-		PSMOUSE_CMD_DISABLE, PSMOUSE_CMD_DISABLE,
-		0xf2, 0xf2, 0xf2,
-	};
-
-	switch (priv->mode) {
-	case HGPK_MODE_MOUSE:
-		psmouse->pktsize = 3;
-		break;
-
-	case HGPK_MODE_GLIDESENSOR:
-	case HGPK_MODE_PENTABLET:
-		psmouse->pktsize = 6;
-
-		/* Switch to 'Advanced mode.', four disables in a row. */
-		for (i = 0; i < ARRAY_SIZE(advanced_init); i++)
-			if (ps2_command(ps2dev, NULL, advanced_init[i]))
-				return -EIO;
-
-		/* select between GlideSensor (mouse) or PenTablet */
-		cmd = priv->mode == HGPK_MODE_GLIDESENSOR ?
-			PSMOUSE_CMD_SETSCALE11 : PSMOUSE_CMD_SETSCALE21;
-
-		if (ps2_command(ps2dev, NULL, cmd))
-			return -EIO;
-		break;
-
-	default:
-		return -EINVAL;
-	}
-
-	return 0;
-}
-
-static void hgpk_setup_input_device(struct input_dev *input,
-				    struct input_dev *old_input,
-				    enum hgpk_mode mode)
-{
-	if (old_input) {
-		input->name = old_input->name;
-		input->phys = old_input->phys;
-		input->id = old_input->id;
-		input->dev.parent = old_input->dev.parent;
-	}
-
-	memset(input->evbit, 0, sizeof(input->evbit));
-	memset(input->relbit, 0, sizeof(input->relbit));
-	memset(input->keybit, 0, sizeof(input->keybit));
-
-	/* All modes report left and right buttons */
-	__set_bit(EV_KEY, input->evbit);
-	__set_bit(BTN_LEFT, input->keybit);
-	__set_bit(BTN_RIGHT, input->keybit);
-
-	switch (mode) {
-	case HGPK_MODE_MOUSE:
-		__set_bit(EV_REL, input->evbit);
-		__set_bit(REL_X, input->relbit);
-		__set_bit(REL_Y, input->relbit);
-		break;
-
-	case HGPK_MODE_GLIDESENSOR:
-		__set_bit(BTN_TOUCH, input->keybit);
-		__set_bit(BTN_TOOL_FINGER, input->keybit);
-
-		__set_bit(EV_ABS, input->evbit);
-
-		/* GlideSensor has pressure sensor, PenTablet does not */
-		input_set_abs_params(input, ABS_PRESSURE, 0, 15, 0, 0);
-
-		/* From device specs */
-		input_set_abs_params(input, ABS_X, 0, 399, 0, 0);
-		input_set_abs_params(input, ABS_Y, 0, 290, 0, 0);
-
-		/* Calculated by hand based on usable size (52mm x 38mm) */
-		input_abs_set_res(input, ABS_X, 8);
-		input_abs_set_res(input, ABS_Y, 8);
-		break;
-
-	case HGPK_MODE_PENTABLET:
-		__set_bit(BTN_TOUCH, input->keybit);
-		__set_bit(BTN_TOOL_FINGER, input->keybit);
-
-		__set_bit(EV_ABS, input->evbit);
-
-		/* From device specs */
-		input_set_abs_params(input, ABS_X, 0, 999, 0, 0);
-		input_set_abs_params(input, ABS_Y, 5, 239, 0, 0);
-
-		/* Calculated by hand based on usable size (156mm x 38mm) */
-		input_abs_set_res(input, ABS_X, 6);
-		input_abs_set_res(input, ABS_Y, 8);
-		break;
-
-	default:
-		BUG();
-	}
-}
-
-static int hgpk_reset_device(struct psmouse *psmouse, bool recalibrate)
-{
-	int err;
-
-	psmouse_reset(psmouse);
-
-	if (recalibrate) {
-		struct ps2dev *ps2dev = &psmouse->ps2dev;
-
-		/* send the recalibrate request */
-		if (ps2_command(ps2dev, NULL, 0xf5) ||
-		    ps2_command(ps2dev, NULL, 0xf5) ||
-		    ps2_command(ps2dev, NULL, 0xe6) ||
-		    ps2_command(ps2dev, NULL, 0xf5)) {
-			return -1;
-		}
-
-		/* according to ALPS, 150mS is required for recalibration */
-		msleep(150);
-	}
-
-	err = hgpk_select_mode(psmouse);
-	if (err) {
-		psmouse_err(psmouse, "failed to select mode\n");
-		return err;
-	}
-
-	hgpk_reset_hack_state(psmouse);
-
-	return 0;
-}
-
-static int hgpk_force_recalibrate(struct psmouse *psmouse)
-{
-	struct hgpk_data *priv = psmouse->private;
-	int err;
-
-	/* C-series touchpads added the recalibrate command */
-	if (psmouse->model < HGPK_MODEL_C)
-		return 0;
-
-	if (!autorecal) {
-		psmouse_dbg(psmouse, "recalibration disabled, ignoring\n");
-		return 0;
-	}
-
-	psmouse_dbg(psmouse, "recalibrating touchpad..\n");
-
-	/* we don't want to race with the irq handler, nor with resyncs */
-	psmouse_set_state(psmouse, PSMOUSE_INITIALIZING);
-
-	/* start by resetting the device */
-	err = hgpk_reset_device(psmouse, true);
-	if (err)
-		return err;
-
-	/*
-	 * XXX: If a finger is down during this delay, recalibration will
-	 * detect capacitance incorrectly.  This is a hardware bug, and
-	 * we don't have a good way to deal with it.  The 2s window stuff
-	 * (below) is our best option for now.
-	 */
-	if (psmouse_activate(psmouse))
-		return -1;
-
-	if (tpdebug)
-		psmouse_dbg(psmouse, "touchpad reactivated\n");
-
-	/*
-	 * If we get packets right away after recalibrating, it's likely
-	 * that a finger was on the touchpad.  If so, it's probably
-	 * miscalibrated, so we optionally schedule another.
-	 */
-	if (recal_guard_time)
-		priv->recalib_window = jiffies +
-			msecs_to_jiffies(recal_guard_time);
-
-	return 0;
-}
-
-/*
- * This puts the touchpad in a power saving mode; according to ALPS, current
- * consumption goes down to 50uA after running this.  To turn power back on,
- * we drive MS-DAT low.  Measuring with a 1mA resolution ammeter says that
- * the current on the SUS_3.3V rail drops from 3mA or 4mA to 0 when we do this.
- *
- * We have no formal spec that details this operation -- the low-power
- * sequence came from a long-lost email trail.
- */
-static int hgpk_toggle_powersave(struct psmouse *psmouse, int enable)
-{
-	struct ps2dev *ps2dev = &psmouse->ps2dev;
-	int timeo;
-	int err;
-
-	/* Added on D-series touchpads */
-	if (psmouse->model < HGPK_MODEL_D)
-		return 0;
-
-	if (enable) {
-		psmouse_set_state(psmouse, PSMOUSE_INITIALIZING);
-
-		/*
-		 * Sending a byte will drive MS-DAT low; this will wake up
-		 * the controller.  Once we get an ACK back from it, it
-		 * means we can continue with the touchpad re-init.  ALPS
-		 * tells us that 1s should be long enough, so set that as
-		 * the upper bound. (in practice, it takes about 3 loops.)
-		 */
-		for (timeo = 20; timeo > 0; timeo--) {
-			if (!ps2_sendbyte(ps2dev, PSMOUSE_CMD_DISABLE, 20))
-				break;
-			msleep(25);
-		}
-
-		err = hgpk_reset_device(psmouse, false);
-		if (err) {
-			psmouse_err(psmouse, "Failed to reset device!\n");
-			return err;
-		}
-
-		/* should be all set, enable the touchpad */
-		psmouse_activate(psmouse);
-		psmouse_dbg(psmouse, "Touchpad powered up.\n");
-	} else {
-		psmouse_dbg(psmouse, "Powering off touchpad.\n");
-
-		if (ps2_command(ps2dev, NULL, 0xec) ||
-		    ps2_command(ps2dev, NULL, 0xec) ||
-		    ps2_command(ps2dev, NULL, 0xea)) {
-			return -1;
-		}
-
-		psmouse_set_state(psmouse, PSMOUSE_IGNORE);
-
-		/* probably won't see an ACK, the touchpad will be off */
-		ps2_sendbyte(ps2dev, 0xec, 20);
-	}
-
-	return 0;
-}
-
-static int hgpk_poll(struct psmouse *psmouse)
-{
-	/* We can't poll, so always return failure. */
-	return -1;
-}
-
-static int hgpk_reconnect(struct psmouse *psmouse)
-{
-	struct hgpk_data *priv = psmouse->private;
-
-	/*
-	 * During suspend/resume the ps2 rails remain powered.  We don't want
-	 * to do a reset because it's flush data out of buffers; however,
-	 * earlier prototypes (B1) had some brokenness that required a reset.
-	 */
-	if (olpc_board_at_least(olpc_board(0xb2)))
-		if (psmouse->ps2dev.serio->dev.power.power_state.event !=
-				PM_EVENT_ON)
-			return 0;
-
-	priv->powered = 1;
-	return hgpk_reset_device(psmouse, false);
-}
-
-static ssize_t hgpk_show_powered(struct psmouse *psmouse, void *data, char *buf)
-{
-	struct hgpk_data *priv = psmouse->private;
-
-	return sprintf(buf, "%d\n", priv->powered);
-}
-
-static ssize_t hgpk_set_powered(struct psmouse *psmouse, void *data,
-				const char *buf, size_t count)
-{
-	struct hgpk_data *priv = psmouse->private;
-	unsigned int value;
-	int err;
-
-	err = kstrtouint(buf, 10, &value);
-	if (err)
-		return err;
-
-	if (value > 1)
-		return -EINVAL;
-
-	if (value != priv->powered) {
-		/*
-		 * hgpk_toggle_power will deal w/ state so
-		 * we're not racing w/ irq
-		 */
-		err = hgpk_toggle_powersave(psmouse, value);
-		if (!err)
-			priv->powered = value;
-	}
-
-	return err ? err : count;
-}
-
-__PSMOUSE_DEFINE_ATTR(powered, S_IWUSR | S_IRUGO, NULL,
-		      hgpk_show_powered, hgpk_set_powered, false);
-
-static ssize_t attr_show_mode(struct psmouse *psmouse, void *data, char *buf)
-{
-	struct hgpk_data *priv = psmouse->private;
-
-	return sprintf(buf, "%s\n", hgpk_mode_names[priv->mode]);
-}
-
-static ssize_t attr_set_mode(struct psmouse *psmouse, void *data,
-			     const char *buf, size_t len)
-{
-	struct hgpk_data *priv = psmouse->private;
-	enum hgpk_mode old_mode = priv->mode;
-	enum hgpk_mode new_mode = hgpk_mode_from_name(buf, len);
-	struct input_dev *old_dev = psmouse->dev;
-	struct input_dev *new_dev;
-	int err;
-
-	if (new_mode == HGPK_MODE_INVALID)
-		return -EINVAL;
-
-	if (old_mode == new_mode)
-		return len;
-
-	new_dev = input_allocate_device();
-	if (!new_dev)
-		return -ENOMEM;
-
-	psmouse_set_state(psmouse, PSMOUSE_INITIALIZING);
-
-	/* Switch device into the new mode */
-	priv->mode = new_mode;
-	err = hgpk_reset_device(psmouse, false);
-	if (err)
-		goto err_try_restore;
-
-	hgpk_setup_input_device(new_dev, old_dev, new_mode);
-
-	psmouse_set_state(psmouse, PSMOUSE_CMD_MODE);
-
-	err = input_register_device(new_dev);
-	if (err)
-		goto err_try_restore;
-
-	psmouse->dev = new_dev;
-	input_unregister_device(old_dev);
-
-	return len;
-
-err_try_restore:
-	input_free_device(new_dev);
-	priv->mode = old_mode;
-	hgpk_reset_device(psmouse, false);
-
-	return err;
-}
-
-PSMOUSE_DEFINE_ATTR(hgpk_mode, S_IWUSR | S_IRUGO, NULL,
-		    attr_show_mode, attr_set_mode);
-
-static ssize_t hgpk_trigger_recal_show(struct psmouse *psmouse,
-		void *data, char *buf)
-{
-	return -EINVAL;
-}
-
-static ssize_t hgpk_trigger_recal(struct psmouse *psmouse, void *data,
-				const char *buf, size_t count)
-{
-	struct hgpk_data *priv = psmouse->private;
-	unsigned int value;
-	int err;
-
-	err = kstrtouint(buf, 10, &value);
-	if (err)
-		return err;
-
-	if (value != 1)
-		return -EINVAL;
-
-	/*
-	 * We queue work instead of doing recalibration right here
-	 * to avoid adding locking to hgpk_force_recalibrate()
-	 * since workqueue provides serialization.
-	 */
-	psmouse_queue_work(psmouse, &priv->recalib_wq, 0);
-	return count;
-}
-
-__PSMOUSE_DEFINE_ATTR(recalibrate, S_IWUSR | S_IRUGO, NULL,
-		      hgpk_trigger_recal_show, hgpk_trigger_recal, false);
-
-static void hgpk_disconnect(struct psmouse *psmouse)
-{
-	struct hgpk_data *priv = psmouse->private;
-
-	device_remove_file(&psmouse->ps2dev.serio->dev,
-			   &psmouse_attr_powered.dattr);
-	device_remove_file(&psmouse->ps2dev.serio->dev,
-			   &psmouse_attr_hgpk_mode.dattr);
-
-	if (psmouse->model >= HGPK_MODEL_C)
-		device_remove_file(&psmouse->ps2dev.serio->dev,
-				   &psmouse_attr_recalibrate.dattr);
-
-	psmouse_reset(psmouse);
-	kfree(priv);
-}
-
-static void hgpk_recalib_work(struct work_struct *work)
-{
-	struct delayed_work *w = to_delayed_work(work);
-	struct hgpk_data *priv = container_of(w, struct hgpk_data, recalib_wq);
-	struct psmouse *psmouse = priv->psmouse;
-
-	if (hgpk_force_recalibrate(psmouse))
-		psmouse_err(psmouse, "recalibration failed!\n");
-}
-
-static int hgpk_register(struct psmouse *psmouse)
-{
-	struct hgpk_data *priv = psmouse->private;
-	int err;
-
-	/* register handlers */
-	psmouse->protocol_handler = hgpk_process_byte;
-	psmouse->poll = hgpk_poll;
-	psmouse->disconnect = hgpk_disconnect;
-	psmouse->reconnect = hgpk_reconnect;
-
-	/* Disable the idle resync. */
-	psmouse->resync_time = 0;
-	/* Reset after a lot of bad bytes. */
-	psmouse->resetafter = 1024;
-
-	hgpk_setup_input_device(psmouse->dev, NULL, priv->mode);
-
-	err = device_create_file(&psmouse->ps2dev.serio->dev,
-				 &psmouse_attr_powered.dattr);
-	if (err) {
-		psmouse_err(psmouse, "Failed creating 'powered' sysfs node\n");
-		return err;
-	}
-
-	err = device_create_file(&psmouse->ps2dev.serio->dev,
-				 &psmouse_attr_hgpk_mode.dattr);
-	if (err) {
-		psmouse_err(psmouse,
-			    "Failed creating 'hgpk_mode' sysfs node\n");
-		goto err_remove_powered;
-	}
-
-	/* C-series touchpads added the recalibrate command */
-	if (psmouse->model >= HGPK_MODEL_C) {
-		err = device_create_file(&psmouse->ps2dev.serio->dev,
-					 &psmouse_attr_recalibrate.dattr);
-		if (err) {
-			psmouse_err(psmouse,
-				    "Failed creating 'recalibrate' sysfs node\n");
-			goto err_remove_mode;
-		}
-	}
-
-	return 0;
-
-err_remove_mode:
-	device_remove_file(&psmouse->ps2dev.serio->dev,
-			   &psmouse_attr_hgpk_mode.dattr);
-err_remove_powered:
-	device_remove_file(&psmouse->ps2dev.serio->dev,
-			   &psmouse_attr_powered.dattr);
-	return err;
-}
-
-int hgpk_init(struct psmouse *psmouse)
-{
-	struct hgpk_data *priv;
-	int err;
-
-	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
-	if (!priv) {
-		err = -ENOMEM;
-		goto alloc_fail;
-	}
-
-	psmouse->private = priv;
-
-	priv->psmouse = psmouse;
-	priv->powered = true;
-	priv->mode = hgpk_default_mode;
-	INIT_DELAYED_WORK(&priv->recalib_wq, hgpk_recalib_work);
-
-	err = hgpk_reset_device(psmouse, false);
-	if (err)
-		goto init_fail;
-
-	err = hgpk_register(psmouse);
-	if (err)
-		goto init_fail;
-
-	return 0;
-
-init_fail:
-	kfree(priv);
-alloc_fail:
-	return err;
-}
-
-static enum hgpk_model_t hgpk_get_model(struct psmouse *psmouse)
-{
-	struct ps2dev *ps2dev = &psmouse->ps2dev;
-	unsigned char param[3];
-
-	/* E7, E7, E7, E9 gets us a 3 byte identifier */
-	if (ps2_command(ps2dev,  NULL, PSMOUSE_CMD_SETSCALE21) ||
-	    ps2_command(ps2dev,  NULL, PSMOUSE_CMD_SETSCALE21) ||
-	    ps2_command(ps2dev,  NULL, PSMOUSE_CMD_SETSCALE21) ||
-	    ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO)) {
-		return -EIO;
-	}
-
-	psmouse_dbg(psmouse, "ID: %*ph\n", 3, param);
-
-	/* HGPK signature: 0x67, 0x00, 0x<model> */
-	if (param[0] != 0x67 || param[1] != 0x00)
-		return -ENODEV;
-
-	psmouse_info(psmouse, "OLPC touchpad revision 0x%x\n", param[2]);
-
-	return param[2];
-}
-
-int hgpk_detect(struct psmouse *psmouse, bool set_properties)
-{
-	int version;
-
-	version = hgpk_get_model(psmouse);
-	if (version < 0)
-		return version;
-
-	if (set_properties) {
-		psmouse->vendor = "ALPS";
-		psmouse->name = "HGPK";
-		psmouse->model = version;
-	}
-
-	return 0;
-}
-
-void hgpk_module_init(void)
-{
-	hgpk_default_mode = hgpk_mode_from_name(hgpk_mode_name,
-						strlen(hgpk_mode_name));
-	if (hgpk_default_mode == HGPK_MODE_INVALID) {
-		hgpk_default_mode = HGPK_MODE_MOUSE;
-		strscpy(hgpk_mode_name, hgpk_mode_names[HGPK_MODE_MOUSE],
-			sizeof(hgpk_mode_name));
-	}
-}
diff --git a/drivers/input/mouse/hgpk.h b/drivers/input/mouse/hgpk.h
deleted file mode 100644
index ce041591f1a8..000000000000
--- a/drivers/input/mouse/hgpk.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * OLPC HGPK (XO-1) touchpad PS/2 mouse driver
- */
-
-#ifndef _HGPK_H
-#define _HGPK_H
-
-#define HGPK_GS		0xff       /* The GlideSensor */
-#define HGPK_PT		0xcf       /* The PenTablet */
-
-enum hgpk_model_t {
-	HGPK_MODEL_PREA = 0x0a,	/* pre-B1s */
-	HGPK_MODEL_A = 0x14,	/* found on B1s, PT disabled in hardware */
-	HGPK_MODEL_B = 0x28,	/* B2s, has capacitance issues */
-	HGPK_MODEL_C = 0x3c,
-	HGPK_MODEL_D = 0x50,	/* C1, mass production */
-};
-
-enum hgpk_spew_flag {
-	NO_SPEW,
-	MAYBE_SPEWING,
-	SPEW_DETECTED,
-	RECALIBRATING,
-};
-
-#define SPEW_WATCH_COUNT 42  /* at 12ms/packet, this is 1/2 second */
-
-enum hgpk_mode {
-	HGPK_MODE_MOUSE,
-	HGPK_MODE_GLIDESENSOR,
-	HGPK_MODE_PENTABLET,
-	HGPK_MODE_INVALID
-};
-
-struct hgpk_data {
-	struct psmouse *psmouse;
-	enum hgpk_mode mode;
-	bool powered;
-	enum hgpk_spew_flag spew_flag;
-	int spew_count, x_tally, y_tally;	/* spew detection */
-	unsigned long recalib_window;
-	struct delayed_work recalib_wq;
-	int abs_x, abs_y;
-	int dupe_count;
-	int xbigj, ybigj, xlast, ylast; /* jumpiness detection */
-	int xsaw_secondary, ysaw_secondary; /* jumpiness detection */
-};
-
-int hgpk_detect(struct psmouse *psmouse, bool set_properties);
-int hgpk_init(struct psmouse *psmouse);
-
-#ifdef CONFIG_MOUSE_PS2_OLPC
-void hgpk_module_init(void);
-#else
-static inline void hgpk_module_init(void)
-{
-}
-#endif
-
-#endif
diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
index 77ea7da3b1c5..e57d843d7b11 100644
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -26,7 +26,6 @@
 #include "synaptics.h"
 #include "logips2pp.h"
 #include "alps.h"
-#include "hgpk.h"
 #include "lifebook.h"
 #include "trackpoint.h"
 #include "touchkit_ps2.h"
@@ -393,9 +392,7 @@ static void psmouse_receive_byte(struct ps2dev *ps2dev, u8 data)
 			return;
 		}
 
-		if (psmouse->packet[1] == PSMOUSE_RET_ID ||
-		    (psmouse->protocol->type == PSMOUSE_HGPK &&
-		     psmouse->packet[1] == PSMOUSE_RET_BAT)) {
+		if (psmouse->packet[1] == PSMOUSE_RET_ID) {
 			__psmouse_set_state(psmouse, PSMOUSE_IGNORE);
 			serio_reconnect(ps2dev->serio);
 			return;
@@ -837,14 +834,6 @@ static const struct psmouse_protocol psmouse_protocols[] = {
 		.detect		= touchkit_ps2_detect,
 	},
 #endif
-#ifdef CONFIG_MOUSE_PS2_OLPC
-	{
-		.type		= PSMOUSE_HGPK,
-		.name		= "OLPC HGPK",
-		.alias		= "hgpk",
-		.detect		= hgpk_detect,
-	},
-#endif
 #ifdef CONFIG_MOUSE_PS2_ELANTECH
 	{
 		.type		= PSMOUSE_ELANTECH,
@@ -1153,13 +1142,6 @@ static int psmouse_extensions(struct psmouse *psmouse,
 			return PSMOUSE_ALPS;
 	}
 
-	/* Try OLPC HGPK touchpad */
-	if (max_proto > PSMOUSE_IMEX &&
-	    psmouse_try_protocol(psmouse, PSMOUSE_HGPK, &max_proto,
-				 set_properties, true)) {
-		return PSMOUSE_HGPK;
-	}
-
 	/* Try Elantech touchpad */
 	if (max_proto > PSMOUSE_IMEX &&
 	    psmouse_try_protocol(psmouse, PSMOUSE_ELANTECH,
@@ -2035,7 +2017,6 @@ static int __init psmouse_init(void)
 
 	lifebook_module_init();
 	synaptics_module_init();
-	hgpk_module_init();
 
 	err = psmouse_smbus_module_init();
 	if (err)
diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h
index 4d8acfe0d82a..783201301e5c 100644
--- a/drivers/input/mouse/psmouse.h
+++ b/drivers/input/mouse/psmouse.h
@@ -59,7 +59,7 @@ enum psmouse_type {
 	PSMOUSE_TRACKPOINT,
 	PSMOUSE_TOUCHKIT_PS2,
 	PSMOUSE_CORTRON,
-	PSMOUSE_HGPK,
+	PSMOUSE_HGPK,		/* No longer used */
 	PSMOUSE_ELANTECH,
 	PSMOUSE_FSP,
 	PSMOUSE_SYNAPTICS_RELATIVE,
-- 
2.53.0.473.g4a7958ca14-goog


-- 
Dmitry

^ permalink raw reply related

* Re: [PATCH v3 01/10] Input: xbox_gip - Add new driver for Xbox GIP
From: Vicki Pfau @ 2026-03-11  0:41 UTC (permalink / raw)
  To: Dmitry Torokhov, linux-input
In-Reply-To: <20260310052017.1289494-2-vi@endrift.com>

Hello,

In my haste, it seems I forgot to run checkpatch before submitting this. There were a bunch of stylistic errors, which I have fixed locally and will have fixed in v4. That said, I would still appreciate a review on things that aren't directly found by checkpatch as I would like v4 to be the last version of the patch submitted before being merged.

On 3/9/26 10:19 PM, Vicki Pfau wrote:
> This introduces a new driver for the Xbox One/Series controller protocol,
> officially known as the Gaming Input Protocol, or GIP for short.
> 
> Microsoft released documentation on (some of) GIP in late 2024, upon which
> this driver is based. Though the documentation was incomplete, it still
> provided enough information to warrant a clean start over the previous,
> incomplete implementation.
> 
> This driver is already at feature parity with the GIP support in xpad,
> along with several more enhancements:
> 
> - Proper support for parsing message length and fragmented messages
> - Metadata parsing, allowing for auto-detection on various parameters,
>   including the presence and location in the message of the share button,
>   as well as detection of specific device types
> 
> The framework set out in this driver also allows future expansion for
> specialized device types and additional features more cleanly than xpad.
> 
> Future plans include:
> 
> - Adding support for more device types, such as arcade sticks, racing
>   wheels and flight sticks.
> - Support for the security handshake, which is required for devices that
>   use wireless dongles.
> - Exposing a raw character device to enable sending vendor-specific
>   commands from userspace.
> - Event logging to either sysfs or dmesg.
> - Support for the headphone jack.
> 
> Signed-off-by: Vicki Pfau <vi@endrift.com>
> ---
>  MAINTAINERS                              |    6 +
>  drivers/input/joystick/Kconfig           |    2 +
>  drivers/input/joystick/Makefile          |    1 +
>  drivers/input/joystick/gip/Kconfig       |   30 +
>  drivers/input/joystick/gip/Makefile      |    3 +
>  drivers/input/joystick/gip/gip-core.c    | 2536 ++++++++++++++++++++++
>  drivers/input/joystick/gip/gip-drivers.c |  210 ++
>  drivers/input/joystick/gip/gip.h         |  309 +++
>  8 files changed, 3097 insertions(+)
>  create mode 100644 drivers/input/joystick/gip/Kconfig
>  create mode 100644 drivers/input/joystick/gip/Makefile
>  create mode 100644 drivers/input/joystick/gip/gip-core.c
>  create mode 100644 drivers/input/joystick/gip/gip-drivers.c
>  create mode 100644 drivers/input/joystick/gip/gip.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9ed6d11a77466..6c744d0af359d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27927,6 +27927,12 @@ S:	Maintained
>  F:	drivers/media/rc/keymaps/rc-xbox-dvd.c
>  F:	drivers/media/rc/xbox_remote.c
>  
> +XBOX GIP
> +M:	Vicki Pfau <vi@endrift.com>
> +L:	linux-input@vger.kernel.org
> +S:	Maintained
> +F:	drivers/input/joystick/gip/
> +
>  XC2028/3028 TUNER DRIVER
>  M:	Mauro Carvalho Chehab <mchehab@kernel.org>
>  L:	linux-media@vger.kernel.org
> diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
> index 7755e5b454d2c..d4665c80a3713 100644
> --- a/drivers/input/joystick/Kconfig
> +++ b/drivers/input/joystick/Kconfig
> @@ -291,6 +291,8 @@ config JOYSTICK_JOYDUMP
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called joydump.
>  
> +source "drivers/input/joystick/gip/Kconfig"
> +
>  config JOYSTICK_XPAD
>  	tristate "Xbox gamepad support"
>  	depends on USB_ARCH_HAS_HCD
> diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
> index 9976f596a9208..323392921b7dc 100644
> --- a/drivers/input/joystick/Makefile
> +++ b/drivers/input/joystick/Makefile
> @@ -39,5 +39,6 @@ obj-$(CONFIG_JOYSTICK_TURBOGRAFX)	+= turbografx.o
>  obj-$(CONFIG_JOYSTICK_TWIDJOY)		+= twidjoy.o
>  obj-$(CONFIG_JOYSTICK_WARRIOR)		+= warrior.o
>  obj-$(CONFIG_JOYSTICK_WALKERA0701)	+= walkera0701.o
> +obj-$(CONFIG_JOYSTICK_XBOX_GIP)		+= gip/
>  obj-$(CONFIG_JOYSTICK_XPAD)		+= xpad.o
>  obj-$(CONFIG_JOYSTICK_ZHENHUA)		+= zhenhua.o
> diff --git a/drivers/input/joystick/gip/Kconfig b/drivers/input/joystick/gip/Kconfig
> new file mode 100644
> index 0000000000000..83293df3b0410
> --- /dev/null
> +++ b/drivers/input/joystick/gip/Kconfig
> @@ -0,0 +1,30 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# Gaming Input Protocol  driver configuration
> +#
> +config JOYSTICK_XBOX_GIP
> +	tristate "Xbox One/Series controller support"
> +	depends on USB_ARCH_HAS_HCD
> +	select USB
> +	help
> +	  Say Y here if you want to use Xbox One and Series controllers with your
> +	  computer. Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV)
> +	  and/or "Event interface support" (CONFIG_INPUT_EVDEV) as well.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called xbox_gip.
> +
> +config JOYSTICK_XBOX_GIP_FF
> +	bool "Xbox One/Series controller rumble support"
> +	depends on JOYSTICK_XBOX_GIP && INPUT
> +	select INPUT_FF_MEMLESS
> +	help
> +	  Say Y here if you want to take advantage of Xbox One/Series rumble.
> +
> +config JOYSTICK_XBOX_GIP_LEDS
> +	bool "LED Support for the Xbox One/Series controller Guide button"
> +	depends on JOYSTICK_XBOX_GIP && LEDS_CLASS_MULTICOLOR
> +	help
> +	  This option enables support for the LED which surrounds the Big X on
> +	  Xbox One/Series controllers.

This config entry isn't used until a later patch and I have already locally moved it to that patch.

> +
> diff --git a/drivers/input/joystick/gip/Makefile b/drivers/input/joystick/gip/Makefile
> new file mode 100644
> index 0000000000000..a75e0cace0f92
> --- /dev/null
> +++ b/drivers/input/joystick/gip/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +obj-$(CONFIG_JOYSTICK_XBOX_GIP)	+= xbox-gip.o
> +xbox-gip-y := gip-core.o gip-drivers.o
> diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
> new file mode 100644
> index 0000000000000..0881797592fea
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip-core.c
> @@ -0,0 +1,2536 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Gaming Input Protocol driver for Xbox One/Series controllers
> + *
> + * Copyright (c) 2025 Valve Software
> + *
> + * TODO:
> + * - Audio device support
> + * - Security packet handshake
> + * - Event logging
> + * - Sending fragmented messages
> + * - Raw character device
> + * - Wheel support
> + * - Flight stick support
> + * - Arcade stick support
> + * - Split into driver-per-attachment GIP-as-a-bus approach drivers
> + *
> + * This driver is based on the Microsoft GIP spec at:
> + * https://aka.ms/gipdocs
> + * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gipusb/e7c90904-5e21-426e-b9ad-d82adeee0dbc
> + */
> +
> +#include <linux/module.h>
> +#include <linux/uuid.h>
> +#include "gip.h"
> +
> +#define GIP_WIRED_INTF_DATA 0
> +#define GIP_WIRED_INTF_AUDIO 1
> +
> +#define MAX_MESSAGE_LENGTH 0x4000
> +
> +#define MAX_AUDIO_MESSAGES 9
> +
> +#define GIP_DATA_CLASS_COMMAND		(0u << 5)
> +#define GIP_DATA_CLASS_LOW_LATENCY	(1u << 5)
> +#define GIP_DATA_CLASS_STANDARD_LATENCY	(2u << 5)
> +#define GIP_DATA_CLASS_AUDIO		(3u << 5)
> +
> +#define GIP_DATA_CLASS_SHIFT 5
> +#define GIP_DATA_CLASS_MASK (7u << 5)
> +
> +/* Undocumented Elite 2 vendor messages */
> +#define GIP_CMD_RAW_REPORT		0x0c
> +#define GIP_CMD_GUIDE_COLOR		0x0e
> +#define GIP_SL_ELITE_CONFIG		0x4d
> +
> +#define GIP_BTN_OFFSET_XBE1 28
> +#define GIP_BTN_OFFSET_XBE2 14
> +
> +#define GIP_FLAG_FRAGMENT	BIT(7)
> +#define GIP_FLAG_INIT_FRAG	BIT(6)
> +#define GIP_FLAG_SYSTEM		BIT(5)
> +#define GIP_FLAG_ACME		BIT(4)
> +#define GIP_FLAG_ATTACHMENT_MASK 0x7
> +
> +#define GIP_AUDIO_FORMAT_NULL		0
> +#define GIP_AUDIO_FORMAT_8000HZ_1CH	1
> +#define GIP_AUDIO_FORMAT_8000HZ_2CH	2
> +#define GIP_AUDIO_FORMAT_12000HZ_1CH	3
> +#define GIP_AUDIO_FORMAT_12000HZ_2CH	4
> +#define GIP_AUDIO_FORMAT_16000HZ_1CH	5
> +#define GIP_AUDIO_FORMAT_16000HZ_2CH	6
> +#define GIP_AUDIO_FORMAT_20000HZ_1CH	7
> +#define GIP_AUDIO_FORMAT_20000HZ_2CH	8
> +#define GIP_AUDIO_FORMAT_24000HZ_1CH	9
> +#define GIP_AUDIO_FORMAT_24000HZ_2CH	10
> +#define GIP_AUDIO_FORMAT_32000HZ_1CH	11
> +#define GIP_AUDIO_FORMAT_32000HZ_2CH	12
> +#define GIP_AUDIO_FORMAT_40000HZ_1CH	13
> +#define GIP_AUDIO_FORMAT_40000HZ_2CH	14
> +#define GIP_AUDIO_FORMAT_48000HZ_1CH	15
> +#define GIP_AUDIO_FORMAT_48000HZ_2CH	16
> +#define GIP_AUDIO_FORMAT_48000HZ_6CH	32
> +#define GIP_AUDIO_FORMAT_48000HZ_8CH	33
> +#define MAX_GIP_AUDIO_FORMAT GIP_AUDIO_FORMAT_48000HZ_8CH
> +
> +/* Protocol Control constants */
> +#define GIP_CONTROL_CODE_ACK	0
> +#define GIP_CONTROL_CODE_NACK	1 /* obsolete */
> +#define GIP_CONTROL_CODE_UNK	2 /* obsolete */
> +#define GIP_CONTROL_CODE_AB	3 /* obsolete */
> +#define GIP_CONTROL_CODE_MPER	4 /* obsolete */
> +#define GIP_CONTROL_CODE_STOP	5 /* obsolete */
> +#define GIP_CONTROL_CODE_START	6 /* obsolete */
> +#define GIP_CONTROL_CODE_ERR	7 /* obsolete */
> +
> +/* Status Device constants */
> +#define GIP_POWER_LEVEL_OFF	0
> +#define GIP_POWER_LEVEL_STANDBY	1 /* obsolete */
> +#define GIP_POWER_LEVEL_FULL	2
> +
> +#define GIP_NOT_CHARGING	0
> +#define GIP_CHARGING		1
> +#define GIP_CHARGE_ERROR	2
> +
> +#define GIP_BATTERY_ABSENT		0
> +#define GIP_BATTERY_STANDARD		1
> +#define GIP_BATTERY_RECHARGEABLE	2
> +
> +#define GIP_BATTERY_CRITICAL	0
> +#define GIP_BATTERY_LOW		1
> +#define GIP_BATTERY_MEDIUM	2
> +#define GIP_BATTERY_FULL	3
> +
> +#define GIP_EVENT_FAULT 0x0002
> +
> +#define GIP_FAULT_UNKNOWN	0
> +#define GIP_FAULT_HARD		1
> +#define GIP_FAULT_NMI		2
> +#define GIP_FAULT_SVC		3
> +#define GIP_FAULT_PEND_SV	4
> +#define GIP_FAULT_SMART_PTR	5
> +#define GIP_FAULT_MCU		6
> +#define GIP_FAULT_BUS		7
> +#define GIP_FAULT_USAGE		8
> +#define GIP_FAULT_RADIO_HANG	9
> +#define GIP_FAULT_WATCHDOG	10
> +#define GIP_FAULT_LINK_STALL	11
> +#define GIP_FAULT_ASSERTION	12
> +
> +/* Metadata constants */
> +#define GIP_MESSAGE_FLAG_BIG_ENDIAN		BIT(0)
> +#define GIP_MESSAGE_FLAG_RELIABLE		BIT(1)
> +#define GIP_MESSAGE_FLAG_SEQUENCED		BIT(2)
> +#define GIP_MESSAGE_FLAG_DOWNSTREAM		BIT(3)
> +#define GIP_MESSAGE_FLAG_UPSTREAM		BIT(4)
> +#define GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE	BIT(5)
> +
> +#define GIP_DATA_TYPE_CUSTOM	1
> +#define GIP_DATA_TYPE_AUDIO	2
> +#define GIP_DATA_TYPE_SECURITY	3
> +#define GIP_DATA_TYPE_GIP	4
> +
> +/* Set Device State constants */
> +#define GIP_STATE_START		0
> +#define GIP_STATE_STOP		1
> +#define GIP_STATE_STANDBY	2 /* obsolete */
> +#define GIP_STATE_FULL_POWER	3
> +#define GIP_STATE_OFF		4
> +#define GIP_STATE_QUIESCE	5
> +#define GIP_STATE_UNK6		6
> +#define GIP_STATE_RESET		7
> +
> +/* Guide Button Status constants */
> +#define GIP_LED_GUIDE	0
> +#define GIP_LED_IR	1 /* deprecated, for Kinect */
> +
> +#define GIP_LED_GUIDE_OFF		0
> +#define GIP_LED_GUIDE_ON		1
> +#define GIP_LED_GUIDE_FAST_BLINK	2
> +#define GIP_LED_GUIDE_SLOW_BLINK	3
> +#define GIP_LED_GUIDE_CHARGING_BLINK	4
> +#define GIP_LED_GUIDE_RAMP_TO_LEVEL	0xd
> +
> +#define GIP_LED_IR_OFF		0
> +#define GIP_LED_IR_ON_100MS	1
> +#define GIP_LED_IR_PATTERN	4
> +
> +/* Direct Motor Command constants */
> +#define GIP_MOTOR_RIGHT_VIBRATION	BIT(0)
> +#define GIP_MOTOR_LEFT_VIBRATION	BIT(1)
> +#define GIP_MOTOR_RIGHT_IMPULSE		BIT(2)
> +#define GIP_MOTOR_LEFT_IMPULSE		BIT(3)
> +#define GIP_MOTOR_ALL 0xf
> +
> +/* Extended Command constants */
> +#define GIP_EXTCMD_GET_CAPABILITIES	0x00
> +#define GIP_EXTCMD_GET_TELEMETRY_DATA	0x01
> +#define GIP_EXTCMD_GET_SERIAL_NUMBER	0x04
> +
> +#define GIP_EXTENDED_STATUS_OK			0
> +#define GIP_EXTENDED_STATUS_NOT_SUPPORTED	1
> +#define GIP_EXTENDED_STATUS_NOT_READY		2
> +#define GIP_EXTENDED_STATUS_ACCESS_DENIED	3
> +#define GIP_EXTENDED_STATUS_FAILED		4
> +
> +/* Internal constants, not part of protocol */
> +#define GIP_DEFAULT_IN_SYSTEM_MESSAGES 0x5e
> +#define GIP_DEFAULT_OUT_SYSTEM_MESSAGES 0x472
> +
> +#define GIP_FEATURE_CONTROLLER				BIT(0)
> +#define GIP_FEATURE_CONSOLE_FUNCTION_MAP		BIT(1)
> +#define GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW	BIT(2)
> +#define GIP_FEATURE_ELITE_BUTTONS			BIT(3)
> +#define GIP_FEATURE_DYNAMIC_LATENCY_INPUT		BIT(4)
> +#define GIP_FEATURE_SECURITY_OPT_OUT			BIT(5)
> +#define GIP_FEATURE_MOTOR_CONTROL			BIT(6)
> +#define GIP_FEATURE_GUIDE_COLOR				BIT(7)
> +#define GIP_FEATURE_EXTENDED_SET_DEVICE_STATE		BIT(8)
> +
> +#define GIP_LED_GUIDE_MAX_BRIGHTNESS	100 /* Spec says 47, but larger values work */
> +#define GIP_LED_GUIDE_INIT_BRIGHTNESS	20
> +
> +#ifndef VK_LWIN
> +#define VK_LWIN 0x5b
> +#endif
> +
> +static const guid_t guid_console_function_map =
> +	GUID_INIT(0xecddd2fe, 0xd387, 0x4294, 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d);
> +static const guid_t guid_console_function_map_overflow =
> +	GUID_INIT(0x137d4bd0, 0x9347, 0x4472, 0xaa, 0x26, 0x8c, 0x34, 0xa0, 0x8f, 0xf9, 0xbd);
> +static const guid_t guid_controller =
> +	GUID_INIT(0x9776ff56, 0x9bfd, 0x4581, 0xad, 0x45, 0xb6, 0x45, 0xbb, 0xa5, 0x26, 0xd6);
> +static const guid_t guid_dev_auth_pc_opt_out =
> +	GUID_INIT(0x7a34ce77, 0x7de2, 0x45c6, 0x8c, 0xa4, 0x00, 0x42, 0xc0, 0x8b, 0xd9, 0x4a);
> +static const guid_t guid_dynamic_latency_input =
> +	GUID_INIT(0x87f2e56b, 0xc3bb, 0x49b1, 0x82, 0x65, 0xff, 0xff, 0xf3, 0x77, 0x99, 0xee);
> +static const guid_t guid_elite_buttons =
> +	GUID_INIT(0x37d19ff7, 0xb5c6, 0x49d1, 0xa7, 0x5e, 0x03, 0xb2, 0x4b, 0xef, 0x8c, 0x89);
> +static const guid_t guid_headset =
> +	GUID_INIT(0xbc25d1a3, 0xc24e, 0x4992, 0x9d, 0xda, 0xef, 0x4f, 0x12, 0x3e, 0xf5, 0xdc);
> +
> +/*
> + * The following GUIDs are observed, but the exact meanings aren't known, so
> + * for now we document them but don't use them anywhere.
> + *
> + * GamepadEmu: GUID_INIT(0xe2e5f1bc, 0xa6e6, 0x41a2, 0x8f, 0x43, 0x33, 0xcf, 0xa2, 0x51, 0x09, 0x81)
> + * IAudioOnly: GUID_INIT(0x92844cd1, 0xf7c8, 0x49ef, 0x97, 0x77, 0x46, 0x7d, 0xa7, 0x08, 0xad, 0x10)
> + * IControllerProfileModeState: GUID_INIT(0xf758dc66, 0x022c, 0x48b8, 0xa4, 0xf6, 0x45, 0x7b, 0xa8, 0x0e, 0x2a, 0x5b)
> + * ICustomAudio: GUID_INIT(0x63fd9cc9, 0x94ee, 0x4b5d, 0x9c, 0x4d, 0x8b, 0x86, 0x4c, 0x14, 0x9c, 0xac)
> + * IExtendedDeviceFlags: GUID_INIT(0x34ad9b1e, 0x36ad, 0x4fb5, 0x8a, 0xc7, 0x17, 0x23, 0x4c, 0x9f, 0x54, 0x6f)
> + * IProgrammableGamepad: GUID_INIT(0x31c1034d, 0xb5b7, 0x4551, 0x98, 0x13, 0x87, 0x69, 0xd4, 0xa0, 0xe4, 0xf9)
> + * IVirtualDevice: GUID_INIT(0xdfd26825, 0x110a, 0x4e94, 0xb9, 0x37, 0xb2, 0x7c, 0xe4, 0x7b, 0x25, 0x40)
> + * OnlineDevAuth: GUID_INIT(0x632b1fd1, 0xa3e9, 0x44f9, 0x84, 0x20, 0x5c, 0xe3, 0x44, 0xa0, 0x64, 0x04)
> + *
> + * Seen on Elite Controller, Adaptive Controller: 9ebd00a3-b5e6-4c08-a33b-673126459ec4
> + * Seen on Adaptive Controller: ce1e58c5-221c-4bdb-9c24-bf3941601320
> + * Seen on Adaptive Joystick: db02f681-5038-4219-8668-c3459c5c3293
> + * Seen on Elite 2 Controller: f758dc66-022c-48b8-a4f6-457ba80e2a5b (IControllerProfileModeState)
> + * Seen on Elite 2 Controller: 31c1034d-b5b7-4551-9813-8769d4a0e4f9 (IProgrammableGamepad)
> + * Seen on Elite 2 Controller: 34ad9b1e-36ad-4fb5-8ac7-17234c9f546f (IExtendedDeviceFlags)
> + * Seen on Elite 2 Controller: 88e0b694-6bd9-4416-a560-e7fafdfa528f
> + * Seen on Elite 2 Controller: ea96c8c0-b216-448b-be80-7e5deb0698e2
> + */
> +
> +static const int gip_data_class_mtu[8] = { 64, 64, 64, 2048, 0, 0, 0, 0 };
> +
> +struct gip_audio_format {
> +	uint16_t rate;
> +	uint8_t channels;
> +};
> +
> +static const struct gip_audio_format gip_audio_format_table[MAX_GIP_AUDIO_FORMAT + 1] = {
> +	[GIP_AUDIO_FORMAT_8000HZ_1CH] = { .rate = 8000, .channels = 1 },
> +	[GIP_AUDIO_FORMAT_8000HZ_2CH] = { .rate = 8000, .channels = 2 },
> +	[GIP_AUDIO_FORMAT_12000HZ_1CH] = { .rate = 12000, .channels = 1 },
> +	[GIP_AUDIO_FORMAT_12000HZ_2CH] = { .rate = 12000, .channels = 2 },
> +	[GIP_AUDIO_FORMAT_16000HZ_1CH] = { .rate = 16000, .channels = 1 },
> +	[GIP_AUDIO_FORMAT_16000HZ_2CH] = { .rate = 16000, .channels = 2 },
> +	[GIP_AUDIO_FORMAT_20000HZ_1CH] = { .rate = 20000, .channels = 1 },
> +	[GIP_AUDIO_FORMAT_20000HZ_2CH] = { .rate = 20000, .channels = 2 },
> +	[GIP_AUDIO_FORMAT_24000HZ_1CH] = { .rate = 24000, .channels = 1 },
> +	[GIP_AUDIO_FORMAT_24000HZ_2CH] = { .rate = 24000, .channels = 2 },
> +	[GIP_AUDIO_FORMAT_32000HZ_1CH] = { .rate = 32000, .channels = 1 },
> +	[GIP_AUDIO_FORMAT_32000HZ_2CH] = { .rate = 32000, .channels = 2 },
> +	[GIP_AUDIO_FORMAT_40000HZ_1CH] = { .rate = 40000, .channels = 1 },
> +	[GIP_AUDIO_FORMAT_40000HZ_2CH] = { .rate = 40000, .channels = 2 },
> +	[GIP_AUDIO_FORMAT_48000HZ_1CH] = { .rate = 48000, .channels = 1 },
> +	[GIP_AUDIO_FORMAT_48000HZ_2CH] = { .rate = 48000, .channels = 2 },
> +	[GIP_AUDIO_FORMAT_48000HZ_6CH] = { .rate = 48000, .channels = 6 },
> +	[GIP_AUDIO_FORMAT_48000HZ_8CH] = { .rate = 48000, .channels = 8 },
> +};
> +
> +
> +static const struct gip_quirks base_quirks[] = {
> +	/* PDP Rock Candy */
> +	{ 0x0e6f, 0x0246, 0, .quirks = GIP_QUIRK_NO_HELLO },
> +
> +	{0},
> +};
> +
> +struct gip_audio_format_pair {
> +	uint8_t inbound;
> +	uint8_t outbound;
> +};
> +static_assert(sizeof(struct gip_audio_format_pair) == 2);
> +
> +struct gip_hello_device {
> +	uint64_t device_id;
> +	uint16_t vendor_id;
> +	uint16_t product_id;
> +	uint16_t firmware_major_version;
> +	uint16_t firmware_minor_version;
> +	uint16_t firmware_build_version;
> +	uint16_t firmware_revision;
> +	uint8_t hardware_major_version;
> +	uint8_t hardware_minor_version;
> +	uint8_t rf_proto_major_version;
> +	uint8_t rf_proto_minor_version;
> +	uint8_t security_major_version;
> +	uint8_t security_minor_version;
> +	uint8_t gip_major_version;
> +	uint8_t gip_minor_version;
> +};
> +
> +struct gip_direct_motor {
> +	uint8_t command;
> +	uint8_t motor_bitmap;
> +	uint8_t left_impulse_level;
> +	uint8_t right_impulse_level;
> +	uint8_t left_vibration_level;
> +	uint8_t right_vibration_level;
> +	uint8_t duration;
> +	uint8_t delay;
> +	uint8_t repeat;
> +};
> +
> +static const struct gip_driver* base_drivers[] = {
> +	&gip_driver_navigation,
> +	&gip_driver_gamepad,
> +	NULL /* Sentinel */
> +};
> +
> +static int gip_decode_length(uint64_t *length, const uint8_t *bytes, int num_bytes)
> +{
> +	*length = 0;
> +	int offset;
> +
> +	for (offset = 0; offset < num_bytes; offset++) {
> +		uint8_t byte = bytes[offset];
> +
> +		*length |= (byte & 0x7full) << (offset * 7);
> +		if (!(byte & 0x80)) {
> +			offset++;
> +			break;
> +		}
> +	}
> +	return offset;
> +}
> +
> +static int gip_encode_length(uint64_t length, uint8_t *bytes, int num_bytes)
> +{
> +	int offset;
> +
> +	for (offset = 0; offset < num_bytes; offset++) {
> +		uint8_t byte = length & 0x7f;
> +
> +		length >>= 7;
> +		if (length)
> +			byte |= 0x80;
> +		bytes[offset] = byte;
> +		if (!length) {
> +			offset++;
> +			break;
> +		}
> +	}
> +	return offset;
> +}
> +
> +static bool gip_supports_system_message(struct gip_attachment *attachment,
> +	uint8_t command, bool upstream)
> +{
> +	if (upstream)
> +		return attachment->metadata.device
> +			.in_system_messages[command >> 5] & (1u << command);
> +	else
> +		return attachment->metadata.device
> +			.out_system_messages[command >> 5] & (1u << command);
> +}
> +
> +bool gip_supports_vendor_message(struct gip_attachment *attachment,
> +	uint8_t command, bool upstream)
> +{
> +	size_t i;
> +
> +	for (i = 0; i < attachment->metadata.num_messages; i++) {
> +		struct gip_message_metadata *metadata =
> +			&attachment->metadata.message_metadata[i];
> +
> +		if (metadata->type != command)
> +			continue;
> +		if (metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE)
> +			return true;
> +
> +		if (upstream)
> +			return metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM;
> +		else
> +			return metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM;
> +	}
> +	return false;
> +}
> +
> +static uint8_t gip_sequence_next(struct gip_attachment *attachment,
> +	uint8_t command, bool system)
> +{
> +	uint8_t seq;
> +
> +	if (system) {
> +		switch (command) {
> +		case GIP_CMD_SECURITY:
> +			seq = attachment->seq_security++;
> +			if (!seq)
> +				seq = attachment->seq_security++;
> +			break;
> +		case GIP_CMD_EXTENDED:
> +			seq = attachment->seq_extended++;
> +			if (!seq)
> +				seq = attachment->seq_extended++;
> +			break;
> +		case GIP_AUDIO_DATA:
> +			seq = attachment->seq_audio++;
> +			if (!seq)
> +				seq = attachment->seq_audio++;
> +			break;
> +		default:
> +			seq = attachment->seq_system++;
> +			if (!seq)
> +				seq = attachment->seq_system++;
> +			break;
> +		}
> +	} else {
> +		seq = attachment->seq_vendor++;
> +		if (!seq)
> +			seq = attachment->seq_vendor++;
> +	}
> +	return seq;
> +}
> +
> +static void gip_handle_quirks_array(struct gip_attachment *attachment,
> +	const struct gip_quirks *quirks)
> +{
> +	size_t i, j;
> +
> +	for (i = 0; quirks[i].vendor_id; i++) {
> +		if (quirks[i].vendor_id != attachment->vendor_id)
> +			continue;
> +		if (quirks[i].product_id != attachment->product_id)
> +			continue;
> +		if (quirks[i].attachment_index != attachment->attachment_index)
> +			continue;
> +
> +		attachment->features |= quirks[i].added_features;
> +		attachment->features &= ~quirks[i].filtered_features;
> +		attachment->quirks |= quirks[i].quirks;
> +
> +		if (quirks[i].override_name)
> +			attachment->name = quirks[i].override_name;
> +
> +		for (j = 0; j < 8; ++j) {
> +			struct gip_device_metadata *metadata = &attachment->metadata.device;
> +
> +			metadata->in_system_messages[j] |= quirks[i].extra_in_system[j];
> +			metadata->out_system_messages[j] |= quirks[i].extra_out_system[j];
> +		}
> +
> +		attachment->extra_buttons = quirks[i].extra_buttons;
> +		attachment->extra_axes = quirks[i].extra_axes;
> +		break;
> +	}
> +
> +}
> +
> +static void gip_handle_quirks(struct gip_attachment *attachment)
> +{
> +	gip_handle_quirks_array(attachment, base_quirks);
> +
> +	if (attachment->driver && attachment->driver->quirks)
> +		gip_handle_quirks_array(attachment, attachment->driver->quirks);
> +}
> +
> +static int gip_send_raw_message(struct gip_device *device,
> +	uint8_t message_type, uint8_t flags, uint8_t seq, const uint8_t *bytes,
> +	int num_bytes)
> +{
> +	struct gip_interface *intf;
> +	int offset = 3;
> +	struct gip_urb *urb = NULL;
> +	int i;
> +	int rc = 0;
> +
> +	if (num_bytes < 0) {
> +		dev_warn(GIP_DEV(device), "Invalid message length %d\n", num_bytes);
> +		return -EINVAL;
> +	}
> +
> +	if (num_bytes > gip_data_class_mtu[message_type >> GIP_DATA_CLASS_SHIFT]) {
> +		dev_err(GIP_DEV(device),
> +			"Attempted to send a message that requires fragmenting, which is not yet supported.\n");
> +		return -ENOTSUPP;
> +	}
> +
> +	if ((message_type & GIP_DATA_CLASS_MASK) == GIP_DATA_CLASS_AUDIO)
> +		intf = &device->audio;
> +	else
> +		intf = &device->data;
> +
> +	if (intf->isoc_messages) {
> +		/* TODO: Needed for audio support */
> +		dev_warn(GIP_DEV(intf), "Unimplemented isochronous message output\n");
> +		return -ENOTSUPP;
> +	}
> +
> +	guard(spinlock_irqsave)(&device->message_lock);
> +	for (i = 0; i < MAX_OUT_MESSAGES && !urb; i++) {
> +		if (!intf->out_queue[i].urb)
> +			continue;
> +		if (!intf->out_queue[i].urb->anchor)
> +			urb = &intf->out_queue[i];
> +	}
> +	if (!urb) {
> +		dev_err(GIP_DEV(device), "Output queue is full; dropping message\n");
> +		return -ENOSPC;
> +	}
> +	urb->data[0] = message_type;
> +	urb->data[1] = flags;
> +	urb->data[2] = seq;
> +	offset += gip_encode_length(num_bytes, &urb->data[offset],
> +		sizeof(urb->data) - offset);
> +
> +	if (num_bytes > 0)
> +		memcpy(&urb->data[offset], bytes, num_bytes);
> +
> +	num_bytes += offset;
> +	urb->urb->transfer_buffer_length = num_bytes;
> +
> +	print_hex_dump_debug(KBUILD_MODNAME ": Sending message: ",
> +		DUMP_PREFIX_OFFSET, 16, 1, urb->data, num_bytes,
> +		false);
> +
> +	usb_anchor_urb(urb->urb, &intf->out_anchor);
> +	rc = usb_submit_urb(urb->urb, GFP_ATOMIC);
> +	if (rc) {
> +		dev_err(&intf->intf->dev,
> +			"%s - usb_submit_urb failed with result %d\n",
> +			__func__, rc);
> +		usb_unanchor_urb(urb->urb);
> +		rc = -EIO;
> +	}
> +
> +	return rc;
> +}
> +
> +int gip_send_system_message(struct gip_attachment *attachment,
> +	uint8_t message_type, uint8_t flags, const void *bytes, int num_bytes)
> +{
> +	return gip_send_raw_message(attachment->device, message_type,
> +		GIP_FLAG_SYSTEM | attachment->attachment_index | flags,
> +		gip_sequence_next(attachment, message_type, true),
> +		bytes, num_bytes);
> +}
> +
> +int gip_send_vendor_message(struct gip_attachment *attachment,
> +	uint8_t message_type, uint8_t flags, const void *bytes, int num_bytes)
> +{
> +	return gip_send_raw_message(attachment->device, message_type, flags,
> +		gip_sequence_next(attachment, message_type, false),
> +		bytes, num_bytes);
> +}
> +
> +static void gip_metadata_free(struct device *dev, struct gip_metadata *metadata)
> +{
> +	devm_kfree(dev, metadata->device.audio_formats);
> +
> +	if (metadata->device.preferred_types) {
> +		int i;
> +
> +		for (i = 0; i < metadata->device.num_preferred_types; i++)
> +			devm_kfree(dev, metadata->device.preferred_types[i]);
> +		devm_kfree(dev, metadata->device.preferred_types);
> +	}
> +	devm_kfree(dev, metadata->device.supported_interfaces);
> +	devm_kfree(dev, metadata->device.hid_descriptor);
> +	devm_kfree(dev, metadata->message_metadata);
> +
> +	memset(metadata, 0, sizeof(*metadata));
> +}
> +
> +static int gip_parse_audio_format_metadata(struct device *dev,
> +	struct gip_device_metadata *dev_metadata, const uint8_t *bytes,
> +	int length, int buffer_offset)
> +{
> +	unsigned int i;
> +
> +	dev_metadata->num_audio_formats = bytes[buffer_offset];
> +	if (buffer_offset + dev_metadata->num_audio_formats * 2 + 1 > length)
> +		return -EINVAL;
> +	dev_metadata->audio_formats = devm_kmalloc_array(dev,
> +		dev_metadata->num_audio_formats, 2, GFP_KERNEL);
> +	if (!dev_metadata->audio_formats)
> +		return -ENOMEM;
> +	memcpy(dev_metadata->audio_formats, &bytes[buffer_offset + 1],
> +		dev_metadata->num_audio_formats * 2);
> +
> +	for (i = 0; i < dev_metadata->num_audio_formats; i++) {
> +		const struct gip_audio_format_pair *pair = &dev_metadata->audio_formats[i];
> +		const struct gip_audio_format *inbound = NULL;
> +		const struct gip_audio_format *outbound = NULL;
> +
> +		if (pair->inbound <= MAX_GIP_AUDIO_FORMAT) {
> +			inbound = &gip_audio_format_table[pair->inbound];
> +			if (pair->inbound != GIP_AUDIO_FORMAT_NULL && inbound->rate == 0)
> +				inbound = NULL;
> +		}
> +		if (!inbound)
> +			dev_warn(dev, "Unknown audio format %u\n", pair->inbound);
> +
> +		if (pair->outbound <= MAX_GIP_AUDIO_FORMAT) {
> +			outbound = &gip_audio_format_table[pair->outbound];
> +			if (pair->outbound != GIP_AUDIO_FORMAT_NULL && outbound->rate == 0)
> +				outbound = NULL;
> +		}
> +		if (!outbound)
> +			dev_warn(dev, "Unknown audio format %u\n", pair->outbound);
> +
> +		if (inbound && outbound)
> +			dev_dbg(dev,
> +				"Supported audio format: %uHz %uch inbound, %uHz %uch outbound\n",
> +				inbound->rate,
> +				inbound->channels,
> +				outbound->rate,
> +				outbound->channels);
> +	}
> +	return 0;
> +}
> +
> +static int gip_parse_preferred_types_metadata(struct device *dev,
> +	struct gip_device_metadata *dev_metadata, const uint8_t *bytes,
> +	int length, int buffer_offset)
> +{
> +	int i;
> +	int count;
> +
> +	dev_metadata->num_preferred_types = bytes[buffer_offset];
> +	dev_metadata->preferred_types = devm_kcalloc(dev,
> +		dev_metadata->num_preferred_types, sizeof(char *), GFP_KERNEL);
> +	if (!dev_metadata->preferred_types)
> +		return -ENOMEM;
> +
> +	buffer_offset++;
> +	for (i = 0; i < dev_metadata->num_preferred_types; i++) {
> +		if (buffer_offset + 2 >= length)
> +			return -EINVAL;
> +
> +		count = bytes[buffer_offset];
> +		count |= bytes[buffer_offset];
> +		buffer_offset += 2;
> +		if (buffer_offset + count > length)
> +			return -EINVAL;
> +
> +		dev_metadata->preferred_types[i] = devm_kcalloc(dev, count + 1,
> +			sizeof(char), GFP_KERNEL);
> +		if (!dev_metadata->preferred_types[i])
> +			return -ENOMEM;
> +		memcpy(dev_metadata->preferred_types[i], &bytes[buffer_offset], count);
> +		buffer_offset += count;
> +	}
> +
> +	return 0;
> +}
> +
> +static int gip_parse_supported_interfaces_metadata(struct device *dev,
> +	struct gip_device_metadata *dev_metadata, const uint8_t *bytes,
> +	int length, int buffer_offset)
> +{
> +	dev_metadata->num_supported_interfaces = bytes[buffer_offset];
> +	if (buffer_offset + 1 +
> +		(int32_t) (dev_metadata->num_supported_interfaces * sizeof(guid_t)) > length)
> +		return -EINVAL;
> +
> +	dev_metadata->supported_interfaces = devm_kmalloc_array(dev,
> +		dev_metadata->num_supported_interfaces, sizeof(guid_t), GFP_KERNEL);
> +	if (!dev_metadata->supported_interfaces)
> +		return -ENOMEM;
> +
> +	memcpy(dev_metadata->supported_interfaces, &bytes[buffer_offset + 1],
> +		sizeof(guid_t) * dev_metadata->num_supported_interfaces);
> +
> +	return 0;
> +}
> +
> +static int gip_parse_hid_descriptor_metadata(struct device *dev,
> +	struct gip_device_metadata *dev_metadata, const uint8_t *bytes,
> +	int length, int buffer_offset)
> +{
> +	dev_metadata->hid_descriptor_size = bytes[buffer_offset];
> +	if (buffer_offset + 1 + dev_metadata->hid_descriptor_size > length)
> +		return -EINVAL;
> +
> +	dev_metadata->hid_descriptor = devm_kmalloc(dev,
> +		dev_metadata->hid_descriptor_size, GFP_KERNEL);
> +	if (!dev_metadata->hid_descriptor)
> +		return -ENOMEM;
> +
> +	memcpy(dev_metadata->hid_descriptor, &bytes[buffer_offset + 1],
> +		dev_metadata->hid_descriptor_size);
> +	print_hex_dump_debug(KBUILD_MODNAME ": Received HID descriptor: ",
> +		DUMP_PREFIX_OFFSET, 16, 1, dev_metadata->hid_descriptor,
> +		dev_metadata->hid_descriptor_size, false);
> +
> +	return 0;
> +}
> +
> +static int gip_parse_device_metadata(struct device *dev,
> +	struct gip_metadata *metadata, const uint8_t *bytes, int num_bytes,
> +	int *offset)
> +{
> +	struct gip_device_metadata *dev_metadata = &metadata->device;
> +	int buffer_offset;
> +	int count;
> +	int length;
> +	int i;
> +	int rc;
> +
> +	bytes = &bytes[*offset];
> +	num_bytes -= *offset;
> +	if (num_bytes < 16)
> +		return -EINVAL;
> +
> +	length = bytes[0];
> +	length |= bytes[1] << 8;
> +	if (num_bytes < length)
> +		return -EINVAL;
> +
> +	/* Skip supported firmware versions for now */
> +
> +	buffer_offset = bytes[4];
> +	buffer_offset |= bytes[5] << 8;
> +	if (buffer_offset >= length)
> +		return -EINVAL;
> +
> +	if (buffer_offset > 0) {
> +		rc = gip_parse_audio_format_metadata(dev, dev_metadata,
> +			bytes, length, buffer_offset);
> +		if (rc)
> +			return rc;
> +	}
> +
> +	buffer_offset = bytes[6];
> +	buffer_offset |= bytes[7] << 8;
> +	if (buffer_offset >= length)
> +		return -EINVAL;
> +
> +	if (buffer_offset > 0) {
> +		count = bytes[buffer_offset];
> +		if (buffer_offset + count + 1 > length)
> +			return -EINVAL;
> +
> +		for (i = 0; i < count; i++) {
> +			uint8_t message = bytes[buffer_offset + 1 + i];
> +
> +			dev_dbg(dev,
> +				"Supported upstream system message %02x\n",
> +				message);
> +			dev_metadata->in_system_messages[message >> 5] |=
> +				BIT(message & 0x1F);
> +		}
> +	}
> +
> +	buffer_offset = bytes[8];
> +	buffer_offset |= bytes[9] << 8;
> +	if (buffer_offset >= length)
> +		return -EINVAL;
> +
> +	if (buffer_offset > 0) {
> +		count = bytes[buffer_offset];
> +		if (buffer_offset + count + 1 > length)
> +			return -EINVAL;
> +
> +		for (i = 0; i < count; i++) {
> +			uint8_t message = bytes[buffer_offset + 1 + i];
> +
> +			dev_dbg(dev,
> +				"Supported downstream system message %02x\n",
> +				message);
> +			dev_metadata->out_system_messages[message >> 5] |=
> +				BIT(message & 0x1F);
> +		}
> +	}
> +
> +	buffer_offset = bytes[10];
> +	buffer_offset |= bytes[11] << 8;
> +	if (buffer_offset >= length)
> +		return -EINVAL;
> +
> +	if (buffer_offset > 0) {
> +		rc = gip_parse_preferred_types_metadata(dev, dev_metadata,
> +			bytes, length, buffer_offset);
> +		if (rc)
> +			return rc;
> +	}
> +
> +	buffer_offset = bytes[12];
> +	buffer_offset |= bytes[13] << 8;
> +	if (buffer_offset >= length)
> +		return -EINVAL;
> +
> +	if (buffer_offset > 0) {
> +		rc = gip_parse_supported_interfaces_metadata(dev,
> +			dev_metadata, bytes, length, buffer_offset);
> +		if (rc)
> +			return rc;
> +	}
> +
> +	if (metadata->version_major > 1 || metadata->version_minor >= 1) {
> +		/* HID descriptor support added in metadata version 1.1 */
> +		buffer_offset = bytes[14];
> +		buffer_offset |= bytes[15] << 8;
> +		if (buffer_offset >= length)
> +			return -EINVAL;
> +
> +		if (buffer_offset > 0) {
> +			rc = gip_parse_hid_descriptor_metadata(dev,
> +				dev_metadata, bytes, length, buffer_offset);
> +			if (rc)
> +				return rc;
> +		}
> +	}
> +
> +	*offset += length;
> +	return 0;
> +}
> +
> +static int gip_parse_message_metadata(struct device *dev,
> +	struct gip_message_metadata *metadata, const uint8_t *bytes,
> +	int num_bytes, int *offset)
> +{
> +	uint16_t length;
> +
> +	bytes = &bytes[*offset];
> +	num_bytes -= *offset;
> +
> +	if (num_bytes < 2)
> +		return -EINVAL;
> +
> +	length = bytes[0];
> +	length |= bytes[1] << 8;
> +	if (num_bytes < length)
> +		return -EINVAL;
> +
> +	if (length < 15)
> +		return -EINVAL;
> +
> +	metadata->type = bytes[2];
> +	metadata->length = bytes[3];
> +	metadata->length |= bytes[4] << 8;
> +	metadata->data_type = bytes[5];
> +	metadata->data_type |= bytes[6] << 8;
> +	metadata->flags = bytes[7];
> +	metadata->flags |= bytes[8] << 8;
> +	metadata->flags |= bytes[9] << 16;
> +	metadata->flags |= bytes[10] << 24;
> +	metadata->period = bytes[11];
> +	metadata->period |= bytes[12] << 8;
> +	metadata->persistence_timeout = bytes[13];
> +	metadata->persistence_timeout |= bytes[14] << 8;
> +
> +	dev_dbg(dev,
> +		"Supported vendor message type %02x of length %d, %s, %s, %s\n",
> +		metadata->type, metadata->length,
> +		metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM ?
> +			(metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "bidirectional" : "upstream") :
> +			metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "downstream" :
> +			metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE ? "downstream request response" :
> +			"unknown direction",
> +		metadata->flags & GIP_MESSAGE_FLAG_SEQUENCED ? "sequenced" : "not sequenced",
> +		metadata->flags & GIP_MESSAGE_FLAG_RELIABLE ? "reliable" : "unreliable");
> +
> +	*offset += length;
> +	return 0;
> +}
> +
> +static bool gip_parse_metadata(struct device *dev,
> +	struct gip_metadata *metadata, const uint8_t *bytes, int num_bytes)
> +{
> +	int header_size;
> +	int metadata_size;
> +	int offset = 0;
> +	int i;
> +	int rc;
> +
> +	if (num_bytes < 16)
> +		return -EINVAL;
> +
> +	print_hex_dump_debug(KBUILD_MODNAME ": Received metadata: ", DUMP_PREFIX_OFFSET,
> +		16, 1, bytes, num_bytes, false);
> +
> +	header_size = bytes[0];
> +	header_size |= bytes[1] << 8;
> +	if (num_bytes < header_size || header_size < 16)
> +		return -EINVAL;
> +
> +	metadata->version_major = bytes[2];
> +	metadata->version_major |= bytes[3] << 8;
> +	metadata->version_minor = bytes[4];
> +	metadata->version_minor |= bytes[5] << 8;
> +	/* Middle bytes are reserved */
> +	metadata_size = bytes[14];
> +	metadata_size |= bytes[15] << 8;
> +
> +	if (num_bytes < metadata_size || metadata_size < header_size)
> +		return -EINVAL;
> +
> +	offset = header_size;
> +
> +	rc = gip_parse_device_metadata(dev, metadata, bytes, num_bytes, &offset);
> +	if (rc)
> +		goto parse_err;
> +
> +	if (offset >= num_bytes)
> +		goto parse_err;
> +
> +	metadata->num_messages = bytes[offset];
> +	offset++;
> +	if (metadata->num_messages > 0) {
> +		metadata->message_metadata = devm_kcalloc(dev,
> +			metadata->num_messages,
> +			sizeof(*metadata->message_metadata), GFP_KERNEL);
> +		if (!metadata->message_metadata)
> +			return -ENOMEM;
> +
> +		for (i = 0; i < metadata->num_messages; i++) {
> +			rc = gip_parse_message_metadata(dev,
> +				&metadata->message_metadata[i], bytes,
> +				num_bytes, &offset);
> +			if (rc)
> +				goto parse_err;
> +		}
> +	}
> +
> +	return 0;
> +
> +parse_err:
> +	gip_metadata_free(dev, metadata);
> +	return rc;
> +}
> +
> +static int gip_acknowledge(struct gip_device *device,
> +	const struct gip_header *header, uint32_t fragment_offset,
> +	uint16_t bytes_remaining)
> +{
> +	uint8_t buffer[] = {
> +		GIP_CONTROL_CODE_ACK,
> +		header->message_type,
> +		header->flags & GIP_FLAG_SYSTEM,
> +		fragment_offset,
> +		fragment_offset >> 8,
> +		fragment_offset >> 16,
> +		fragment_offset >> 24,
> +		bytes_remaining,
> +		bytes_remaining >> 8,
> +	};
> +
> +	return gip_send_raw_message(device, GIP_CMD_PROTO_CONTROL,
> +		GIP_FLAG_SYSTEM | (header->flags & GIP_FLAG_ATTACHMENT_MASK),
> +		header->sequence_id, buffer, sizeof(buffer));
> +}
> +
> +static int gip_fragment_failed(struct gip_attachment *attachment,
> +	const struct gip_header *header)
> +{
> +	attachment->fragment_retries++;
> +	if (attachment->fragment_retries > 8) {
> +		devm_kfree(GIP_DEV(attachment), attachment->fragment_data);
> +		attachment->fragment_data = NULL;
> +		attachment->fragment_message = 0;
> +	}
> +	return gip_acknowledge(attachment->device, header,
> +		attachment->fragment_offset,
> +		attachment->total_length - attachment->fragment_offset);
> +}
> +
> +static int gip_bind_driver(struct gip_attachment *attachment, const struct gip_driver *driver)
> +{
> +	if (driver->probe) {
> +		int rc = driver->probe(attachment);
> +
> +		if (rc)
> +			return rc;
> +	}
> +
> +	attachment->driver = driver;
> +	memcpy(attachment->vendor_handlers, driver->vendor_handlers,
> +		sizeof(attachment->vendor_handlers));
> +	return 0;
> +}
> +
> +static int gip_enable_elite_buttons(struct gip_attachment *attachment)
> +{
> +	if (attachment->vendor_id == 0x045e) {
> +		if (attachment->product_id == 0x02e3) {
> +			attachment->xbe_format = GIP_BTN_FMT_XBE1;
> +		} else if (attachment->product_id == 0x0b00) {
> +			if (attachment->firmware_major_version == 4) {
> +				attachment->xbe_format = GIP_BTN_FMT_XBE2_4;
> +			} else if (attachment->firmware_major_version == 5) {
> +				/*
> +				 * The exact range for this being necessary is
> +				 * unknown, but it starts at 5.11 and at either
> +				 * 5.16 or 5.17. This approach still works on
> +				 * 5.21, even if it's not necessary, so having
> +				 * a loose upper limit is fine.
> +				 */
> +				if (attachment->firmware_minor_version >= 11 &&
> +					attachment->firmware_minor_version < 17)
> +					attachment->xbe_format = GIP_BTN_FMT_XBE2_RAW;
> +				else
> +					attachment->xbe_format = GIP_BTN_FMT_XBE2_5;
> +			}
> +		}
> +	}
> +
> +	if (attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) {
> +		/*
> +		 * The meaning of this packet is unknown and not documented, but
> +		 * it's needed for the Elite 2 controller to send raw reports
> +		 */
> +		static const uint8_t enable_raw_report[] = { 7, 0 };
> +
> +		return gip_send_vendor_message(attachment, GIP_SL_ELITE_CONFIG,
> +			0, enable_raw_report, sizeof(enable_raw_report));
> +	}
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_JOYSTICK_XBOX_GIP_FF
> +static int gip_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
> +{
> +	struct gip_attachment *attachment = input_get_drvdata(dev);
> +	struct gip_direct_motor control = {
> +		.motor_bitmap = GIP_MOTOR_LEFT_VIBRATION | GIP_MOTOR_RIGHT_VIBRATION
> +	};
> +
> +	if (effect->type != FF_RUMBLE)
> +		return 0;
> +
> +	control.left_vibration_level = effect->u.rumble.strong_magnitude * 100 / 0xFFFF;
> +	control.right_vibration_level = effect->u.rumble.weak_magnitude * 100 / 0xFFFF;
> +	control.duration = 255;
> +
> +	return gip_send_vendor_message(attachment, GIP_CMD_DIRECT_MOTOR,
> +		0, &control, sizeof(control));
> +}
> +#endif
> +
> +static int gip_send_guide_button_led(struct gip_attachment *attachment,
> +	uint8_t pattern, uint8_t intensity)
> +{
> +	uint8_t buffer[] = {
> +		GIP_LED_GUIDE,
> +		pattern,
> +		intensity,
> +	};
> +
> +	if (!gip_supports_system_message(attachment, GIP_CMD_LED, false))
> +		return 0;
> +
> +	return gip_send_system_message(attachment, GIP_CMD_LED, 0, buffer, sizeof(buffer));
> +}
> +
> +static bool gip_send_set_device_state(struct gip_attachment *attachment, uint8_t state)
> +{
> +	uint8_t buffer[] = { state };
> +
> +	return gip_send_system_message(attachment, GIP_CMD_SET_DEVICE_STATE,
> +		attachment->attachment_index, buffer, sizeof(buffer));
> +}
> +
> +static int gip_handle_command_raw_report(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	struct input_dev *input;
> +
> +	if (num_bytes < 17) {
> +		dev_dbg(GIP_DEV(attachment), "Discarding too-short raw report\n");
> +		return -EINVAL;
> +	}
> +	guard(rcu)();
> +	input = rcu_dereference(attachment->input);
> +	if (!input)
> +		return -ENODEV;
> +
> +	if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS)
> +		&& attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) {
> +		input_report_abs(input, ABS_PROFILE, bytes[15] & 3);
> +		if (bytes[15] & 3) {
> +			input_report_key(input, BTN_GRIPL, 0);
> +			input_report_key(input, BTN_GRIPR, 0);
> +			input_report_key(input, BTN_GRIPL2, 0);
> +			input_report_key(input, BTN_GRIPR2, 0);
> +		} else {
> +			input_report_key(input, BTN_GRIPL,
> +				bytes[GIP_BTN_OFFSET_XBE2] & BIT(2));
> +			input_report_key(input, BTN_GRIPR,
> +				bytes[GIP_BTN_OFFSET_XBE2] & BIT(0));
> +			input_report_key(input, BTN_GRIPL2,
> +				bytes[GIP_BTN_OFFSET_XBE2] & BIT(3));
> +			input_report_key(input, BTN_GRIPR2,
> +				bytes[GIP_BTN_OFFSET_XBE2] & BIT(1));
> +		}
> +
> +		input_sync(input);
> +	}
> +	return 0;
> +}
> +
> +static int gip_setup_input_device(struct gip_attachment *attachment)
> +{
> +	struct input_dev *input;
> +	int rc;
> +
> +	if (!attachment->driver || !attachment->driver->setup_input)
> +		return -ENODEV;
> +
> +	rcu_read_lock();
> +	input = rcu_dereference(attachment->input);
> +	rcu_read_unlock();
> +	if (input)
> +		return 0;
> +
> +	input = input_allocate_device();
> +	if (!input)
> +		return -ENOMEM;
> +	input->id.bustype = BUS_USB;
> +	input->id.vendor = attachment->vendor_id;
> +	input->id.product = attachment->product_id;
> +	input->uniq = attachment->uniq;
> +	if (attachment->name)
> +		input->name = attachment->name;
> +	else if (attachment->attachment_index == 0)
> +		input->name = attachment->device->udev->product;
> +	input->phys = attachment->phys;
> +
> +	rc = attachment->driver->setup_input(attachment, input);
> +	if (rc < 0)
> +		goto err_free_device;
> +
> +	if (attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP)
> +		input_set_capability(input, EV_KEY, KEY_RECORD);
> +
> +	if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) {
> +		input_set_capability(input, EV_KEY, BTN_GRIPL);
> +		input_set_capability(input, EV_KEY, BTN_GRIPR);
> +		input_set_capability(input, EV_KEY, BTN_GRIPL2);
> +		input_set_capability(input, EV_KEY, BTN_GRIPR2);
> +		if (attachment->xbe_format == GIP_BTN_FMT_XBE1)
> +			input_set_abs_params(input, ABS_PROFILE, 0, 1, 0, 0);
> +		else
> +			input_set_abs_params(input, ABS_PROFILE, 0, 3, 0, 0);
> +
> +		attachment->vendor_handlers[GIP_CMD_RAW_REPORT] = gip_handle_command_raw_report;
> +	}
> +
> +#ifdef CONFIG_JOYSTICK_XBOX_GIP_FF
> +	if (attachment->features & GIP_FEATURE_MOTOR_CONTROL) {
> +		input_set_capability(input, EV_FF, FF_RUMBLE);
> +		input_ff_create_memless(input, NULL, gip_play_effect);
> +	}
> +#endif
> +
> +	input_set_drvdata(input, attachment);
> +	rcu_assign_pointer(attachment->input, input);
> +	rc = input_register_device(input);
> +	if (rc)
> +		goto err_free_device;
> +
> +	return 0;
> +
> +err_free_device:
> +	input_free_device(input);
> +	return rc;
> +}
> +
> +static int gip_send_init_sequence(struct gip_attachment *attachment)
> +{
> +	int rc = 0;
> +	size_t len;
> +
> +	if (attachment->features & GIP_FEATURE_EXTENDED_SET_DEVICE_STATE) {
> +		/*
> +		 * The meaning of this packet is unknown and not documented, but it's
> +		 * needed for the Elite 2 controller to start up on older firmwares
> +		 */
> +		static const uint8_t set_device_state[] = {
> +			GIP_STATE_UNK6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
> +			0x55, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
> +		};
> +
> +		rc = gip_send_system_message(attachment,
> +			GIP_CMD_SET_DEVICE_STATE, 0, set_device_state,
> +			sizeof(set_device_state));
> +		if (rc)
> +			return rc;
> +	}
> +	rc = gip_enable_elite_buttons(attachment);
> +	if (rc)
> +		return rc;
> +	if (!gip_supports_system_message(attachment, GIP_CMD_AUDIO_CONTROL, false)) {
> +		rc = gip_send_set_device_state(attachment, GIP_STATE_START);
> +		if (rc)
> +			return rc;
> +		attachment->device_state = GIP_STATE_START;
> +	} else {
> +		rc = gip_send_set_device_state(attachment, GIP_STATE_STOP);
> +		if (rc)
> +			return rc;
> +		attachment->device_state = GIP_STATE_STOP;
> +	}
> +
> +	rc = gip_send_guide_button_led(attachment,
> +		GIP_LED_GUIDE_ON,
> +		GIP_LED_GUIDE_INIT_BRIGHTNESS);
> +	if (rc)
> +		return rc;
> +
> +	if (gip_supports_system_message(attachment, GIP_CMD_SECURITY, false)
> +		&& !(attachment->features & GIP_FEATURE_SECURITY_OPT_OUT)) {
> +		/* TODO: Implement Security command property */
> +		uint8_t buffer[] = { 0x1, 0x0 };
> +
> +		rc = gip_send_system_message(attachment, GIP_CMD_SECURITY, 0,
> +			buffer, sizeof(buffer));
> +		if (rc)
> +			return rc;
> +	}
> +
> +	usb_make_path(attachment->device->udev, attachment->phys,
> +		sizeof(attachment->phys));
> +	len = strlen(attachment->phys);
> +	if (len < sizeof(attachment->phys) - 1)
> +		snprintf(attachment->phys + len,
> +			sizeof(attachment->phys) - len, "/input%d",
> +			attachment->attachment_index);
> +
> +	if (attachment->driver && attachment->driver->init) {
> +		rc = attachment->driver->init(attachment);
> +		if (rc < 0)
> +			return rc;
> +	}
> +
> +	if (rc != GIP_INIT_NO_INPUT && (attachment->features & GIP_FEATURE_CONTROLLER)) {
> +		rc = gip_setup_input_device(attachment);
> +		if (rc == -ENODEV)
> +			return 0;
> +		if (rc)
> +			return rc;
> +	}
> +
> +	return 0;
> +}
> +
> +static void gip_fragment_timeout(struct work_struct *work)
> +{
> +	struct gip_attachment *attachment = container_of(to_delayed_work(work),
> +		struct gip_attachment, fragment_timeout);
> +
> +	guard(mutex)(&attachment->lock);
> +	devm_kfree(GIP_DEV(attachment), attachment->fragment_data);
> +	attachment->fragment_data = NULL;
> +	attachment->fragment_message = 0;
> +}
> +
> +static void gip_retry_metadata(struct work_struct *work)
> +{
> +	struct gip_attachment *attachment = container_of(to_delayed_work(work),
> +		struct gip_attachment, metadata_next);
> +
> +	guard(mutex)(&attachment->lock);
> +	if (attachment->metadata_retries < 4) {
> +		attachment->metadata_retries++;
> +		schedule_delayed_work(&attachment->metadata_next, HZ / 2);
> +		gip_send_system_message(attachment, GIP_CMD_METADATA, 0, NULL, 0);
> +	} else {
> +		dev_info(GIP_DEV(attachment),
> +			"Unable to obtain metadata, attempting to reset device\n");
> +		gip_send_set_device_state(attachment, GIP_STATE_RESET);
> +	}
> +}
> +
> +static int gip_ensure_metadata(struct gip_attachment *attachment)
> +{
> +	switch (attachment->got_metadata) {
> +	case GIP_METADATA_GOT:
> +	case GIP_METADATA_FAKED:
> +		return 0;
> +	case GIP_METADATA_NONE:
> +		attachment->got_metadata = GIP_METADATA_PENDING;
> +		cancel_delayed_work_sync(&attachment->metadata_next);
> +		schedule_delayed_work(&attachment->metadata_next, HZ / 2);
> +		attachment->metadata_retries = 0;
> +		return gip_send_system_message(attachment, GIP_CMD_METADATA, 0, NULL, 0);
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static void gip_set_metadata_defaults(struct gip_attachment *attachment)
> +{
> +	if (attachment->got_metadata != GIP_METADATA_NONE)
> +		return;
> +
> +	attachment->metadata.device.in_system_messages[0] =
> +		GIP_DEFAULT_IN_SYSTEM_MESSAGES;
> +	attachment->metadata.device.out_system_messages[0] =
> +		GIP_DEFAULT_OUT_SYSTEM_MESSAGES;
> +	if (attachment->attachment_index == 0) {
> +		/* Some decent default settings */
> +		attachment->features |= GIP_FEATURE_CONTROLLER;
> +		attachment->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_GUIDE_BUTTON);
> +	}
> +
> +	gip_handle_quirks(attachment);
> +	if (attachment->quirks & GIP_QUIRK_NO_HELLO)
> +		gip_ensure_metadata(attachment);
> +
> +	attachment->got_metadata = GIP_METADATA_FAKED;
> +}
> +
> +static bool gip_handle_command_protocol_control(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	/* TODO */
> +	dev_warn(GIP_DEV(attachment), "Unimplemented Protocol Control message\n");
> +	return -ENOTSUPP;
> +}
> +
> +static bool gip_handle_command_hello_device(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	struct gip_hello_device message = {0};
> +
> +	if (num_bytes != 28)
> +		return -EINVAL;
> +
> +	message.device_id = (uint64_t) bytes[0];
> +	message.device_id |= (uint64_t) bytes[1] << 8;
> +	message.device_id |= (uint64_t) bytes[2] << 16;
> +	message.device_id |= (uint64_t) bytes[3] << 24;
> +	message.device_id |= (uint64_t) bytes[4] << 32;
> +	message.device_id |= (uint64_t) bytes[5] << 40;
> +	message.device_id |= (uint64_t) bytes[6] << 48;
> +	message.device_id |= (uint64_t) bytes[7] << 56;
> +
> +	message.vendor_id = bytes[8];
> +	message.vendor_id |= bytes[9] << 8;
> +
> +	message.product_id = bytes[10];
> +	message.product_id |= bytes[11] << 8;
> +
> +	message.firmware_major_version = bytes[12];
> +	message.firmware_major_version |= bytes[13] << 8;
> +
> +	message.firmware_minor_version = bytes[14];
> +	message.firmware_minor_version |= bytes[15] << 8;
> +
> +	message.firmware_build_version = bytes[16];
> +	message.firmware_build_version |= bytes[17] << 8;
> +
> +	message.firmware_revision = bytes[18];
> +	message.firmware_revision |= bytes[19] << 8;
> +
> +	message.hardware_major_version = bytes[20];
> +	message.hardware_minor_version = bytes[21];
> +
> +	message.rf_proto_major_version = bytes[22];
> +	message.rf_proto_minor_version = bytes[23];
> +
> +	message.security_major_version = bytes[24];
> +	message.security_minor_version = bytes[25];
> +
> +	message.gip_major_version = bytes[26];
> +	message.gip_minor_version = bytes[27];
> +
> +	dev_dbg(GIP_DEV(attachment), "Device hello from %llx (%04x:%04x)\n",
> +		message.device_id, message.vendor_id, message.product_id);
> +	dev_dbg(GIP_DEV(attachment), "Firmware version %d.%d.%d rev %d\n",
> +		message.firmware_major_version, message.firmware_minor_version,
> +		message.firmware_build_version, message.firmware_revision);
> +
> +	/*
> +	 * The GIP spec specifies that the host should reject the device if any of these are wrong.
> +	 * I don't know if Windows or an Xbox do, however, so let's just log warnings instead.
> +	 */
> +	if (message.rf_proto_major_version != 1 && message.rf_proto_minor_version != 0)
> +		dev_warn(GIP_DEV(attachment),
> +			"Invalid RF protocol version %d.%d, expected 1.0\n",
> +			message.rf_proto_major_version, message.rf_proto_minor_version);
> +
> +	if (message.security_major_version != 1 && message.security_minor_version != 0)
> +		dev_warn(GIP_DEV(attachment),
> +			"Invalid security protocol version %d.%d, expected 1.0\n",
> +			message.security_major_version, message.security_minor_version);
> +
> +	if (message.gip_major_version != 1 && message.gip_minor_version != 0)
> +		dev_warn(GIP_DEV(attachment),
> +			"Invalid GIP version %d.%d, expected 1.0\n",
> +			message.gip_major_version, message.gip_minor_version);
> +
> +	attachment->firmware_major_version = message.firmware_major_version;
> +	attachment->firmware_minor_version = message.firmware_minor_version;
> +	attachment->vendor_id = message.vendor_id;
> +	attachment->product_id = message.product_id;
> +	attachment->uniq = devm_kasprintf(GIP_DEV(attachment),
> +		GFP_KERNEL, "%llx", message.device_id);
> +
> +	if (header->flags & GIP_FLAG_ATTACHMENT_MASK)
> +		return gip_send_system_message(attachment, GIP_CMD_METADATA, 0, NULL, 0);
> +	if (attachment->got_metadata == GIP_METADATA_FAKED)
> +		attachment->got_metadata = GIP_METADATA_NONE;
> +	return gip_ensure_metadata(attachment);
> +}
> +
> +static int gip_handle_command_status_device(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	int i;
> +
> +	if (num_bytes < 1)
> +		return -EINVAL;
> +
> +	attachment->status.base.battery_level = bytes[0] & 3;
> +	attachment->status.base.battery_type = (bytes[0] >> 2) & 3;
> +	attachment->status.base.charge = (bytes[0] >> 4) & 3;
> +	attachment->status.base.power_level = (bytes[0] >> 6) & 3;
> +
> +	if (num_bytes >= 4) {
> +		attachment->status.device_active = bytes[1] & 1;
> +		if (bytes[1] & 2) {
> +			/* Events present */
> +			if (num_bytes < 5)
> +				return -EINVAL;
> +
> +			attachment->status.num_events = bytes[4];
> +			if (attachment->status.num_events > 5) {
> +				dev_info(GIP_DEV(attachment),
> +					"Device reported too many events, %d > 5\n",
> +					attachment->status.num_events);
> +				return -EINVAL;
> +			}
> +			if (5 + attachment->status.num_events * 10 > num_bytes)
> +				return -EINVAL;
> +
> +			for (i = 0; i < attachment->status.num_events; i++) {
> +				struct gip_status_event *event = &attachment->status.events[i];
> +
> +				event->event_type = bytes[i * 10 + 5];
> +				event->event_type |= bytes[i * 10 + 6] << 8;
> +				event->fault_tag = bytes[i * 10 + 7];
> +				event->fault_tag |= bytes[i * 10 + 8] << 8;
> +				event->fault_tag |= bytes[i * 10 + 9] << 16;
> +				event->fault_tag |= bytes[i * 10 + 10] << 24;
> +				event->fault_address = bytes[i * 10 + 11];
> +				event->fault_address |= bytes[i * 10 + 12] << 8;
> +				event->fault_address |= bytes[i * 10 + 13] << 16;
> +				event->fault_address |= bytes[i * 10 + 14] << 24;
> +
> +				dev_info(GIP_DEV(attachment),
> +					"Attachment %i event type %i, tag %i address %x\n",
> +					attachment->attachment_index,
> +					event->event_type,
> +					event->fault_tag,
> +					event->fault_address);
> +			}
> +		}
> +	}
> +
> +	return gip_ensure_metadata(attachment);
> +}
> +
> +static int gip_handle_command_metadata_respose(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	struct gip_metadata metadata = {0};
> +	const guid_t *expected_guid = NULL;
> +	bool found_expected_guid;
> +	bool found_controller_guid = false;
> +	int i, j, k;
> +	int rc;
> +
> +	rc = gip_parse_metadata(GIP_DEV(attachment), &metadata, bytes, num_bytes);
> +	if (rc)
> +		return rc;
> +
> +	if (attachment->got_metadata == GIP_METADATA_GOT) {
> +		struct input_dev *input;
> +
> +		gip_metadata_free(GIP_DEV(attachment), &attachment->metadata);
> +		rcu_read_lock();
> +		input = rcu_dereference(attachment->input);
> +		rcu_read_unlock();
> +		if (input) {
> +			rcu_assign_pointer(attachment->input, NULL);
> +			synchronize_rcu();
> +			input_unregister_device(input);
> +		}
> +	}
> +
> +	attachment->metadata = metadata;
> +	attachment->got_metadata = GIP_METADATA_GOT;
> +	attachment->features = 0;
> +	cancel_delayed_work_sync(&attachment->metadata_next);
> +
> +	for (i = 0; i < metadata.device.num_preferred_types; i++) {
> +		const char *type = metadata.device.preferred_types[i];
> +
> +		dev_dbg(GIP_DEV(attachment), "Device preferred type: %s\n",
> +			type);
> +	}
> +	for (i = 0; i < metadata.device.num_preferred_types; i++) {
> +		const char *type = metadata.device.preferred_types[i];
> +
> +		for (j = 0; base_drivers[j] && !expected_guid; j++) {
> +			for (k = 0; base_drivers[j]->types[k] && !expected_guid; k++) {
> +				if (strcmp(type, base_drivers[j]->types[k]) == 0) {
> +					rc = gip_bind_driver(attachment, base_drivers[j]);
> +					if (rc == 0)
> +						expected_guid = &base_drivers[j]->guid;
> +					else if (rc != -ENODEV)
> +						return rc;
> +				}
> +			}
> +		}
> +		if (expected_guid)
> +			break;
> +
> +		if (strcmp(type, "Windows.Xbox.Input.Chatpad") == 0) {
> +			break;
> +		}

This is one of the aforementioned stylistic errors. I have fixed it locally.

> +		if (strcmp(type, "Windows.Xbox.Input.Headset") == 0) {
> +			expected_guid = &guid_headset;
> +			break;
> +		}
> +	}
> +
> +	found_expected_guid = !expected_guid;
> +	for (i = 0; i < metadata.device.num_supported_interfaces; i++) {
> +		const guid_t *guid = &metadata.device.supported_interfaces[i];
> +
> +		dev_dbg(GIP_DEV(attachment), "Supported interface: %pUl\n", guid);
> +		if (expected_guid && guid_equal(expected_guid, guid))
> +			found_expected_guid = true;
> +
> +		if (guid_equal(&guid_controller, guid)) {
> +			found_controller_guid = true;
> +			continue;
> +		}
> +		if (guid_equal(&gip_driver_navigation.guid, guid)) {
> +			attachment->features |= GIP_FEATURE_CONTROLLER;
> +			continue;
> +		}
> +		if (guid_equal(&guid_dev_auth_pc_opt_out, guid)) {
> +			attachment->features |= GIP_FEATURE_SECURITY_OPT_OUT;
> +			continue;
> +		}
> +		if (guid_equal(&guid_console_function_map, guid)) {
> +			attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP;
> +			continue;
> +		}
> +		if (guid_equal(&guid_console_function_map_overflow, guid)) {
> +			attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW;
> +			continue;
> +		}
> +		if (guid_equal(&guid_elite_buttons, guid)) {
> +			attachment->features |= GIP_FEATURE_ELITE_BUTTONS;
> +			continue;
> +		}
> +		if (guid_equal(&guid_dynamic_latency_input, guid)) {
> +			attachment->features |= GIP_FEATURE_DYNAMIC_LATENCY_INPUT;
> +			continue;
> +		}
> +	}
> +
> +	for (i = 0; i < metadata.num_messages; i++) {
> +		struct gip_message_metadata *message = &metadata.message_metadata[i];
> +
> +		if (message->type == GIP_CMD_DIRECT_MOTOR && message->length >= 9
> +			&& (message->flags & GIP_MESSAGE_FLAG_DOWNSTREAM))
> +			attachment->features |= GIP_FEATURE_MOTOR_CONTROL;
> +	}
> +
> +	if (!found_expected_guid || !found_controller_guid)
> +		dev_dbg(GIP_DEV(attachment),
> +			"Controller was missing expected GUID. "
> +			"This controller probably won't work on an actual Xbox.\n");
> +
> +	gip_handle_quirks(attachment);
> +
> +	if ((attachment->features & GIP_FEATURE_GUIDE_COLOR)
> +		&& !gip_supports_vendor_message(attachment,
> +			GIP_CMD_GUIDE_COLOR, false))
> +		attachment->features &= ~GIP_FEATURE_GUIDE_COLOR;
> +
> +	dev_dbg(GIP_DEV(attachment),
> +		"Attachment %i has features: %02x\n",
> +		attachment->attachment_index, attachment->features);
> +
> +	return gip_send_init_sequence(attachment);
> +}
> +
> +static int gip_handle_command_security(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	/* TODO: Needed for controllers that connect via dongles */
> +	dev_warn(GIP_DEV(attachment), "Unimplemented Security message\n");
> +	return -ENOTSUPP;
> +}
> +
> +static int gip_handle_command_guide_button_status(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	struct input_dev *input;
> +
> +	if (num_bytes < 2)
> +		return -EINVAL;
> +
> +	guard(rcu)();
> +	input = rcu_dereference(attachment->input);
> +	if (!input)
> +		return -ENODEV;
> +
> +	if (bytes[1] == VK_LWIN) {
> +		input_report_key(input, BTN_MODE, bytes[0] & 3);
> +		input_sync(input);
> +	}
> +
> +	return 0;
> +}
> +
> +static int gip_handle_command_audio_control(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	/* TODO: Needed for audio */
> +	dev_warn(GIP_DEV(attachment), "Unimplemented Audio Control message\n");
> +	return -ENOTSUPP;
> +}
> +
> +static int gip_handle_command_firmware(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	if (num_bytes < 1)
> +		return -EINVAL;
> +
> +	if (bytes[0] == 1) {
> +		uint16_t major, minor, build, rev;
> +
> +		if (num_bytes < 14) {
> +			dev_dbg(GIP_DEV(attachment),
> +				"Discarding too-short firmware message\n");
> +
> +			return -EINVAL;
> +		}
> +		major = bytes[6];
> +		major |= bytes[7] << 8;
> +		minor = bytes[8];
> +		minor |= bytes[9] << 8;
> +		build = bytes[10];
> +		build |= bytes[11] << 8;
> +		rev = bytes[12];
> +		rev |= bytes[13] << 8;
> +
> +		dev_dbg(GIP_DEV(attachment),
> +			"Firmware version: %d.%d.%d rev %d\n", major, minor, build, rev);
> +
> +		attachment->firmware_major_version = major;
> +		attachment->firmware_minor_version = minor;
> +
> +		if (attachment->vendor_id == 0x045e
> +			&& attachment->product_id == 0x0b00)
> +			return gip_enable_elite_buttons(attachment);
> +
> +		return 0;
> +	}
> +
> +	dev_warn(GIP_DEV(attachment), "Unimplemented Firmware message\n");
> +
> +	return -ENOTSUPP;
> +}
> +
> +static int gip_handle_command_hid_report(struct gip_attachment *attachment,
> +	const struct gip_header *header, uint8_t *bytes, int num_bytes)
> +{
> +	dev_warn(GIP_DEV(attachment), "Unimplemented HID report message\n");
> +
> +	return -ENOTSUPP;
> +}
> +
> +static int gip_handle_command_extended(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	if (num_bytes < 2)
> +		return -EINVAL;
> +
> +	if (bytes[1] != GIP_EXTENDED_STATUS_OK) {
> +		dev_dbg(GIP_DEV(attachment),
> +			"Extended message type %02x failed with status %i\n",
> +			bytes[0], bytes[1]);
> +		return -EPROTO;
> +	}
> +
> +	switch (bytes[0]) {
> +	case GIP_EXTCMD_GET_SERIAL_NUMBER:
> +		memcpy(attachment->serial, &bytes[2],
> +			min(sizeof(attachment->serial), (size_t)(num_bytes - 2)));
> +		break;
> +	default:
> +		/* TODO */
> +		dev_dbg(GIP_DEV(attachment), "Unimplemented extended message type %02x\n",
> +			bytes[0]);
> +		return -ENOTSUPP;
> +	}
> +
> +	return 0;
> +}
> +
> +static int gip_handle_elite_buttons(struct gip_attachment *attachment,
> +	struct input_dev *input, const uint8_t *bytes, int num_bytes)
> +{
> +	bool grip[4] = { 0, 0, 0, 0 };
> +	int profile = -1;
> +
> +	if (attachment->xbe_format == GIP_BTN_FMT_XBE1
> +		&& num_bytes > GIP_BTN_OFFSET_XBE1) {
> +		profile = bytes[GIP_BTN_OFFSET_XBE1] >> 4;
> +		if (profile) {
> +			grip[0] = bytes[GIP_BTN_OFFSET_XBE1] & BIT(0);
> +			grip[1] = bytes[GIP_BTN_OFFSET_XBE1] & BIT(1);
> +			grip[2] = bytes[GIP_BTN_OFFSET_XBE1] & BIT(2);
> +			grip[3] = bytes[GIP_BTN_OFFSET_XBE1] & BIT(3);
> +		}
> +	} else if ((attachment->xbe_format == GIP_BTN_FMT_XBE2_4
> +		|| attachment->xbe_format == GIP_BTN_FMT_XBE2_5)
> +		&& num_bytes > GIP_BTN_OFFSET_XBE2) {
> +		int profile_offset;
> +
> +		if (attachment->xbe_format == GIP_BTN_FMT_XBE2_4)
> +			profile_offset = 15;
> +		else
> +			profile_offset = 20;
> +		profile = bytes[profile_offset] & 3;
> +
> +		if (!profile) {
> +			grip[0] = bytes[GIP_BTN_OFFSET_XBE2] & BIT(2);
> +			grip[1] = bytes[GIP_BTN_OFFSET_XBE2] & BIT(0);
> +			grip[2] = bytes[GIP_BTN_OFFSET_XBE2] & BIT(3);
> +			grip[3] = bytes[GIP_BTN_OFFSET_XBE2] & BIT(1);
> +		}
> +	}
> +	if (profile >= 0) {
> +		input_report_key(input, BTN_GRIPL, grip[0]);
> +		input_report_key(input, BTN_GRIPR, grip[1]);
> +		input_report_key(input, BTN_GRIPL2, grip[2]);
> +		input_report_key(input, BTN_GRIPR2, grip[3]);
> +		input_report_abs(input, ABS_PROFILE, profile);
> +	}
> +	return 0;
> +}
> +
> +static int gip_handle_console_map(struct gip_attachment *attachment,
> +	struct input_dev *input, const uint8_t *bytes, int num_bytes)
> +{
> +	int function_map_offset = -1;
> +	if (num_bytes < 32)
> +		return 0;
> +
> +	if (attachment->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) {
> +		/* The dynamic latency input bytes are after the console function map */
> +		if (num_bytes >= 40)
> +			function_map_offset = num_bytes - 26;
> +	} else {
> +		function_map_offset = num_bytes - 18;
> +	}
> +	if (function_map_offset >= 14) {
> +		input_report_key(input, KEY_RECORD,
> +			bytes[function_map_offset] & BIT(0));
> +	}
> +	return 0;
> +}
> +
> +static int gip_handle_ll_input_report(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	struct input_dev *input;
> +	int rc = 0;
> +
> +	guard(rcu)();
> +	input = rcu_dereference(attachment->input);
> +	if (!input)
> +		return -ENODEV;
> +
> +	if (attachment->device_state != GIP_STATE_START) {
> +		dev_dbg(GIP_DEV(attachment), "Discarding early input report\n");
> +		attachment->device_state = GIP_STATE_START;
> +		return 0;
> +	}
> +
> +	if (attachment->driver && attachment->driver->handle_input_report) {
> +		rc = attachment->driver->handle_input_report(attachment, input, bytes, num_bytes);
> +		if (rc < 0)
> +			return rc;
> +	}
> +
> +	if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) {
> +		rc = gip_handle_elite_buttons(attachment, input, bytes, num_bytes);
> +		if (rc < 0)
> +			goto exit;
> +	}
> +
> +	rc = gip_handle_console_map(attachment, input, bytes, num_bytes);
> +
> +exit:
> +	input_sync(input);
> +
> +	return rc;
> +}
> +
> +static int gip_handle_ll_overflow_input_report(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	/* TODO: Unknown if any devices actually use this */
> +	dev_dbg(GIP_DEV(attachment), "Unimplemented Overflow Input Report message\n");
> +	return -ENOTSUPP;
> +}
> +
> +static int gip_handle_audio_data(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	/* TODO: Needed for audio support */
> +	dev_dbg(GIP_DEV(attachment), "Unimplemented Audio Data message\n");
> +	return -ENOTSUPP;
> +}
> +
> +static int gip_handle_system_message(struct gip_attachment *attachment,
> +	const struct gip_header *header, uint8_t *bytes, int num_bytes)
> +{
> +	if (!gip_supports_system_message(attachment, header->message_type, true)) {
> +		dev_warn(GIP_DEV(attachment),
> +			"Received claimed-unsupported system message type %02x\n",
> +			header->message_type);
> +		return -EINVAL;
> +	}
> +	switch (header->message_type) {
> +	case GIP_CMD_PROTO_CONTROL:
> +		return gip_handle_command_protocol_control(attachment, header,
> +			bytes, num_bytes);
> +	case GIP_CMD_HELLO_DEVICE:
> +		return gip_handle_command_hello_device(attachment, header,
> +			bytes, num_bytes);
> +	case GIP_CMD_STATUS_DEVICE:
> +		return gip_handle_command_status_device(attachment, header,
> +			bytes, num_bytes);
> +	case GIP_CMD_METADATA:
> +		return gip_handle_command_metadata_respose(attachment, header,
> +			bytes, num_bytes);
> +	case GIP_CMD_SECURITY:
> +		return gip_handle_command_security(attachment, header, bytes,
> +			num_bytes);
> +	case GIP_CMD_GUIDE_BUTTON:
> +		return gip_handle_command_guide_button_status(attachment,
> +			header, bytes, num_bytes);
> +	case GIP_CMD_AUDIO_CONTROL:
> +		return gip_handle_command_audio_control(attachment, header,
> +			bytes, num_bytes);
> +	case GIP_CMD_FIRMWARE:
> +		return gip_handle_command_firmware(attachment, header, bytes,
> +			num_bytes);
> +	case GIP_CMD_HID_REPORT:
> +		return gip_handle_command_hid_report(attachment, header,
> +			bytes, num_bytes);
> +	case GIP_CMD_EXTENDED:
> +		return gip_handle_command_extended(attachment, header, bytes,
> +			num_bytes);
> +	case GIP_AUDIO_DATA:
> +		return gip_handle_audio_data(attachment, header, bytes,
> +			num_bytes);
> +	default:
> +		dev_warn(GIP_DEV(attachment),
> +			"Received unknown system message type %02x\n",
> +			header->message_type);
> +		return -EINVAL;
> +	}
> +}
> +
> +static struct gip_attachment *gip_ensure_attachment(struct gip_device *device,
> +	uint8_t attachment_index)
> +{
> +	struct gip_attachment *attachment = device->attachments[attachment_index];
> +
> +	if (!attachment) {
> +		attachment = devm_kzalloc(GIP_DEV(device),
> +			sizeof(*attachment), GFP_KERNEL);
> +		if (!attachment)
> +			return ERR_PTR(-ENOMEM);
> +
> +		attachment->attachment_index = attachment_index;
> +		attachment->device = device;
> +
> +		if (attachment_index == 0) {
> +			attachment->vendor_id = device->udev->descriptor.idVendor;
> +			attachment->product_id = device->udev->descriptor.idProduct;
> +		}
> +
> +		device->attachments[attachment_index] = attachment;
> +
> +		mutex_init(&attachment->lock);
> +		INIT_DELAYED_WORK(&attachment->fragment_timeout, gip_fragment_timeout);
> +		INIT_DELAYED_WORK(&attachment->metadata_next, gip_retry_metadata);
> +
> +		gip_set_metadata_defaults(attachment);
> +	}
> +	return attachment;
> +}
> +
> +static int gip_handle_message(struct gip_attachment *attachment,
> +	const struct gip_header *header, uint8_t *bytes, int num_bytes)
> +{
> +	if (header->flags & GIP_FLAG_SYSTEM)
> +		return gip_handle_system_message(attachment, header, bytes,
> +			num_bytes);
> +
> +	if (header->message_type < MAX_GIP_CMD && attachment->vendor_handlers[header->message_type])
> +		return attachment->vendor_handlers[header->message_type](attachment,
> +			header, bytes, num_bytes);
> +
> +	switch (header->message_type) {
> +	case GIP_LL_INPUT_REPORT:
> +		return gip_handle_ll_input_report(attachment, header, bytes,
> +			num_bytes);
> +	case GIP_LL_OVERFLOW_INPUT_REPORT:
> +		return gip_handle_ll_overflow_input_report(attachment, header,
> +			bytes, num_bytes);
> +	}
> +	dev_warn(GIP_DEV(attachment),
> +		"Received unknown vendor message type %02x\n",
> +		header->message_type);
> +	return -ENOTSUPP;
> +}
> +
> +static int gip_receive_fragment(struct gip_attachment *attachment,
> +	const struct gip_header *header, int offset,
> +	uint64_t *fragment_offset, uint16_t *bytes_remaining, uint8_t *bytes,
> +	int num_bytes)
> +{
> +	int rc = 0;
> +
> +	if (header->flags & GIP_FLAG_INIT_FRAG) {
> +		uint64_t total_length;
> +
> +		if (attachment->fragment_message) {
> +			/*
> +			 * Reset fragment buffer if we get a new initial
> +			 * fragment before finishing the last message.
> +			 * TODO: Is this the correct behavior?
> +			 */
> +			devm_kfree(GIP_DEV(attachment), attachment->fragment_data);
> +			attachment->fragment_data = NULL;
> +		}
> +		offset += gip_decode_length(&total_length, &bytes[offset],
> +			num_bytes - offset);
> +		if (total_length > MAX_MESSAGE_LENGTH)
> +			return -EINVAL;
> +
> +		attachment->total_length = total_length;
> +		attachment->fragment_message = header->message_type;
> +		if (header->length > num_bytes - offset) {
> +			dev_warn(GIP_DEV(attachment),
> +				"Received fragment that claims to be %llu bytes, expected %i\n",
> +				header->length, num_bytes - offset);
> +			return -EINVAL;
> +		}
> +		if (header->length > total_length) {
> +			dev_warn(GIP_DEV(attachment),
> +				"Received too long fragment, %llu bytes, exceeds %d\n",
> +				header->length, attachment->total_length);
> +			return -EINVAL;
> +		}
> +		attachment->fragment_data = devm_kmalloc(GIP_DEV(attachment),
> +			attachment->total_length, GFP_KERNEL);
> +		if (!attachment->fragment_data)
> +			return -ENOMEM;
> +		memcpy(attachment->fragment_data, &bytes[offset],
> +			header->length);
> +		*fragment_offset = header->length;
> +		attachment->fragment_offset = header->length;
> +		*bytes_remaining = attachment->total_length - header->length;
> +	} else {
> +		if (header->message_type != attachment->fragment_message) {
> +			dev_warn(GIP_DEV(attachment),
> +				"Received out of sequence message type %02x, expected %02x\n",
> +				header->message_type, attachment->fragment_message);
> +			gip_fragment_failed(attachment, header);
> +			return -EINVAL;
> +		}
> +
> +		offset += gip_decode_length(fragment_offset, &bytes[offset],
> +			num_bytes - offset);
> +		if (*fragment_offset != attachment->fragment_offset) {
> +			dev_warn(GIP_DEV(attachment),
> +				"Received out of sequence fragment, (claimed %llu, expected %d)\n",
> +				*fragment_offset, attachment->fragment_offset);
> +			gip_acknowledge(attachment->device, header,
> +				attachment->fragment_offset,
> +				attachment->total_length - attachment->fragment_offset);
> +			return -EINVAL;
> +		} else if (*fragment_offset + header->length > attachment->total_length) {
> +			dev_warn(GIP_DEV(attachment),
> +				"Received too long fragment, %llu exceeds %d\n",
> +				*fragment_offset + header->length, attachment->total_length);
> +			gip_fragment_failed(attachment, header);
> +			return -EINVAL;
> +		}
> +
> +		*bytes_remaining = attachment->total_length -
> +			(*fragment_offset + header->length);
> +		if (header->length != 0) {
> +			memcpy(&attachment->fragment_data[*fragment_offset],
> +				&bytes[offset], header->length);
> +		} else {
> +			rc = gip_handle_message(attachment, header,
> +				attachment->fragment_data,
> +				attachment->total_length);
> +			devm_kfree(GIP_DEV(attachment), attachment->fragment_data);
> +			attachment->fragment_data = NULL;
> +			attachment->fragment_message = 0;
> +		}
> +		*fragment_offset += header->length;
> +		attachment->fragment_offset = *fragment_offset;
> +	}
> +	cancel_delayed_work_sync(&attachment->fragment_timeout);
> +	schedule_delayed_work(&attachment->fragment_timeout, HZ);
> +
> +	return rc;
> +}
> +
> +static int gip_receive_message(struct gip_device *device, uint8_t *bytes,
> +	int num_bytes)
> +{
> +	struct gip_header header;
> +	int offset = 3;
> +	int rc = 0;
> +	uint64_t fragment_offset = 0;
> +	uint16_t bytes_remaining = 0;
> +	bool is_fragment;
> +	uint8_t attachment_index;
> +	struct gip_attachment *attachment;
> +
> +	if (num_bytes < 5)
> +		return -EINVAL;
> +
> +	header.message_type = bytes[0];
> +	header.flags = bytes[1];
> +	header.sequence_id = bytes[2];
> +	offset += gip_decode_length(&header.length, &bytes[offset], num_bytes - offset);
> +
> +	is_fragment = header.flags & GIP_FLAG_FRAGMENT;
> +	attachment_index = header.flags & GIP_FLAG_ATTACHMENT_MASK;
> +	attachment = gip_ensure_attachment(device, attachment_index);
> +
> +	print_hex_dump_debug(KBUILD_MODNAME ": Received message: ", DUMP_PREFIX_OFFSET,
> +		16, 1, bytes, num_bytes, false);
> +
> +	guard(mutex)(&attachment->lock);
> +	/* Handle coalescing fragmented messages */
> +	if (is_fragment) {
> +		rc = gip_receive_fragment(attachment, &header, offset,
> +			&fragment_offset, &bytes_remaining, bytes, num_bytes);
> +	} else if (header.length + offset > num_bytes) {
> +		dev_warn(GIP_DEV(device),
> +			"Received message with erroneous length (claimed %llu, actual %d), discarding\n",
> +			header.length + offset, num_bytes);
> +		rc = -EINVAL;
> +	} else {
> +		num_bytes -= offset;
> +		bytes += offset;
> +		fragment_offset = header.length;
> +		rc = gip_handle_message(attachment, &header, bytes, num_bytes);
> +	}
> +
> +	if (!rc && (header.flags & GIP_FLAG_ACME))
> +		gip_acknowledge(device, &header, fragment_offset, bytes_remaining);
> +
> +	return rc;
> +}
> +
> +static void gip_receive_work(struct work_struct *work)
> +{
> +	struct gip_device *device = container_of(work, struct gip_device,
> +		receive_message);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&device->message_lock, flags);
> +	while (device->pending_in_messages) {
> +		struct gip_raw_message *message = &device->in_queue[device->next_in_message];
> +
> +		spin_unlock_irqrestore(&device->message_lock, flags);
> +
> +		gip_receive_message(device, message->bytes, message->num_bytes);
> +
> +		spin_lock_irqsave(&device->message_lock, flags);
> +		device->next_in_message = (device->next_in_message + 1) % MAX_IN_MESSAGES;
> +		device->pending_in_messages--;
> +	}
> +	spin_unlock_irqrestore(&device->message_lock, flags);
> +}
> +
> +static void gip_urb_in(struct urb *urb)
> +{
> +	struct gip_interface *intf = urb->context;
> +	struct gip_device *gip = intf->device;
> +	struct device *dev = &intf->intf->dev;
> +	int status = urb->status;
> +	int message_id;
> +	struct gip_raw_message *message;
> +	unsigned long flags;
> +
> +	switch (status) {
> +	case 0:
> +		/* success */
> +		break;
> +	case -ECONNRESET:
> +	case -ENOENT:
> +	case -ESHUTDOWN:
> +		/* this urb is terminated, clean up */
> +		dev_dbg(dev, "%s - urb shutting down with status: %d\n",
> +			__func__, status);
> +		return;
> +	default:
> +		dev_dbg(dev, "%s - urb has status of: %d\n",
> +			__func__, status);
> +		goto exit;
> +	}
> +	if (intf->isoc_messages) {
> +		/* TODO: Needed for audio support */
> +		dev_warn(GIP_DEV(gip), "Unimplemented isochronous message input\n");
> +		goto exit;
> +	}
> +
> +	spin_lock_irqsave(&gip->message_lock, flags);
> +	if (gip->pending_in_messages >= MAX_IN_MESSAGES) {
> +		dev_err(GIP_DEV(gip), "Input queue is full; dropping message\n");
> +	} else {
> +		message_id = (gip->next_in_message + gip->pending_in_messages) % MAX_IN_MESSAGES;
> +		message = &gip->in_queue[message_id];
> +		gip->pending_in_messages++;
> +		memcpy(message->bytes, intf->in_data, urb->actual_length);
> +		message->num_bytes = urb->actual_length;
> +	}
> +	spin_unlock_irqrestore(&gip->message_lock, flags);
> +	schedule_work(&gip->receive_message);
> +
> +exit:
> +	status = usb_submit_urb(urb, GFP_ATOMIC);
> +	if (status)
> +		dev_err(dev, "%s - usb_submit_urb failed with result %d\n",
> +			__func__, status);
> +}
> +
> +static void gip_urb_out(struct urb *urb)
> +{
> +	struct gip_interface *intf = urb->context;
> +	struct device *dev = &intf->intf->dev;
> +	int status = urb->status;
> +
> +	guard(spinlock_irqsave)(&intf->device->message_lock);
> +
> +	switch (status) {
> +	case 0:
> +		/* success */
> +		break;
> +
> +	case -ECONNRESET:
> +	case -ENOENT:
> +	case -ESHUTDOWN:
> +		/* this urb is terminated, clean up */
> +		dev_dbg(dev, "%s - urb shutting down with status: %d\n",
> +			__func__, status);
> +		break;
> +
> +	default:
> +		dev_dbg(dev, "%s - nonzero urb status received: %d\n",
> +			__func__, status);
> +		break;
> +	}
> +}
> +
> +static int gip_init_input(struct gip_interface *intf,
> +	struct usb_endpoint_descriptor *ep_in)
> +{
> +	int error;
> +	struct usb_device *udev = interface_to_usbdev(intf->intf);
> +
> +	intf->urb_in = usb_alloc_urb(intf->isoc_messages, GFP_KERNEL);
> +	if (!intf->urb_in)
> +		return -ENOMEM;
> +
> +	intf->in_data = usb_alloc_coherent(udev, intf->mtu, GFP_KERNEL,
> +		&intf->urb_in->transfer_dma);
> +
> +	if (!intf->in_data) {
> +		return -ENOMEM;
> +		goto err_free_urb;
> +	}
> +
> +	usb_fill_int_urb(intf->urb_in, udev,
> +		usb_rcvintpipe(udev, ep_in->bEndpointAddress),
> +		intf->in_data, intf->mtu, gip_urb_in, intf,
> +		ep_in->bInterval);
> +	intf->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> +
> +	if (intf->isoc_messages)
> +		intf->urb_in->transfer_flags |= URB_ISO_ASAP;
> +
> +	return 0;
> +
> +err_free_urb:
> +	usb_free_urb(intf->urb_in);
> +	intf->urb_in = NULL;
> +
> +	return error;
> +}
> +
> +static int gip_init_output(struct gip_interface *intf,
> +	struct usb_endpoint_descriptor *ep_out)
> +{
> +	int error;
> +	struct usb_device *udev = interface_to_usbdev(intf->intf);
> +	int i;
> +
> +	if (usb_ifnum_to_if(udev, GIP_WIRED_INTF_AUDIO)) {
> +		/*
> +		 * Explicitly disable the audio interface. This is needed
> +		 * for some controllers, such as the PowerA Enhanced Wired
> +		 * Controller for Series X|S (0x20d6:0x200e) to report the
> +		 * guide button.
> +		 */
> +		error = usb_set_interface(udev, GIP_WIRED_INTF_AUDIO, 0);
> +		if (error)
> +			dev_warn(GIP_DEV(intf),
> +				"unable to disable audio interface: %d\n",
> +				error);
> +	}
> +
> +	init_usb_anchor(&intf->out_anchor);
> +
> +	for (i = 0; i < MAX_OUT_MESSAGES; i++) {
> +		intf->out_queue[i].urb = usb_alloc_urb(intf->isoc_messages, GFP_KERNEL);
> +		if (!intf->out_queue[i].urb) {
> +			error = -ENOMEM;
> +			goto err_free_urbs;
> +		}
> +
> +		intf->out_queue[i].data = usb_alloc_coherent(udev, intf->mtu, GFP_KERNEL,
> +			&intf->out_queue[i].urb->transfer_dma);
> +
> +		if (!intf->out_queue[i].data) {
> +			return -ENOMEM;
> +			goto err_free_urbs;
> +		}
> +
> +		usb_fill_int_urb(intf->out_queue[i].urb, udev,
> +			usb_sndintpipe(udev, ep_out->bEndpointAddress),
> +			intf->out_queue[i].data, intf->mtu, gip_urb_out, intf,
> +			ep_out->bInterval);
> +		intf->out_queue[i].urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> +
> +		if (intf->isoc_messages)
> +			intf->out_queue[i].urb->transfer_flags |= URB_ISO_ASAP;
> +	}
> +
> +	return 0;
> +
> +err_free_urbs:
> +	for (i = 0; i < MAX_OUT_MESSAGES; i++) {
> +		if (intf->out_queue[i].data)
> +			usb_free_coherent(udev, intf->mtu, intf->out_queue[i].data,
> +				intf->out_queue[i].urb->transfer_dma);
> +		if (intf->out_queue[i].urb) {
> +			usb_free_urb(intf->out_queue[i].urb);
> +			intf->out_queue[i].urb = NULL;
> +		}
> +	}
> +	return error;
> +}
> +
> +static void gip_deinit_output(struct gip_interface *intf)
> +{
> +	int i;
> +
> +	for (i = 0; i < MAX_OUT_MESSAGES; i++) {
> +		if (!intf->out_queue[i].urb)
> +			continue;
> +		usb_free_coherent(interface_to_usbdev(intf->intf), intf->mtu,
> +			intf->out_queue[i].data, intf->out_queue[i].urb->transfer_dma);
> +		usb_free_urb(intf->out_queue[i].urb);
> +		intf->out_queue[i].data = NULL;
> +		intf->out_queue[i].urb = NULL;
> +	}
> +}
> +
> +static void gip_deinit_input(struct gip_interface *intf)
> +{
> +	usb_free_coherent(interface_to_usbdev(intf->intf), intf->mtu,
> +		intf->in_data, intf->urb_in->transfer_dma);
> +	usb_free_urb(intf->urb_in);
> +	intf->urb_in = NULL;
> +}
> +
> +static int gip_interface_init(struct gip_interface *intf)
> +{
> +	struct usb_endpoint_descriptor *ep_in = NULL;
> +	struct usb_endpoint_descriptor *ep_out = NULL;
> +	int error = usb_find_common_endpoints(intf->intf->cur_altsetting,
> +		NULL, NULL, &ep_in, &ep_out);
> +
> +	if (error)
> +		return error;
> +
> +	if (!ep_in || !ep_out)
> +		return -ENODEV;
> +
> +	error = gip_init_input(intf, ep_in);
> +	if (error)
> +		return error;
> +
> +	error = gip_init_output(intf, ep_out);
> +	if (error)
> +		goto err_free_input;
> +
> +	if (usb_submit_urb(intf->urb_in, GFP_KERNEL)) {
> +		error = -EIO;
> +		goto err_free_output;
> +	}
> +
> +	return 0;
> +
> +err_free_output:
> +	gip_deinit_output(intf);
> +err_free_input:
> +	gip_deinit_input(intf);
> +	return error;
> +}
> +
> +static int gip_probe(struct usb_interface *intf, const struct usb_device_id *id)
> +{
> +	struct usb_device *udev = interface_to_usbdev(intf);
> +	struct gip_device *gip = NULL;
> +	struct gip_attachment *attachment;
> +	int rc;
> +
> +	if (intf->cur_altsetting->desc.bInterfaceNumber != GIP_WIRED_INTF_DATA) {
> +		/*
> +		 * The Xbox One controller lists three interfaces all with the
> +		 * same interface class, subclass and protocol. Differentiate by
> +		 * interface number.
> +		 */
> +		return 0;
> +	}
> +
> +	gip = devm_kzalloc(&udev->dev, sizeof(*gip), GFP_KERNEL);
> +	if (!gip)
> +		return -ENOMEM;
> +
> +	gip->udev = udev;
> +	gip->data.device = gip;
> +	gip->data.intf = intf;
> +	gip->data.mtu = BASE_GIP_MTU;
> +	gip->audio.device = gip;
> +	gip->audio.mtu = MAX_GIP_MTU;
> +	gip->audio.isoc_messages = MAX_AUDIO_MESSAGES;
> +
> +	INIT_WORK(&gip->receive_message, gip_receive_work);
> +	spin_lock_init(&gip->message_lock);
> +
> +	rc = gip_interface_init(&gip->data);
> +	if (rc) {
> +		devm_kfree(GIP_DEV(gip), gip);
> +		return rc;
> +	}
> +	/* Don't init audio interface -- we aren't using it yet */
> +
> +	usb_set_intfdata(intf, gip);
> +
> +	/* Pre-create the first attachment, as it should always exist */
> +	attachment = gip_ensure_attachment(gip, 0);
> +	if (IS_ERR(attachment))
> +		return PTR_ERR(attachment);
> +
> +	return 0;
> +}
> +
> +static int gip_shutdown(struct gip_device *device)
> +{
> +	int i;
> +
> +	cancel_work_sync(&device->receive_message);
> +
> +	for (i = 0; i < MAX_ATTACHMENTS; i++) {
> +		struct gip_attachment *attachment = device->attachments[i];
> +		struct input_dev *input;
> +
> +		if (!attachment)
> +			continue;
> +
> +		scoped_guard (mutex, &attachment->lock) {
> +			cancel_delayed_work_sync(&attachment->metadata_next);
> +			cancel_delayed_work_sync(&attachment->fragment_timeout);
> +
> +			rcu_read_lock();
> +			input = rcu_dereference(attachment->input);
> +			rcu_read_unlock();
> +
> +			rcu_assign_pointer(attachment->input, NULL);
> +			synchronize_rcu();
> +		}
> +
> +		if (input)
> +			input_unregister_device(input);
> +	}
> +
> +	return 0;
> +}
> +
> +static void gip_disconnect(struct usb_interface *intf)
> +{
> +	struct gip_device *gip = usb_get_intfdata(intf);
> +	unsigned long flags;
> +	int i;
> +
> +	if (!gip)
> +		return;
> +
> +	usb_kill_urb(gip->data.urb_in);
> +	if (gip->audio.intf)
> +		usb_kill_urb(gip->audio.urb_in);
> +
> +	gip_shutdown(gip);
> +
> +	spin_lock_irqsave(&gip->message_lock, flags);
> +	gip_deinit_input(&gip->data);
> +	gip_deinit_output(&gip->data);
> +	if (gip->audio.intf) {
> +		gip_deinit_input(&gip->audio);
> +		gip_deinit_output(&gip->audio);
> +	}
> +	spin_unlock_irqrestore(&gip->message_lock, flags);
> +
> +	usb_set_intfdata(intf, NULL);
> +
> +	for (i = 0; i < MAX_ATTACHMENTS; i++) {
> +		struct gip_attachment *attachment = gip->attachments[i];
> +
> +		if (!attachment)
> +			continue;
> +		devm_kfree(GIP_DEV(attachment), attachment->uniq);
> +		devm_kfree(GIP_DEV(attachment), attachment);
> +	}
> +
> +	devm_kfree(GIP_DEV(gip), gip);
> +}
> +
> +static int gip_suspend(struct usb_interface *intf, pm_message_t message)
> +{
> +	struct gip_device *gip = usb_get_intfdata(intf);
> +
> +	if (!gip)
> +		return 0;
> +
> +	usb_kill_urb(gip->data.urb_in);
> +	if (gip->audio.intf)
> +		usb_kill_urb(gip->audio.urb_in);
> +
> +	if (gip->attachments[0]) {
> +		struct gip_attachment *attachment = gip->attachments[0];
> +
> +		guard(mutex)(&attachment->lock);
> +		gip_send_set_device_state(attachment, GIP_STATE_OFF);
> +		attachment->device_state = GIP_STATE_OFF;
> +	}
> +
> +	return gip_shutdown(gip);
> +}
> +
> +static int gip_resume(struct usb_interface *intf)
> +{
> +	struct gip_device *gip = usb_get_intfdata(intf);
> +
> +	if (!gip)
> +		return 0;
> +
> +	if (usb_submit_urb(gip->data.urb_in, GFP_KERNEL))
> +		return -EIO;
> +
> +	return 0;
> +}
> +
> +/* The Xbox One controller uses subclass 71 and protocol 208. */
> +#define GIP_VENDOR(vend) \
> +	{ \
> +		.match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, \
> +		.idVendor = (vend), \
> +		.bInterfaceClass = USB_CLASS_VENDOR_SPEC, \
> +		.bInterfaceSubClass = 71, \
> +		.bInterfaceProtocol = 208 \
> +	}
> +
> +static const struct usb_device_id gip_table[] = {
> +	/*
> +	 * Please keep this list sorted by vendor ID.
> +	 */
> +	GIP_VENDOR(0x03f0),		/* HP/HyperX */
> +	GIP_VENDOR(0x044f),		/* ThrustMaster */
> +	GIP_VENDOR(0x045e),		/* Microsoft */
> +	GIP_VENDOR(0x046d),		/* Logitech */
> +	GIP_VENDOR(0x0738),		/* Mad Catz */
> +	GIP_VENDOR(0x0b05),		/* ASUS */
> +	GIP_VENDOR(0x0e6f),		/* PDP */
> +	GIP_VENDOR(0x0f0d),		/* Hori */
> +	GIP_VENDOR(0x10f5),		/* Turtle Beach */
> +	GIP_VENDOR(0x1532),		/* Razer */
> +	GIP_VENDOR(0x20d6),		/* PowerA/BDA */
> +	GIP_VENDOR(0x24c6),		/* PowerA/BDA/ThrustMaster */
> +	GIP_VENDOR(0x294b),		/* Snakebyte */
> +	GIP_VENDOR(0x2dc8),		/* 8BitDo */
> +	GIP_VENDOR(0x2e24),		/* Hyperkin */
> +	GIP_VENDOR(0x2e95),		/* SCUF Gaming */
> +	GIP_VENDOR(0x3285),		/* Nacon */
> +	GIP_VENDOR(0x3537),		/* GameSir */
> +	GIP_VENDOR(0x366c),		/* ByoWave */
> +	{ }
> +};
> +
> +MODULE_DEVICE_TABLE(usb, gip_table);
> +
> +static struct usb_driver gip_driver = {
> +	.name		= "xbox-gip",
> +	.probe		= gip_probe,
> +	.disconnect	= gip_disconnect,
> +	.suspend	= gip_suspend,
> +	.resume		= gip_resume,
> +	.id_table	= gip_table,
> +};
> +
> +module_usb_driver(gip_driver);
> +
> +MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
> +MODULE_DESCRIPTION("Xbox Gaming Input Protocol driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/input/joystick/gip/gip-drivers.c b/drivers/input/joystick/gip/gip-drivers.c
> new file mode 100644
> index 0000000000000..f5507e6215a94
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip-drivers.c
> @@ -0,0 +1,210 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Base drivers for common GIP devices
> + *
> + * Copyright (c) 2025 Valve Software
> + *
> + * This driver is based on the Microsoft GIP spec at:
> + * https://aka.ms/gipdocs
> + * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gipusb/e7c90904-5e21-426e-b9ad-d82adeee0dbc
> + */
> +#include "gip.h"
> +
> +struct gip_device_capabilities_response {
> +	uint8_t extra_button_count;
> +	uint8_t extra_axis_count;
> +	uint8_t led_count;
> +	uint8_t max_global_led_gain;
> +};
> +
> +static bool dpad_as_buttons;
> +
> +static int gip_setup_gamepad_input(struct gip_attachment *attachment, struct input_dev* input)

This is one of the aforementioned stylistic errors. I have fixed all locations of this in the series.

> +{
> +	int ret = gip_driver_navigation.setup_input(attachment, input);
> +
> +	if (ret < 0)
> +		return ret;
> +	input_set_capability(input, EV_KEY, BTN_THUMBR);
> +	input_set_capability(input, EV_KEY, BTN_THUMBL);
> +	input_set_abs_params(input, ABS_X, -32768, 32767, 16, 128);
> +	input_set_abs_params(input, ABS_Y, -32768, 32767, 16, 128);
> +	input_set_abs_params(input, ABS_RX, -32768, 32767, 16, 128);
> +	input_set_abs_params(input, ABS_RY, -32768, 32767, 16, 128);
> +	input_set_abs_params(input, ABS_Z, 0, 1023, 0, 0);
> +	input_set_abs_params(input, ABS_RZ, 0, 1023, 0, 0);
> +
> +	/* Xbox Adaptive Controller */
> +	if (attachment->vendor_id == 0x045e && attachment->product_id == 0x0b0a)
> +		input_set_abs_params(input, ABS_PROFILE, 0, 3, 0, 0);
> +	return 0;
> +}
> +
> +static int gip_handle_gamepad_report(struct gip_attachment *attachment,
> +	struct input_dev *input, const uint8_t *bytes, int num_bytes)
> +{
> +	int16_t axis;
> +	int ret = gip_driver_navigation.handle_input_report(attachment, input, bytes, num_bytes);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	if (num_bytes < 14) {
> +		dev_dbg(GIP_DEV(attachment), "Discarding too-short input report\n");
> +		return -EINVAL;
> +	}
> +
> +	input_report_key(input, BTN_THUMBL, bytes[1] & BIT(6));
> +	input_report_key(input, BTN_THUMBR, bytes[1] & BIT(7));
> +
> +	axis = bytes[2];
> +	axis |= bytes[3] << 8;
> +	input_report_abs(input, ABS_Z, axis);
> +
> +	axis = bytes[4];
> +	axis |= bytes[5] << 8;
> +	input_report_abs(input, ABS_RZ, axis);
> +
> +	axis = bytes[6];
> +	axis |= bytes[7] << 8;
> +	input_report_abs(input, ABS_X, axis);
> +	axis = bytes[8];
> +	axis |= bytes[9] << 8;
> +	input_report_abs(input, ABS_Y, ~axis);
> +	axis = bytes[10];
> +	axis |= bytes[11] << 8;
> +	input_report_abs(input, ABS_RX, axis);
> +	axis = bytes[12];
> +	axis |= bytes[13] << 8;
> +	input_report_abs(input, ABS_RY, ~axis);
> +
> +	/* Xbox Adaptive Controller */
> +	if (attachment->vendor_id == 0x045e && attachment->product_id == 0x0b0a && num_bytes >= 31)
> +		input_report_abs(input, ABS_PROFILE, bytes[30] & 3);
> +
> +	return 0;
> +}
> +
> +const struct gip_driver gip_driver_gamepad = {
> +	.types = (const char* const[]) { "Windows.Xbox.Input.Gamepad", NULL },
> +	.guid = GUID_INIT(0x082e402c, 0x07df, 0x45e1, 0xa5, 0xab,
> +		0xa3, 0x12, 0x7a, 0xf1, 0x97, 0xb5),
> +
> +	.quirks = (const struct gip_quirks[]) {
> +		/* Xbox One Controller (model 1573) */
> +		{ 0x045e, 0x02d1, 0, .override_name = "Xbox One Controller" },
> +
> +		/* Xbox One Controller (model 1697) */
> +		{ 0x045e, 0x02dd, 0, .override_name = "Xbox One Controller" },
> +
> +		/* Xbox Elite */
> +		{ 0x045e, 0x02e3, 0,
> +			.override_name = "Xbox Elite Controller",
> +			.added_features = GIP_FEATURE_ELITE_BUTTONS,
> +			.filtered_features = GIP_FEATURE_CONSOLE_FUNCTION_MAP },
> +
> +		/* Xbox One Controller (model 1708) */
> +		{ 0x045e, 0x02ea, 0, .override_name = "Xbox One Controller" },
> +
> +		/* Xbox Elite 2 */
> +		{ 0x045e, 0x0b00, 0,
> +			.override_name = "Xbox Elite Series 2 Controller",
> +			.added_features = GIP_FEATURE_GUIDE_COLOR |
> +				GIP_FEATURE_EXTENDED_SET_DEVICE_STATE },
> +
> +		/* Xbox Adaptive Controller */
> +		{ 0x045e, 0x0b0a, 0, .override_name = "Xbox Adaptive Controller" },
> +
> +		/* Xbox Wireless Controller */
> +		{ 0x045e, 0x0b12, 0, .override_name = "Xbox Wireless Controller" },
> +
> +		{0},
> +	},
> +
> +	.probe = NULL,
> +	.remove = NULL,
> +	.init = NULL,
> +	.setup_input = gip_setup_gamepad_input,
> +	.handle_input_report = gip_handle_gamepad_report,
> +};
> +
> +static int gip_setup_navigation_input(struct gip_attachment *attachment, struct input_dev *input)
> +{
> +	input_set_capability(input, EV_KEY, BTN_Y);
> +	input_set_capability(input, EV_KEY, BTN_B);
> +	input_set_capability(input, EV_KEY, BTN_X);
> +	input_set_capability(input, EV_KEY, BTN_A);
> +	input_set_capability(input, EV_KEY, BTN_SELECT);
> +	input_set_capability(input, EV_KEY, BTN_MODE);
> +	input_set_capability(input, EV_KEY, BTN_START);
> +	input_set_capability(input, EV_KEY, BTN_TR);
> +	input_set_capability(input, EV_KEY, BTN_TL);
> +
> +	attachment->dpad_as_buttons = dpad_as_buttons;
> +	if (attachment->dpad_as_buttons) {
> +		input_set_capability(input, EV_KEY, BTN_DPAD_UP);
> +		input_set_capability(input, EV_KEY, BTN_DPAD_RIGHT);
> +		input_set_capability(input, EV_KEY, BTN_DPAD_LEFT);
> +		input_set_capability(input, EV_KEY, BTN_DPAD_DOWN);
> +	} else {
> +		input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
> +		input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
> +	}
> +
> +	return 0;
> +}
> +
> +static int gip_handle_navigation_report(struct gip_attachment *attachment,
> +	struct input_dev *input, const uint8_t *bytes, int num_bytes)
> +{
> +	if (num_bytes < 2) {
> +		dev_dbg(GIP_DEV(attachment), "Discarding too-short input report\n");
> +		return -EINVAL;
> +	}
> +
> +	input_report_key(input, BTN_START, bytes[0] & BIT(2));
> +	input_report_key(input, BTN_SELECT, bytes[0] & BIT(3));
> +	input_report_key(input, BTN_A, bytes[0] & BIT(4));
> +	input_report_key(input, BTN_B, bytes[0] & BIT(5));
> +	input_report_key(input, BTN_X, bytes[0] & BIT(6));
> +	input_report_key(input, BTN_Y, bytes[0] & BIT(7));
> +
> +	if (attachment->dpad_as_buttons) {
> +		input_report_key(input, BTN_DPAD_UP, bytes[1] & BIT(0));
> +		input_report_key(input, BTN_DPAD_DOWN, bytes[1] & BIT(1));
> +		input_report_key(input, BTN_DPAD_LEFT, bytes[1] & BIT(2));
> +		input_report_key(input, BTN_DPAD_RIGHT, bytes[1] & BIT(3));
> +	} else {
> +		input_report_abs(input, ABS_HAT0X,
> +			!!(bytes[1] & BIT(3)) - !!(bytes[1] & BIT(2)));
> +		input_report_abs(input, ABS_HAT0Y,
> +			!!(bytes[1] & BIT(1)) - !!(bytes[1] & BIT(0)));
> +	}
> +
> +	if (attachment->quirks & GIP_QUIRK_SWAP_LB_RB) {
> +		/* Previous */
> +		input_report_key(input, BTN_TR, bytes[1] & BIT(4));
> +		/* Next */
> +		input_report_key(input, BTN_TL, bytes[1] & BIT(5));
> +	} else {
> +		input_report_key(input, BTN_TL, bytes[1] & BIT(4));
> +		input_report_key(input, BTN_TR, bytes[1] & BIT(5));
> +	}
> +
> +	return 0;
> +}
> +
> +const struct gip_driver gip_driver_navigation = {
> +	.types = (const char* const[]) { "Windows.Xbox.Input.NavigationController", NULL },
> +	.guid = GUID_INIT(0xb8f31fe7, 0x7386, 0x40e9, 0xa9, 0xf8,
> +		0x2f, 0x21, 0x26, 0x3a, 0xcf, 0xb7),
> +
> +	.probe = NULL,
> +	.remove = NULL,
> +	.init = NULL,
> +	.setup_input = gip_setup_navigation_input,
> +	.handle_input_report = gip_handle_navigation_report,
> +};
> +
> +module_param(dpad_as_buttons, bool, 0444);
> +MODULE_PARM_DESC(dpad_as_buttons, "Map the D-Pad as buttons instead of axes");
> diff --git a/drivers/input/joystick/gip/gip.h b/drivers/input/joystick/gip/gip.h
> new file mode 100644
> index 0000000000000..2c60430c81590
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip.h
> @@ -0,0 +1,309 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Gaming Input Protocol driver for Xbox One/Series controllers
> + *
> + * Copyright (c) 2025 Valve Software
> + *
> + * This driver is based on the Microsoft GIP spec at:
> + * https://aka.ms/gipdocs
> + * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gipusb/e7c90904-5e21-426e-b9ad-d82adeee0dbc
> + */
> +
> +#ifndef _GIP_H
> +#define _GIP_H
> +
> +#include <linux/rcupdate.h>
> +#include <linux/usb/input.h>
> +
> +#define BASE_GIP_MTU 64
> +#define MAX_GIP_MTU 2048
> +
> +#define MAX_ATTACHMENTS 8
> +
> +#define MAX_IN_MESSAGES 8
> +#define MAX_OUT_MESSAGES 8
> +
> +#define GIP_QUIRK_NO_HELLO		BIT(0)
> +#define GIP_QUIRK_NO_IMPULSE_VIBRATION	BIT(1)
> +#define GIP_QUIRK_SWAP_LB_RB		BIT(2)
> +
> +#define GIP_FEATURE_CONTROLLER				BIT(0)
> +#define GIP_FEATURE_CONSOLE_FUNCTION_MAP		BIT(1)
> +#define GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW	BIT(2)
> +#define GIP_FEATURE_ELITE_BUTTONS			BIT(3)
> +#define GIP_FEATURE_DYNAMIC_LATENCY_INPUT		BIT(4)
> +#define GIP_FEATURE_SECURITY_OPT_OUT			BIT(5)
> +#define GIP_FEATURE_MOTOR_CONTROL			BIT(6)
> +#define GIP_FEATURE_GUIDE_COLOR				BIT(7)
> +#define GIP_FEATURE_EXTENDED_SET_DEVICE_STATE		BIT(8)
> +
> +/* System messages */
> +#define GIP_CMD_PROTO_CONTROL		0x01
> +#define GIP_CMD_HELLO_DEVICE		0x02
> +#define GIP_CMD_STATUS_DEVICE		0x03
> +#define GIP_CMD_METADATA		0x04
> +#define GIP_CMD_SET_DEVICE_STATE	0x05
> +#define GIP_CMD_SECURITY		0x06
> +#define GIP_CMD_GUIDE_BUTTON		0x07
> +#define GIP_CMD_AUDIO_CONTROL		0x08
> +#define GIP_CMD_LED			0x0a
> +#define GIP_CMD_HID_REPORT		0x0b
> +#define GIP_CMD_FIRMWARE		0x0c
> +#define GIP_CMD_EXTENDED		0x1e
> +#define GIP_CMD_DEBUG			0x1f
> +#define GIP_AUDIO_DATA			0x60
> +
> +/* Navigation vendor messages */
> +#define GIP_CMD_DIRECT_MOTOR		0x09
> +#define GIP_LL_INPUT_REPORT		0x20
> +#define GIP_LL_OVERFLOW_INPUT_REPORT	0x26
> +
> +/* Wheel and ArcadeStick vendor messages */
> +#define GIP_CMD_INITIAL_REPORTS_REQUEST	0x0a
> +#define GIP_LL_STATIC_CONFIGURATION	0x21
> +#define GIP_LL_BUTTON_INFO_REPORT	0x22
> +
> +#define MAX_GIP_CMD 0x80
> +
> +#define GIP_DEV(p) \
> +	_Generic((p), \
> +		struct gip_attachment * : gip_attachment_dev, \
> +		struct gip_interface * : gip_interface_dev, \
> +		struct gip_device * : gip_device_dev)(p)
> +
> +enum gip_init_status {
> +	GIP_INIT_OK = 0,
> +	GIP_INIT_NO_INPUT = 1,
> +};
> +
> +enum gip_metadata_status {
> +	GIP_METADATA_NONE = 0,
> +	GIP_METADATA_GOT = 1,
> +	GIP_METADATA_FAKED = 2,
> +	GIP_METADATA_PENDING = 3,
> +};
> +
> +enum gip_elite_button_format {
> +	GIP_BTN_FMT_UNKNOWN,
> +	GIP_BTN_FMT_XBE1,
> +	GIP_BTN_FMT_XBE2_RAW,
> +	GIP_BTN_FMT_XBE2_4,
> +	GIP_BTN_FMT_XBE2_5,
> +};
> +
> +struct gip_header {
> +	uint8_t message_type;
> +	uint8_t flags;
> +	uint8_t sequence_id;
> +	uint64_t length;
> +};
> +
> +struct gip_raw_message {
> +	uint16_t num_bytes;
> +	uint8_t bytes[BASE_GIP_MTU];
> +};
> +
> +struct gip_device_metadata {
> +	uint8_t num_audio_formats;
> +	uint8_t num_preferred_types;
> +	uint8_t num_supported_interfaces;
> +	uint8_t hid_descriptor_size;
> +
> +	uint32_t in_system_messages[8];
> +	uint32_t out_system_messages[8];
> +
> +	struct gip_audio_format_pair *audio_formats;
> +	char **preferred_types;
> +	guid_t *supported_interfaces;
> +	uint8_t *hid_descriptor;
> +};
> +
> +struct gip_message_metadata {
> +	uint8_t type;
> +	uint16_t length;
> +	uint16_t data_type;
> +	uint32_t flags;
> +	uint16_t period;
> +	uint16_t persistence_timeout;
> +};
> +
> +struct gip_metadata {
> +	uint16_t version_major;
> +	uint16_t version_minor;
> +
> +	struct gip_device_metadata device;
> +
> +	uint8_t num_messages;
> +	struct gip_message_metadata *message_metadata;
> +};
> +
> +struct gip_status {
> +	int power_level;
> +	int charge;
> +	int battery_type;
> +	int battery_level;
> +};
> +
> +struct gip_status_event {
> +	uint16_t event_type;
> +	uint32_t fault_tag;
> +	uint32_t fault_address;
> +};
> +
> +struct gip_extended_status {
> +	struct gip_status base;
> +	bool device_active;
> +
> +	int num_events;
> +	struct gip_status_event events[5];
> +};
> +
> +struct gip_attachment;
> +typedef int (*gip_command_handler)(struct gip_attachment *a, const struct gip_header *header,
> +		const uint8_t *bytes, int num_bytes);
> +
> +struct gip_device;
> +struct gip_attachment {
> +	const struct gip_driver *driver;
> +	struct gip_device *device;
> +	void *driver_data;
> +	gip_command_handler vendor_handlers[MAX_GIP_CMD];
> +
> +	uint8_t attachment_index;
> +	struct input_dev __rcu *input;
> +	uint16_t vendor_id;
> +	uint16_t product_id;
> +	char *uniq;
> +	const char *name;
> +	char phys[32];
> +	char serial[32];
> +	struct mutex lock;
> +
> +	uint8_t fragment_message;
> +	uint16_t total_length;
> +	uint8_t *fragment_data;
> +	uint32_t fragment_offset;
> +	struct delayed_work fragment_timeout;
> +	int fragment_retries;
> +
> +	uint16_t firmware_major_version;
> +	uint16_t firmware_minor_version;
> +
> +	enum gip_metadata_status got_metadata;
> +	struct delayed_work metadata_next;
> +	int metadata_retries;
> +	struct gip_metadata metadata;
> +
> +	uint8_t seq_system;
> +	uint8_t seq_security;
> +	uint8_t seq_extended;
> +	uint8_t seq_audio;
> +	uint8_t seq_vendor;
> +
> +	int device_state;
> +
> +	struct gip_extended_status status;
> +
> +	enum gip_elite_button_format xbe_format;
> +	uint32_t features;
> +	uint32_t quirks;
> +
> +	int extra_buttons;
> +	int extra_axes;
> +
> +	bool dpad_as_buttons;
> +	struct hid_device __rcu *hdev;
> +};
> +
> +struct gip_urb {
> +	struct urb *urb;
> +	uint8_t *data;
> +	unsigned int offset;
> +};
> +
> +struct gip_interface {
> +	struct gip_device *device;
> +	struct usb_interface *intf;
> +	uint32_t mtu;
> +	int isoc_messages;
> +
> +	struct urb *urb_in;
> +	uint8_t *in_data;
> +
> +	struct usb_anchor out_anchor;
> +	struct gip_urb out_queue[MAX_OUT_MESSAGES];
> +};
> +
> +struct gip_device {
> +	struct usb_device *udev;
> +
> +	struct gip_interface data;
> +	struct gip_interface audio;
> +
> +	struct gip_raw_message in_queue[MAX_IN_MESSAGES];
> +	int pending_in_messages;
> +	int next_in_message;
> +
> +	struct work_struct receive_message;
> +	spinlock_t message_lock;
> +
> +	struct gip_attachment *attachments[MAX_ATTACHMENTS];
> +};
> +
> +struct gip_quirks {
> +	uint16_t vendor_id;
> +	uint16_t product_id;
> +	uint8_t attachment_index;
> +	const char *override_name;
> +	uint32_t added_features;
> +	uint32_t filtered_features;
> +	uint32_t quirks;
> +	uint32_t extra_in_system[8];
> +	uint32_t extra_out_system[8];
> +	uint8_t extra_buttons;
> +	uint8_t extra_axes;
> +};
> +
> +struct gip_driver {
> +	const char *const *types;
> +	guid_t guid;
> +
> +	const struct gip_quirks *quirks;
> +
> +	int (*probe)(struct gip_attachment *a);
> +	void (*remove)(struct gip_attachment *a);
> +	int (*init)(struct gip_attachment *a);
> +	int (*setup_input)(struct gip_attachment *a, struct input_dev* input);
> +	int (*handle_input_report)(struct gip_attachment *a,
> +		struct input_dev* input, const uint8_t *bytes, int num_bytes);

These are two of the aforementioned stylistic errors. I have fixed all locations of this in the series.

> +	gip_command_handler vendor_handlers[MAX_GIP_CMD];
> +};
> +
> +static inline struct device *gip_attachment_dev(struct gip_attachment *attachment)
> +{
> +	return &attachment->device->udev->dev;
> +}
> +
> +static inline struct device *gip_interface_dev(struct gip_interface *intf)
> +{
> +	return &intf->device->udev->dev;
> +}
> +
> +static inline struct device *gip_device_dev(struct gip_device *device)
> +{
> +	return &device->udev->dev;
> +}
> +
> +bool gip_supports_vendor_message(struct gip_attachment *attachment, uint8_t command, bool upstream);
> +
> +int gip_send_system_message(struct gip_attachment *attachment,
> +	uint8_t message_type, uint8_t flags, const void *bytes, int num_bytes);
> +int gip_send_vendor_message(struct gip_attachment *attachment,
> +	uint8_t message_type, uint8_t flags, const void *bytes, int num_bytes);
> +
> +extern const struct gip_driver gip_driver_navigation;
> +extern const struct gip_driver gip_driver_gamepad;
> +extern const struct gip_driver gip_driver_arcade_stick;
> +extern const struct gip_driver gip_driver_wheel;
> +extern const struct gip_driver gip_driver_flight_stick;
> +#endif

Thanks,
Vicki


^ permalink raw reply

* Re: [PATCH 2/2] Input: Touchscreen: tsc200x - delegate wakeup IRQ management to I2C core
From: Dmitry Torokhov @ 2026-03-11  0:21 UTC (permalink / raw)
  To: phucduc.bui
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Ingo Molnar,
	Thomas Gleixner, Marek Vasut, Michael Welling, linux-input,
	devicetree, linux-kernel
In-Reply-To: <20260309110045.108209-3-phucduc.bui@gmail.com>

On Mon, Mar 09, 2026 at 06:00:44PM +0700, phucduc.bui@gmail.com wrote:
> From: bui duc phuc <phucduc.bui@gmail.com>
> 
> The tsc200x driver supports both I2C (tsc2004) and SPI (tsc2005)
>  interfaces.
> Currently, the driver attempts to manually manage the wakeup interrupt by
> calling enable_irq_wake() and disable_irq_wake() during suspend and resume.
> 
> However, for I2C devices, the I2C core already automatically handles the
> wakeup source initialization and IRQ management if the "wakeup-source"
> property is present in the device tree. Manually managing it again in the
> driver is redundant and can lead to unbalanced IRQ wake reference counts.
> 
> Clean up the wakeup IRQ handling by checking the bus type:
> - For I2C (BUS_I2C): Rely entirely on the I2C core for wakeup management.
> - For SPI (BUS_SPI): Explicitly call device_init_wakeup() in probe and
>   manually manage enable/disable_irq_wake() during suspend/resume.
> 
> The ts->wake_irq_enabled flag is also updated accordingly to ensure the
> driver accurately tracks the wakeup state across both buses.
> 
> Note: This patch is based on code analysis of the I2C subsystem and
> has not been verified on actual hardware yet.
> 
> Signed-off-by: bui duc phuc <phucduc.bui@gmail.com>
> ---
>  drivers/input/touchscreen/tsc200x-core.c | 18 +++++++++++++-----
>  1 file changed, 13 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/input/touchscreen/tsc200x-core.c b/drivers/input/touchscreen/tsc200x-core.c
> index eba53613b005..d14d967845c8 100644
> --- a/drivers/input/touchscreen/tsc200x-core.c
> +++ b/drivers/input/touchscreen/tsc200x-core.c
> @@ -465,6 +465,7 @@ int tsc200x_probe(struct device *dev, int irq, const struct input_id *tsc_id,
>  	ts->idev = input_dev;
>  	ts->regmap = regmap;
>  	ts->tsc200x_cmd = tsc200x_cmd;
> +	ts->bustype = tsc_id->bustype;
>  
>  	error = device_property_read_u32(dev, "ti,x-plate-ohms", &x_plate_ohm);
>  	ts->x_plate_ohm = error ? TSC200X_DEF_RESISTOR : x_plate_ohm;
> @@ -547,8 +548,9 @@ int tsc200x_probe(struct device *dev, int irq, const struct input_id *tsc_id,
>  		return error;
>  	}
>  
> -	device_init_wakeup(dev,
> -			   device_property_read_bool(dev, "wakeup-source"));
> +	if (ts->bustype == BUS_SPI)
> +		device_init_wakeup(dev,
> +				 device_property_read_bool(dev, "wakeup-source"));
>  
>  	return 0;
>  }
> @@ -565,8 +567,13 @@ static int tsc200x_suspend(struct device *dev)
>  
>  	ts->suspended = true;
>  
> -	if (device_may_wakeup(dev))
> -		ts->wake_irq_enabled = enable_irq_wake(ts->irq) == 0;
> +	if (device_may_wakeup(dev)) {
> +		if (ts->bustype == BUS_SPI)
> +			ts->wake_irq_enabled = enable_irq_wake(ts->irq) == 0;
> +		else
> +			ts->wake_irq_enabled = true;
> +	} else
> +		ts->wake_irq_enabled = false;

Sorry, but this just makes it all worse. There is no downside from
letting the driver to control wakeup if it wants to, so I'd rather leave
it as it was, at least for now.

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH 38/61] net: Prefer IS_ERR_OR_NULL over manual NULL check
From: Russell King (Oracle) @ 2026-03-11  0:16 UTC (permalink / raw)
  To: Philipp Hahn
  Cc: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
	gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
	linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
	linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
	linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
	linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
	linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
	linux-sctp, linux-security-module, linux-sh, linux-sound,
	linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
	netdev, ntfs3, samba-technical, sched-ext, target-devel,
	tipc-discussion, v9fs, Igor Russkikh, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Pavan Chebbi, Michael Chan, Potnuri Bharat Teja, Tony Nguyen,
	Przemek Kitszel, Taras Chornyi, Maxime Coquelin, Alexandre Torgue,
	Iyappan Subramanian, Keyur Chudgar, Quan Nguyen, Heiner Kallweit
In-Reply-To: <20260310-b4-is_err_or_null-v1-38-bd63b656022d@avm.de>

On Tue, Mar 10, 2026 at 12:49:04PM +0100, Philipp Hahn wrote:
> diff --git a/drivers/net/mdio/mdio-xgene.c b/drivers/net/mdio/mdio-xgene.c
> index a8f91a4b7fed0927ee14e408000cd3a2bfb9b09a..09b30b563295c6085dc1358ac361301e5cf6b2a8 100644
> --- a/drivers/net/mdio/mdio-xgene.c
> +++ b/drivers/net/mdio/mdio-xgene.c
> @@ -265,7 +265,7 @@ struct phy_device *xgene_enet_phy_register(struct mii_bus *bus, int phy_addr)
>  	struct phy_device *phy_dev;
>  
>  	phy_dev = get_phy_device(bus, phy_addr, false);
> -	if (!phy_dev || IS_ERR(phy_dev))
> +	if (IS_ERR_OR_NULL(phy_dev))

As noted in reply to your cover message, the check for NULL here is
incorrect - get_phy_device() returns either a valid pointer or an
error pointer, but never NULL.

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!

^ permalink raw reply

* Re: [PATCH 00/61] treewide: Use IS_ERR_OR_NULL over manual NULL check - refactor
From: Russell King (Oracle) @ 2026-03-11  0:09 UTC (permalink / raw)
  To: Philipp Hahn
  Cc: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
	gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
	linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
	linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
	linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
	linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
	linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
	linux-sctp, linux-security-module, linux-sh, linux-sound,
	linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
	netdev, ntfs3, samba-technical, sched-ext, target-devel,
	tipc-discussion, v9fs, Julia Lawall, Nicolas Palix, Chris Mason,
	David Sterba, Ilya Dryomov, Alex Markuze, Viacheslav Dubeyko,
	Theodore Ts'o, Andreas Dilger, Steve French, Paulo Alcantara,
	Ronnie Sahlberg, Shyam Prasad N, Tom Talpey, Bharath SM,
	Eric Van Hensbergen, Latchesar Ionkov, Dominique Martinet,
	Christian Schoenebeck, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
	Sandeep Dhavale, Hongbo Li, Chunhai Guo, Miklos Szeredi,
	Konstantin Komarov, Andreas Gruenbacher, Kees Cook, Tony Luck,
	Guilherme G. Piccoli, Jan Kara, Phillip Lougher, Alexander Viro,
	Christian Brauner, Jan Kara, Steven Rostedt, Masami Hiramatsu,
	Mathieu Desnoyers, Tejun Heo, David Vernet, Andrea Righi,
	Changwoo Min, Ingo Molnar, Peter Zijlstra, Juri Lelli,
	Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
	Valentin Schneider, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
	Sami Tolvanen, Aaron Tomlin, Sylwester Nawrocki, Liam Girdwood,
	Mark Brown, Jaroslav Kysela, Takashi Iwai, Max Filippov,
	Paolo Bonzini, John Johansen, Paul Moore, James Morris,
	Serge E. Hallyn, Andrew Morton, Alasdair Kergon, Mike Snitzer,
	Mikulas Patocka, Benjamin Marzinski, David S. Miller, David Ahern,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Marcel Holtmann, Johan Hedberg, Luiz Augusto von Dentz,
	Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
	John Fastabend, Stanislav Fomichev, Jamal Hadi Salim, Jiri Pirko,
	Marcelo Ricardo Leitner, Xin Long, Trond Myklebust,
	Anna Schumaker, Chuck Lever, Jeff Layton, NeilBrown,
	Olga Kornievskaia, Dai Ngo, Jon Maloy, Johannes Berg,
	Catalin Marinas, John Crispin, Thomas Bogendoerfer,
	Yoshinori Sato, Rich Felker, John Paul Adrian Glaubitz,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Zhenyu Wang,
	Zhi Wang, Jani Nikula, Joonas Lahtinen, Rodrigo Vivi,
	Tvrtko Ursulin, Alex Deucher, Christian König, Sandy Huang,
	Heiko Stübner, Andy Yan, Igor Russkikh, Andrew Lunn,
	Pavan Chebbi, Michael Chan, Potnuri Bharat Teja, Tony Nguyen,
	Przemek Kitszel, Taras Chornyi, Maxime Coquelin, Alexandre Torgue,
	Iyappan Subramanian, Keyur Chudgar, Quan Nguyen, Heiner Kallweit,
	Marc Zyngier, Thomas Gleixner, Andrew Lunn, Gregory Clement,
	Sebastian Hesselbarth, Vinod Koul, Linus Walleij, Ulf Hansson,
	Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
	Christian Borntraeger, Sven Schnelle, Martin K. Petersen,
	Eduardo Valentin, Keerthy, Rafael J. Wysocki, Daniel Lezcano,
	Zhang Rui, Lukasz Luba, Alex Williamson, Mark Greer,
	Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
	Shuah Khan, Kieran Bingham, Mauro Carvalho Chehab, Joerg Roedel,
	Will Deacon, Robin Murphy, Lee Jones, Pavel Machek, Dave Penkler,
	K. Y. Srinivasan, Haiyang Zhang, Wei Liu, Dexuan Cui, Long Li,
	Justin Sanders, Jens Axboe, Georgi Djakov, Michael Turquette,
	Stephen Boyd, Philipp Zabel, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Pali Rohár, Dmitry Torokhov
In-Reply-To: <20260310-b4-is_err_or_null-v1-0-bd63b656022d@avm.de>

On Tue, Mar 10, 2026 at 12:48:26PM +0100, Philipp Hahn wrote:
> While doing some static code analysis I stumbled over a common pattern,
> where IS_ERR() is combined with a NULL check. For that there is
> IS_ERR_OR_NULL().

One thing you need to check for each of these cases is whether the tests
are actually correct.

There are certainly cases amongst those that you have identified where
the check for NULL is redundant.

For example, get_phy_device() never returns NULL, yet in your netdev
patch, you have at least one instance where the return value of
get_phy_device() is checked for NULL and IS_ERR() which you then
turn into IS_ERR_OR_NULL(). Instead, the NULL check should be dropped
(as a separate patch.)

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!

^ permalink raw reply

* Re: [PATCH] Input: mpr121: Drop redundant wakeup handling
From: Dmitry Torokhov @ 2026-03-11  0:06 UTC (permalink / raw)
  To: phucduc.bui; +Cc: linux-input, linux-kernel
In-Reply-To: <20260309071413.92709-1-phucduc.bui@gmail.com>

On Mon, Mar 09, 2026 at 02:14:13PM +0700, phucduc.bui@gmail.com wrote:
> From: bui duc phuc <phucduc.bui@gmail.com>
> 
> The driver currently calls device_init_wakeup() and manually toggles
> IRQ wake in suspend and resume paths. This is unnecessary since the
> I2C core already handles wakeup configuration when the device is
> described in Device Tree with the "wakeup-source" property.
> 
> Note: Compile-tested only, not verified on hardware.
> 
> Signed-off-by: bui duc phuc <phucduc.bui@gmail.com>

Applied, thank you.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH v4 1/2] dt-bindings: input: touchscreen: sitronix,st1232: Add wakeup-source
From: Dmitry Torokhov @ 2026-03-10 23:35 UTC (permalink / raw)
  To: phucduc.bui
  Cc: krzk+dt, geert+renesas, krzk, krzysztof.kozlowski, conor+dt,
	devicetree, hechtb, javier.carrasco, jeff, linux-input,
	linux-kernel, linux-renesas-soc, magnus.damm, robh, wsa+renesas
In-Reply-To: <20260309000319.74880-2-phucduc.bui@gmail.com>

On Mon, Mar 09, 2026 at 07:03:18AM +0700, phucduc.bui@gmail.com wrote:
> From: bui duc phuc <phucduc.bui@gmail.com>
> 
> Document the 'wakeup-source' property for Sitronix ST1232 touchscreen
> controllers to allow the device to wake the system from suspend.
> 
> Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
> Signed-off-by: bui duc phuc <phucduc.bui@gmail.com>

Applied, thank you.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH v4 0/2] Input: st1232 - add system wakeup support
From: Wolfram Sang @ 2026-03-10 23:09 UTC (permalink / raw)
  To: phucduc.bui
  Cc: krzk+dt, geert+renesas, krzk, krzysztof.kozlowski, conor+dt,
	devicetree, dmitry.torokhov, hechtb, javier.carrasco, jeff,
	linux-input, linux-kernel, linux-renesas-soc, magnus.damm, robh
In-Reply-To: <20260309000319.74880-1-phucduc.bui@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 215 bytes --]

Hi,

> Demo video showing wakeup from suspend:
> https://youtu.be/POJhbguiA7A

Nice video! You really put some effort here, kudos.

Really awesome seeing Linux 7 on this old platform :)

Happy hacking,

   Wolfram


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply

* Re: [PATCH v4 2/2] arm: dts: renesas: r8a7740-armadillo800eva: Add wakeup-source to st1232
From: Wolfram Sang @ 2026-03-10 23:08 UTC (permalink / raw)
  To: phucduc.bui
  Cc: krzk+dt, geert+renesas, krzk, krzysztof.kozlowski, conor+dt,
	devicetree, dmitry.torokhov, hechtb, javier.carrasco, jeff,
	linux-input, linux-kernel, linux-renesas-soc, magnus.damm, robh
In-Reply-To: <20260309000319.74880-3-phucduc.bui@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 431 bytes --]

On Mon, Mar 09, 2026 at 07:03:19AM +0700, phucduc.bui@gmail.com wrote:
> From: bui duc phuc <phucduc.bui@gmail.com>
> 
> Add the wakeup-source property to the ST1232 touchscreen node
> in the device tree so that the touchscreen interrupt can wake
> the system from suspend when the panel is touched.
> 
> Signed-off-by: bui duc phuc <phucduc.bui@gmail.com>

Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com>


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ 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