* [PATCH 3/3] arm64: dts: qcom: qcm6490-fairphone-fp5: Add vibrator support
From: Griffin Kroah-Hartman @ 2025-08-06 15:10 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Luca Weiss
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm,
Griffin Kroah-Hartman
In-Reply-To: <20250806-aw86927-v1-0-23d8a6d0f2b2@fairphone.com>
Add the required node for haptic playback (Awinic AW86927).
Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
---
arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
index 4c6cb4a644e2..9576efdf1e8d 100644
--- a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
+++ b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
@@ -866,7 +866,16 @@ ocp96011_sbu_mux: endpoint {
};
};
- /* AW86927FCR haptics @ 5a */
+ vibrator@5a {
+ compatible = "awinic,aw86927";
+ reg = <0x5a>;
+
+ interrupts-extended = <&tlmm 101 IRQ_TYPE_EDGE_FALLING>;
+ reset-gpios = <&tlmm 100 GPIO_ACTIVE_LOW>;
+
+ pinctrl-0 = <&aw86927_int_default>;
+ pinctrl-names = "default";
+ };
};
&i2c2 {
@@ -1415,6 +1424,13 @@ usb_redrive_1v8_en_default: usb-redrive-1v8-en-default-state {
bias-disable;
output-high;
};
+
+ aw86927_int_default: aw86927-int-default-state {
+ pins = "gpio101";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-pull-up;
+ };
};
&uart5 {
--
2.43.0
^ permalink raw reply related
* [PATCH 2/3] Input: aw86927 - add driver for Awinic AW86927
From: Griffin Kroah-Hartman @ 2025-08-06 15:10 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Luca Weiss
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm,
Griffin Kroah-Hartman
In-Reply-To: <20250806-aw86927-v1-0-23d8a6d0f2b2@fairphone.com>
Add support for the I2C-connected Awinic AW86927 LRA haptic driver.
This driver includes a hardcoded sine waveform to be uploaded to the
AW86927's SRAM for haptic playback.
This driver does not currently support all the capabilities of the
AW86927, such as F0 calibration, RTP mode, and CONT mode.
Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
---
drivers/input/misc/Kconfig | 11 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/aw86927.c | 841 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 853 insertions(+)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index f5496ca0c0d2..20a5f552d9f4 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -126,6 +126,17 @@ config INPUT_ATMEL_CAPTOUCH
To compile this driver as a module, choose M here: the
module will be called atmel_captouch.
+config INPUT_AW86927
+ tristate "Awinic AW86927 Haptic Driver Support"
+ depends on I2C && INPUT
+ select INPUT_FF_MEMLESS
+ select REGMAP_I2C
+ help
+ Say Y here if you have an Awinic AW86927 haptic chip.
+
+ To compile this driver as a module, choose M here: the
+ module will be called aw86927.
+
config INPUT_BBNSM_PWRKEY
tristate "NXP BBNSM Power Key Driver"
depends on ARCH_MXC || COMPILE_TEST
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 6d91804d0a6f..a311a84d1b70 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_INPUT_ATC260X_ONKEY) += atc260x-onkey.o
obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_remote2.o
obj-$(CONFIG_INPUT_ATLAS_BTNS) += atlas_btns.o
obj-$(CONFIG_INPUT_ATMEL_CAPTOUCH) += atmel_captouch.o
+obj-$(CONFIG_INPUT_AW86927) += aw86927.o
obj-$(CONFIG_INPUT_BBNSM_PWRKEY) += nxp-bbnsm-pwrkey.o
obj-$(CONFIG_INPUT_BMA150) += bma150.o
obj-$(CONFIG_INPUT_CM109) += cm109.o
diff --git a/drivers/input/misc/aw86927.c b/drivers/input/misc/aw86927.c
new file mode 100644
index 000000000000..d08fafbde124
--- /dev/null
+++ b/drivers/input/misc/aw86927.c
@@ -0,0 +1,841 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
+ *
+ * Partially based on vendor driver:
+ * Copyright (c) 2021 AWINIC Technology CO., LTD
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define AW86927_RSTCFG (0x00)
+#define AW86927_RSTCFG_SOFTRST (0xaa)
+
+#define AW86927_SYSINT (0x02)
+#define AW86927_SYSINT_BST_SCPI BIT(7)
+#define AW86927_SYSINT_BST_OVPI BIT(6)
+#define AW86927_SYSINT_UVLI BIT(5)
+#define AW86927_SYSINT_FF_AEI BIT(4)
+#define AW86927_SYSINT_FF_AFI BIT(3)
+#define AW86927_SYSINT_OCDI BIT(2)
+#define AW86927_SYSINT_OTI BIT(1)
+#define AW86927_SYSINT_DONEI BIT(0)
+
+#define AW86927_SYSINTM (0x03)
+#define AW86927_SYSINTM_BST_OVPM BIT(6)
+#define AW86927_SYSINTM_FF_AEM BIT(4)
+#define AW86927_SYSINTM_FF_AFM BIT(3)
+#define AW86927_SYSINTM_DONEM BIT(0)
+
+#define AW86927_PLAYCFG1 (0x06)
+#define AW86927_PLAYCFG1_BST_MODE_MASK GENMASK(7, 7)
+#define AW86927_PLAYCFG1_BST_MODE_BYPASS (0)
+#define AW86927_PLAYCFG1_BST_VOUT_VREFSET_MASK GENMASK(6, 0)
+#define AW86927_PLAYCFG1_BST_8500MV (0x50)
+
+#define AW86927_PLAYCFG2 (0x07)
+
+#define AW86927_PLAYCFG3 (0x08)
+#define AW86927_PLAYCFG3_AUTO_BST_MASK GENMASK(4, 4)
+#define AW86927_PLAYCFG3_AUTO_BST_ENABLE (1)
+#define AW86927_PLAYCFG3_AUTO_BST_DISABLE (0)
+#define AW86927_PLAYCFG3_PLAY_MODE_MASK GENMASK(1, 0)
+#define AW86927_PLAYCFG3_PLAY_MODE_RAM (0)
+
+#define AW86927_PLAYCFG4 (0x09)
+#define AW86927_PLAYCFG4_STOP BIT(1)
+#define AW86927_PLAYCFG4_GO BIT(0)
+
+#define AW86927_WAVCFG1 (0x0a)
+#define AW86927_WAVCFG1_WAVSEQ1_MASK GENMASK(6, 0)
+
+#define AW86927_WAVCFG2 (0x0b)
+#define AW86927_WAVCFG2_WAVSEQ2_MASK GENMASK(6, 0)
+
+#define AW86927_WAVCFG9 (0x12)
+#define AW86927_WAVCFG9_SEQ1LOOP_MASK GENMASK(7, 4)
+#define AW86927_WAVCFG9_SEQ1LOOP_INFINITELY (0x0f)
+
+#define AW86927_CONTCFG1 (0x18)
+#define AW86927_CONTCFG1_BRK_BST_MD_MASK GENMASK(6, 6)
+
+#define AW86927_CONTCFG5 (0x1c)
+#define AW86927_CONTCFG5_BST_BRK_GAIN_MASK GENMASK(7, 4)
+#define AW86927_CONTCFG5_BRK_GAIN_MASK GENMASK(3, 0)
+
+#define AW86927_CONTCFG10 (0x21)
+#define AW86927_CONTCFG10_BRK_TIME_MASK GENMASK(7, 0)
+#define AW86927_CONTCFG10_BRK_TIME_DEFAULT (8)
+
+#define AW86927_CONTCFG13 (0x24)
+#define AW86927_CONTCFG13_TSET_MASK GENMASK(7, 4)
+#define AW86927_CONTCFG13_BEME_SET_MASK GENMASK(3, 0)
+
+#define AW86927_BASEADDRH (0x2d)
+#define AW86927_BASEADDRL (0x2e)
+
+#define AW86927_GLBRD5 (0x3f)
+#define AW86927_GLBRD5_STATE_MASK GENMASK(3, 0)
+#define AW86927_GLBRD5_STATE_STANDBY (0)
+
+#define AW86927_RAMADDRH (0x40)
+#define AW86927_RAMADDRL (0x41)
+#define AW86927_RAMDATA (0x42)
+
+#define AW86927_SYSCTRL3 (0x45)
+#define AW86927_SYSCTRL3_STANDBY_MASK GENMASK(5, 5)
+#define AW86927_SYSCTRL3_STANDBY_ON (1)
+#define AW86927_SYSCTRL3_STANDBY_OFF (0)
+#define AW86927_SYSCTRL3_EN_RAMINIT_MASK GENMASK(2, 2)
+#define AW86927_SYSCTRL3_EN_RAMINIT_ON (1)
+#define AW86927_SYSCTRL3_EN_RAMINIT_OFF (0)
+
+#define AW86927_SYSCTRL4 (0x46)
+#define AW86927_SYSCTRL4_WAVDAT_MODE_MASK GENMASK(6, 5)
+#define AW86927_SYSCTRL4_WAVDAT_24K (0)
+#define AW86927_SYSCTRL4_INT_EDGE_MODE_MASK GENMASK(4, 4)
+#define AW86927_SYSCTRL4_INT_EDGE_MODE_POS (0)
+#define AW86927_SYSCTRL4_INT_MODE_MASK GENMASK(3, 3)
+#define AW86927_SYSCTRL4_INT_MODE_EDGE (1)
+#define AW86927_SYSCTRL4_GAIN_BYPASS_MASK GENMASK(0, 0)
+
+#define AW86927_PWMCFG1 (0x48)
+#define AW86927_PWMCFG1_PRC_EN_MASK GENMASK(7, 7)
+#define AW86927_PWMCFG1_PRC_DISABLE (0)
+
+#define AW86927_PWMCFG3 (0x4a)
+#define AW86927_PWMCFG3_PR_EN_MASK GENMASK(7, 7)
+#define AW86927_PWMCFG3_PRCTIME_MASK GENMASK(6, 0)
+
+#define AW86927_PWMCFG4 (0x4b)
+#define AW86927_PWMCFG4_PRTIME_MASK GENMASK(7, 0)
+
+#define AW86927_VBATCTRL (0x4c)
+#define AW86927_VBATCTRL_VBAT_MODE_MASK GENMASK(6, 6)
+#define AW86927_VBATCTRL_VBAT_MODE_SW (0)
+
+#define AW86927_DETCFG1 (0x4d)
+#define AW86927_DETCFG1_DET_GO_MASK GENMASK(1, 0)
+#define AW86927_DETCFG1_DET_GO_DET_SEQ0 (1)
+#define AW86927_DETCFG1_DET_GO_NA (0)
+
+#define AW86927_DETCFG2 (0x4e)
+#define AW86927_DETCFG2_DET_SEQ0_MASK GENMASK(6, 3)
+#define AW86927_DETCFG2_DET_SEQ0_VBAT (0)
+#define AW86927_DETCFG2_D2S_GAIN_MASK GENMASK(2, 0)
+#define AW86927_DETCFG2_D2S_GAIN_10 (4)
+
+#define AW86927_CHIPIDH (0x57)
+#define AW86927_CHIPIDL (0x58)
+#define AW86927_CHIPID (0x9270)
+
+#define AW86927_TMCFG (0x5b)
+#define AW86927_TMCFG_UNLOCK (0x7d)
+#define AW86927_TMCFG_LOCK (0x00)
+
+#define AW86927_ANACFG11 (0x70)
+#define AW86927_ANACFG12 (0x71)
+#define AW86927_ANACFG12_BST_SKIP_MASK GENMASK(7, 7)
+#define AW86927_ANACFG12_BST_SKIP_SHUTDOWN (1)
+
+#define AW86927_ANACFG13 (0x72)
+#define AW86927_ANACFG13_BST_PC_MASK GENMASK(7, 4)
+#define AW86927_ANACFG13_BST_PEAKCUR_3P45A (6)
+
+#define AW86927_ANACFG15 (0x74)
+#define AW86927_ANACFG15_BST_PEAK_MODE_MASK GENMASK(7, 7)
+#define AW86927_ANACFG15_BST_PEAK_BACK (1)
+
+#define AW86927_ANACFG16 (0x75)
+#define AW86927_ANACFG16_BST_SRC_MASK GENMASK(4, 4)
+#define AW86927_ANACFG16_BST_SRC_3NS (0)
+
+/* default value of base addr */
+#define AW86927_RAM_BASE_ADDR (0x800)
+#define AW86927_BASEADDRH_VAL (0x08)
+#define AW86927_BASEADDRL_VAL (0x00)
+
+enum aw86927_work_mode {
+ AW86927_STANDBY_MODE,
+ AW86927_RAM_MODE,
+};
+
+struct aw86927_data {
+ struct work_struct play_work;
+ struct device *dev;
+ struct input_dev *input_dev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct gpio_desc *reset_gpio;
+ bool running;
+};
+
+static const struct regmap_config aw86927_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .cache_type = REGCACHE_NONE,
+ .max_register = 0x80,
+};
+
+/*
+ * Sine wave representing the magnitude of the drive to be used.
+ * Data is encoded in two's complement.
+ * round(84 * sin(x / 16.25))
+ */
+static const uint8_t aw86927_waveform[] = {
+ 0x00, 0x05, 0x0a, 0x0f, 0x14, 0x1a, 0x1f, 0x23, 0x28, 0x2d, 0x31, 0x35,
+ 0x39, 0x3d, 0x41, 0x44, 0x47, 0x4a, 0x4c, 0x4f, 0x51, 0x52, 0x53, 0x54,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, 0x52, 0x51, 0x4f, 0x4d, 0x4a, 0x47,
+ 0x44, 0x41, 0x3d, 0x3a, 0x36, 0x31, 0x2d, 0x28, 0x24, 0x1f, 0x1a, 0x15,
+ 0x10, 0x0a, 0x05, 0x00, 0xfc, 0xf6, 0xf1, 0xec, 0xe7, 0xe2, 0xdd, 0xd8,
+ 0xd4, 0xcf, 0xcb, 0xc7, 0xc3, 0xbf, 0xbc, 0xb9, 0xb6, 0xb4, 0xb1, 0xb0,
+ 0xae, 0xad, 0xac, 0xab, 0xab, 0xab, 0xab, 0xab, 0xac, 0xae, 0xaf, 0xb1,
+ 0xb3, 0xb6, 0xb8, 0xbc, 0xbf, 0xc2, 0xc6, 0xca, 0xce, 0xd3, 0xd7, 0xdc,
+ 0xe1, 0xe6, 0xeb, 0xf0, 0xf5, 0xfb
+};
+
+struct aw86927_sram_waveform_header {
+ uint8_t version;
+ struct {
+ __be16 start_address;
+ __be16 end_address;
+ } __packed waveform_address[1];
+} __packed;
+
+static const struct aw86927_sram_waveform_header sram_waveform_header = {
+ .version = 0x01,
+ .waveform_address = {
+ /* Simple sine wave defined above */
+ {
+ .start_address = cpu_to_be16(AW86927_RAM_BASE_ADDR +
+ sizeof(struct aw86927_sram_waveform_header)),
+ .end_address = cpu_to_be16(AW86927_RAM_BASE_ADDR +
+ sizeof(struct aw86927_sram_waveform_header) +
+ ARRAY_SIZE(aw86927_waveform) - 1),
+ }
+ }
+};
+
+static int aw86927_wait_enter_standby(struct aw86927_data *haptics)
+{
+ unsigned int reg_val;
+ int err;
+
+ err = regmap_read_poll_timeout(haptics->regmap,
+ AW86927_GLBRD5, reg_val,
+ (FIELD_GET(AW86927_GLBRD5_STATE_MASK, reg_val) == AW86927_GLBRD5_STATE_STANDBY),
+ 2500, 2500 * 100);
+
+ if (err)
+ dev_err(haptics->dev, "did not enter standby: %d\n", err);
+ return err;
+}
+
+static int aw86927_play_mode(struct aw86927_data *haptics, uint8_t play_mode)
+{
+ int err;
+
+ switch (play_mode) {
+ case AW86927_STANDBY_MODE:
+ /* Briefly toggle standby, then toggle back to standby off */
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL3,
+ AW86927_SYSCTRL3_STANDBY_MASK,
+ FIELD_PREP(AW86927_SYSCTRL3_STANDBY_MASK,
+ AW86927_SYSCTRL3_STANDBY_ON));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL3,
+ AW86927_SYSCTRL3_STANDBY_MASK,
+ FIELD_PREP(AW86927_SYSCTRL3_STANDBY_MASK,
+ AW86927_SYSCTRL3_STANDBY_OFF));
+ if (err)
+ return err;
+ break;
+ case AW86927_RAM_MODE:
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_PLAYCFG3,
+ AW86927_PLAYCFG3_PLAY_MODE_MASK,
+ FIELD_PREP(AW86927_PLAYCFG3_PLAY_MODE_MASK,
+ AW86927_PLAYCFG3_PLAY_MODE_RAM));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_PLAYCFG1,
+ AW86927_PLAYCFG1_BST_MODE_MASK,
+ FIELD_PREP(AW86927_PLAYCFG1_BST_MODE_MASK,
+ AW86927_PLAYCFG1_BST_MODE_BYPASS));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_VBATCTRL,
+ AW86927_VBATCTRL_VBAT_MODE_MASK,
+ FIELD_PREP(AW86927_VBATCTRL_VBAT_MODE_MASK,
+ AW86927_VBATCTRL_VBAT_MODE_SW));
+ if (err)
+ return err;
+ break;
+ }
+ return 0;
+}
+
+static int aw86927_stop(struct aw86927_data *haptics)
+{
+ int err;
+
+ err = regmap_write(haptics->regmap, AW86927_PLAYCFG4, AW86927_PLAYCFG4_STOP);
+ if (err) {
+ dev_err(haptics->dev, "Failed to stop playback: %d\n", err);
+ return err;
+ }
+
+ err = aw86927_wait_enter_standby(haptics);
+ if (err) {
+ dev_err(haptics->dev, "Failed to enter standby, trying to force it\n");
+ err = aw86927_play_mode(haptics, AW86927_STANDBY_MODE);
+ if (err)
+ return err;
+ }
+ return 0;
+}
+
+static int aw86927_haptics_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+ struct aw86927_data *haptics = input_get_drvdata(dev);
+ int level;
+
+ level = effect->u.rumble.strong_magnitude;
+ if (!level)
+ level = effect->u.rumble.weak_magnitude;
+
+ /* If already running, don't restart playback */
+ if (haptics->running && level)
+ return 0;
+
+ haptics->running = level;
+ schedule_work(&haptics->play_work);
+
+ return 0;
+}
+
+static int aw86927_play_sine(struct aw86927_data *haptics)
+{
+ int err;
+
+ err = aw86927_stop(haptics);
+ if (err)
+ return err;
+
+ err = aw86927_play_mode(haptics, AW86927_RAM_MODE);
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap, AW86927_PLAYCFG3,
+ AW86927_PLAYCFG3_AUTO_BST_MASK,
+ FIELD_PREP(AW86927_PLAYCFG3_AUTO_BST_MASK,
+ AW86927_PLAYCFG3_AUTO_BST_ENABLE));
+ if (err)
+ return err;
+
+ /* Set waveseq 1 to the first wave */
+ err = regmap_update_bits(haptics->regmap, AW86927_WAVCFG1,
+ AW86927_WAVCFG1_WAVSEQ1_MASK,
+ FIELD_PREP(AW86927_WAVCFG1_WAVSEQ1_MASK,
+ 1));
+ if (err)
+ return err;
+
+ /* set wavseq 2 to zero */
+ err = regmap_update_bits(haptics->regmap, AW86927_WAVCFG2,
+ AW86927_WAVCFG2_WAVSEQ2_MASK,
+ FIELD_PREP(AW86927_WAVCFG2_WAVSEQ2_MASK,
+ 0));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_WAVCFG9,
+ AW86927_WAVCFG9_SEQ1LOOP_MASK,
+ FIELD_PREP(AW86927_WAVCFG9_SEQ1LOOP_MASK,
+ AW86927_WAVCFG9_SEQ1LOOP_INFINITELY));
+ if (err)
+ return err;
+
+ /* set gain to value lower than 0x80 to avoid distorted playback */
+ err = regmap_write(haptics->regmap, AW86927_PLAYCFG2, 0x7c);
+ if (err)
+ return err;
+
+ /* Start playback */
+ return regmap_write(haptics->regmap, AW86927_PLAYCFG4, AW86927_PLAYCFG4_GO);
+}
+
+static void aw86927_close(struct input_dev *input)
+{
+ struct aw86927_data *haptics = input_get_drvdata(input);
+ struct device *dev = &haptics->client->dev;
+ int err;
+
+ cancel_work_sync(&haptics->play_work);
+
+ err = aw86927_stop(haptics);
+ if (err)
+ dev_err(dev, "Failed to close the Driver: %d\n", err);
+}
+
+static void aw86927_haptics_play_work(struct work_struct *work)
+{
+ struct aw86927_data *haptics =
+ container_of(work, struct aw86927_data, play_work);
+ struct device *dev = &haptics->client->dev;
+ int err;
+
+ if (haptics->running)
+ err = aw86927_play_sine(haptics);
+ else
+ err = aw86927_stop(haptics);
+
+ if (err)
+ dev_err(dev, "Failed to execute work command: %d\n", err);
+}
+
+static void aw86927_hw_reset(struct aw86927_data *haptics)
+{
+ /* Assert reset */
+ gpiod_set_value_cansleep(haptics->reset_gpio, 1);
+ /* Wait ~1ms */
+ usleep_range(1000, 2000);
+ /* Deassert reset */
+ gpiod_set_value_cansleep(haptics->reset_gpio, 0);
+ /* Wait ~8ms until I2C is accessible */
+ usleep_range(8000, 8500);
+}
+
+static int aw86927_haptic_init(struct aw86927_data *haptics)
+{
+ int err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL4,
+ AW86927_SYSCTRL4_WAVDAT_MODE_MASK,
+ FIELD_PREP(AW86927_SYSCTRL4_WAVDAT_MODE_MASK,
+ AW86927_SYSCTRL4_WAVDAT_24K));
+ if (err)
+ return err;
+
+ /* enable gain bypass */
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL4,
+ AW86927_SYSCTRL4_GAIN_BYPASS_MASK,
+ FIELD_PREP(AW86927_SYSCTRL4_GAIN_BYPASS_MASK,
+ 0x01));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_TMCFG,
+ AW86927_TMCFG_UNLOCK);
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_ANACFG11,
+ 0x0f);
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_ANACFG12,
+ AW86927_ANACFG12_BST_SKIP_MASK,
+ FIELD_PREP(AW86927_ANACFG12_BST_SKIP_MASK,
+ AW86927_ANACFG12_BST_SKIP_SHUTDOWN));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_ANACFG15,
+ AW86927_ANACFG15_BST_PEAK_MODE_MASK,
+ FIELD_PREP(AW86927_ANACFG15_BST_PEAK_MODE_MASK,
+ AW86927_ANACFG15_BST_PEAK_BACK));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_ANACFG16,
+ AW86927_ANACFG16_BST_SRC_MASK,
+ FIELD_PREP(AW86927_ANACFG16_BST_SRC_MASK,
+ AW86927_ANACFG16_BST_SRC_3NS));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_TMCFG,
+ AW86927_TMCFG_LOCK);
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_CONTCFG1,
+ AW86927_CONTCFG1_BRK_BST_MD_MASK,
+ FIELD_PREP(AW86927_CONTCFG1_BRK_BST_MD_MASK,
+ 0x00));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_CONTCFG5,
+ FIELD_PREP(AW86927_CONTCFG5_BST_BRK_GAIN_MASK, 0x05) |
+ FIELD_PREP(AW86927_CONTCFG5_BRK_GAIN_MASK, 0x08));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap, AW86927_CONTCFG10,
+ AW86927_CONTCFG10_BRK_TIME_MASK,
+ FIELD_PREP(AW86927_CONTCFG10_BRK_TIME_MASK,
+ AW86927_CONTCFG10_BRK_TIME_DEFAULT));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_CONTCFG13,
+ FIELD_PREP(AW86927_CONTCFG13_TSET_MASK, 0x06) |
+ FIELD_PREP(AW86927_CONTCFG13_BEME_SET_MASK, 0x02));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_DETCFG2,
+ AW86927_DETCFG2_D2S_GAIN_MASK,
+ FIELD_PREP(AW86927_DETCFG2_D2S_GAIN_MASK,
+ AW86927_DETCFG2_D2S_GAIN_10));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_PWMCFG1,
+ AW86927_PWMCFG1_PRC_EN_MASK,
+ FIELD_PREP(AW86927_PWMCFG1_PRC_EN_MASK,
+ AW86927_PWMCFG1_PRC_DISABLE));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_PWMCFG3,
+ FIELD_PREP(AW86927_PWMCFG3_PR_EN_MASK, 0x01) |
+ FIELD_PREP(AW86927_PWMCFG3_PRCTIME_MASK, 0x3f));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_PWMCFG4,
+ AW86927_PWMCFG4_PRTIME_MASK,
+ FIELD_PREP(AW86927_PWMCFG4_PRTIME_MASK,
+ 0x32));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_TMCFG,
+ AW86927_TMCFG_UNLOCK);
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_ANACFG13,
+ AW86927_ANACFG13_BST_PC_MASK,
+ FIELD_PREP(AW86927_ANACFG13_BST_PC_MASK,
+ AW86927_ANACFG13_BST_PEAKCUR_3P45A));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_TMCFG,
+ AW86927_TMCFG_LOCK);
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_PLAYCFG1,
+ AW86927_PLAYCFG1_BST_VOUT_VREFSET_MASK,
+ FIELD_PREP(AW86927_PLAYCFG1_BST_VOUT_VREFSET_MASK,
+ AW86927_PLAYCFG1_BST_8500MV));
+ if (err)
+ return err;
+
+ return regmap_update_bits(haptics->regmap,
+ AW86927_PLAYCFG3,
+ AW86927_PLAYCFG3_AUTO_BST_MASK,
+ FIELD_PREP(AW86927_PLAYCFG3_AUTO_BST_MASK,
+ AW86927_PLAYCFG3_AUTO_BST_DISABLE));
+}
+
+static int aw86927_ram_init(struct aw86927_data *haptics)
+{
+ int err;
+
+ err = aw86927_wait_enter_standby(haptics);
+ if (err)
+ return err;
+
+ /* Enable SRAM init */
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL3,
+ AW86927_SYSCTRL3_EN_RAMINIT_MASK,
+ FIELD_PREP(AW86927_SYSCTRL3_EN_RAMINIT_MASK,
+ AW86927_SYSCTRL3_EN_RAMINIT_ON));
+
+ /* Set base address for the start of the SRAM waveforms */
+ err = regmap_write(haptics->regmap,
+ AW86927_BASEADDRH,
+ AW86927_BASEADDRH_VAL);
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_BASEADDRL,
+ AW86927_BASEADDRL_VAL);
+ if (err)
+ return err;
+
+ /* Set start of SRAM, before the data is written it will be the same as the base */
+ err = regmap_write(haptics->regmap,
+ AW86927_RAMADDRH,
+ AW86927_BASEADDRH_VAL);
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_RAMADDRL,
+ AW86927_BASEADDRL_VAL);
+ if (err)
+ return err;
+
+ /* Write waveform header to SRAM */
+ err = regmap_noinc_write(haptics->regmap, AW86927_RAMDATA,
+ &sram_waveform_header, sizeof(sram_waveform_header));
+
+ /* Write waveform to SRAM */
+ err = regmap_noinc_write(haptics->regmap, AW86927_RAMDATA,
+ aw86927_waveform, ARRAY_SIZE(aw86927_waveform));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_DETCFG2,
+ AW86927_DETCFG2_DET_SEQ0_MASK,
+ FIELD_PREP(AW86927_DETCFG2_DET_SEQ0_MASK,
+ AW86927_DETCFG2_DET_SEQ0_VBAT));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_DETCFG1,
+ AW86927_DETCFG1_DET_GO_MASK,
+ FIELD_PREP(AW86927_DETCFG1_DET_GO_MASK,
+ AW86927_DETCFG1_DET_GO_DET_SEQ0));
+ if (err)
+ return err;
+
+ usleep_range(3000, 3500);
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_DETCFG1,
+ AW86927_DETCFG1_DET_GO_MASK,
+ FIELD_PREP(AW86927_DETCFG1_DET_GO_MASK,
+ AW86927_DETCFG1_DET_GO_NA));
+ if (err)
+ return err;
+
+ /* Disable SRAM init */
+ return regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL3,
+ AW86927_SYSCTRL3_EN_RAMINIT_MASK,
+ FIELD_PREP(AW86927_SYSCTRL3_EN_RAMINIT_MASK,
+ AW86927_SYSCTRL3_EN_RAMINIT_OFF));
+}
+
+static irqreturn_t aw86927_irq(int irq, void *data)
+{
+ struct aw86927_data *haptics = data;
+ struct device *dev = &haptics->client->dev;
+ unsigned int reg_val;
+ int err;
+
+ err = regmap_read(haptics->regmap, AW86927_SYSINT, ®_val);
+ if (err) {
+ dev_err(dev, "Failed to read SYSINT register: %d\n", err);
+ return IRQ_NONE;
+ }
+
+ if (reg_val & AW86927_SYSINT_BST_SCPI)
+ dev_err(dev, "Received a Short Circuit Protection interrupt\n");
+ if (reg_val & AW86927_SYSINT_BST_OVPI)
+ dev_err(dev, "Received an Over Voltage Protection interrupt\n");
+ if (reg_val & AW86927_SYSINT_UVLI)
+ dev_err(dev, "Received an Under Voltage Lock Out interrupt\n");
+ if (reg_val & AW86927_SYSINT_OCDI)
+ dev_err(dev, "Received an Over Current interrupt\n");
+ if (reg_val & AW86927_SYSINT_OTI)
+ dev_err(dev, "Received an Over Temperature interrupt\n");
+
+ if (reg_val & AW86927_SYSINT_DONEI)
+ dev_dbg(dev, "Chip playback done!\n");
+ if (reg_val & AW86927_SYSINT_FF_AFI)
+ dev_dbg(dev, "The RTP mode FIFO is almost full!\n");
+ if (reg_val & AW86927_SYSINT_FF_AEI)
+ dev_dbg(dev, "The RTP mode FIFO is almost empty!\n");
+
+ return IRQ_HANDLED;
+}
+
+static int aw86927_detect(struct aw86927_data *haptics)
+{
+ __be16 read_buf;
+ u16 chip_id;
+ int err;
+
+ err = regmap_bulk_read(haptics->regmap, AW86927_CHIPIDH, &read_buf, 2);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Failed to read CHIPID registers\n");
+
+ chip_id = be16_to_cpu(read_buf);
+
+ if (chip_id != AW86927_CHIPID) {
+ dev_err(haptics->dev, "Unexpected CHIPID value 0x%x\n", chip_id);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int aw86927_probe(struct i2c_client *client)
+{
+ struct aw86927_data *haptics;
+ unsigned int read_buf;
+ int err;
+
+ haptics = devm_kzalloc(&client->dev, sizeof(struct aw86927_data), GFP_KERNEL);
+ if (!haptics)
+ return -ENOMEM;
+
+ haptics->dev = &client->dev;
+ haptics->client = client;
+
+ i2c_set_clientdata(client, haptics);
+ dev_set_drvdata(&client->dev, haptics);
+
+ haptics->regmap = devm_regmap_init_i2c(client, &aw86927_regmap_config);
+ if (IS_ERR(haptics->regmap))
+ return dev_err_probe(haptics->dev, PTR_ERR(haptics->regmap),
+ "Failed to allocate register map\n");
+
+ haptics->input_dev = devm_input_allocate_device(haptics->dev);
+ if (!haptics->input_dev)
+ return -ENOMEM;
+
+ haptics->reset_gpio = devm_gpiod_get(haptics->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(haptics->reset_gpio))
+ return dev_err_probe(haptics->dev, PTR_ERR(haptics->reset_gpio),
+ "Failed to get reset gpio\n");
+
+ /* Hardware reset */
+ aw86927_hw_reset(haptics);
+
+ /* Software reset */
+ err = regmap_write(haptics->regmap, AW86927_RSTCFG, AW86927_RSTCFG_SOFTRST);
+ if (err)
+ return dev_err_probe(haptics->dev, PTR_ERR(haptics->regmap),
+ "Failed Software reset\n");
+
+ /* Wait ~3s until I2C is accessible */
+ usleep_range(3000, 3500);
+
+ err = aw86927_detect(haptics);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Failed to find chip\n");
+
+ /* IRQ config */
+ err = regmap_write(haptics->regmap, AW86927_SYSCTRL4,
+ FIELD_PREP(AW86927_SYSCTRL4_INT_MODE_MASK,
+ AW86927_SYSCTRL4_INT_MODE_EDGE) |
+ FIELD_PREP(AW86927_SYSCTRL4_INT_EDGE_MODE_MASK,
+ AW86927_SYSCTRL4_INT_EDGE_MODE_POS));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap, AW86927_SYSINTM,
+ AW86927_SYSINTM_BST_OVPM |
+ AW86927_SYSINTM_FF_AEM |
+ AW86927_SYSINTM_FF_AFM |
+ AW86927_SYSINTM_DONEM);
+ if (err)
+ return err;
+
+ err = devm_request_threaded_irq(haptics->dev, client->irq, NULL,
+ aw86927_irq, IRQF_ONESHOT, NULL, haptics);
+
+ INIT_WORK(&haptics->play_work, aw86927_haptics_play_work);
+
+ haptics->input_dev->name = "aw86927-haptics";
+ haptics->input_dev->close = aw86927_close;
+
+ input_set_drvdata(haptics->input_dev, haptics);
+ input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
+
+ err = input_ff_create_memless(haptics->input_dev, NULL,
+ aw86927_haptics_play);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Failed to create FF dev\n");
+
+ /* Set up registers */
+ err = aw86927_play_mode(haptics, AW86927_STANDBY_MODE);
+ if (err)
+ return err;
+
+ err = aw86927_haptic_init(haptics);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Haptic init failed\n");
+
+ /* RAM init, upload the waveform for playback */
+ err = aw86927_ram_init(haptics);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Failed to init aw86927 sram\n");
+
+ err = input_register_device(haptics->input_dev);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Failed to register input device\n");
+
+ return 0;
+}
+
+static const struct of_device_id aw86927_of_id[] = {
+ { .compatible = "awinic,aw86927" },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, aw86927_of_id);
+
+static struct i2c_driver aw86927_driver = {
+ .driver = {
+ .name = "aw86927-haptics",
+ .of_match_table = aw86927_of_id,
+ },
+ .probe = aw86927_probe,
+};
+
+module_i2c_driver(aw86927_driver);
+
+MODULE_AUTHOR("Griffin Kroah-Hartman <griffin.kroah@fairphone.com>");
+MODULE_DESCRIPTION("AWINIC AW86927 LRA Haptic Driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* Re: [PATCH 3/3] arm64: dts: qcom: qcm6490-fairphone-fp5: Add vibrator support
From: Konrad Dybcio @ 2025-08-06 15:13 UTC (permalink / raw)
To: Griffin Kroah-Hartman, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio,
Luca Weiss
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm
In-Reply-To: <20250806-aw86927-v1-3-23d8a6d0f2b2@fairphone.com>
On 8/6/25 5:10 PM, Griffin Kroah-Hartman wrote:
> Add the required node for haptic playback (Awinic AW86927).
>
> Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
> ---
> arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts | 18 +++++++++++++++++-
> 1 file changed, 17 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
> index 4c6cb4a644e2..9576efdf1e8d 100644
> --- a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
> +++ b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
> @@ -866,7 +866,16 @@ ocp96011_sbu_mux: endpoint {
> };
> };
>
> - /* AW86927FCR haptics @ 5a */
> + vibrator@5a {
> + compatible = "awinic,aw86927";
I noticed the suffix is absent in the compatible - does it matter
for discerning the particular chip and other variants?
Anyway, so long as you and the bindings folks agree on it:
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Konrad
^ permalink raw reply
* Re: [PATCH 3/3] arm64: dts: qcom: qcm6490-fairphone-fp5: Add vibrator support
From: Luca Weiss @ 2025-08-06 15:20 UTC (permalink / raw)
To: Konrad Dybcio, Griffin Kroah-Hartman, Dmitry Torokhov,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm
In-Reply-To: <581aaa8f-9e08-475e-ad23-3369fa05e53e@oss.qualcomm.com>
On Wed Aug 6, 2025 at 5:13 PM CEST, Konrad Dybcio wrote:
> On 8/6/25 5:10 PM, Griffin Kroah-Hartman wrote:
>> Add the required node for haptic playback (Awinic AW86927).
>>
>> Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
>> ---
>> arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts | 18 +++++++++++++++++-
>> 1 file changed, 17 insertions(+), 1 deletion(-)
>>
>> diff --git a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
>> index 4c6cb4a644e2..9576efdf1e8d 100644
>> --- a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
>> +++ b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
>> @@ -866,7 +866,16 @@ ocp96011_sbu_mux: endpoint {
>> };
>> };
>>
>> - /* AW86927FCR haptics @ 5a */
>> + vibrator@5a {
>> + compatible = "awinic,aw86927";
>
> I noticed the suffix is absent in the compatible - does it matter
> for discerning the particular chip and other variants?
> Anyway, so long as you and the bindings folks agree on it:
According to the datasheet, the main name is AW86927, while the part
number of this particular one is AW86927FCR.
I'm not aware of any other variants of the 86927 though. At least for
some audio chip from Awinic there seems to be different variants with
the same number with a different suffix that have a different amount of
pins, but I really don't know 100%.
https://www.awinic.com/en/search/all?titleSearch=AW86927
At least I don't find any non-FCR variants here.
>
> Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> Konrad
^ permalink raw reply
* Re: [PATCH 2/3] Input: aw86927 - add driver for Awinic AW86927
From: Alexey Klimov @ 2025-08-06 15:28 UTC (permalink / raw)
To: Griffin Kroah-Hartman, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio,
Luca Weiss
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm
In-Reply-To: <20250806-aw86927-v1-2-23d8a6d0f2b2@fairphone.com>
Hi Griffin,
On Wed Aug 6, 2025 at 4:10 PM BST, Griffin Kroah-Hartman wrote:
> Add support for the I2C-connected Awinic AW86927 LRA haptic driver.
>
> This driver includes a hardcoded sine waveform to be uploaded to the
> AW86927's SRAM for haptic playback.
> This driver does not currently support all the capabilities of the
> AW86927, such as F0 calibration, RTP mode, and CONT mode.
>
> Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
> ---
> drivers/input/misc/Kconfig | 11 +
> drivers/input/misc/Makefile | 1 +
> drivers/input/misc/aw86927.c | 841 +++++++++++++++++++++++++++++++++++++++++++
[...]
> +static int aw86927_probe(struct i2c_client *client)
> +{
> + struct aw86927_data *haptics;
> + unsigned int read_buf;
> + int err;
> +
> + haptics = devm_kzalloc(&client->dev, sizeof(struct aw86927_data), GFP_KERNEL);
> + if (!haptics)
> + return -ENOMEM;
> +
> + haptics->dev = &client->dev;
> + haptics->client = client;
> +
> + i2c_set_clientdata(client, haptics);
> + dev_set_drvdata(&client->dev, haptics);
> +
> + haptics->regmap = devm_regmap_init_i2c(client, &aw86927_regmap_config);
> + if (IS_ERR(haptics->regmap))
> + return dev_err_probe(haptics->dev, PTR_ERR(haptics->regmap),
> + "Failed to allocate register map\n");
> +
> + haptics->input_dev = devm_input_allocate_device(haptics->dev);
> + if (!haptics->input_dev)
> + return -ENOMEM;
> +
> + haptics->reset_gpio = devm_gpiod_get(haptics->dev, "reset", GPIOD_OUT_HIGH);
> + if (IS_ERR(haptics->reset_gpio))
> + return dev_err_probe(haptics->dev, PTR_ERR(haptics->reset_gpio),
> + "Failed to get reset gpio\n");
> +
> + /* Hardware reset */
> + aw86927_hw_reset(haptics);
> +
> + /* Software reset */
> + err = regmap_write(haptics->regmap, AW86927_RSTCFG, AW86927_RSTCFG_SOFTRST);
> + if (err)
> + return dev_err_probe(haptics->dev, PTR_ERR(haptics->regmap),
> + "Failed Software reset\n");
> +
> + /* Wait ~3s until I2C is accessible */
> + usleep_range(3000, 3500);
3 ms or 3 seconds?
[..]
Best regards,
Alexey
^ permalink raw reply
* Re: [PATCH 1/3] Input: mtk-pmic-keys - MT6359 has a specific release irq
From: Dmitry Torokhov @ 2025-08-06 16:49 UTC (permalink / raw)
To: AngeloGioacchino Del Regno
Cc: Julien Massot, kernel, Matthias Brugger, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-input, linux-kernel,
linux-arm-kernel, linux-mediatek, devicetree
In-Reply-To: <a9cd33f5-cc37-4a1d-b1fd-094761a146ed@collabora.com>
On Mon, Aug 04, 2025 at 10:05:21AM +0200, AngeloGioacchino Del Regno wrote:
> Il 01/08/25 15:16, Julien Massot ha scritto:
> > A recent commit in linux-next added support for key events.
> > However, the key release event is not properly handled: only key press events
> > are generated, leaving key states stuck in "pressed".
> >
> > This patch ensures that both key press and key release events are properly
> > emitted by handling the release logic correctly.
> >
> > Note: the code was introduced in linux-next by commit
> > bc25e6bf032e ("Input: mtk-pmic-keys - add support for MT6359 PMIC keys")
> > and is not yet present in mainline.
> >
> > Signed-off-by: Julien Massot <julien.massot@collabora.com>
>
> Well, you are effectively fixing the commit that you pointed out, so this needs
>
> Fixes: bc25e6bf032e ("Input: mtk-pmic-keys - add support for MT6359 PMIC keys")
>
> and
>
> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
I am really interested in how exactly this was developed, tested, and
reviewed...
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 1/3] dt-bindings: input: Add bindings for Awinic AW86927
From: Rob Herring @ 2025-08-07 0:08 UTC (permalink / raw)
To: Griffin Kroah-Hartman
Cc: Dmitry Torokhov, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Luca Weiss, linux-input,
devicetree, linux-kernel, linux-arm-msm
In-Reply-To: <20250806-aw86927-v1-1-23d8a6d0f2b2@fairphone.com>
On Wed, Aug 06, 2025 at 05:10:08PM +0200, Griffin Kroah-Hartman wrote:
> Add bindings for the Awinic AW86927 haptic chip which can be found in
> smartphones.
>
> Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
> ---
> .../devicetree/bindings/input/awinic,aw86927.yaml | 48 ++++++++++++++++++++++
> 1 file changed, 48 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/input/awinic,aw86927.yaml b/Documentation/devicetree/bindings/input/awinic,aw86927.yaml
> new file mode 100644
> index 000000000000..43012f28d9aa
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/awinic,aw86927.yaml
> @@ -0,0 +1,48 @@
> +# SPDX-License-Identifier: (GPL-2.0-only)
Dual license please: OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/input/awinic,aw86927.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Awinic AW86927 LRA Haptics driver
The binding is for a device, not a driver. So drop 'driver'.
Otherwise, looks good.
> +
> +maintainers:
> + - Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
> +
> +properties:
> + compatible:
> + const: awinic,aw86927
> +
> + reg:
> + maxItems: 1
> +
> + reset-gpios:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> +required:
> + - compatible
> + - reg
> + - reset-gpios
> + - interrupts
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + vibrator@5a {
> + compatible = "awinic,aw86927";
> + reg = <0x5a>;
> + interrupts-extended = <&tlmm 101 IRQ_TYPE_EDGE_FALLING>;
> + reset-gpios = <&tlmm 100 GPIO_ACTIVE_LOW>;
> + };
> + };
>
> --
> 2.43.0
>
^ permalink raw reply
* [dtor-input:next 25/27] ERROR: modpost: __ex_table+0x1e28 references non-executable section '.rodata.xgene_rng_init.str1.4'
From: kernel test robot @ 2025-08-07 2:37 UTC (permalink / raw)
To: Lee Jones; +Cc: oe-kbuild-all, linux-input, Dmitry Torokhov
tree: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
head: 4f67c41894674d351a4b4e7dd3471380b71b5bb3
commit: a7bee4e7f78089c101be2ad51f4b5ec64782053e [25/27] Merge tag 'ib-mfd-gpio-input-pwm-v6.17' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd into next
config: riscv-randconfig-002-20250807 (https://download.01.org/0day-ci/archive/20250807/202508071018.TcyVGL9C-lkp@intel.com/config)
compiler: riscv32-linux-gcc (GCC) 8.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250807/202508071018.TcyVGL9C-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202508071018.TcyVGL9C-lkp@intel.com/
All errors (new ones prefixed by >>, old ones prefixed by <<):
ERROR: modpost: __ex_table+0x1c3c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1c44 (section: __ex_table) -> .LASF1336 (section: .debug_str)
ERROR: modpost: __ex_table+0x1c44 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1c48 (section: __ex_table) -> .LASF1320 (section: .debug_str)
ERROR: modpost: __ex_table+0x1c48 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1c50 (section: __ex_table) -> .LASF1339 (section: .debug_str)
ERROR: modpost: __ex_table+0x1c50 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1c54 (section: __ex_table) -> .LASF1320 (section: .debug_str)
ERROR: modpost: __ex_table+0x1c54 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1c5c (section: __ex_table) -> .LASF1342 (section: .debug_str)
ERROR: modpost: __ex_table+0x1c5c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1c60 (section: __ex_table) -> .LASF1320 (section: .debug_str)
ERROR: modpost: __ex_table+0x1c60 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d28 (section: __ex_table) -> .LASF179 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d28 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d2c (section: __ex_table) -> .LASF181 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d2c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d34 (section: __ex_table) -> .LASF183 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d34 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d38 (section: __ex_table) -> .LASF185 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d38 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d40 (section: __ex_table) -> .LASF187 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d40 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d44 (section: __ex_table) -> .LASF189 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d44 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d4c (section: __ex_table) -> .LASF191 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d4c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d50 (section: __ex_table) -> .LASF189 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d50 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d58 (section: __ex_table) -> .LASF194 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d58 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d5c (section: __ex_table) -> .LASF196 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d5c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d64 (section: __ex_table) -> .LASF198 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d64 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d68 (section: __ex_table) -> .LASF200 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d68 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d70 (section: __ex_table) -> .LASF202 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d70 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d74 (section: __ex_table) -> .LASF204 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d74 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d7c (section: __ex_table) -> .LASF206 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d7c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d80 (section: __ex_table) -> .LASF204 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d80 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d88 (section: __ex_table) -> .LASF209 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d88 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d8c (section: __ex_table) -> .LASF211 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d8c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d94 (section: __ex_table) -> .LASF213 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d94 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1d98 (section: __ex_table) -> .LASF215 (section: .debug_str)
ERROR: modpost: __ex_table+0x1d98 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1da0 (section: __ex_table) -> .LASF1069 (section: .debug_str)
ERROR: modpost: __ex_table+0x1da0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1da4 (section: __ex_table) -> .LASF1071 (section: .debug_str)
ERROR: modpost: __ex_table+0x1da4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1dac (section: __ex_table) -> .LASF1073 (section: .debug_str)
ERROR: modpost: __ex_table+0x1dac references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1db0 (section: __ex_table) -> .LASF1075 (section: .debug_str)
ERROR: modpost: __ex_table+0x1db0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1db8 (section: __ex_table) -> .LASF3682 (section: .debug_str)
ERROR: modpost: __ex_table+0x1db8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1dbc (section: __ex_table) -> .LASF3679 (section: .debug_str)
ERROR: modpost: __ex_table+0x1dbc references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1dc4 (section: __ex_table) -> .LASF3685 (section: .debug_str)
ERROR: modpost: __ex_table+0x1dc4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1dc8 (section: __ex_table) -> .LASF3679 (section: .debug_str)
ERROR: modpost: __ex_table+0x1dc8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1dd0 (section: __ex_table) -> .LASF3688 (section: .debug_str)
ERROR: modpost: __ex_table+0x1dd0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1dd4 (section: __ex_table) -> .LASF3679 (section: .debug_str)
ERROR: modpost: __ex_table+0x1dd4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ddc (section: __ex_table) -> .LASF3691 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ddc references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1de0 (section: __ex_table) -> .LASF3693 (section: .debug_str)
ERROR: modpost: __ex_table+0x1de0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1de8 (section: __ex_table) -> .LASF3695 (section: .debug_str)
ERROR: modpost: __ex_table+0x1de8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1dec (section: __ex_table) -> .LASF3693 (section: .debug_str)
ERROR: modpost: __ex_table+0x1dec references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1df4 (section: __ex_table) -> .LASF3698 (section: .debug_str)
ERROR: modpost: __ex_table+0x1df4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1df8 (section: __ex_table) -> .LASF3693 (section: .debug_str)
ERROR: modpost: __ex_table+0x1df8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e00 (section: __ex_table) -> .LASF328 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e00 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e04 (section: __ex_table) -> .LASF330 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e04 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e0c (section: __ex_table) -> .LASF2574 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e0c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e10 (section: __ex_table) -> .LASF2576 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e10 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e18 (section: __ex_table) -> .LASF2578 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e18 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e1c (section: __ex_table) -> .LASF2580 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e1c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e24 (section: __ex_table) -> __key (section: .bss.__key.49711)
ERROR: modpost: __ex_table+0x1e24 references non-executable section '.bss.__key.49711'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e28 (section: __ex_table) -> .LC4 (section: .rodata.xgene_rng_init.str1.4)
>> ERROR: modpost: __ex_table+0x1e28 references non-executable section '.rodata.xgene_rng_init.str1.4'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e54 (section: __ex_table) -> .LLST123 (section: .debug_loc)
ERROR: modpost: __ex_table+0x1e54 references non-executable section '.debug_loc'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e60 (section: __ex_table) -> .LASF3011 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e60 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e64 (section: __ex_table) -> .LASF3013 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e64 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e6c (section: __ex_table) -> .LASF2740 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e6c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e70 (section: __ex_table) -> .LASF2742 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e70 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e78 (section: __ex_table) -> .LASF2744 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e78 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e7c (section: __ex_table) -> .LASF2742 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e7c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e84 (section: __ex_table) -> .LASF3290 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e84 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e88 (section: __ex_table) -> .LASF3292 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e88 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e90 (section: __ex_table) -> .LASF3294 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e90 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e94 (section: __ex_table) -> .LASF3292 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e94 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1e9c (section: __ex_table) -> .LASF3297 (section: .debug_str)
ERROR: modpost: __ex_table+0x1e9c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ea0 (section: __ex_table) -> .LASF3299 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ea0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ea8 (section: __ex_table) -> .LASF3301 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ea8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1eac (section: __ex_table) -> .LASF3303 (section: .debug_str)
ERROR: modpost: __ex_table+0x1eac references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1eb4 (section: __ex_table) -> .LASF1607 (section: .debug_str)
ERROR: modpost: __ex_table+0x1eb4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1eb8 (section: __ex_table) -> .LASF1609 (section: .debug_str)
ERROR: modpost: __ex_table+0x1eb8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ec0 (section: __ex_table) -> .LASF1149 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ec0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ec4 (section: __ex_table) -> .LASF1151 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ec4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ecc (section: __ex_table) -> .LASF1153 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ecc references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ed0 (section: __ex_table) -> .LASF1155 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ed0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ed8 (section: __ex_table) -> .LASF1157 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ed8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1edc (section: __ex_table) -> .LASF1155 (section: .debug_str)
ERROR: modpost: __ex_table+0x1edc references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ee4 (section: __ex_table) -> .LASF1160 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ee4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ee8 (section: __ex_table) -> .LASF1162 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ee8 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ef0 (section: __ex_table) -> .LASF243 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ef0 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1ef4 (section: __ex_table) -> .LASF245 (section: .debug_str)
ERROR: modpost: __ex_table+0x1ef4 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1efc (section: __ex_table) -> .LASF2038 (section: .debug_str)
ERROR: modpost: __ex_table+0x1efc references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f00 (section: __ex_table) -> .LASF2040 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f00 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f08 (section: __ex_table) -> .LASF2042 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f08 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f0c (section: __ex_table) -> .LASF2044 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f0c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f14 (section: __ex_table) -> .LASF2046 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f14 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f18 (section: __ex_table) -> .LASF2040 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f18 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f20 (section: __ex_table) -> .LASF2049 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f20 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f24 (section: __ex_table) -> .LASF2051 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f24 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f2c (section: __ex_table) -> .LASF2053 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f2c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f30 (section: __ex_table) -> .LASF2055 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f30 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f38 (section: __ex_table) -> .LASF2057 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f38 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f3c (section: __ex_table) -> .LASF2059 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f3c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f44 (section: __ex_table) -> .LLST161 (section: .debug_loc)
ERROR: modpost: __ex_table+0x1f44 references non-executable section '.debug_loc'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f48 (section: __ex_table) -> .LLST162 (section: .debug_loc)
ERROR: modpost: __ex_table+0x1f48 references non-executable section '.debug_loc'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f54 (section: __ex_table) -> .LLST164 (section: .debug_loc)
ERROR: modpost: __ex_table+0x1f54 references non-executable section '.debug_loc'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f5c (section: __ex_table) -> .LASF1143 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f5c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f60 (section: __ex_table) -> .LASF1145 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f60 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f68 (section: __ex_table) -> .LASF1147 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f68 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f6c (section: __ex_table) -> .LASF1149 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f6c references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f74 (section: __ex_table) -> .LASF1151 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f74 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f78 (section: __ex_table) -> .LASF1153 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f78 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f80 (section: __ex_table) -> .LASF1155 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f80 references non-executable section '.debug_str'
WARNING: modpost: vmlinux: section mismatch in reference: 0x1f84 (section: __ex_table) -> .LASF1157 (section: .debug_str)
ERROR: modpost: __ex_table+0x1f84 references non-executable section '.debug_str'
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [git pull] Input updates for v6.17-rc0
From: Dmitry Torokhov @ 2025-08-07 4:25 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, linux-input, Bartosz Golaszewski
Hi Linus,
Please pull from:
git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git tags/input-for-v6.17-rc0
to receive updates for the input subsystem. You will get:
- updates to several drivers consuming GPIO APIs to use setters
returning error codes
- an infrastructure allowing to define "overlays" for touchscreens
carving out regions implementing buttons and other elements from a
bigger sensors and a corresponding update to st1232 driver
- an update to AT/PS2 keyboard driver to map F13-F24 by default
- Samsung keypad driver got a facelift
- evdev input handler will now bind to all devices using EV_SYN event
instead of abusing id->driver_info
- 2 new sub-drivers implementing 1A (capacitive buttons) and 21
(forcepad button) functions in Synaptics RMI driver
- support for polling mode in Goodix touchscreen driver
- support for support for FocalTech FT8716 in edt-ft5x06 driver
- support for MT6359 in mtk-pmic-keys driver
- removal of pcf50633-input driver since platform it was used on is gone
- new definitions for game controller "grip" buttons (BTN_GRIP*) and
corresponding changes to xpad and hid-steam controller drivers
- a new definition for "performance" key (Turbo button is back!)
Changelog:
---------
André Apitzsch (2):
dt-bindings: input: syna,rmi4: Document F1A function
Input: synaptics-rmi4 - add support for F1A
Bartosz Golaszewski (3):
Input: ad7879 - use new GPIO line value setter callbacks
Input: adp5588 - use new GPIO line value setter callbacks
Input: adp5589 - use new GPIO line value setter callbacks
Chen Ni (1):
Input: cs40l50 - remove redundant flush_workqueue() calls
Dmitry Torokhov (10):
Input: samsung-keypad - switch to using devm_clk_get_prepared()
Input: samsung-keypad - do not set input device's parent explicitly
Input: samsung-keypad - do not combine memory allocation checks
Input: samsung-keypad - use struct_size() helper
Input: samsung-keypad - use devm to disable runtime PM
Input: samsung-keypad - use guard notation to acquire mutex
Input: samsung-keypad - use per-chip parameters
Input: samsung-keypad - use BIT() and GENMASK() where appropriate
Input: evdev - switch matching to EV_SYN
Input: remove special handling of id->driver_info when matching
Dr. David Alan Gilbert (1):
Input: pcf50633-input - remove the driver
Frank Li (2):
dt-bindings: input: touchscreen: convert lpc32xx-tsc.txt to yaml format
dt-bindings: input: touchscreen: convert tsc2007.txt to yaml format
Javier Carrasco (4):
dt-bindings: touchscreen: add touch-overlay property
Input: touch-overlay - add touchscreen overlay handling
dt-bindings: input: touchscreen: st1232: add touch-overlay example
Input: st1232 - add touch-overlay handling
Jens Reidel (2):
dt-bindings: input: touchscreen: edt-ft5x06: Document FT8716 support
Input: edt-ft5x06 - add support for FocalTech FT8716
Joseph Guo (1):
Input: goodix - add support for polling mode
Louis-Alexis Eyraud (1):
Input: mtk-pmic-keys - add support for MT6359 PMIC keys
Marcos Alano (1):
Input: add keycode for performance mode key
Marge Yang (1):
Input: synaptics-rmi4 - add support for Forcepads (F21)
Uwe Kleine-König (2):
Input: max8997_haptic - optimize PWM configuration
Input: max77693 - convert to atomic pwm operation
Vicki Pfau (5):
Documentation: Fix capitalization of XBox -> Xbox
Input: xpad - change buttons the D-Pad gets mapped as to BTN_DPAD_*
Input: Add and document BTN_GRIP*
Input: xpad - use new BTN_GRIP* buttons
HID: hid-steam: Use new BTN_GRIP* buttons
Werner Sembach (1):
Input: atkbd - correctly map F13 - F24
Wolfram Sang (1):
Input: edt-ft5x06 - use per-client debugfs directory
Diffstat:
--------
.../devicetree/bindings/input/syna,rmi4.yaml | 20 ++
.../bindings/input/touchscreen/edt-ft5x06.yaml | 1 +
.../bindings/input/touchscreen/lpc32xx-tsc.txt | 16 --
.../input/touchscreen/nxp,lpc3220-tsc.yaml | 43 ++++
.../input/touchscreen/sitronix,st1232.yaml | 29 +++
.../bindings/input/touchscreen/ti.tsc2007.yaml | 75 ++++++
.../bindings/input/touchscreen/touchscreen.yaml | 119 +++++++++
.../bindings/input/touchscreen/tsc2007.txt | 39 ---
Documentation/input/devices/edt-ft5x06.rst | 21 +-
Documentation/input/gamepad.rst | 19 +-
Documentation/userspace-api/media/rc/rc-protos.rst | 4 +-
MAINTAINERS | 7 +
drivers/hid/hid-debug.c | 2 +
drivers/hid/hid-steam.c | 35 ++-
drivers/input/Makefile | 2 +-
drivers/input/evdev.c | 8 +-
drivers/input/input.c | 2 +-
drivers/input/joystick/xpad.c | 64 ++---
drivers/input/keyboard/adp5588-keys.c | 9 +-
drivers/input/keyboard/atkbd.c | 12 +-
drivers/input/keyboard/mtk-pmic-keys.c | 17 ++
drivers/input/keyboard/samsung-keypad.c | 137 +++++-----
drivers/input/misc/Kconfig | 7 -
drivers/input/misc/Makefile | 1 -
drivers/input/misc/cs40l50-vibra.c | 1 -
drivers/input/misc/max77693-haptic.c | 41 ++-
drivers/input/misc/max8997_haptic.c | 96 ++++---
drivers/input/misc/pcf50633-input.c | 113 ---------
drivers/input/rmi4/Kconfig | 15 ++
drivers/input/rmi4/Makefile | 2 +
drivers/input/rmi4/rmi_bus.c | 6 +
drivers/input/rmi4/rmi_driver.h | 2 +
drivers/input/rmi4/rmi_f1a.c | 143 +++++++++++
drivers/input/rmi4/rmi_f21.c | 179 +++++++++++++
drivers/input/touch-overlay.c | 277 +++++++++++++++++++++
drivers/input/touchscreen/ad7879.c | 11 +-
drivers/input/touchscreen/edt-ft5x06.c | 26 +-
drivers/input/touchscreen/goodix.c | 50 +++-
drivers/input/touchscreen/st1232.c | 35 ++-
include/linux/input/touch-overlay.h | 25 ++
include/uapi/linux/input-event-codes.h | 8 +
41 files changed, 1295 insertions(+), 424 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/input/touchscreen/lpc32xx-tsc.txt
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/nxp,lpc3220-tsc.yaml
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/ti.tsc2007.yaml
delete mode 100644 Documentation/devicetree/bindings/input/touchscreen/tsc2007.txt
delete mode 100644 drivers/input/misc/pcf50633-input.c
create mode 100644 drivers/input/rmi4/rmi_f1a.c
create mode 100644 drivers/input/rmi4/rmi_f21.c
create mode 100644 drivers/input/touch-overlay.c
create mode 100644 include/linux/input/touch-overlay.h
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH RESEND v5 0/7] Input: synaptics-rmi4 - add quirks for third party touchscreen controllers
From: Dmitry Torokhov @ 2025-08-07 4:29 UTC (permalink / raw)
To: david
Cc: Kaustabh Chakraborty, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Jason A. Donenfeld, Matthias Schiffer,
Vincent Huang, linux-input, devicetree, linux-kernel, phone-devel,
~postmarketos/upstreaming, Casey Connolly, Krzysztof Kozlowski
In-Reply-To: <20250731-synaptics-rmi4-v5-0-cd0d87d34afa@ixit.cz>
Hi David,
On Thu, Jul 31, 2025 at 11:06:50PM +0200, David Heidelberg via B4 Relay wrote:
> With the growing popularity of running upstream Linux on mobile devices,
> we're beginning to run into more and more edgecases. The OnePlus 6 is a
> fairly well supported 2018 era smartphone, selling over a million units
> in it's first 22 days. With this level of popularity, it's almost
> inevitable that we get third party replacement displays, and as a
> result, replacement touchscreen controllers.
>
> The OnePlus 6 shipped with an extremely usecase specific touchscreen
> driver, it implemented only the bare minimum parts of the highly generic
> rmi4 protocol, instead hardcoding most of the register addresses.
>
> As a result, the third party touchscreen controllers that are often
> found in replacement screens, implement only the registers that the
> downstream driver reads from. They additionally have other restrictions
> such as heavy penalties on unaligned reads.
>
> This series attempts to implement the necessary workaround to support
> some of these chips with the rmi4 driver. Although it's worth noting
> that at the time of writing there are other unofficial controllers in
> the wild that don't work even with these patches.
>
> We have been shipping these patches in postmarketOS for the last several
> years, and they are known to not cause any regressions on the OnePlus
> 6/6T (with the official Synaptics controller), however I don't own any
> other rmi4 hardware to further validate this.
Sorry for not handling the patches in the last few submissions. I am
planning on addressing them once merge window opens.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] Input: max77693 - Convert to atomic pwm operation
From: Dmitry Torokhov @ 2025-08-07 4:33 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Dzmitry Sankouski, Marek Szyprowski, Krzysztof Kozlowski,
linux-input, linux-pwm
In-Reply-To: <zxeva5asxre7oc6vakfoyiehegt5c4i7qwaeue5woxk4xir3di@thcg7lyadvn3>
Hi Uwe,
On Tue, Jul 29, 2025 at 10:04:06PM +0200, Uwe Kleine-König wrote:
> Hello Dmitry,
>
> On Wed, Jul 02, 2025 at 08:02:33AM +0200, Uwe Kleine-König wrote:
> > On Tue, Jul 01, 2025 at 11:06:50AM -0700, Dmitry Torokhov wrote:
> > > On Tue, Jul 01, 2025 at 07:49:22AM +0200, Uwe Kleine-König wrote:
> > > >
> > > > I had something like that at first, but didn't like it. With that
> > > > approach you have two places that have to know how to set the PWM's
> > > > duty_cycle. Also I think the control flow is more complicated.
> > > >
> > > > I considered renaming max77693_haptic_enable() to something that better
> > > > matches what it does in my variant, but max77693_haptic_configure() was
> > > > already taken.
> > > >
> > > > But that might all be subjective? If you like your version better,
> > > > that's fine, it still gets rid of pwm_config(), pwm_enable() and
> > > > pwm_apply_args() which is my main objective.
> > >
> > > Yes, I agree that it is subjective. I know that you do not quite like
> > > the version I posted, still will you be OK if it is attributed to you?
> >
> > Yes, feel free to apply it as you suggested.
>
> As of today's next that didn't happen. Do you have this patch still on
> your radar?
My bad, I lost track of it when I reshuffled my queue.
>
> This is the last driver making use of pwm_config(), it would be great to
> get rid of that.
The patch is in the pull request I just sent to Linus, hope this
unblocks you.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [git pull] Input updates for v6.17-rc0
From: pr-tracker-bot @ 2025-08-07 5:30 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Linus Torvalds, linux-kernel, linux-input, Bartosz Golaszewski
In-Reply-To: <7ool7vscmhygq3jtks25bxhxbm2aijk6wp3cpy6njoifw6b5ri@2le5vfs3ztzo>
The pull request you sent on Wed, 6 Aug 2025 21:25:21 -0700:
> git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git tags/input-for-v6.17-rc0
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/6e64f4580381e32c06ee146ca807c555b8f73e24
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply
* [dtor-input:next] BUILD REGRESSION 4f67c41894674d351a4b4e7dd3471380b71b5bb3
From: kernel test robot @ 2025-08-07 5:44 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
branch HEAD: 4f67c41894674d351a4b4e7dd3471380b71b5bb3 HID: hid-steam: Use new BTN_GRIP* buttons
Error/Warning (recently discovered and may have been fixed):
https://lore.kernel.org/oe-kbuild-all/202508041754.ykl25o1q-lkp@intel.com
https://lore.kernel.org/oe-kbuild-all/202508071018.TcyVGL9C-lkp@intel.com
ERROR: modpost: __ex_table+0x1e28 references non-executable section '.rodata.xgene_rng_init.str1.4'
drivers/input/touch-overlay.c: warning: EXPORT_SYMBOL() is used, but #include <linux/export.h> is missing
Error/Warning ids grouped by kconfigs:
recent_errors
|-- alpha-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- alpha-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- arc-allmodconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- arc-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- arc-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- arc-randconfig-001-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- arc-randconfig-002-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- arm-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- arm-randconfig-001-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- arm-randconfig-002-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- arm-randconfig-003-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- arm64-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- csky-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- csky-randconfig-001-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- csky-randconfig-002-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- hexagon-allmodconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- hexagon-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- hexagon-randconfig-001-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- hexagon-randconfig-002-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- i386-buildonly-randconfig-001-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- i386-buildonly-randconfig-002-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- i386-buildonly-randconfig-003-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- i386-buildonly-randconfig-004-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- i386-buildonly-randconfig-005-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- i386-buildonly-randconfig-006-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- loongarch-allmodconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- loongarch-randconfig-001-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- m68k-allmodconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- m68k-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- m68k-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- microblaze-allmodconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- microblaze-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- microblaze-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- mips-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- nios2-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- openrisc-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- openrisc-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- parisc-allmodconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- parisc-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- parisc-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- powerpc-allmodconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- powerpc-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- powerpc-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- powerpc-randconfig-001-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- powerpc-randconfig-002-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- powerpc-randconfig-003-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- powerpc64-randconfig-002-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- riscv-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- riscv-randconfig-001-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- riscv-randconfig-002-20250807
| |-- ERROR:__ex_table-references-non-executable-section-.rodata.xgene_rng_init.str1.
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- s390-allmodconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- s390-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- s390-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- s390-randconfig-001-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- s390-randconfig-002-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- sh-allmodconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- sh-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- sh-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- sh-randconfig-001-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- um-allmodconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- um-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- um-randconfig-002-20250807
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- x86_64-allnoconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- x86_64-allyesconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
|-- x86_64-defconfig
| `-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
`-- x86_64-rhel-9.4-rust
`-- drivers-input-touch-overlay.c:warning:EXPORT_SYMBOL()-is-used-but-include-linux-export.h-is-missing
elapsed time: 728m
configs tested: 112
configs skipped: 3
tested configs:
alpha allnoconfig gcc-15.1.0
alpha allyesconfig gcc-15.1.0
arc allmodconfig gcc-15.1.0
arc allnoconfig gcc-15.1.0
arc allyesconfig gcc-15.1.0
arc randconfig-001-20250807 gcc-13.4.0
arc randconfig-002-20250807 gcc-12.5.0
arm allmodconfig gcc-15.1.0
arm allnoconfig clang-22
arm allyesconfig gcc-15.1.0
arm randconfig-001-20250807 clang-22
arm randconfig-002-20250807 gcc-10.5.0
arm randconfig-003-20250807 clang-22
arm randconfig-004-20250807 gcc-8.5.0
arm64 allmodconfig clang-19
arm64 allnoconfig gcc-15.1.0
arm64 randconfig-001-20250807 gcc-8.5.0
arm64 randconfig-002-20250807 gcc-8.5.0
arm64 randconfig-003-20250807 clang-22
arm64 randconfig-004-20250807 gcc-14.3.0
csky allnoconfig gcc-15.1.0
csky randconfig-001-20250807 gcc-15.1.0
csky randconfig-002-20250807 gcc-15.1.0
hexagon allmodconfig clang-17
hexagon allnoconfig clang-22
hexagon allyesconfig clang-22
hexagon randconfig-001-20250807 clang-22
hexagon randconfig-002-20250807 clang-22
i386 allmodconfig gcc-12
i386 allnoconfig gcc-12
i386 allyesconfig gcc-12
i386 buildonly-randconfig-001-20250807 clang-20
i386 buildonly-randconfig-002-20250807 clang-20
i386 buildonly-randconfig-003-20250807 gcc-12
i386 buildonly-randconfig-004-20250807 gcc-11
i386 buildonly-randconfig-005-20250807 gcc-12
i386 buildonly-randconfig-006-20250807 gcc-12
i386 defconfig clang-20
loongarch allmodconfig clang-19
loongarch allnoconfig clang-22
loongarch randconfig-001-20250807 clang-22
loongarch randconfig-002-20250807 gcc-15.1.0
m68k allmodconfig gcc-15.1.0
m68k allnoconfig gcc-15.1.0
m68k allyesconfig gcc-15.1.0
microblaze allmodconfig gcc-15.1.0
microblaze allnoconfig gcc-15.1.0
microblaze allyesconfig gcc-15.1.0
mips allnoconfig gcc-15.1.0
nios2 allnoconfig gcc-11.5.0
nios2 randconfig-001-20250807 gcc-11.5.0
nios2 randconfig-002-20250807 gcc-8.5.0
openrisc allnoconfig gcc-15.1.0
openrisc allyesconfig gcc-15.1.0
openrisc defconfig gcc-15.1.0
parisc allmodconfig gcc-15.1.0
parisc allnoconfig gcc-15.1.0
parisc allyesconfig gcc-15.1.0
parisc randconfig-001-20250807 gcc-8.5.0
parisc randconfig-002-20250807 gcc-11.5.0
powerpc allmodconfig gcc-15.1.0
powerpc allnoconfig gcc-15.1.0
powerpc allyesconfig clang-22
powerpc randconfig-001-20250807 gcc-12.5.0
powerpc randconfig-002-20250807 gcc-10.5.0
powerpc randconfig-003-20250807 gcc-11.5.0
powerpc64 randconfig-001-20250807 gcc-11.5.0
powerpc64 randconfig-002-20250807 clang-22
powerpc64 randconfig-003-20250807 gcc-13.4.0
riscv allmodconfig clang-22
riscv allnoconfig gcc-15.1.0
riscv allyesconfig clang-16
riscv defconfig clang-22
riscv randconfig-001-20250807 gcc-12.5.0
riscv randconfig-002-20250807 gcc-8.5.0
s390 allmodconfig clang-18
s390 allnoconfig clang-22
s390 allyesconfig gcc-15.1.0
s390 defconfig clang-22
s390 randconfig-001-20250807 clang-22
s390 randconfig-002-20250807 clang-22
sh allmodconfig gcc-15.1.0
sh allnoconfig gcc-15.1.0
sh allyesconfig gcc-15.1.0
sh defconfig gcc-15.1.0
sh randconfig-001-20250807 gcc-14.3.0
sh randconfig-002-20250807 gcc-9.5.0
sparc allmodconfig gcc-15.1.0
sparc allnoconfig gcc-15.1.0
sparc randconfig-001-20250807 gcc-14.3.0
sparc randconfig-002-20250807 gcc-15.1.0
sparc64 defconfig clang-20
sparc64 randconfig-001-20250807 clang-22
sparc64 randconfig-002-20250807 clang-22
um allmodconfig clang-19
um allnoconfig clang-22
um allyesconfig gcc-12
um randconfig-001-20250807 gcc-12
um randconfig-002-20250807 gcc-11
x86_64 allnoconfig clang-20
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20250807 clang-20
x86_64 buildonly-randconfig-002-20250807 gcc-12
x86_64 buildonly-randconfig-003-20250807 clang-20
x86_64 buildonly-randconfig-004-20250807 clang-20
x86_64 buildonly-randconfig-005-20250807 gcc-11
x86_64 buildonly-randconfig-006-20250807 gcc-12
x86_64 defconfig gcc-11
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig gcc-15.1.0
xtensa randconfig-001-20250807 gcc-15.1.0
xtensa randconfig-002-20250807 gcc-8.5.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH] Input: max77693 - Convert to atomic pwm operation
From: Uwe Kleine-König @ 2025-08-07 6:42 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Dzmitry Sankouski, Marek Szyprowski, Krzysztof Kozlowski,
linux-input, linux-pwm
In-Reply-To: <wfdklejfruh4m54kmekyu2xssd6uss7es2nyvhqhki5zgbziwk@6bounno2zauu>
[-- Attachment #1: Type: text/plain, Size: 251 bytes --]
Hello Dmitry,
On Wed, Aug 06, 2025 at 09:33:25PM -0700, Dmitry Torokhov wrote:
> The patch is in the pull request I just sent to Linus, hope this
> unblocks you.
I'm not under great pressure, but if I know it's not stalled that's
great.
Thanks
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH v2 1/2] Input: allocate a keycode for Fn+space
From: Anton Khirnov @ 2025-08-07 7:04 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: platform-driver-x86, linux-kernel, linux-input, Corentin Chary,
Luke D. Jones, Hans de Goede, Ilpo Järvinen
In-Reply-To: <20250714150756.21197-2-anton@khirnov.net>
Hi Dmitry,
could you please look at this?
Thanks,
--
Anton Khirnov
^ permalink raw reply
* Re: [PATCH v2] HID: amd_sfh: Enable operating mode
From: butmonkeh @ 2025-08-07 12:42 UTC (permalink / raw)
To: jikos, bentiss, Basavaraj Natikar
Cc: Mario.Limonciello, linux-input, Basavaraj Natikar, Eric Naim,
Akshata MukundShetty
In-Reply-To: <20250623085453.2267034-1-Basavaraj.Natikar@amd.com>
On Monday, 23 June 2025 09:54:53 British Summer Time Basavaraj Natikar wrote:
> Add changes to enable operating modes in the driver to allow the FW to
> activate and retrieve data from relevant sensors. This enables the FW to
> take necessary actions based on the operating modes.
>
> Tested-by: Eric Naim <dnaim@cachyos.org>
> Co-developed-by: Akshata MukundShetty <akshata.mukundshetty@amd.com>
> Signed-off-by: Akshata MukundShetty <akshata.mukundshetty@amd.com>
> Signed-off-by: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
> ---
> v2:
> * Increased missing device count.
> ---
> drivers/hid/amd-sfh-hid/amd_sfh_client.c | 23 +++++++++++++++++++++++
> drivers/hid/amd-sfh-hid/amd_sfh_hid.h | 2 +-
> drivers/hid/amd-sfh-hid/amd_sfh_pcie.c | 4 ++++
> drivers/hid/amd-sfh-hid/amd_sfh_pcie.h | 1 +
> 4 files changed, 29 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c
> b/drivers/hid/amd-sfh-hid/amd_sfh_client.c index 3438d392920f..0f2cbae39b2b
> 100644
> --- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c
> +++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c
> @@ -146,6 +146,8 @@ static const char *get_sensor_name(int idx)
> return "gyroscope";
> case mag_idx:
> return "magnetometer";
> + case op_idx:
> + return "operating-mode";
> case als_idx:
> case ACS_IDX: /* ambient color sensor */
> return "ALS";
> @@ -243,6 +245,20 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev
> *privdata) rc = -ENOMEM;
> goto cleanup;
> }
> +
> + if (cl_data->sensor_idx[i] == op_idx) {
> + info.period = AMD_SFH_IDLE_LOOP;
> + info.sensor_idx = cl_data->sensor_idx[i];
> + info.dma_address = cl_data->sensor_dma_addr[i];
> + mp2_ops->start(privdata, info);
> + cl_data->sensor_sts[i] =
amd_sfh_wait_for_response(privdata,
> +
cl_data->sensor_idx[i],
> +
SENSOR_ENABLED);
> + if (cl_data->sensor_sts[i] == SENSOR_ENABLED)
> + cl_data->is_any_sensor_enabled = true;
> + continue;
> + }
> +
> cl_data->sensor_sts[i] = SENSOR_DISABLED;
> cl_data->sensor_requested_cnt[i] = 0;
> cl_data->cur_hid_dev = i;
> @@ -303,6 +319,13 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev
> *privdata)
>
> for (i = 0; i < cl_data->num_hid_devices; i++) {
> cl_data->cur_hid_dev = i;
> + if (cl_data->sensor_idx[i] == op_idx) {
> + dev_dbg(dev, "sid 0x%x (%s) status 0x%x\n",
> + cl_data->sensor_idx[i],
get_sensor_name(cl_data->sensor_idx[i]),
> + cl_data->sensor_sts[i]);
> + continue;
> + }
> +
> if (cl_data->sensor_sts[i] == SENSOR_ENABLED) {
> rc = amdtp_hid_probe(i, cl_data);
> if (rc)
> diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
> b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h index 1c91be8daedd..7452b0302953
> 100644
> --- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
> +++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
> @@ -11,7 +11,7 @@
> #ifndef AMDSFH_HID_H
> #define AMDSFH_HID_H
>
> -#define MAX_HID_DEVICES 6
> +#define MAX_HID_DEVICES 7
> #define AMD_SFH_HID_VENDOR 0x1022
> #define AMD_SFH_HID_PRODUCT 0x0001
>
> diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
> b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index 1c1fd63330c9..2983af969579
> 100644
> --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
> +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
> @@ -29,6 +29,7 @@
> #define ACEL_EN BIT(0)
> #define GYRO_EN BIT(1)
> #define MAGNO_EN BIT(2)
> +#define OP_EN BIT(15)
> #define HPD_EN BIT(16)
> #define ALS_EN BIT(19)
> #define ACS_EN BIT(22)
> @@ -232,6 +233,9 @@ int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata,
> u8 *sensor_id) if (MAGNO_EN & activestatus)
> sensor_id[num_of_sensors++] = mag_idx;
>
> + if (OP_EN & activestatus)
> + sensor_id[num_of_sensors++] = op_idx;
> +
> if (ALS_EN & activestatus)
> sensor_id[num_of_sensors++] = als_idx;
>
> diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h
> b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h index 05e400a4a83e..2eb61f4e8434
> 100644
> --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h
> +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h
> @@ -79,6 +79,7 @@ enum sensor_idx {
> accel_idx = 0,
> gyro_idx = 1,
> mag_idx = 2,
> + op_idx = 15,
> als_idx = 19
> };
Tested-by: Marc H (butmonkeh@gmail.com) Asus ROG Flow X13 (GV301) - working
^ permalink raw reply
* [dtor-input:for-linus] BUILD SUCCESS ab93e0dd72c37d378dd936f031ffb83ff2bd87ce
From: kernel test robot @ 2025-08-07 13:55 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git for-linus
branch HEAD: ab93e0dd72c37d378dd936f031ffb83ff2bd87ce Merge branch 'next' into for-linus
elapsed time: 1220m
configs tested: 250
configs skipped: 4
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig clang-22
alpha allnoconfig gcc-15.1.0
alpha allyesconfig clang-19
alpha allyesconfig gcc-15.1.0
alpha defconfig clang-19
arc allmodconfig clang-19
arc allmodconfig gcc-15.1.0
arc allnoconfig clang-22
arc allnoconfig gcc-15.1.0
arc allyesconfig clang-19
arc allyesconfig gcc-15.1.0
arc defconfig clang-19
arc randconfig-001-20250807 clang-22
arc randconfig-001-20250807 gcc-13.4.0
arc randconfig-002-20250807 clang-22
arc randconfig-002-20250807 gcc-12.5.0
arm allmodconfig clang-19
arm allmodconfig gcc-15.1.0
arm allnoconfig clang-22
arm allyesconfig clang-19
arm allyesconfig gcc-15.1.0
arm axm55xx_defconfig clang-22
arm defconfig clang-19
arm jornada720_defconfig clang-22
arm mvebu_v7_defconfig clang-22
arm mxs_defconfig clang-22
arm randconfig-001-20250807 clang-22
arm randconfig-002-20250807 clang-22
arm randconfig-002-20250807 gcc-10.5.0
arm randconfig-003-20250807 clang-22
arm randconfig-004-20250807 clang-22
arm randconfig-004-20250807 gcc-8.5.0
arm64 allmodconfig clang-19
arm64 allnoconfig clang-22
arm64 allnoconfig gcc-15.1.0
arm64 defconfig clang-19
arm64 randconfig-001-20250807 clang-22
arm64 randconfig-001-20250807 gcc-8.5.0
arm64 randconfig-002-20250807 clang-22
arm64 randconfig-002-20250807 gcc-8.5.0
arm64 randconfig-003-20250807 clang-22
arm64 randconfig-004-20250807 clang-22
arm64 randconfig-004-20250807 gcc-14.3.0
csky allnoconfig clang-22
csky allnoconfig gcc-15.1.0
csky defconfig clang-19
csky defconfig clang-22
csky randconfig-001-20250807 gcc-15.1.0
csky randconfig-002-20250807 gcc-15.1.0
hexagon allmodconfig clang-17
hexagon allmodconfig clang-19
hexagon allnoconfig clang-22
hexagon allyesconfig clang-19
hexagon allyesconfig clang-22
hexagon defconfig clang-19
hexagon randconfig-001-20250807 clang-22
hexagon randconfig-001-20250807 gcc-15.1.0
hexagon randconfig-002-20250807 clang-22
hexagon randconfig-002-20250807 gcc-15.1.0
i386 allmodconfig clang-20
i386 allmodconfig gcc-12
i386 allnoconfig clang-20
i386 allnoconfig gcc-12
i386 allyesconfig clang-20
i386 allyesconfig gcc-12
i386 buildonly-randconfig-001-20250807 clang-20
i386 buildonly-randconfig-001-20250807 gcc-12
i386 buildonly-randconfig-002-20250807 clang-20
i386 buildonly-randconfig-002-20250807 gcc-12
i386 buildonly-randconfig-003-20250807 gcc-12
i386 buildonly-randconfig-004-20250807 gcc-11
i386 buildonly-randconfig-004-20250807 gcc-12
i386 buildonly-randconfig-005-20250807 gcc-12
i386 buildonly-randconfig-006-20250807 gcc-12
i386 defconfig clang-20
i386 randconfig-001-20250807 gcc-12
i386 randconfig-002-20250807 gcc-12
i386 randconfig-003-20250807 gcc-12
i386 randconfig-004-20250807 gcc-12
i386 randconfig-005-20250807 gcc-12
i386 randconfig-006-20250807 gcc-12
i386 randconfig-007-20250807 gcc-12
i386 randconfig-011-20250807 clang-20
i386 randconfig-012-20250807 clang-20
i386 randconfig-013-20250807 clang-20
i386 randconfig-014-20250807 clang-20
i386 randconfig-015-20250807 clang-20
i386 randconfig-016-20250807 clang-20
i386 randconfig-017-20250807 clang-20
loongarch allmodconfig clang-19
loongarch allnoconfig clang-22
loongarch defconfig clang-19
loongarch randconfig-001-20250807 clang-22
loongarch randconfig-001-20250807 gcc-15.1.0
loongarch randconfig-002-20250807 gcc-15.1.0
m68k allmodconfig clang-19
m68k allmodconfig gcc-15.1.0
m68k allnoconfig gcc-15.1.0
m68k allyesconfig clang-19
m68k allyesconfig gcc-15.1.0
m68k defconfig clang-19
microblaze allmodconfig clang-19
microblaze allmodconfig gcc-15.1.0
microblaze allnoconfig gcc-15.1.0
microblaze allyesconfig clang-19
microblaze allyesconfig gcc-15.1.0
microblaze defconfig gcc-15.1.0
mips allnoconfig gcc-15.1.0
mips rb532_defconfig clang-22
nios2 alldefconfig clang-22
nios2 allnoconfig gcc-11.5.0
nios2 allnoconfig gcc-15.1.0
nios2 defconfig gcc-15.1.0
nios2 randconfig-001-20250807 gcc-11.5.0
nios2 randconfig-001-20250807 gcc-15.1.0
nios2 randconfig-002-20250807 gcc-15.1.0
nios2 randconfig-002-20250807 gcc-8.5.0
openrisc allnoconfig clang-22
openrisc allnoconfig gcc-15.1.0
openrisc allyesconfig gcc-15.1.0
openrisc defconfig gcc-12
parisc allmodconfig gcc-15.1.0
parisc allnoconfig clang-22
parisc allnoconfig gcc-15.1.0
parisc allyesconfig gcc-15.1.0
parisc defconfig gcc-15.1.0
parisc randconfig-001-20250807 gcc-15.1.0
parisc randconfig-001-20250807 gcc-8.5.0
parisc randconfig-002-20250807 gcc-11.5.0
parisc randconfig-002-20250807 gcc-15.1.0
parisc64 defconfig gcc-15.1.0
powerpc allmodconfig gcc-15.1.0
powerpc allnoconfig clang-22
powerpc allnoconfig gcc-15.1.0
powerpc allyesconfig clang-22
powerpc allyesconfig gcc-15.1.0
powerpc amigaone_defconfig clang-22
powerpc currituck_defconfig clang-22
powerpc ppc64_defconfig clang-22
powerpc randconfig-001-20250807 gcc-12.5.0
powerpc randconfig-001-20250807 gcc-15.1.0
powerpc randconfig-002-20250807 gcc-10.5.0
powerpc randconfig-002-20250807 gcc-15.1.0
powerpc randconfig-003-20250807 gcc-11.5.0
powerpc randconfig-003-20250807 gcc-15.1.0
powerpc wii_defconfig clang-22
powerpc64 randconfig-001-20250807 gcc-11.5.0
powerpc64 randconfig-001-20250807 gcc-15.1.0
powerpc64 randconfig-002-20250807 clang-22
powerpc64 randconfig-002-20250807 gcc-15.1.0
powerpc64 randconfig-003-20250807 gcc-13.4.0
powerpc64 randconfig-003-20250807 gcc-15.1.0
riscv allmodconfig clang-22
riscv allmodconfig gcc-15.1.0
riscv allnoconfig clang-22
riscv allnoconfig gcc-15.1.0
riscv allyesconfig clang-16
riscv allyesconfig gcc-15.1.0
riscv defconfig gcc-12
riscv nommu_virt_defconfig clang-22
riscv randconfig-001-20250807 gcc-12.5.0
riscv randconfig-002-20250807 gcc-12.5.0
riscv randconfig-002-20250807 gcc-8.5.0
s390 allmodconfig clang-18
s390 allmodconfig gcc-15.1.0
s390 allnoconfig clang-22
s390 allyesconfig gcc-15.1.0
s390 defconfig gcc-12
s390 randconfig-001-20250807 clang-22
s390 randconfig-001-20250807 gcc-12.5.0
s390 randconfig-002-20250807 clang-22
s390 randconfig-002-20250807 gcc-12.5.0
sh allmodconfig gcc-15.1.0
sh allnoconfig gcc-15.1.0
sh allyesconfig gcc-15.1.0
sh defconfig gcc-12
sh randconfig-001-20250807 gcc-12.5.0
sh randconfig-001-20250807 gcc-14.3.0
sh randconfig-002-20250807 gcc-12.5.0
sh randconfig-002-20250807 gcc-9.5.0
sh rts7751r2d1_defconfig clang-22
sh se7721_defconfig clang-22
sparc allmodconfig gcc-15.1.0
sparc allnoconfig gcc-15.1.0
sparc defconfig gcc-15.1.0
sparc randconfig-001-20250807 gcc-12.5.0
sparc randconfig-001-20250807 gcc-14.3.0
sparc randconfig-002-20250807 gcc-12.5.0
sparc randconfig-002-20250807 gcc-15.1.0
sparc64 defconfig gcc-12
sparc64 randconfig-001-20250807 clang-22
sparc64 randconfig-001-20250807 gcc-12.5.0
sparc64 randconfig-002-20250807 clang-22
sparc64 randconfig-002-20250807 gcc-12.5.0
um allmodconfig clang-19
um allnoconfig clang-22
um allyesconfig clang-19
um allyesconfig gcc-12
um defconfig gcc-12
um i386_defconfig gcc-12
um randconfig-001-20250807 gcc-12
um randconfig-001-20250807 gcc-12.5.0
um randconfig-002-20250807 gcc-11
um randconfig-002-20250807 gcc-12.5.0
um x86_64_defconfig gcc-12
x86_64 allnoconfig clang-20
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20250807 clang-20
x86_64 buildonly-randconfig-001-20250807 gcc-11
x86_64 buildonly-randconfig-002-20250807 gcc-11
x86_64 buildonly-randconfig-002-20250807 gcc-12
x86_64 buildonly-randconfig-003-20250807 clang-20
x86_64 buildonly-randconfig-003-20250807 gcc-11
x86_64 buildonly-randconfig-004-20250807 clang-20
x86_64 buildonly-randconfig-004-20250807 gcc-11
x86_64 buildonly-randconfig-005-20250807 gcc-11
x86_64 buildonly-randconfig-006-20250807 gcc-11
x86_64 buildonly-randconfig-006-20250807 gcc-12
x86_64 defconfig clang-20
x86_64 defconfig gcc-11
x86_64 kexec clang-20
x86_64 randconfig-001-20250807 gcc-12
x86_64 randconfig-002-20250807 gcc-12
x86_64 randconfig-003-20250807 gcc-12
x86_64 randconfig-004-20250807 gcc-12
x86_64 randconfig-005-20250807 gcc-12
x86_64 randconfig-006-20250807 gcc-12
x86_64 randconfig-007-20250807 gcc-12
x86_64 randconfig-008-20250807 gcc-12
x86_64 randconfig-071-20250807 clang-20
x86_64 randconfig-072-20250807 clang-20
x86_64 randconfig-073-20250807 clang-20
x86_64 randconfig-074-20250807 clang-20
x86_64 randconfig-075-20250807 clang-20
x86_64 randconfig-076-20250807 clang-20
x86_64 randconfig-077-20250807 clang-20
x86_64 randconfig-078-20250807 clang-20
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-12
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-12
x86_64 rhel-9.4-ltp gcc-12
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig gcc-15.1.0
xtensa randconfig-001-20250807 gcc-12.5.0
xtensa randconfig-001-20250807 gcc-15.1.0
xtensa randconfig-002-20250807 gcc-12.5.0
xtensa randconfig-002-20250807 gcc-8.5.0
xtensa virt_defconfig clang-22
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH 0/6] Input: xbox_gip - Add new driver for Xbox GIP
From: Vicki Pfau @ 2025-08-08 4:30 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
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
- Controllable LED support
- HID passthrough for the Chatpad
- Preliminary support for racing wheels and flight sticks
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:
- Improved support for racing wheels and flight sticks, including force
feedback and LED support.
- 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.
Also included in this series is the addition of three new ABS input types, with
the two relevant ones to HID added to the mappings
Vicki Pfau (6):
Input: xbox_gip - Add new driver for Xbox GIP
Input: xpad - Remove Xbox One support
Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER
HID: Map more automobile simulation inputs
Input: xbox_gip - Add wheel support
Input: xbox_gip - Add flight stick support
Documentation/input/devices/xpad.rst | 17 +-
MAINTAINERS | 6 +
drivers/hid/hid-debug.c | 16 +-
drivers/hid/hid-input.c | 2 +
drivers/input/joystick/Kconfig | 26 +
drivers/input/joystick/Makefile | 1 +
drivers/input/joystick/xbox_gip.c | 3434 ++++++++++++++++++++++++
drivers/input/joystick/xpad.c | 634 +----
include/uapi/linux/input-event-codes.h | 3 +
9 files changed, 3492 insertions(+), 647 deletions(-)
create mode 100644 drivers/input/joystick/xbox_gip.c
--
2.50.1
^ permalink raw reply
* [PATCH 1/6] Input: xbox_gip - Add new driver for Xbox GIP
From: Vicki Pfau @ 2025-08-08 4:30 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
In-Reply-To: <20250808043017.1953101-1-vi@endrift.com>
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
- Controllable LED support
- HID passthrough for the Chatpad
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 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 | 26 +
drivers/input/joystick/Makefile | 1 +
drivers/input/joystick/xbox_gip.c | 3121 +++++++++++++++++++++++++++++
4 files changed, 3154 insertions(+)
create mode 100644 drivers/input/joystick/xbox_gip.c
diff --git a/MAINTAINERS b/MAINTAINERS
index c0b444e5fd5ad..0088831b7f717 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26993,6 +26993,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/xbox_gip.c
+
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..609edc8d78c5d 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -291,6 +291,32 @@ config JOYSTICK_JOYDUMP
To compile this driver as a module, choose M here: the
module will be called joydump.
+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.
+
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..ad92f1b64b96a 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) += xbox_gip.o
obj-$(CONFIG_JOYSTICK_XPAD) += xpad.o
obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o
diff --git a/drivers/input/joystick/xbox_gip.c b/drivers/input/joystick/xbox_gip.c
new file mode 100644
index 0000000000000..afb70311c064e
--- /dev/null
+++ b/drivers/input/joystick/xbox_gip.c
@@ -0,0 +1,3121 @@
+// 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
+ * - More arcade stick testing
+ * - Arcade stick extra buttons
+ *
+ * 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/hid.h>
+#include <linux/module.h>
+#include <linux/usb/input.h>
+#include <linux/uuid.h>
+#ifdef CONFIG_JOYSTICK_XBOX_GIP_LEDS
+#include <linux/led-class-multicolor.h>
+#endif
+
+#define GIP_WIRED_INTF_DATA 0
+#define GIP_WIRED_INTF_AUDIO 1
+
+#define BASE_GIP_MTU 64
+#define MAX_GIP_MTU 2048
+
+#define MAX_MESSAGE_LENGTH 0x4000
+#define MAX_ATTACHMENTS 8
+
+#define MAX_IN_MESSAGES 8
+#define MAX_OUT_MESSAGES 8
+
+#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)
+
+/* 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
+
+/* Wheel vendor messages */
+#define GIP_CMD_SET_APPLICATION_MEMORY 0x0b
+#define GIP_CMD_SET_EQUATIONS_STATES 0x0c
+#define GIP_CMD_SET_EQUATION 0x0d
+
+/* FlightStick vendor messages */
+#define GIP_CMD_DEVICE_CAPABILITIES 0x00
+#define GIP_CMD_LED_CAPABILITIES 0x01
+#define GIP_CMD_SET_LED_STATE 0x02
+
+/* 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_LID_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_CONSOLE_FUNCTION_MAP BIT(0)
+#define GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW BIT(1)
+#define GIP_FEATURE_ELITE_BUTTONS BIT(2)
+#define GIP_FEATURE_DYNAMIC_LATENCY_INPUT BIT(3)
+#define GIP_FEATURE_SECURITY_OPT_OUT BIT(4)
+#define GIP_FEATURE_MOTOR_CONTROL BIT(5)
+#define GIP_FEATURE_GUIDE_COLOR BIT(6)
+#define GIP_FEATURE_EXTENDED_SET_DEVICE_STATE BIT(7)
+
+#define GIP_QUIRK_NO_HELLO BIT(0)
+#define GIP_QUIRK_NO_IMPULSE_VIBRATION BIT(1)
+
+#define GIP_LED_GUIDE_MAX_BRIGHTNESS 100 /* Spec says 47, but larger values work */
+#define GIP_LED_GUIDE_INIT_BRIGHTNESS 20
+
+#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)
+
+static bool dpad_as_buttons;
+
+enum gip_metadata_status {
+ GIP_METADATA_NONE = 0,
+ GIP_METADATA_GOT = 1,
+ GIP_METADATA_FAKED = 2,
+ GIP_METADATA_PENDING = 3,
+};
+
+#ifndef VK_LWIN
+#define VK_LWIN 0x5b
+#endif
+
+enum gip_attachment_type {
+ GIP_TYPE_UNKNOWN = -1,
+ GIP_TYPE_GAMEPAD = 0,
+ GIP_TYPE_ARCADE_STICK = 1,
+ GIP_TYPE_WHEEL = 2,
+ GIP_TYPE_FLIGHT_STICK = 3,
+ GIP_TYPE_NAVIGATION_CONTROLLER = 4,
+ GIP_TYPE_CHATPAD = 5,
+ GIP_TYPE_HEADSET = 6,
+};
+
+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,
+};
+
+static const guid_t guid_arcade_stick =
+ GUID_INIT(0x332054cc, 0xa34b, 0x41d5, 0xa3, 0x4a, 0xa6, 0xa6, 0x71, 0x1e, 0xc4, 0xb3);
+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_flight_stick =
+ GUID_INIT(0x03f1a011, 0xefe9, 0x4cc1, 0x96, 0x9c, 0x38, 0xdc, 0x55, 0xf4, 0x04, 0xd0);
+static const guid_t guid_gamepad =
+ GUID_INIT(0x082e402c, 0x07df, 0x45e1, 0xa5, 0xab, 0xa3, 0x12, 0x7a, 0xf1, 0x97, 0xb5);
+static const guid_t guid_headset =
+ GUID_INIT(0xbc25d1a3, 0xc24e, 0x4992, 0x9d, 0xda, 0xef, 0x4f, 0x12, 0x3e, 0xf5, 0xdc);
+static const guid_t guid_navigation =
+ GUID_INIT(0xb8f31fe7, 0x7386, 0x40e9, 0xa9, 0xf8, 0x2f, 0x21, 0x26, 0x3a, 0xcf, 0xb7);
+static const guid_t guid_wheel =
+ GUID_INIT(0x646979cf, 0x6b71, 0x4e96, 0x8d, 0xf9, 0x59, 0xe3, 0x98, 0xd7, 0x42, 0x0c);
+
+static const guid_t guid_logi_true_force_wheel =
+ GUID_INIT(0x6ca319e5, 0x0bc0, 0x41be, 0x83, 0x19, 0x6b, 0xb7, 0x10, 0x81, 0xec, 0x55);
+
+/*
+ * 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 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 },
+};
+
+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];
+ enum gip_attachment_type device_type;
+ uint8_t extra_buttons;
+ uint8_t extra_axes;
+};
+
+static const struct gip_quirks 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" },
+
+ /* PDP Rock Candy */
+ { 0x0e6f, 0x0246, 0, .quirks = GIP_QUIRK_NO_HELLO },
+
+ {0},
+};
+
+struct gip_header {
+ uint8_t message_type;
+ uint8_t flags;
+ uint8_t sequence_id;
+ uint64_t length;
+};
+
+struct gip_audio_format_pair {
+ uint8_t inbound;
+ uint8_t outbound;
+};
+static_assert(sizeof(struct gip_audio_format_pair) == 2);
+
+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;
+
+ enum gip_attachment_type device_type;
+};
+
+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_device;
+struct gip_attachment {
+ struct gip_device *device;
+ uint8_t attachment_index;
+ struct input_dev *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;
+#ifdef CONFIG_JOYSTICK_XBOX_GIP_LEDS
+ union {
+ struct led_classdev standard;
+ struct led_classdev_mc color;
+ } guide_led;
+#endif
+
+ enum gip_attachment_type attachment_type;
+ 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 *hdev;
+};
+
+struct gip_raw_message {
+ uint16_t num_bytes;
+ uint8_t bytes[BASE_GIP_MTU];
+};
+
+struct gip_interface {
+ struct gip_device *device;
+ struct usb_interface *intf;
+ uint32_t mtu;
+
+ struct urb *urb_in;
+ uint8_t *in_data;
+
+ struct urb *urb_out;
+ struct usb_anchor out_anchor;
+ bool urb_out_active;
+ uint8_t *out_data;
+
+ struct gip_raw_message out_queue[MAX_OUT_MESSAGES];
+ int pending_out;
+ int next_out;
+};
+
+struct gip_device {
+ struct usb_device *udev;
+
+ struct gip_interface data;
+
+ 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_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_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_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;
+};
+
+struct gip_initial_reports_request {
+ uint8_t type;
+ uint8_t data[2];
+};
+
+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 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;
+}
+
+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);
+}
+
+static 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(struct gip_attachment *attachment)
+{
+ 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;
+ attachment->attachment_type = quirks[i].device_type;
+
+ 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 int gip_prepare_urb(struct gip_interface *intf)
+{
+ int rc;
+
+ if (!intf->urb_out)
+ return -ENODEV;
+
+ struct gip_raw_message *message = &intf->out_queue[intf->next_out];
+
+ intf->pending_out--;
+ intf->next_out = (intf->next_out + 1) % MAX_OUT_MESSAGES;
+
+ memcpy(intf->out_data, message->bytes, message->num_bytes);
+ intf->urb_out->transfer_buffer_length = message->num_bytes;
+
+ usb_anchor_urb(intf->urb_out, &intf->out_anchor);
+ rc = usb_submit_urb(intf->urb_out, GFP_ATOMIC);
+ if (rc) {
+ dev_err(&intf->intf->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, rc);
+ usb_unanchor_urb(intf->urb_out);
+ intf->urb_out_active = false;
+ rc = -EIO;
+ } else {
+ intf->urb_out_active = true;
+ }
+
+ return rc;
+}
+
+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;
+ unsigned long irqflags;
+ 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)
+ /* TODO: Needs isochronous transfer support */
+ return -ENOTSUPP;
+ else
+ intf = &device->data;
+
+ spin_lock_irqsave(&device->message_lock, irqflags);
+ if (intf->pending_out >= MAX_OUT_MESSAGES) {
+ dev_err(GIP_DEV(device), "Output queue is full; dropping message\n");
+ } else {
+ int message_id = (intf->next_out + intf->pending_out) % MAX_OUT_MESSAGES;
+ struct gip_raw_message *message = &intf->out_queue[message_id];
+
+ intf->pending_out++;
+
+ message->bytes[0] = message_type;
+ message->bytes[1] = flags;
+ message->bytes[2] = seq;
+ offset += gip_encode_length(num_bytes, &message->bytes[offset],
+ sizeof(message->bytes) - offset);
+
+ if (num_bytes > 0)
+ memcpy(&message->bytes[offset], bytes, num_bytes);
+
+ num_bytes += offset;
+ message->num_bytes = num_bytes;
+
+ print_hex_dump_debug(KBUILD_MODNAME ": Sending message: ",
+ DUMP_PREFIX_OFFSET, 16, 1, message->bytes, num_bytes,
+ false);
+ }
+ if (!intf->urb_out_active)
+ rc = gip_prepare_urb(intf);
+
+ spin_unlock_irqrestore(&device->message_lock, irqflags);
+
+ return rc;
+}
+
+static 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);
+}
+
+static 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 int gip_hid_ll_parse(struct hid_device *hdev)
+{
+ struct gip_attachment *attachment = hdev->driver_data;
+
+ return hid_parse_report(hdev,
+ attachment->metadata.device.hid_descriptor,
+ attachment->metadata.device.hid_descriptor_size);
+}
+
+static int gip_hid_ll_start(struct hid_device *hdev)
+{
+ return 0;
+}
+
+static void gip_hid_ll_stop(struct hid_device *hdev)
+{
+}
+
+static int gip_hid_ll_open(struct hid_device *hdev)
+{
+ return 0;
+}
+
+static void gip_hid_ll_close(struct hid_device *hdev)
+{
+}
+
+static int gip_hid_ll_raw_request(struct hid_device *hdev,
+ unsigned char reportnum, uint8_t *buf, size_t count,
+ unsigned char report_type, int reqtype)
+{
+ /*
+ * TODO: Based on the metadata, output reports appear to be possible,
+ * but the chatpad doesn't have the LEDs it claims to support, so
+ * it's not clear how to test we're sending them properly.
+ */
+ return 0;
+}
+
+static const struct hid_ll_driver gip_hid_ll_driver = {
+ .parse = gip_hid_ll_parse,
+ .start = gip_hid_ll_start,
+ .stop = gip_hid_ll_stop,
+ .open = gip_hid_ll_open,
+ .close = gip_hid_ll_close,
+ .raw_request = gip_hid_ll_raw_request,
+};
+
+static bool gip_attachment_is_controller(struct gip_attachment *attachment)
+{
+ return attachment->attachment_type != GIP_TYPE_CHATPAD &&
+ attachment->attachment_type != GIP_TYPE_HEADSET;
+}
+
+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_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;
+
+#ifdef CONFIG_JOYSTICK_XBOX_GIP_LEDS
+ if (!(attachment->features & GIP_FEATURE_GUIDE_COLOR))
+ attachment->guide_led.standard.brightness = intensity;
+#endif
+
+ return gip_send_system_message(attachment, GIP_CMD_LED, 0, buffer, sizeof(buffer));
+}
+
+#ifdef CONFIG_JOYSTICK_XBOX_GIP_LEDS
+static int gip_send_guide_button_color_led(struct gip_attachment *attachment,
+ uint8_t r, uint8_t g, uint8_t b, uint8_t w)
+{
+ uint8_t buffer[] = { 0x00, w, r, g, b };
+
+ if (!(attachment->features & GIP_FEATURE_GUIDE_COLOR))
+ return -EINVAL;
+
+ attachment->guide_led.color.subled_info[0].brightness = r;
+ attachment->guide_led.color.subled_info[1].brightness = g;
+ attachment->guide_led.color.subled_info[2].brightness = b;
+ attachment->guide_led.color.subled_info[3].brightness = w;
+
+ return gip_send_vendor_message(attachment, GIP_CMD_GUIDE_COLOR, 0, buffer, sizeof(buffer));
+}
+
+static int gip_guide_led_set(struct led_classdev *led,
+ enum led_brightness value)
+{
+ struct gip_attachment *attachment = container_of(led,
+ struct gip_attachment, guide_led.standard);
+
+ guard(mutex)(&attachment->lock);
+ return gip_send_guide_button_led(attachment, GIP_LED_GUIDE_ON, value);
+}
+
+static int gip_guide_color_led_set(struct led_classdev *led,
+ enum led_brightness value)
+{
+ struct led_classdev_mc *mc_cdev = container_of(led,
+ struct led_classdev_mc, led_cdev);
+ struct gip_attachment *attachment = container_of(mc_cdev,
+ struct gip_attachment, guide_led.color);
+
+ led_mc_calc_color_components(mc_cdev, value);
+ guard(mutex)(&attachment->lock);
+ return gip_send_guide_button_color_led(attachment,
+ mc_cdev->subled_info[0].brightness,
+ mc_cdev->subled_info[1].brightness,
+ mc_cdev->subled_info[2].brightness,
+ mc_cdev->subled_info[3].brightness);
+}
+
+static int gip_guide_led_probe(struct gip_attachment *attachment, struct device *dev)
+{
+ int rc;
+
+ if (!gip_supports_system_message(attachment, GIP_CMD_LED, false))
+ return 0;
+
+ if (attachment->features & GIP_FEATURE_GUIDE_COLOR) {
+ struct mc_subled *mc_led_info;
+ struct led_classdev_mc *mc_cdev = &attachment->guide_led.color;
+ struct led_classdev *cdev = &mc_cdev->led_cdev;
+
+ mc_led_info = devm_kcalloc(dev, 4,
+ sizeof(*mc_led_info), GFP_KERNEL);
+ if (!mc_led_info)
+ return -ENOMEM;
+
+ mc_led_info[0].color_index = LED_COLOR_ID_RED;
+ mc_led_info[1].color_index = LED_COLOR_ID_GREEN;
+ mc_led_info[2].color_index = LED_COLOR_ID_BLUE;
+ mc_led_info[3].color_index = LED_COLOR_ID_WHITE;
+
+ mc_cdev->subled_info = mc_led_info;
+ mc_cdev->num_colors = 4;
+
+ cdev->brightness = 51;
+ cdev->max_brightness = 255;
+ cdev->flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
+ cdev->brightness_set_blocking = gip_guide_color_led_set;
+ cdev->name = devm_kasprintf(dev, GFP_KERNEL,
+ "%s:rgb:power", dev_name(dev));
+
+ rc = devm_led_classdev_multicolor_register(dev,
+ &attachment->guide_led.color);
+
+ if (rc)
+ devm_kfree(dev, mc_led_info);
+ } else {
+ struct led_classdev *cdev = &attachment->guide_led.standard;
+
+ cdev->max_brightness = GIP_LED_GUIDE_MAX_BRIGHTNESS;
+ cdev->brightness = GIP_LED_GUIDE_INIT_BRIGHTNESS;
+ cdev->flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
+ cdev->brightness_set_blocking = gip_guide_led_set;
+ cdev->name = devm_kasprintf(dev, GFP_KERNEL,
+ "%s:white:power", dev_name(dev));
+
+ rc = devm_led_classdev_register(dev,
+ &attachment->guide_led.standard);
+ }
+
+ return rc;
+}
+#endif
+
+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_setup_input_device(struct gip_attachment *attachment)
+{
+ struct input_dev *input;
+ int rc;
+
+ 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;
+
+ /* Navigation buttons */
+ 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);
+ }
+
+ switch (attachment->attachment_type) {
+ case GIP_TYPE_GAMEPAD:
+ 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);
+ break;
+ case GIP_TYPE_ARCADE_STICK:
+ input_set_capability(input, EV_KEY, BTN_THUMBR);
+ input_set_capability(input, EV_KEY, BTN_THUMBL);
+ break;
+ case GIP_TYPE_FLIGHT_STICK:
+ case GIP_TYPE_WHEEL:
+ case GIP_TYPE_UNKNOWN:
+ case GIP_TYPE_NAVIGATION_CONTROLLER:
+ break;
+ case GIP_TYPE_CHATPAD:
+ case GIP_TYPE_HEADSET:
+ rc = -ENODEV;
+ 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);
+ }
+
+ /* Xbox Adaptive Controller */
+ if (attachment->vendor_id == 0x045e && attachment->product_id == 0x0b0a)
+ input_set_abs_params(input, ABS_PROFILE, 0, 3, 0, 0);
+
+#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);
+ attachment->input = input;
+ rc = input_register_device(input);
+ if (rc)
+ goto err_free_device;
+
+#ifdef CONFIG_JOYSTICK_XBOX_GIP_LEDS
+ rc = gip_guide_led_probe(attachment, &input->dev);
+ if (rc)
+ dev_err(GIP_DEV(attachment), "Failed to register LEDs: %d\n", rc);
+#endif
+
+ 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 (attachment->attachment_type != GIP_TYPE_HEADSET) {
+ 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 (gip_attachment_is_controller(attachment) && !attachment->input) {
+ rc = gip_setup_input_device(attachment);
+ if (rc == -ENODEV)
+ return 0;
+ }
+
+ if (attachment->metadata.device.hid_descriptor) {
+ struct hid_device *hdev = hid_allocate_device();
+
+ if (IS_ERR(hdev))
+ return PTR_ERR(hdev);
+
+ hdev->ll_driver = &gip_hid_ll_driver;
+ hdev->bus = BUS_USB;
+ hdev->vendor = attachment->vendor_id;
+ hdev->product = attachment->product_id;
+ hdev->dev.parent = GIP_DEV(attachment);
+ hdev->driver_data = attachment;
+ if (attachment->name)
+ strscpy(hdev->name, attachment->name);
+ else
+ strscpy(hdev->name, "Xbox Chatpad");
+ strscpy(hdev->phys, attachment->phys);
+ rc = hid_add_device(hdev);
+ if (rc) {
+ dev_err(GIP_DEV(attachment), "HID device add failed: %d\n", rc);
+ hid_destroy_device(hdev);
+ } else {
+ attachment->hdev = hdev;
+ }
+ }
+
+ return rc;
+}
+
+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 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);
+
+ if (header->flags & GIP_FLAG_ATTACHMENT_MASK)
+ return gip_send_system_message(attachment, GIP_CMD_METADATA, 0, NULL, 0);
+
+ 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 (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)
+{
+ struct gip_extended_status status = {{0}};
+ int i;
+
+ if (num_bytes < 1)
+ return -EINVAL;
+
+ status.base.battery_level = bytes[0] & 3;
+ status.base.battery_type = (bytes[0] >> 2) & 3;
+ status.base.charge = (bytes[0] >> 4) & 3;
+ status.base.power_level = (bytes[0] >> 6) & 3;
+
+ if (num_bytes >= 4) {
+ status.device_active = bytes[1] & 1;
+ if (bytes[1] & 2) {
+ /* Events present */
+ if (num_bytes < 5)
+ return -EINVAL;
+
+ status.num_events = bytes[4];
+ if (status.num_events > 5) {
+ dev_info(GIP_DEV(attachment),
+ "Device reported too many events, %d > 5\n",
+ status.num_events);
+ return -EINVAL;
+ }
+ if (5 + status.num_events * 10 > num_bytes)
+ return -EINVAL;
+
+ for (i = 0; i < status.num_events; i++) {
+ status.events[i].event_type = bytes[i * 10 + 5];
+ status.events[i].event_type |= bytes[i * 10 + 6] << 8;
+ status.events[i].fault_tag = bytes[i * 10 + 7];
+ status.events[i].fault_tag |= bytes[i * 10 + 8] << 8;
+ status.events[i].fault_tag |= bytes[i * 10 + 9] << 16;
+ status.events[i].fault_tag |= bytes[i * 10 + 10] << 24;
+ status.events[i].fault_address = bytes[i * 10 + 11];
+ status.events[i].fault_address |= bytes[i * 10 + 12] << 8;
+ status.events[i].fault_address |= bytes[i * 10 + 13] << 16;
+ status.events[i].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,
+ status.events[i].event_type,
+ status.events[i].fault_tag,
+ status.events[i].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;
+ int rc;
+
+ rc = gip_parse_metadata(GIP_DEV(attachment), &metadata, bytes, num_bytes);
+ if (rc)
+ return rc;
+
+ if (attachment->got_metadata == GIP_METADATA_GOT) {
+ gip_metadata_free(GIP_DEV(attachment), &attachment->metadata);
+ if (attachment->input) {
+ input_unregister_device(attachment->input);
+ attachment->input = NULL;
+ }
+ }
+
+ attachment->metadata = metadata;
+ attachment->got_metadata = GIP_METADATA_GOT;
+ attachment->features = 0;
+ cancel_delayed_work_sync(&attachment->metadata_next);
+
+ attachment->attachment_type = GIP_TYPE_UNKNOWN;
+ 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];
+
+ if (strcmp(type, "Windows.Xbox.Input.Gamepad") == 0) {
+ attachment->attachment_type = GIP_TYPE_GAMEPAD;
+ expected_guid = &guid_gamepad;
+ break;
+ }
+ if (strcmp(type, "Microsoft.Xbox.Input.ArcadeStick") == 0) {
+ attachment->attachment_type = GIP_TYPE_ARCADE_STICK;
+ expected_guid = &guid_arcade_stick;
+ break;
+ }
+ if (strcmp(type, "Windows.Xbox.Input.ArcadeStick") == 0) {
+ attachment->attachment_type = GIP_TYPE_ARCADE_STICK;
+ expected_guid = &guid_arcade_stick;
+ break;
+ }
+ if (strcmp(type, "Microsoft.Xbox.Input.FlightStick") == 0) {
+ attachment->attachment_type = GIP_TYPE_FLIGHT_STICK;
+ expected_guid = &guid_flight_stick;
+ break;
+ }
+ if (strcmp(type, "Windows.Xbox.Input.FlightStick") == 0) {
+ attachment->attachment_type = GIP_TYPE_FLIGHT_STICK;
+ expected_guid = &guid_flight_stick;
+ break;
+ }
+ if (strcmp(type, "Microsoft.Xbox.Input.Wheel") == 0) {
+ attachment->attachment_type = GIP_TYPE_WHEEL;
+ expected_guid = &guid_wheel;
+ break;
+ }
+ if (strcmp(type, "Windows.Xbox.Input.Wheel") == 0) {
+ attachment->attachment_type = GIP_TYPE_WHEEL;
+ expected_guid = &guid_wheel;
+ break;
+ }
+ if (strcmp(type, "Windows.Xbox.Input.NavigationController") == 0) {
+ attachment->attachment_type = GIP_TYPE_NAVIGATION_CONTROLLER;
+ expected_guid = &guid_navigation;
+ break;
+ }
+ if (strcmp(type, "Windows.Xbox.Input.Chatpad") == 0) {
+ attachment->attachment_type = GIP_TYPE_CHATPAD;
+ break;
+ }
+ if (strcmp(type, "Windows.Xbox.Input.Headset") == 0) {
+ attachment->attachment_type = GIP_TYPE_HEADSET;
+ 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(&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 || (gip_attachment_is_controller(attachment)
+ && !found_controller_guid))
+ dev_dbg(GIP_DEV(attachment),
+ "Controller was missing expected GUID. "
+ "This controller probably won't work on an actual Xbox.\n");
+
+ if ((attachment->features & GIP_FEATURE_GUIDE_COLOR)
+ && !gip_supports_vendor_message(attachment,
+ GIP_CMD_GUIDE_COLOR, false))
+ attachment->features &= ~GIP_FEATURE_GUIDE_COLOR;
+
+ gip_handle_quirks(attachment);
+
+ dev_dbg(GIP_DEV(attachment),
+ "Attachment %i of type %i has features: %02x\n",
+ attachment->attachment_index, attachment->attachment_type,
+ 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)
+{
+ if (num_bytes < 2)
+ return -EINVAL;
+ if (!attachment->input)
+ return -ENODEV;
+
+ if (bytes[1] == VK_LWIN) {
+ input_report_key(attachment->input, BTN_MODE, bytes[0] & 3);
+ input_sync(attachment->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_raw_report(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ if (!attachment->input)
+ return -ENODEV;
+
+ if (num_bytes < 17) {
+ dev_dbg(GIP_DEV(attachment), "Discarding too-short raw report\n");
+ return -EINVAL;
+ }
+
+ if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS)
+ && attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) {
+ input_report_abs(attachment->input, ABS_PROFILE, bytes[15] & 3);
+ if (bytes[15] & 3) {
+ input_report_key(attachment->input, BTN_GRIPL, 0);
+ input_report_key(attachment->input, BTN_GRIPR, 0);
+ input_report_key(attachment->input, BTN_GRIPL2, 0);
+ input_report_key(attachment->input, BTN_GRIPR2, 0);
+ } else {
+ input_report_key(attachment->input, BTN_GRIPL,
+ bytes[GIP_BTN_OFFSET_XBE2] & BIT(2));
+ input_report_key(attachment->input, BTN_GRIPR,
+ bytes[GIP_BTN_OFFSET_XBE2] & BIT(0));
+ input_report_key(attachment->input, BTN_GRIPL2,
+ bytes[GIP_BTN_OFFSET_XBE2] & BIT(3));
+ input_report_key(attachment->input, BTN_GRIPR2,
+ bytes[GIP_BTN_OFFSET_XBE2] & BIT(1));
+ }
+
+ input_sync(attachment->input);
+ }
+ return 0;
+}
+
+static int gip_handle_command_hid_report(struct gip_attachment *attachment,
+ const struct gip_header *header, uint8_t *bytes, int num_bytes)
+{
+ if (attachment->hdev)
+ return hid_input_report(attachment->hdev, HID_INPUT_REPORT,
+ bytes, num_bytes, true);
+ dev_warn(GIP_DEV(attachment), "Got HID report with no HID descriptor\n");
+ return -EINVAL;
+}
+
+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 void gip_handle_navigation_report(struct gip_attachment *attachment,
+ struct input_dev *dev, const u8 *bytes, int num_bytes)
+{
+ input_report_key(dev, BTN_START, bytes[0] & BIT(2));
+ input_report_key(dev, BTN_SELECT, bytes[0] & BIT(3));
+ input_report_key(dev, BTN_A, bytes[0] & BIT(4));
+ input_report_key(dev, BTN_B, bytes[0] & BIT(5));
+ input_report_key(dev, BTN_X, bytes[0] & BIT(6));
+ input_report_key(dev, BTN_Y, bytes[0] & BIT(7));
+
+ if (attachment->dpad_as_buttons) {
+ input_report_key(dev, BTN_DPAD_UP, bytes[1] & BIT(0));
+ input_report_key(dev, BTN_DPAD_DOWN, bytes[1] & BIT(1));
+ input_report_key(dev, BTN_DPAD_LEFT, bytes[1] & BIT(2));
+ input_report_key(dev, BTN_DPAD_RIGHT, bytes[1] & BIT(3));
+ } else {
+ input_report_abs(dev, ABS_HAT0X,
+ !!(bytes[1] & BIT(3)) - !!(bytes[1] & BIT(2)));
+ input_report_abs(dev, ABS_HAT0Y,
+ !!(bytes[1] & BIT(1)) - !!(bytes[1] & BIT(0)));
+ }
+
+ if (attachment->attachment_type == GIP_TYPE_ARCADE_STICK) {
+ /* Previous */
+ input_report_key(dev, BTN_TR, bytes[1] & BIT(4));
+ /* Next */
+ input_report_key(dev, BTN_TL, bytes[1] & BIT(5));
+ } else {
+ input_report_key(dev, BTN_TL, bytes[1] & BIT(4));
+ input_report_key(dev, BTN_TR, bytes[1] & BIT(5));
+ }
+}
+
+static void gip_handle_gamepad_report(struct gip_attachment *attachment,
+ struct input_dev *dev, const uint8_t *bytes, int num_bytes)
+{
+ int16_t axis;
+
+ input_report_key(dev, BTN_THUMBL, bytes[1] & BIT(6));
+ input_report_key(dev, BTN_THUMBR, bytes[1] & BIT(7));
+
+ axis = bytes[2];
+ axis |= bytes[3] << 8;
+ input_report_abs(dev, ABS_Z, axis);
+
+ axis = bytes[4];
+ axis |= bytes[5] << 8;
+ input_report_abs(dev, ABS_RZ, axis);
+
+ axis = bytes[6];
+ axis |= bytes[7] << 8;
+ input_report_abs(dev, ABS_X, axis);
+ axis = bytes[8];
+ axis |= bytes[9] << 8;
+ input_report_abs(dev, ABS_Y, ~axis);
+ axis = bytes[10];
+ axis |= bytes[11] << 8;
+ input_report_abs(dev, ABS_RX, axis);
+ axis = bytes[12];
+ axis |= bytes[13] << 8;
+ input_report_abs(dev, ABS_RY, ~axis);
+}
+
+static void gip_handle_arcade_stick_report(struct gip_attachment *attachment,
+ struct input_dev *dev, const uint8_t *bytes, int num_bytes)
+{
+ if (attachment->extra_axes >= 1) {
+ int16_t axis;
+
+ axis = bytes[2];
+ axis |= bytes[3] << 8;
+ input_report_abs(dev, ABS_Z, axis);
+ }
+
+ if (attachment->extra_axes >= 2) {
+ int16_t axis;
+
+ axis = bytes[4];
+ axis |= bytes[5] << 8;
+ input_report_abs(dev, ABS_RZ, axis);
+ }
+
+ if (num_bytes >= 19) {
+ /* Extra button 6 */
+ input_report_abs(dev, ABS_RZ, (bytes[18] & BIT(6)) ? 32767 : -32768);
+ /* Extra button 7 */
+ input_report_abs(dev, ABS_Z, (bytes[18] & BIT(7)) ? 32767 : -32768);
+ }
+}
+
+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 *dev = attachment->input;
+
+ if (!dev)
+ 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 (num_bytes < 14) {
+ dev_dbg(GIP_DEV(attachment), "Discarding too-short input report\n");
+ return -EINVAL;
+ }
+
+ gip_handle_navigation_report(attachment, dev, bytes, num_bytes);
+
+ switch (attachment->attachment_type) {
+ case GIP_TYPE_GAMEPAD:
+ default:
+ gip_handle_gamepad_report(attachment, dev, bytes, num_bytes);
+ break;
+ case GIP_TYPE_ARCADE_STICK:
+ gip_handle_arcade_stick_report(attachment, dev, bytes, num_bytes);
+ break;
+ }
+
+ if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) {
+ 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(attachment->input, BTN_GRIPL,
+ grip[0]);
+ input_report_key(attachment->input, BTN_GRIPR,
+ grip[1]);
+ input_report_key(attachment->input, BTN_GRIPL2,
+ grip[2]);
+ input_report_key(attachment->input, BTN_GRIPR2,
+ grip[3]);
+ input_report_abs(attachment->input, ABS_PROFILE,
+ profile);
+ }
+ }
+
+ if (attachment->vendor_id == 0x045e && attachment->product_id == 0x0b0a
+ && num_bytes >= 31)
+ input_report_abs(attachment->input, ABS_PROFILE,
+ bytes[30] & 3);
+
+ if ((attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP)
+ && num_bytes >= 32) {
+ int function_map_offset = -1;
+
+ 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(dev, KEY_RECORD,
+ bytes[function_map_offset] & BIT(0));
+ }
+ }
+
+ input_sync(dev);
+
+ return 0;
+}
+
+static int gip_handle_ll_static_configuration(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ /* TODO */
+ dev_dbg(GIP_DEV(attachment), "Unimplemented Static Configuration message\n");
+ return -ENOTSUPP;
+}
+
+static int gip_handle_ll_button_info_report(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ /* TODO */
+ dev_dbg(GIP_DEV(attachment), "Unimplemented Button Info Report message\n");
+ return -ENOTSUPP;
+}
+
+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;
+ if (attachment_index > 0)
+ attachment->attachment_type = GIP_TYPE_UNKNOWN;
+
+ attachment->device = device;
+ attachment->metadata.device.in_system_messages[0] =
+ GIP_DEFAULT_IN_SYSTEM_MESSAGES;
+ attachment->metadata.device.out_system_messages[0] =
+ GIP_DEFAULT_OUT_SYSTEM_MESSAGES;
+ 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);
+ }
+ 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);
+
+ switch (header->message_type) {
+ case GIP_CMD_RAW_REPORT:
+ if (attachment->features & GIP_FEATURE_ELITE_BUTTONS)
+ return gip_handle_command_raw_report(attachment,
+ header, bytes, num_bytes);
+ break;
+ case GIP_LL_INPUT_REPORT:
+ return gip_handle_ll_input_report(attachment, header, bytes,
+ num_bytes);
+ case GIP_LL_STATIC_CONFIGURATION:
+ return gip_handle_ll_static_configuration(attachment, header,
+ bytes, num_bytes);
+ case GIP_LL_BUTTON_INFO_REPORT:
+ return gip_handle_ll_button_info_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;
+ }
+
+ 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 */
+ if (intf->pending_out)
+ gip_prepare_urb(intf);
+ else
+ intf->urb_out_active = false;
+ 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);
+ intf->urb_out_active = false;
+ 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(0, 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;
+
+ 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);
+
+ 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);
+
+ intf->urb_out = usb_alloc_urb(0, GFP_KERNEL);
+ if (!intf->urb_out)
+ error = -ENOMEM;
+
+ intf->out_data = usb_alloc_coherent(udev, intf->mtu, GFP_KERNEL,
+ &intf->urb_out->transfer_dma);
+
+ if (!intf->out_data) {
+ return -ENOMEM;
+ goto err_free_urb;
+ }
+
+ usb_fill_int_urb(intf->urb_out, udev,
+ usb_sndintpipe(udev, ep_out->bEndpointAddress),
+ intf->out_data, intf->mtu, gip_urb_out, intf, ep_out->bInterval);
+ intf->urb_out->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ return 0;
+
+err_free_urb:
+ usb_free_urb(intf->urb_out);
+ intf->urb_out = NULL;
+ return error;
+}
+
+static void gip_deinit_output(struct gip_interface *intf)
+{
+ usb_free_coherent(interface_to_usbdev(intf->intf), intf->mtu, intf->out_data,
+ intf->urb_out->transfer_dma);
+ usb_free_urb(intf->urb_out);
+ intf->out_data = NULL;
+ intf->urb_out = 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;
+ int error;
+
+ 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;
+
+ INIT_WORK(&gip->receive_message, gip_receive_work);
+ spin_lock_init(&gip->message_lock);
+
+ error = gip_interface_init(&gip->data);
+ if (error) {
+ devm_kfree(GIP_DEV(gip), gip);
+ return error;
+ }
+
+ usb_set_intfdata(intf, gip);
+ 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];
+
+ if (!attachment)
+ continue;
+
+ guard(mutex)(&attachment->lock);
+ cancel_delayed_work_sync(&attachment->metadata_next);
+ cancel_delayed_work_sync(&attachment->fragment_timeout);
+
+ if (attachment->input) {
+ input_unregister_device(attachment->input);
+ attachment->input = NULL;
+ }
+ if (attachment->hdev) {
+ hid_destroy_device(attachment->hdev);
+ attachment->hdev = NULL;
+ }
+ }
+
+ 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);
+
+ gip_shutdown(gip);
+
+ spin_lock_irqsave(&gip->message_lock, flags);
+ gip_deinit_input(&gip->data);
+ gip_deinit_output(&gip->data);
+ 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->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;
+}
+
+module_param(dpad_as_buttons, bool, 0444);
+MODULE_PARM_DESC(dpad_as_buttons, "Map the D-Pad as buttons instead of axes");
+
+/* 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");
--
2.50.1
^ permalink raw reply related
* [PATCH 4/6] HID: Map more automobile simulation inputs
From: Vicki Pfau @ 2025-08-08 4:30 UTC (permalink / raw)
To: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input; +Cc: Vicki Pfau
In-Reply-To: <20250808043017.1953101-1-vi@endrift.com>
The HID usage tables section 5.3 specify clutch and shifter values that had
previously been ignored. As the ABS_CLUTCH and ABS_SHIFTER bits now exist,
we should use them appropriately.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/hid/hid-input.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index ff1784b5c2a47..adcffbcb30834 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -782,6 +782,8 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
case 0xbb: map_abs(ABS_THROTTLE); break;
case 0xc4: map_abs(ABS_GAS); break;
case 0xc5: map_abs(ABS_BRAKE); break;
+ case 0xc6: map_abs(ABS_CLUTCH); break;
+ case 0xc7: map_abs(ABS_SHIFTER); break;
case 0xc8: map_abs(ABS_WHEEL); break;
default: goto ignore;
}
--
2.50.1
^ permalink raw reply related
* [PATCH 3/6] Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER
From: Vicki Pfau @ 2025-08-08 4:30 UTC (permalink / raw)
To: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input; +Cc: Vicki Pfau
In-Reply-To: <20250808043017.1953101-1-vi@endrift.com>
Add new absolute axes for racing game controllers
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/hid/hid-debug.c | 16 +++++++++-------
include/uapi/linux/input-event-codes.h | 3 +++
2 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index 264eefaed08fb..534df8e4082da 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -3505,13 +3505,15 @@ static const char *absolutes[ABS_CNT] = {
[ABS_RY] = "Ry", [ABS_RZ] = "Rz",
[ABS_THROTTLE] = "Throttle", [ABS_RUDDER] = "Rudder",
[ABS_WHEEL] = "Wheel", [ABS_GAS] = "Gas",
- [ABS_BRAKE] = "Brake", [ABS_HAT0X] = "Hat0X",
- [ABS_HAT0Y] = "Hat0Y", [ABS_HAT1X] = "Hat1X",
- [ABS_HAT1Y] = "Hat1Y", [ABS_HAT2X] = "Hat2X",
- [ABS_HAT2Y] = "Hat2Y", [ABS_HAT3X] = "Hat3X",
- [ABS_HAT3Y] = "Hat 3Y", [ABS_PRESSURE] = "Pressure",
- [ABS_DISTANCE] = "Distance", [ABS_TILT_X] = "XTilt",
- [ABS_TILT_Y] = "YTilt", [ABS_TOOL_WIDTH] = "ToolWidth",
+ [ABS_BRAKE] = "Brake", [ABS_CLUTCH] = "Clutch",
+ [ABS_HANDBRAKE] = "Handbrake", [ABS_SHIFTER] = "Shifter",
+ [ABS_HAT0X] = "Hat0X", [ABS_HAT0Y] = "Hat0Y",
+ [ABS_HAT1X] = "Hat1X", [ABS_HAT1Y] = "Hat1Y",
+ [ABS_HAT2X] = "Hat2X", [ABS_HAT2Y] = "Hat2Y",
+ [ABS_HAT3X] = "Hat3X", [ABS_HAT3Y] = "Hat3Y",
+ [ABS_PRESSURE] = "Pressure", [ABS_DISTANCE] = "Distance",
+ [ABS_TILT_X] = "XTilt", [ABS_TILT_Y] = "YTilt",
+ [ABS_TOOL_WIDTH] = "ToolWidth",
[ABS_VOLUME] = "Volume", [ABS_PROFILE] = "Profile",
[ABS_MISC] = "Misc",
[ABS_MT_SLOT] = "MTSlot",
diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
index 08cb157ab5936..195139e895ffb 100644
--- a/include/uapi/linux/input-event-codes.h
+++ b/include/uapi/linux/input-event-codes.h
@@ -859,6 +859,9 @@
#define ABS_WHEEL 0x08
#define ABS_GAS 0x09
#define ABS_BRAKE 0x0a
+#define ABS_CLUTCH 0x0b
+#define ABS_HANDBRAKE 0x0c
+#define ABS_SHIFTER 0x0d
#define ABS_HAT0X 0x10
#define ABS_HAT0Y 0x11
#define ABS_HAT1X 0x12
--
2.50.1
^ permalink raw reply related
* [PATCH 2/6] Input: xpad - Remove Xbox One support
From: Vicki Pfau @ 2025-08-08 4:30 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
In-Reply-To: <20250808043017.1953101-1-vi@endrift.com>
It has been superseded by xbox_gip. As the new driver is already at feature
parity, removing the fairly rough Xbox One support from xpad is safe and
will prevent any potential conflicts.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
Documentation/input/devices/xpad.rst | 17 +-
drivers/input/joystick/xpad.c | 634 +--------------------------
2 files changed, 11 insertions(+), 640 deletions(-)
diff --git a/Documentation/input/devices/xpad.rst b/Documentation/input/devices/xpad.rst
index a480bc781565e..fd2afdd7b4059 100644
--- a/Documentation/input/devices/xpad.rst
+++ b/Documentation/input/devices/xpad.rst
@@ -2,19 +2,19 @@
xpad - Linux USB driver for Xbox compatible controllers
=======================================================
-This driver exposes all first-party and third-party Xbox compatible
-controllers. It has a long history and has enjoyed considerable usage
-as Windows' xinput library caused most PC games to focus on Xbox
-controller compatibility.
+This driver exposes all first-party and third-party Xbox and Xbox 360
+compatible controllers. It has a long history and has enjoyed considerable
+usage as Windows' xinput library caused most PC games to focus on Xbox
+controller compatibility. Xbox One/Series controller support has been
+superseded by the xbox_gip driver, which specializes in the Gaming Input
+Protocl that is introduced on the Xbox One.
Due to backwards compatibility all buttons are reported as digital.
This only affects Original Xbox controllers. All later controller models
have only digital face buttons.
Rumble is supported on some models of Xbox 360 controllers but not of
-Original Xbox controllers nor on Xbox One controllers. As of writing
-the Xbox One's rumble protocol has not been reverse-engineered but in
-the future could be supported.
+Original Xbox controllers.
Notes
@@ -98,9 +98,6 @@ All generations of Xbox controllers speak USB over the wire.
- Wireless Xbox 360 controllers require a 'Xbox 360 Wireless Gaming Receiver
for Windows'
- Wired Xbox 360 controllers use standard USB connectors.
-- Xbox One controllers can be wireless but speak Wi-Fi Direct and are not
- yet supported.
-- Xbox One controllers can be wired and use standard Micro-USB connectors.
diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index 1d8c579b54331..b6dfd6a84058f 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c
@@ -17,7 +17,6 @@
* - the iForce driver drivers/char/joystick/iforce.c
* - the skeleton-driver drivers/usb/usb-skeleton.c
* - Xbox 360 information http://www.free60.org/wiki/Gamepad
- * - Xbox One information https://github.com/quantus/xbox-one-controller-protocol
*
* Thanks to:
* - ITO Takayuki for providing essential xpad information on his website
@@ -80,10 +79,6 @@
#define MAP_DPAD_TO_BUTTONS BIT(0)
#define MAP_TRIGGERS_TO_BUTTONS BIT(1)
#define MAP_STICKS_TO_NULL BIT(2)
-#define MAP_SHARE_BUTTON BIT(3)
-#define MAP_PADDLES BIT(4)
-#define MAP_PROFILE_BUTTON BIT(5)
-#define MAP_SHARE_OFFSET BIT(6)
#define DANCEPAD_MAP_CONFIG (MAP_DPAD_TO_BUTTONS | \
MAP_TRIGGERS_TO_BUTTONS | MAP_STICKS_TO_NULL)
@@ -91,8 +86,7 @@
#define XTYPE_XBOX 0
#define XTYPE_XBOX360 1
#define XTYPE_XBOX360W 2
-#define XTYPE_XBOXONE 3
-#define XTYPE_UNKNOWN 4
+#define XTYPE_UNKNOWN 3
/* Send power-off packet to xpad360w after holding the mode button for this many
* seconds
@@ -137,16 +131,11 @@ static const struct xpad_device {
{ 0x03eb, 0xff02, "Wooting Two (Legacy)", 0, XTYPE_XBOX360 },
{ 0x03f0, 0x038D, "HyperX Clutch", 0, XTYPE_XBOX360 }, /* wired */
{ 0x03f0, 0x048D, "HyperX Clutch", 0, XTYPE_XBOX360 }, /* wireless */
- { 0x03f0, 0x0495, "HyperX Clutch Gladiate", 0, XTYPE_XBOXONE },
- { 0x03f0, 0x07A0, "HyperX Clutch Gladiate RGB", 0, XTYPE_XBOXONE },
- { 0x03f0, 0x08B6, "HyperX Clutch Gladiate", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, /* v2 */
- { 0x03f0, 0x09B4, "HyperX Clutch Tanto", 0, XTYPE_XBOXONE },
{ 0x044f, 0x0f00, "Thrustmaster Wheel", 0, XTYPE_XBOX },
{ 0x044f, 0x0f03, "Thrustmaster Wheel", 0, XTYPE_XBOX },
{ 0x044f, 0x0f07, "Thrustmaster, Inc. Controller", 0, XTYPE_XBOX },
{ 0x044f, 0x0f10, "Thrustmaster Modena GT Wheel", 0, XTYPE_XBOX },
{ 0x044f, 0xb326, "Thrustmaster Gamepad GP XID", 0, XTYPE_XBOX360 },
- { 0x044f, 0xd01e, "ThrustMaster, Inc. ESWAP X 2 ELDEN RING EDITION", 0, XTYPE_XBOXONE },
{ 0x045e, 0x0202, "Microsoft X-Box pad v1 (US)", 0, XTYPE_XBOX },
{ 0x045e, 0x0285, "Microsoft X-Box pad (Japan)", 0, XTYPE_XBOX },
{ 0x045e, 0x0287, "Microsoft Xbox Controller S", 0, XTYPE_XBOX },
@@ -156,14 +145,7 @@ static const struct xpad_device {
{ 0x045e, 0x028f, "Microsoft X-Box 360 pad v2", 0, XTYPE_XBOX360 },
{ 0x045e, 0x0291, "Xbox 360 Wireless Receiver (XBOX)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W },
{ 0x045e, 0x02a9, "Xbox 360 Wireless Receiver (Unofficial)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W },
- { 0x045e, 0x02d1, "Microsoft X-Box One pad", 0, XTYPE_XBOXONE },
- { 0x045e, 0x02dd, "Microsoft X-Box One pad (Firmware 2015)", 0, XTYPE_XBOXONE },
- { 0x045e, 0x02e3, "Microsoft X-Box One Elite pad", MAP_PADDLES, XTYPE_XBOXONE },
- { 0x045e, 0x02ea, "Microsoft X-Box One S pad", 0, XTYPE_XBOXONE },
{ 0x045e, 0x0719, "Xbox 360 Wireless Receiver", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W },
- { 0x045e, 0x0b00, "Microsoft X-Box One Elite 2 pad", MAP_PADDLES, XTYPE_XBOXONE },
- { 0x045e, 0x0b0a, "Microsoft X-Box Adaptive Controller", MAP_PROFILE_BUTTON, XTYPE_XBOXONE },
- { 0x045e, 0x0b12, "Microsoft Xbox Series S|X Controller", MAP_SHARE_BUTTON | MAP_SHARE_OFFSET, XTYPE_XBOXONE },
{ 0x046d, 0xc21d, "Logitech Gamepad F310", 0, XTYPE_XBOX360 },
{ 0x046d, 0xc21e, "Logitech Gamepad F510", 0, XTYPE_XBOX360 },
{ 0x046d, 0xc21f, "Logitech Gamepad F710", 0, XTYPE_XBOX360 },
@@ -183,7 +165,6 @@ static const struct xpad_device {
{ 0x06a3, 0x0200, "Saitek Racing Wheel", 0, XTYPE_XBOX },
{ 0x06a3, 0x0201, "Saitek Adrenalin", 0, XTYPE_XBOX },
{ 0x06a3, 0xf51a, "Saitek P3600", 0, XTYPE_XBOX360 },
- { 0x0738, 0x4503, "Mad Catz Racing Wheel", 0, XTYPE_XBOXONE },
{ 0x0738, 0x4506, "Mad Catz 4506 Wireless Controller", 0, XTYPE_XBOX },
{ 0x0738, 0x4516, "Mad Catz Control Pad", 0, XTYPE_XBOX },
{ 0x0738, 0x4520, "Mad Catz Control Pad Pro", 0, XTYPE_XBOX },
@@ -205,7 +186,6 @@ static const struct xpad_device {
{ 0x0738, 0x4740, "Mad Catz Beat Pad", 0, XTYPE_XBOX360 },
{ 0x0738, 0x4743, "Mad Catz Beat Pad Pro", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
{ 0x0738, 0x4758, "Mad Catz Arcade Game Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
- { 0x0738, 0x4a01, "Mad Catz FightStick TE 2", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
{ 0x0738, 0x6040, "Mad Catz Beat Pad Pro", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
{ 0x0738, 0x9871, "Mad Catz Portable Drum", 0, XTYPE_XBOX360 },
{ 0x0738, 0xb726, "Mad Catz Xbox controller - MW2", 0, XTYPE_XBOX360 },
@@ -216,8 +196,6 @@ static const struct xpad_device {
{ 0x0738, 0xcb29, "Saitek Aviator Stick AV8R02", 0, XTYPE_XBOX360 },
{ 0x0738, 0xf738, "Super SFIV FightStick TE S", 0, XTYPE_XBOX360 },
{ 0x07ff, 0xffff, "Mad Catz GamePad", 0, XTYPE_XBOX360 },
- { 0x0b05, 0x1a38, "ASUS ROG RAIKIRI", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
- { 0x0b05, 0x1abb, "ASUS ROG RAIKIRI PRO", 0, XTYPE_XBOXONE },
{ 0x0c12, 0x0005, "Intec wireless", 0, XTYPE_XBOX },
{ 0x0c12, 0x8801, "Nyko Xbox Controller", 0, XTYPE_XBOX },
{ 0x0c12, 0x8802, "Zeroplus Xbox Controller", 0, XTYPE_XBOX },
@@ -240,34 +218,10 @@ static const struct xpad_device {
{ 0x0e6f, 0x011f, "Rock Candy Gamepad Wired Controller", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x0131, "PDP EA Sports Controller", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x0133, "Xbox 360 Wired Controller", 0, XTYPE_XBOX360 },
- { 0x0e6f, 0x0139, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x013a, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0146, "Rock Candy Wired Controller for Xbox One", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0147, "PDP Marvel Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x015c, "PDP Xbox One Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
- { 0x0e6f, 0x015d, "PDP Mirror's Edge Official Wired Controller for Xbox One", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0161, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0162, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0163, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0164, "PDP Battlefield One", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0165, "PDP Titanfall 2", 0, XTYPE_XBOXONE },
{ 0x0e6f, 0x0201, "Pelican PL-3601 'TSZ' Wired Xbox 360 Controller", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360", 0, XTYPE_XBOX360 },
- { 0x0e6f, 0x0246, "Rock Candy Gamepad for Xbox One 2015", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a0, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a1, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a2, "PDP Wired Controller for Xbox One - Crimson Red", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a4, "PDP Wired Controller for Xbox One - Stealth Series", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a6, "PDP Wired Controller for Xbox One - Camo Series", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a7, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a8, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02ab, "PDP Controller for Xbox One", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02ad, "PDP Wired Controller for Xbox One - Stealth Series", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02b3, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02b8, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE },
{ 0x0e6f, 0x0301, "Logic3 Controller", 0, XTYPE_XBOX360 },
- { 0x0e6f, 0x0346, "Rock Candy Gamepad for Xbox One 2016", 0, XTYPE_XBOXONE },
{ 0x0e6f, 0x0401, "Logic3 Controller", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x0413, "Afterglow AX.1 Gamepad for Xbox 360", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x0501, "PDP Xbox 360 Controller", 0, XTYPE_XBOX360 },
@@ -279,23 +233,13 @@ static const struct xpad_device {
{ 0x0f0d, 0x000d, "Hori Fighting Stick EX2", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
{ 0x0f0d, 0x0016, "Hori Real Arcade Pro.EX", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
{ 0x0f0d, 0x001b, "Hori Real Arcade Pro VX", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
- { 0x0f0d, 0x0063, "Hori Real Arcade Pro Hayabusa (USA) Xbox One", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
- { 0x0f0d, 0x0067, "HORIPAD ONE", 0, XTYPE_XBOXONE },
- { 0x0f0d, 0x0078, "Hori Real Arcade Pro V Kai Xbox One", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
- { 0x0f0d, 0x00c5, "Hori Fighting Commander ONE", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
{ 0x0f0d, 0x00dc, "HORIPAD FPS for Nintendo Switch", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
- { 0x0f0d, 0x0151, "Hori Racing Wheel Overdrive for Xbox Series X", 0, XTYPE_XBOXONE },
- { 0x0f0d, 0x0152, "Hori Racing Wheel Overdrive for Xbox Series X", 0, XTYPE_XBOXONE },
- { 0x0f0d, 0x01b2, "HORI Taiko No Tatsujin Drum Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x0f30, 0x010b, "Philips Recoil", 0, XTYPE_XBOX },
{ 0x0f30, 0x0202, "Joytech Advanced Controller", 0, XTYPE_XBOX },
{ 0x0f30, 0x8888, "BigBen XBMiniPad Controller", 0, XTYPE_XBOX },
{ 0x102c, 0xff0c, "Joytech Wireless Advanced Controller", 0, XTYPE_XBOX },
{ 0x1038, 0x1430, "SteelSeries Stratus Duo", 0, XTYPE_XBOX360 },
{ 0x1038, 0x1431, "SteelSeries Stratus Duo", 0, XTYPE_XBOX360 },
- { 0x10f5, 0x7005, "Turtle Beach Recon Controller", 0, XTYPE_XBOXONE },
- { 0x10f5, 0x7008, "Turtle Beach Recon Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
- { 0x10f5, 0x7073, "Turtle Beach Stealth Ultra Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x11c9, 0x55f0, "Nacon GC-100XF", 0, XTYPE_XBOX360 },
{ 0x11ff, 0x0511, "PXN V900", 0, XTYPE_XBOX360 },
{ 0x1209, 0x2882, "Ardwiino Controller", 0, XTYPE_XBOX360 },
@@ -308,9 +252,6 @@ static const struct xpad_device {
{ 0x1430, 0xf801, "RedOctane Controller", 0, XTYPE_XBOX360 },
{ 0x146b, 0x0601, "BigBen Interactive XBOX 360 Controller", 0, XTYPE_XBOX360 },
{ 0x146b, 0x0604, "Bigben Interactive DAIJA Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
- { 0x1532, 0x0a00, "Razer Atrox Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
- { 0x1532, 0x0a03, "Razer Wildcat", 0, XTYPE_XBOXONE },
- { 0x1532, 0x0a29, "Razer Wolverine V2", 0, XTYPE_XBOXONE },
{ 0x15e4, 0x3f00, "Power A Mini Pro Elite", 0, XTYPE_XBOX360 },
{ 0x15e4, 0x3f0a, "Xbox Airflo wired controller", 0, XTYPE_XBOX360 },
{ 0x15e4, 0x3f10, "Batarang Xbox 360 controller", 0, XTYPE_XBOX360 },
@@ -358,12 +299,7 @@ static const struct xpad_device {
{ 0x1bad, 0xfd00, "Razer Onza TE", 0, XTYPE_XBOX360 },
{ 0x1bad, 0xfd01, "Razer Onza", 0, XTYPE_XBOX360 },
{ 0x1ee9, 0x1590, "ZOTAC Gaming Zone", 0, XTYPE_XBOX360 },
- { 0x20d6, 0x2001, "BDA Xbox Series X Wired Controller", 0, XTYPE_XBOXONE },
- { 0x20d6, 0x2009, "PowerA Enhanced Wired Controller for Xbox Series X|S", 0, XTYPE_XBOXONE },
- { 0x20d6, 0x2064, "PowerA Wired Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x20d6, 0x281f, "PowerA Wired Controller For Xbox 360", 0, XTYPE_XBOX360 },
- { 0x20d6, 0x400b, "PowerA FUSION Pro 4 Wired Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
- { 0x20d6, 0x890b, "PowerA MOGA XP-Ultra Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x2345, 0xe00b, "Machenike G5 Pro Controller", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5000, "Razer Atrox Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
{ 0x24c6, 0x5300, "PowerA MINI PROEX Controller", 0, XTYPE_XBOX360 },
@@ -371,9 +307,6 @@ static const struct xpad_device {
{ 0x24c6, 0x530a, "Xbox 360 Pro EX Controller", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x531a, "PowerA Pro Ex", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5397, "FUS1ON Tournament Controller", 0, XTYPE_XBOX360 },
- { 0x24c6, 0x541a, "PowerA Xbox One Mini Wired Controller", 0, XTYPE_XBOXONE },
- { 0x24c6, 0x542a, "Xbox ONE spectra", 0, XTYPE_XBOXONE },
- { 0x24c6, 0x543a, "PowerA Xbox One wired controller", 0, XTYPE_XBOXONE },
{ 0x24c6, 0x5500, "Hori XBOX 360 EX 2 with Turbo", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5501, "Hori Real Arcade Pro VX-SA", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5502, "Hori Fighting Stick VX Alt", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
@@ -382,29 +315,18 @@ static const struct xpad_device {
{ 0x24c6, 0x550d, "Hori GEM Xbox controller", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x550e, "Hori Real Arcade Pro V Kai 360", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
{ 0x24c6, 0x5510, "Hori Fighting Commander ONE (Xbox 360/PC Mode)", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
- { 0x24c6, 0x551a, "PowerA FUSION Pro Controller", 0, XTYPE_XBOXONE },
- { 0x24c6, 0x561a, "PowerA FUSION Controller", 0, XTYPE_XBOXONE },
- { 0x24c6, 0x581a, "ThrustMaster XB1 Classic Controller", 0, XTYPE_XBOXONE },
{ 0x24c6, 0x5b00, "ThrustMaster Ferrari 458 Racing Wheel", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5b02, "Thrustmaster, Inc. GPX Controller", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5b03, "Thrustmaster Ferrari 458 Racing Wheel", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5d04, "Razer Sabertooth", 0, XTYPE_XBOX360 },
{ 0x24c6, 0xfafe, "Rock Candy Gamepad for Xbox 360", 0, XTYPE_XBOX360 },
{ 0x2563, 0x058d, "OneXPlayer Gamepad", 0, XTYPE_XBOX360 },
- { 0x294b, 0x3303, "Snakebyte GAMEPAD BASE X", 0, XTYPE_XBOXONE },
- { 0x294b, 0x3404, "Snakebyte GAMEPAD RGB X", 0, XTYPE_XBOXONE },
{ 0x2993, 0x2001, "TECNO Pocket Go", 0, XTYPE_XBOX360 },
- { 0x2dc8, 0x2000, "8BitDo Pro 2 Wired Controller fox Xbox", 0, XTYPE_XBOXONE },
- { 0x2dc8, 0x200f, "8BitDo Ultimate 3-mode Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x2dc8, 0x3106, "8BitDo Ultimate Wireless / Pro 2 Wired Controller", 0, XTYPE_XBOX360 },
{ 0x2dc8, 0x3109, "8BitDo Ultimate Wireless Bluetooth", 0, XTYPE_XBOX360 },
{ 0x2dc8, 0x310a, "8BitDo Ultimate 2C Wireless Controller", 0, XTYPE_XBOX360 },
{ 0x2dc8, 0x310b, "8BitDo Ultimate 2 Wireless Controller", 0, XTYPE_XBOX360 },
{ 0x2dc8, 0x6001, "8BitDo SN30 Pro", 0, XTYPE_XBOX360 },
- { 0x2e24, 0x0423, "Hyperkin DuchesS Xbox One pad", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
- { 0x2e24, 0x0652, "Hyperkin Duke X-Box One pad", 0, XTYPE_XBOXONE },
- { 0x2e24, 0x1688, "Hyperkin X91 X-Box One pad", 0, XTYPE_XBOXONE },
- { 0x2e95, 0x0504, "SCUF Gaming Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x31e3, 0x1100, "Wooting One", 0, XTYPE_XBOX360 },
{ 0x31e3, 0x1200, "Wooting Two", 0, XTYPE_XBOX360 },
{ 0x31e3, 0x1210, "Wooting Lekker", 0, XTYPE_XBOX360 },
@@ -412,15 +334,9 @@ static const struct xpad_device {
{ 0x31e3, 0x1230, "Wooting Two HE (ARM)", 0, XTYPE_XBOX360 },
{ 0x31e3, 0x1300, "Wooting 60HE (AVR)", 0, XTYPE_XBOX360 },
{ 0x31e3, 0x1310, "Wooting 60HE (ARM)", 0, XTYPE_XBOX360 },
- { 0x3285, 0x0603, "Nacon Pro Compact controller for Xbox", 0, XTYPE_XBOXONE },
{ 0x3285, 0x0607, "Nacon GC-100", 0, XTYPE_XBOX360 },
- { 0x3285, 0x0614, "Nacon Pro Compact", 0, XTYPE_XBOXONE },
- { 0x3285, 0x0646, "Nacon Pro Compact", 0, XTYPE_XBOXONE },
{ 0x3285, 0x0662, "Nacon Revolution5 Pro", 0, XTYPE_XBOX360 },
- { 0x3285, 0x0663, "Nacon Evol-X", 0, XTYPE_XBOXONE },
{ 0x3537, 0x1004, "GameSir T4 Kaleid", 0, XTYPE_XBOX360 },
- { 0x3537, 0x1010, "GameSir G7 SE", 0, XTYPE_XBOXONE },
- { 0x366c, 0x0005, "ByoWave Proteus Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE, FLAG_DELAY_INIT },
{ 0x3767, 0x0101, "Fanatec Speedster 3 Forceshock Wheel", 0, XTYPE_XBOX },
{ 0x413d, 0x2104, "Black Shark Green Ghost Gamepad", 0, XTYPE_XBOX360 },
{ 0xffff, 0xffff, "Chinese-made Xbox Controller", 0, XTYPE_XBOX },
@@ -477,13 +393,6 @@ static const signed short xpad_abs_triggers[] = {
-1
};
-/* used when the controller has extra paddle buttons */
-static const signed short xpad_btn_paddles[] = {
- BTN_TRIGGER_HAPPY5, BTN_TRIGGER_HAPPY6, /* paddle upper right, lower right */
- BTN_TRIGGER_HAPPY7, BTN_TRIGGER_HAPPY8, /* paddle upper left, lower left */
- -1 /* terminating entry */
-};
-
/*
* Xbox 360 has a vendor-specific class, so we cannot match it with only
* USB_INTERFACE_INFO (also specifically refused by USB subsystem), so we
@@ -500,47 +409,28 @@ static const signed short xpad_btn_paddles[] = {
{ XPAD_XBOX360_VENDOR_PROTOCOL((vend), 1) }, \
{ XPAD_XBOX360_VENDOR_PROTOCOL((vend), 129) }
-/* The Xbox One controller uses subclass 71 and protocol 208. */
-#define XPAD_XBOXONE_VENDOR_PROTOCOL(vend, pr) \
- .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, \
- .idVendor = (vend), \
- .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \
- .bInterfaceSubClass = 71, \
- .bInterfaceProtocol = (pr)
-#define XPAD_XBOXONE_VENDOR(vend) \
- { XPAD_XBOXONE_VENDOR_PROTOCOL((vend), 208) }
-
static const struct usb_device_id xpad_table[] = {
/*
- * Please keep this list sorted by vendor ID. Note that there are 2
- * macros - XPAD_XBOX360_VENDOR and XPAD_XBOXONE_VENDOR.
+ * Please keep this list sorted by vendor ID
*/
{ USB_INTERFACE_INFO('X', 'B', 0) }, /* Xbox USB-IF not-approved class */
XPAD_XBOX360_VENDOR(0x0079), /* GPD Win 2 controller */
XPAD_XBOX360_VENDOR(0x03eb), /* Wooting Keyboards (Legacy) */
XPAD_XBOX360_VENDOR(0x03f0), /* HP HyperX Xbox 360 controllers */
- XPAD_XBOXONE_VENDOR(0x03f0), /* HP HyperX Xbox One controllers */
XPAD_XBOX360_VENDOR(0x044f), /* Thrustmaster Xbox 360 controllers */
- XPAD_XBOXONE_VENDOR(0x044f), /* Thrustmaster Xbox One controllers */
XPAD_XBOX360_VENDOR(0x045e), /* Microsoft Xbox 360 controllers */
- XPAD_XBOXONE_VENDOR(0x045e), /* Microsoft Xbox One controllers */
XPAD_XBOX360_VENDOR(0x046d), /* Logitech Xbox 360-style controllers */
XPAD_XBOX360_VENDOR(0x0502), /* Acer Inc. Xbox 360 style controllers */
XPAD_XBOX360_VENDOR(0x056e), /* Elecom JC-U3613M */
XPAD_XBOX360_VENDOR(0x06a3), /* Saitek P3600 */
XPAD_XBOX360_VENDOR(0x0738), /* Mad Catz Xbox 360 controllers */
{ USB_DEVICE(0x0738, 0x4540) }, /* Mad Catz Beat Pad */
- XPAD_XBOXONE_VENDOR(0x0738), /* Mad Catz FightStick TE 2 */
XPAD_XBOX360_VENDOR(0x07ff), /* Mad Catz Gamepad */
- XPAD_XBOXONE_VENDOR(0x0b05), /* ASUS controllers */
XPAD_XBOX360_VENDOR(0x0c12), /* Zeroplus X-Box 360 controllers */
XPAD_XBOX360_VENDOR(0x0db0), /* Micro Star International X-Box 360 controllers */
XPAD_XBOX360_VENDOR(0x0e6f), /* 0x0e6f Xbox 360 controllers */
- XPAD_XBOXONE_VENDOR(0x0e6f), /* 0x0e6f Xbox One controllers */
XPAD_XBOX360_VENDOR(0x0f0d), /* Hori controllers */
- XPAD_XBOXONE_VENDOR(0x0f0d), /* Hori controllers */
XPAD_XBOX360_VENDOR(0x1038), /* SteelSeries controllers */
- XPAD_XBOXONE_VENDOR(0x10f5), /* Turtle Beach Controllers */
XPAD_XBOX360_VENDOR(0x11c9), /* Nacon GC100XF */
XPAD_XBOX360_VENDOR(0x11ff), /* PXN V900 */
XPAD_XBOX360_VENDOR(0x1209), /* Ardwiino Controllers */
@@ -548,7 +438,6 @@ static const struct usb_device_id xpad_table[] = {
XPAD_XBOX360_VENDOR(0x1430), /* RedOctane Xbox 360 controllers */
XPAD_XBOX360_VENDOR(0x146b), /* Bigben Interactive controllers */
XPAD_XBOX360_VENDOR(0x1532), /* Razer Sabertooth */
- XPAD_XBOXONE_VENDOR(0x1532), /* Razer Wildcat */
XPAD_XBOX360_VENDOR(0x15e4), /* Numark Xbox 360 controllers */
XPAD_XBOX360_VENDOR(0x162e), /* Joytech Xbox 360 controllers */
XPAD_XBOX360_VENDOR(0x1689), /* Razer Onza */
@@ -558,188 +447,23 @@ static const struct usb_device_id xpad_table[] = {
XPAD_XBOX360_VENDOR(0x1bad), /* Harmonix Rock Band guitar and drums */
XPAD_XBOX360_VENDOR(0x1ee9), /* ZOTAC Technology Limited */
XPAD_XBOX360_VENDOR(0x20d6), /* PowerA controllers */
- XPAD_XBOXONE_VENDOR(0x20d6), /* PowerA controllers */
XPAD_XBOX360_VENDOR(0x2345), /* Machenike Controllers */
XPAD_XBOX360_VENDOR(0x24c6), /* PowerA controllers */
- XPAD_XBOXONE_VENDOR(0x24c6), /* PowerA controllers */
XPAD_XBOX360_VENDOR(0x2563), /* OneXPlayer Gamepad */
XPAD_XBOX360_VENDOR(0x260d), /* Dareu H101 */
- XPAD_XBOXONE_VENDOR(0x294b), /* Snakebyte */
XPAD_XBOX360_VENDOR(0x2993), /* TECNO Mobile */
XPAD_XBOX360_VENDOR(0x2c22), /* Qanba Controllers */
XPAD_XBOX360_VENDOR(0x2dc8), /* 8BitDo Controllers */
- XPAD_XBOXONE_VENDOR(0x2dc8), /* 8BitDo Controllers */
- XPAD_XBOXONE_VENDOR(0x2e24), /* Hyperkin Controllers */
XPAD_XBOX360_VENDOR(0x2f24), /* GameSir Controllers */
- XPAD_XBOXONE_VENDOR(0x2e95), /* SCUF Gaming Controller */
XPAD_XBOX360_VENDOR(0x31e3), /* Wooting Keyboards */
XPAD_XBOX360_VENDOR(0x3285), /* Nacon GC-100 */
- XPAD_XBOXONE_VENDOR(0x3285), /* Nacon Evol-X */
XPAD_XBOX360_VENDOR(0x3537), /* GameSir Controllers */
- XPAD_XBOXONE_VENDOR(0x3537), /* GameSir Controllers */
- XPAD_XBOXONE_VENDOR(0x366c), /* ByoWave controllers */
XPAD_XBOX360_VENDOR(0x413d), /* Black Shark Green Ghost Controller */
{ }
};
MODULE_DEVICE_TABLE(usb, xpad_table);
-struct xboxone_init_packet {
- u16 idVendor;
- u16 idProduct;
- const u8 *data;
- u8 len;
-};
-
-#define XBOXONE_INIT_PKT(_vid, _pid, _data) \
- { \
- .idVendor = (_vid), \
- .idProduct = (_pid), \
- .data = (_data), \
- .len = ARRAY_SIZE(_data), \
- }
-
-/*
- * starting with xbox one, the game input protocol is used
- * magic numbers are taken from
- * - https://github.com/xpadneo/gip-dissector/blob/main/src/gip-dissector.lua
- * - https://github.com/medusalix/xone/blob/master/bus/protocol.c
- */
-#define GIP_CMD_ACK 0x01
-#define GIP_CMD_ANNOUNCE 0x02
-#define GIP_CMD_IDENTIFY 0x04
-#define GIP_CMD_POWER 0x05
-#define GIP_CMD_AUTHENTICATE 0x06
-#define GIP_CMD_VIRTUAL_KEY 0x07
-#define GIP_CMD_RUMBLE 0x09
-#define GIP_CMD_LED 0x0a
-#define GIP_CMD_FIRMWARE 0x0c
-#define GIP_CMD_INPUT 0x20
-
-#define GIP_SEQ0 0x00
-
-#define GIP_OPT_ACK 0x10
-#define GIP_OPT_INTERNAL 0x20
-
-/*
- * length of the command payload encoded with
- * https://en.wikipedia.org/wiki/LEB128
- * which is a no-op for N < 128
- */
-#define GIP_PL_LEN(N) (N)
-
-/*
- * payload specific defines
- */
-#define GIP_PWR_ON 0x00
-#define GIP_LED_ON 0x01
-
-#define GIP_MOTOR_R BIT(0)
-#define GIP_MOTOR_L BIT(1)
-#define GIP_MOTOR_RT BIT(2)
-#define GIP_MOTOR_LT BIT(3)
-#define GIP_MOTOR_ALL (GIP_MOTOR_R | GIP_MOTOR_L | GIP_MOTOR_RT | GIP_MOTOR_LT)
-
-#define GIP_WIRED_INTF_DATA 0
-#define GIP_WIRED_INTF_AUDIO 1
-
-/*
- * This packet is required for all Xbox One pads with 2015
- * or later firmware installed (or present from the factory).
- */
-static const u8 xboxone_power_on[] = {
- GIP_CMD_POWER, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(1), GIP_PWR_ON
-};
-
-/*
- * This packet is required for Xbox One S (0x045e:0x02ea)
- * and Xbox One Elite Series 2 (0x045e:0x0b00) pads to
- * initialize the controller that was previously used in
- * Bluetooth mode.
- */
-static const u8 xboxone_s_init[] = {
- GIP_CMD_POWER, GIP_OPT_INTERNAL, GIP_SEQ0, 0x0f, 0x06
-};
-
-/*
- * This packet is required to get additional input data
- * from Xbox One Elite Series 2 (0x045e:0x0b00) pads.
- * We mostly do this right now to get paddle data
- */
-static const u8 extra_input_packet_init[] = {
- 0x4d, 0x10, 0x01, 0x02, 0x07, 0x00
-};
-
-/*
- * This packet is required for the Titanfall 2 Xbox One pads
- * (0x0e6f:0x0165) to finish initialization and for Hori pads
- * (0x0f0d:0x0067) to make the analog sticks work.
- */
-static const u8 xboxone_hori_ack_id[] = {
- GIP_CMD_ACK, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(9),
- 0x00, GIP_CMD_IDENTIFY, GIP_OPT_INTERNAL, 0x3a, 0x00, 0x00, 0x00, 0x80, 0x00
-};
-
-/*
- * This packet is sent by default on Windows, and is required for some pads to
- * start sending input reports, including most (all?) of the PDP. These pads
- * include: (0x0e6f:0x02ab), (0x0e6f:0x02a4), (0x0e6f:0x02a6).
- */
-static const u8 xboxone_led_on[] = { GIP_CMD_LED, GIP_OPT_INTERNAL, GIP_SEQ0,
-GIP_PL_LEN(3), 0x00, GIP_LED_ON, 0x14 };
-
-/*
- * This packet is required for most (all?) of the PDP pads to start
- * sending input reports. These pads include: (0x0e6f:0x02ab),
- * (0x0e6f:0x02a4), (0x0e6f:0x02a6).
- */
-static const u8 xboxone_auth_done[] = {
- GIP_CMD_AUTHENTICATE, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(2), 0x01, 0x00
-};
-
-/*
- * A specific rumble packet is required for some PowerA pads to start
- * sending input reports. One of those pads is (0x24c6:0x543a).
- */
-static const u8 xboxone_rumblebegin_init[] = {
- GIP_CMD_RUMBLE, 0x00, GIP_SEQ0, GIP_PL_LEN(9),
- 0x00, GIP_MOTOR_ALL, 0x00, 0x00, 0x1D, 0x1D, 0xFF, 0x00, 0x00
-};
-
-/*
- * A rumble packet with zero FF intensity will immediately
- * terminate the rumbling required to init PowerA pads.
- * This should happen fast enough that the motors don't
- * spin up to enough speed to actually vibrate the gamepad.
- */
-static const u8 xboxone_rumbleend_init[] = {
- GIP_CMD_RUMBLE, 0x00, GIP_SEQ0, GIP_PL_LEN(9),
- 0x00, GIP_MOTOR_ALL, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-
-/*
- * This specifies the selection of init packets that a gamepad
- * will be sent on init *and* the order in which they will be
- * sent. The correct sequence number will be added when the
- * packet is going to be sent.
- */
-static const struct xboxone_init_packet xboxone_init_packets[] = {
- XBOXONE_INIT_PKT(0x0e6f, 0x0165, xboxone_hori_ack_id),
- XBOXONE_INIT_PKT(0x0f0d, 0x0067, xboxone_hori_ack_id),
- XBOXONE_INIT_PKT(0x0000, 0x0000, xboxone_power_on),
- XBOXONE_INIT_PKT(0x045e, 0x02ea, xboxone_s_init),
- XBOXONE_INIT_PKT(0x045e, 0x0b00, xboxone_s_init),
- XBOXONE_INIT_PKT(0x045e, 0x0b00, extra_input_packet_init),
- XBOXONE_INIT_PKT(0x0000, 0x0000, xboxone_led_on),
- XBOXONE_INIT_PKT(0x0000, 0x0000, xboxone_auth_done),
- XBOXONE_INIT_PKT(0x24c6, 0x541a, xboxone_rumblebegin_init),
- XBOXONE_INIT_PKT(0x24c6, 0x542a, xboxone_rumblebegin_init),
- XBOXONE_INIT_PKT(0x24c6, 0x543a, xboxone_rumblebegin_init),
- XBOXONE_INIT_PKT(0x24c6, 0x541a, xboxone_rumbleend_init),
- XBOXONE_INIT_PKT(0x24c6, 0x542a, xboxone_rumbleend_init),
- XBOXONE_INIT_PKT(0x24c6, 0x543a, xboxone_rumbleend_init),
-};
-
struct xpad_output_packet {
u8 data[XPAD_PKT_LEN];
u8 len;
@@ -769,7 +493,6 @@ struct usb_xpad {
struct urb *irq_out; /* urb for interrupt out report */
struct usb_anchor irq_out_anchor;
bool irq_out_active; /* we must not use an active URB */
- u8 odata_serial; /* serial number for xbox one protocol */
unsigned char *odata; /* output data */
dma_addr_t odata_dma;
spinlock_t odata_lock;
@@ -797,8 +520,6 @@ struct usb_xpad {
static int xpad_init_input(struct usb_xpad *xpad);
static void xpad_deinit_input(struct usb_xpad *xpad);
-static int xpad_start_input(struct usb_xpad *xpad);
-static void xpadone_ack_mode_report(struct usb_xpad *xpad, u8 seq_num);
static void xpad360w_poweroff_controller(struct usb_xpad *xpad);
/*
@@ -1036,187 +757,6 @@ static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned cha
rcu_read_unlock();
}
-/*
- * xpadone_process_packet
- *
- * Completes a request by converting the data into events for the
- * input subsystem. This version is for the Xbox One controller.
- *
- * The report format was gleaned from
- * https://github.com/kylelemons/xbox/blob/master/xbox.go
- */
-static void xpadone_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data, u32 len)
-{
- struct input_dev *dev = xpad->dev;
- bool do_sync = false;
-
- /* the xbox button has its own special report */
- if (data[0] == GIP_CMD_VIRTUAL_KEY) {
- /*
- * The Xbox One S controller requires these reports to be
- * acked otherwise it continues sending them forever and
- * won't report further mode button events.
- */
- if (data[1] == (GIP_OPT_ACK | GIP_OPT_INTERNAL))
- xpadone_ack_mode_report(xpad, data[2]);
-
- input_report_key(dev, BTN_MODE, data[4] & GENMASK(1, 0));
- input_sync(dev);
-
- do_sync = true;
- } else if (data[0] == GIP_CMD_FIRMWARE) {
- /* Some packet formats force us to use this separate to poll paddle inputs */
- if (xpad->packet_type == PKT_XBE2_FW_5_11) {
- /* Mute paddles if controller is in a custom profile slot
- * Checked by looking at the active profile slot to
- * verify it's the default slot
- */
- if (data[19] != 0)
- data[18] = 0;
-
- /* Elite Series 2 split packet paddle bits */
- input_report_key(dev, BTN_TRIGGER_HAPPY5, data[18] & BIT(0));
- input_report_key(dev, BTN_TRIGGER_HAPPY6, data[18] & BIT(1));
- input_report_key(dev, BTN_TRIGGER_HAPPY7, data[18] & BIT(2));
- input_report_key(dev, BTN_TRIGGER_HAPPY8, data[18] & BIT(3));
-
- do_sync = true;
- }
- } else if (data[0] == GIP_CMD_ANNOUNCE) {
- int error;
-
- if (xpad->delay_init && !xpad->delayed_init_done) {
- xpad->delayed_init_done = true;
- error = xpad_start_input(xpad);
- if (error)
- dev_warn(&xpad->dev->dev,
- "unable to start delayed input: %d\n",
- error);
- }
- } else if (data[0] == GIP_CMD_INPUT) { /* The main valid packet type for inputs */
- /* menu/view buttons */
- input_report_key(dev, BTN_START, data[4] & BIT(2));
- input_report_key(dev, BTN_SELECT, data[4] & BIT(3));
- if (xpad->mapping & MAP_SHARE_BUTTON) {
- if (xpad->mapping & MAP_SHARE_OFFSET)
- input_report_key(dev, KEY_RECORD, data[len - 26] & BIT(0));
- else
- input_report_key(dev, KEY_RECORD, data[len - 18] & BIT(0));
- }
-
- /* buttons A,B,X,Y */
- input_report_key(dev, BTN_A, data[4] & BIT(4));
- input_report_key(dev, BTN_B, data[4] & BIT(5));
- input_report_key(dev, BTN_X, data[4] & BIT(6));
- input_report_key(dev, BTN_Y, data[4] & BIT(7));
-
- /* digital pad */
- if (xpad->mapping & MAP_DPAD_TO_BUTTONS) {
- /* dpad as buttons (left, right, up, down) */
- input_report_key(dev, BTN_TRIGGER_HAPPY1, data[5] & BIT(2));
- input_report_key(dev, BTN_TRIGGER_HAPPY2, data[5] & BIT(3));
- input_report_key(dev, BTN_TRIGGER_HAPPY3, data[5] & BIT(0));
- input_report_key(dev, BTN_TRIGGER_HAPPY4, data[5] & BIT(1));
- } else {
- input_report_abs(dev, ABS_HAT0X,
- !!(data[5] & 0x08) - !!(data[5] & 0x04));
- input_report_abs(dev, ABS_HAT0Y,
- !!(data[5] & 0x02) - !!(data[5] & 0x01));
- }
-
- /* TL/TR */
- input_report_key(dev, BTN_TL, data[5] & BIT(4));
- input_report_key(dev, BTN_TR, data[5] & BIT(5));
-
- /* stick press left/right */
- input_report_key(dev, BTN_THUMBL, data[5] & BIT(6));
- input_report_key(dev, BTN_THUMBR, data[5] & BIT(7));
-
- if (!(xpad->mapping & MAP_STICKS_TO_NULL)) {
- /* left stick */
- input_report_abs(dev, ABS_X,
- (__s16) le16_to_cpup((__le16 *)(data + 10)));
- input_report_abs(dev, ABS_Y,
- ~(__s16) le16_to_cpup((__le16 *)(data + 12)));
-
- /* right stick */
- input_report_abs(dev, ABS_RX,
- (__s16) le16_to_cpup((__le16 *)(data + 14)));
- input_report_abs(dev, ABS_RY,
- ~(__s16) le16_to_cpup((__le16 *)(data + 16)));
- }
-
- /* triggers left/right */
- if (xpad->mapping & MAP_TRIGGERS_TO_BUTTONS) {
- input_report_key(dev, BTN_TL2,
- (__u16) le16_to_cpup((__le16 *)(data + 6)));
- input_report_key(dev, BTN_TR2,
- (__u16) le16_to_cpup((__le16 *)(data + 8)));
- } else {
- input_report_abs(dev, ABS_Z,
- (__u16) le16_to_cpup((__le16 *)(data + 6)));
- input_report_abs(dev, ABS_RZ,
- (__u16) le16_to_cpup((__le16 *)(data + 8)));
- }
-
- /* Profile button has a value of 0-3, so it is reported as an axis */
- if (xpad->mapping & MAP_PROFILE_BUTTON)
- input_report_abs(dev, ABS_PROFILE, data[34]);
-
- /* paddle handling */
- /* based on SDL's SDL_hidapi_xboxone.c */
- if (xpad->mapping & MAP_PADDLES) {
- if (xpad->packet_type == PKT_XBE1) {
- /* Mute paddles if controller has a custom mapping applied.
- * Checked by comparing the current mapping
- * config against the factory mapping config
- */
- if (memcmp(&data[4], &data[18], 2) != 0)
- data[32] = 0;
-
- /* OG Elite Series Controller paddle bits */
- input_report_key(dev, BTN_TRIGGER_HAPPY5, data[32] & BIT(1));
- input_report_key(dev, BTN_TRIGGER_HAPPY6, data[32] & BIT(3));
- input_report_key(dev, BTN_TRIGGER_HAPPY7, data[32] & BIT(0));
- input_report_key(dev, BTN_TRIGGER_HAPPY8, data[32] & BIT(2));
- } else if (xpad->packet_type == PKT_XBE2_FW_OLD) {
- /* Mute paddles if controller has a custom mapping applied.
- * Checked by comparing the current mapping
- * config against the factory mapping config
- */
- if (data[19] != 0)
- data[18] = 0;
-
- /* Elite Series 2 4.x firmware paddle bits */
- input_report_key(dev, BTN_TRIGGER_HAPPY5, data[18] & BIT(0));
- input_report_key(dev, BTN_TRIGGER_HAPPY6, data[18] & BIT(1));
- input_report_key(dev, BTN_TRIGGER_HAPPY7, data[18] & BIT(2));
- input_report_key(dev, BTN_TRIGGER_HAPPY8, data[18] & BIT(3));
- } else if (xpad->packet_type == PKT_XBE2_FW_5_EARLY) {
- /* Mute paddles if controller has a custom mapping applied.
- * Checked by comparing the current mapping
- * config against the factory mapping config
- */
- if (data[23] != 0)
- data[22] = 0;
-
- /* Elite Series 2 5.x firmware paddle bits
- * (before the packet was split)
- */
- input_report_key(dev, BTN_TRIGGER_HAPPY5, data[22] & BIT(0));
- input_report_key(dev, BTN_TRIGGER_HAPPY6, data[22] & BIT(1));
- input_report_key(dev, BTN_TRIGGER_HAPPY7, data[22] & BIT(2));
- input_report_key(dev, BTN_TRIGGER_HAPPY8, data[22] & BIT(3));
- }
- }
-
- do_sync = true;
- }
-
- if (do_sync)
- input_sync(dev);
-}
-
static void xpad_irq_in(struct urb *urb)
{
struct usb_xpad *xpad = urb->context;
@@ -1249,9 +789,6 @@ static void xpad_irq_in(struct urb *urb)
case XTYPE_XBOX360W:
xpad360w_process_packet(xpad, 0, xpad->idata);
break;
- case XTYPE_XBOXONE:
- xpadone_process_packet(xpad, 0, xpad->idata, urb->actual_length);
- break;
default:
xpad_process_packet(xpad, 0, xpad->idata);
}
@@ -1263,56 +800,12 @@ static void xpad_irq_in(struct urb *urb)
__func__, retval);
}
-/* Callers must hold xpad->odata_lock spinlock */
-static bool xpad_prepare_next_init_packet(struct usb_xpad *xpad)
-{
- const struct xboxone_init_packet *init_packet;
-
- if (xpad->xtype != XTYPE_XBOXONE)
- return false;
-
- /*
- * Some dongles will discard init packets if they're sent before the
- * controller connects. In these cases, we need to wait until we get
- * an announce packet from them to send the init packet sequence.
- */
- if (xpad->delay_init && !xpad->delayed_init_done)
- return false;
-
- /* Perform initialization sequence for Xbox One pads that require it */
- while (xpad->init_seq < ARRAY_SIZE(xboxone_init_packets)) {
- init_packet = &xboxone_init_packets[xpad->init_seq++];
-
- if (init_packet->idVendor != 0 &&
- init_packet->idVendor != xpad->dev->id.vendor)
- continue;
-
- if (init_packet->idProduct != 0 &&
- init_packet->idProduct != xpad->dev->id.product)
- continue;
-
- /* This packet applies to our device, so prepare to send it */
- memcpy(xpad->odata, init_packet->data, init_packet->len);
- xpad->irq_out->transfer_buffer_length = init_packet->len;
-
- /* Update packet with current sequence number */
- xpad->odata[2] = xpad->odata_serial++;
- return true;
- }
-
- return false;
-}
-
/* Callers must hold xpad->odata_lock spinlock */
static bool xpad_prepare_next_out_packet(struct usb_xpad *xpad)
{
struct xpad_output_packet *pkt, *packet = NULL;
int i;
- /* We may have init packets to send before we can send user commands */
- if (xpad_prepare_next_init_packet(xpad))
- return true;
-
for (i = 0; i < XPAD_NUM_OUT_PACKETS; i++) {
if (++xpad->last_out_packet >= XPAD_NUM_OUT_PACKETS)
xpad->last_out_packet = 0;
@@ -1488,57 +981,6 @@ static int xpad_inquiry_pad_presence(struct usb_xpad *xpad)
return xpad_try_sending_next_out_packet(xpad);
}
-static int xpad_start_xbox_one(struct usb_xpad *xpad)
-{
- int error;
-
- if (usb_ifnum_to_if(xpad->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(xpad->udev,
- GIP_WIRED_INTF_AUDIO, 0);
- if (error)
- dev_warn(&xpad->dev->dev,
- "unable to disable audio interface: %d\n",
- error);
- }
-
- guard(spinlock_irqsave)(&xpad->odata_lock);
-
- /*
- * Begin the init sequence by attempting to send a packet.
- * We will cycle through the init packet sequence before
- * sending any packets from the output ring.
- */
- xpad->init_seq = 0;
- return xpad_try_sending_next_out_packet(xpad);
-}
-
-static void xpadone_ack_mode_report(struct usb_xpad *xpad, u8 seq_num)
-{
- struct xpad_output_packet *packet =
- &xpad->out_packets[XPAD_OUT_CMD_IDX];
- static const u8 mode_report_ack[] = {
- GIP_CMD_ACK, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(9),
- 0x00, GIP_CMD_VIRTUAL_KEY, GIP_OPT_INTERNAL, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
- };
-
- guard(spinlock_irqsave)(&xpad->odata_lock);
-
- packet->len = sizeof(mode_report_ack);
- memcpy(packet->data, mode_report_ack, packet->len);
- packet->data[2] = seq_num;
- packet->pending = true;
-
- /* Reset the sequence so we send out the ack now */
- xpad->last_out_packet = -1;
- xpad_try_sending_next_out_packet(xpad);
-}
-
#ifdef CONFIG_JOYSTICK_XPAD_FF
static int xpad_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
{
@@ -1597,24 +1039,6 @@ static int xpad_play_effect(struct input_dev *dev, void *data, struct ff_effect
packet->pending = true;
break;
- case XTYPE_XBOXONE:
- packet->data[0] = GIP_CMD_RUMBLE; /* activate rumble */
- packet->data[1] = 0x00;
- packet->data[2] = xpad->odata_serial++;
- packet->data[3] = GIP_PL_LEN(9);
- packet->data[4] = 0x00;
- packet->data[5] = GIP_MOTOR_ALL;
- packet->data[6] = 0x00; /* left trigger */
- packet->data[7] = 0x00; /* right trigger */
- packet->data[8] = strong / 512; /* left actuator */
- packet->data[9] = weak / 512; /* right actuator */
- packet->data[10] = 0xFF; /* on period */
- packet->data[11] = 0x00; /* off period */
- packet->data[12] = 0xFF; /* repeat count */
- packet->len = 13;
- packet->pending = true;
- break;
-
default:
dev_dbg(&xpad->dev->dev,
"%s - rumble command sent to unsupported xpad type: %d\n",
@@ -1793,13 +1217,6 @@ static int xpad_start_input(struct usb_xpad *xpad)
if (usb_submit_urb(xpad->irq_in, GFP_KERNEL))
return -EIO;
- if (xpad->xtype == XTYPE_XBOXONE) {
- error = xpad_start_xbox_one(xpad);
- if (error) {
- usb_kill_urb(xpad->irq_in);
- return error;
- }
- }
if (xpad->xtype == XTYPE_XBOX360) {
/*
* Some third-party controllers Xbox 360-style controllers
@@ -1905,8 +1322,6 @@ static void xpad_close(struct input_dev *dev)
static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs)
{
- struct usb_xpad *xpad = input_get_drvdata(input_dev);
-
switch (abs) {
case ABS_X:
case ABS_Y:
@@ -1916,18 +1331,12 @@ static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs)
break;
case ABS_Z:
case ABS_RZ: /* the triggers (if mapped to axes) */
- if (xpad->xtype == XTYPE_XBOXONE)
- input_set_abs_params(input_dev, abs, 0, 1023, 0, 0);
- else
- input_set_abs_params(input_dev, abs, 0, 255, 0, 0);
+ input_set_abs_params(input_dev, abs, 0, 255, 0, 0);
break;
case ABS_HAT0X:
case ABS_HAT0Y: /* the d-pad (only if dpad is mapped to axes */
input_set_abs_params(input_dev, abs, -1, 1, 0, 0);
break;
- case ABS_PROFILE: /* 4 value profile button (such as on XAC) */
- input_set_abs_params(input_dev, abs, 0, 4, 0, 0);
- break;
default:
input_set_abs_params(input_dev, abs, 0, 0, 0, 0);
break;
@@ -1982,12 +1391,9 @@ static int xpad_init_input(struct usb_xpad *xpad)
input_set_capability(input_dev, EV_KEY, xpad_common_btn[i]);
/* set up model-specific ones */
- if (xpad->xtype == XTYPE_XBOX360 || xpad->xtype == XTYPE_XBOX360W ||
- xpad->xtype == XTYPE_XBOXONE) {
+ if (xpad->xtype == XTYPE_XBOX360 || xpad->xtype == XTYPE_XBOX360W) {
for (i = 0; xpad360_btn[i] >= 0; i++)
input_set_capability(input_dev, EV_KEY, xpad360_btn[i]);
- if (xpad->mapping & MAP_SHARE_BUTTON)
- input_set_capability(input_dev, EV_KEY, KEY_RECORD);
} else {
for (i = 0; xpad_btn[i] >= 0; i++)
input_set_capability(input_dev, EV_KEY, xpad_btn[i]);
@@ -1999,12 +1405,6 @@ static int xpad_init_input(struct usb_xpad *xpad)
xpad_btn_pad[i]);
}
- /* set up paddles if the controller has them */
- if (xpad->mapping & MAP_PADDLES) {
- for (i = 0; xpad_btn_paddles[i] >= 0; i++)
- input_set_capability(input_dev, EV_KEY, xpad_btn_paddles[i]);
- }
-
/*
* This should be a simple else block. However historically
* xbox360w has mapped DPAD to buttons while xbox360 did not. This
@@ -2026,10 +1426,6 @@ static int xpad_init_input(struct usb_xpad *xpad)
xpad_set_up_abs(input_dev, xpad_abs_triggers[i]);
}
- /* setup profile button as an axis with 4 possible values */
- if (xpad->mapping & MAP_PROFILE_BUTTON)
- xpad_set_up_abs(input_dev, ABS_PROFILE);
-
error = xpad_init_ff(xpad);
if (error)
goto err_free_input;
@@ -2105,8 +1501,6 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
if (intf->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC) {
if (intf->cur_altsetting->desc.bInterfaceProtocol == 129)
xpad->xtype = XTYPE_XBOX360W;
- else if (intf->cur_altsetting->desc.bInterfaceProtocol == 208)
- xpad->xtype = XTYPE_XBOXONE;
else
xpad->xtype = XTYPE_XBOX360;
} else {
@@ -2121,17 +1515,6 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
xpad->mapping |= MAP_STICKS_TO_NULL;
}
- if (xpad->xtype == XTYPE_XBOXONE &&
- 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.
- */
- error = -ENODEV;
- goto err_free_in_urb;
- }
-
ep_irq_in = ep_irq_out = NULL;
for (i = 0; i < 2; i++) {
@@ -2306,15 +1689,6 @@ static int xpad_resume(struct usb_interface *intf)
if (input_device_enabled(input))
return xpad_start_input(xpad);
- if (xpad->xtype == XTYPE_XBOXONE) {
- /*
- * Even if there are no users, we'll send Xbox One pads
- * the startup sequence so they don't sit there and
- * blink until somebody opens the input device again.
- */
- return xpad_start_xbox_one(xpad);
- }
-
return 0;
}
--
2.50.1
^ permalink raw reply related
* [PATCH 6/6] Input: xbox_gip - Add flight stick support
From: Vicki Pfau @ 2025-08-08 4:30 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
In-Reply-To: <20250808043017.1953101-1-vi@endrift.com>
This adds preliminary flight stick support, with a few caveats:
- Flight sticks support up to 64 extra buttons. This only exposes the first
50, as there isn't any good place to map the remainder.
- Flight sticks support up to 12 extra axes. This picks a fairly abritrary
mapping for them, as there's again no good place to map them.
Flight sticks also have addressible LEDs, but I don't have a device that
supports them so I can't test them yet.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/input/joystick/xbox_gip.c | 126 +++++++++++++++++++++++++++++-
1 file changed, 123 insertions(+), 3 deletions(-)
diff --git a/drivers/input/joystick/xbox_gip.c b/drivers/input/joystick/xbox_gip.c
index 874705a9b6bfb..36611ba8d038e 100644
--- a/drivers/input/joystick/xbox_gip.c
+++ b/drivers/input/joystick/xbox_gip.c
@@ -11,9 +11,10 @@
* - Sending fragmented messages
* - Raw character device
* - Wheel force feedback
- * - Flight stick support
* - More arcade stick testing
* - Arcade stick extra buttons
+ * - More flight stick testing
+ * - Flight stick LEDs
*
* This driver is based on the Microsoft GIP spec at:
* https://aka.ms/gipdocs
@@ -37,6 +38,9 @@
#define MAX_MESSAGE_LENGTH 0x4000
#define MAX_ATTACHMENTS 8
+#define MAX_GIP_FLIGHT_STICK_BUTTONS 64
+#define MAX_GIP_FLIGHT_STICK_AXES 12
+
#define MAX_IN_MESSAGES 8
#define MAX_OUT_MESSAGES 8
@@ -377,6 +381,21 @@ static const struct gip_audio_format gip_audio_format_table[MAX_GIP_AUDIO_FORMAT
[GIP_AUDIO_FORMAT_48000HZ_8CH] = { .rate = 48000, .channels = 8 },
};
+static const unsigned int gip_flight_stick_extra_axes[MAX_GIP_FLIGHT_STICK_AXES] = {
+ ABS_RUDDER,
+ ABS_WHEEL,
+ ABS_GAS,
+ ABS_BRAKE,
+ ABS_CLUTCH,
+ ABS_HANDBRAKE,
+ ABS_HAT1X,
+ ABS_HAT1Y,
+ ABS_HAT2X,
+ ABS_HAT2Y,
+ ABS_HAT3X,
+ ABS_HAT3Y,
+};
+
struct gip_wheel_info {
uint8_t connections;
uint8_t shifter_type: 3;
@@ -1564,6 +1583,7 @@ static bool gip_send_set_device_state(struct gip_attachment *attachment, uint8_t
static int gip_setup_input_device(struct gip_attachment *attachment)
{
struct input_dev *input;
+ int i;
int rc;
input = input_allocate_device();
@@ -1616,6 +1636,24 @@ static int gip_setup_input_device(struct gip_attachment *attachment)
input_set_capability(input, EV_KEY, BTN_THUMBR);
input_set_capability(input, EV_KEY, BTN_THUMBL);
break;
+ case GIP_TYPE_FLIGHT_STICK:
+ input_set_capability(input, EV_KEY, BTN_TOP);
+ input_set_capability(input, EV_KEY, BTN_TOP2);
+ for (i = 0; i < attachment->extra_buttons && i < 10; i++)
+ input_set_capability(input, EV_KEY, BTN_0 + i);
+ for (i = 10; i < attachment->extra_buttons && i - 10 < 40; i++)
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY + i - 10);
+ if (attachment->extra_buttons > 50)
+ dev_info(GIP_DEV(attachment),
+ "Device has too many extra buttons, 51 through %i ignored\n",
+ attachment->extra_buttons);
+ input_set_abs_params(input, ABS_X, -32768, 32767, 0, 0);
+ input_set_abs_params(input, ABS_Y, -32768, 32767, 0, 0);
+ input_set_abs_params(input, ABS_Z, -32768, 32767, 0, 0);
+ input_set_abs_params(input, ABS_THROTTLE, 0, 65535, 0, 0);
+ for (i = 0; i < attachment->extra_axes && i < MAX_GIP_FLIGHT_STICK_AXES; i++)
+ input_set_abs_params(input, gip_flight_stick_extra_axes[i], 0, 65535, 0, 0);
+ break;
case GIP_TYPE_WHEEL:
input_set_abs_params(input, ABS_WHEEL,
-attachment->wheel.max_angle - 1,
@@ -1653,7 +1691,6 @@ static int gip_setup_input_device(struct gip_attachment *attachment)
break;
case GIP_TYPE_UNKNOWN:
case GIP_TYPE_NAVIGATION_CONTROLLER:
- case GIP_TYPE_FLIGHT_STICK:
break;
case GIP_TYPE_CHATPAD:
case GIP_TYPE_HEADSET:
@@ -1770,6 +1807,12 @@ static int gip_send_init_sequence(struct gip_attachment *attachment)
sizeof(request));
}
+ if (attachment->attachment_type == GIP_TYPE_FLIGHT_STICK
+ && gip_supports_vendor_message(attachment,
+ GIP_CMD_DEVICE_CAPABILITIES, false))
+ gip_send_vendor_message(attachment,
+ GIP_CMD_DEVICE_CAPABILITIES, 0, NULL, 0);
+
usb_make_path(attachment->device->udev, attachment->phys,
sizeof(attachment->phys));
len = strlen(attachment->phys);
@@ -1779,7 +1822,8 @@ static int gip_send_init_sequence(struct gip_attachment *attachment)
attachment->attachment_index);
if (gip_attachment_is_controller(attachment) && !attachment->input
- && attachment->attachment_type != GIP_TYPE_WHEEL) {
+ && attachment->attachment_type != GIP_TYPE_WHEEL
+ && attachment->attachment_type != GIP_TYPE_FLIGHT_STICK) {
rc = gip_setup_input_device(attachment);
if (rc == -ENODEV)
return 0;
@@ -2242,6 +2286,22 @@ static int gip_handle_command_firmware(struct gip_attachment *attachment,
return -ENOTSUPP;
}
+static int gip_handle_device_capabilities(struct gip_attachment *attachment,
+ const struct gip_header *header, const void *bytes, int num_bytes)
+{
+ const struct gip_device_capabilities_response *response = bytes;
+
+ if (attachment->input)
+ return 0;
+
+ if (num_bytes < 4)
+ return -EINVAL;
+
+ attachment->extra_axes = response->extra_axis_count;
+ attachment->extra_buttons = response->extra_button_count;
+ return gip_setup_input_device(attachment);
+}
+
static int gip_handle_command_raw_report(struct gip_attachment *attachment,
const struct gip_header *header, const uint8_t *bytes, int num_bytes)
{
@@ -2405,6 +2465,58 @@ static void gip_handle_arcade_stick_report(struct gip_attachment *attachment,
}
}
+static void gip_handle_flight_stick_report(struct gip_attachment *attachment,
+ struct input_dev *dev, const uint8_t *bytes, int num_bytes)
+{
+ int32_t axis;
+ int i;
+
+ if (num_bytes < 19)
+ return;
+
+ /* Fire 1 and 2 */
+ input_report_key(dev, BTN_TOP, bytes[2] & BIT(0));
+ input_report_key(dev, BTN_TOP2, bytes[2] & BIT(1));
+
+ for (i = 0; i < attachment->extra_buttons && i < 10; i++) {
+ input_report_key(dev, BTN_0 + i,
+ bytes[i / 8 + 3] & BIT(i));
+ }
+ for (i = 10; i < attachment->extra_buttons && i - 10 < 40; i++) {
+ input_report_key(dev, BTN_TRIGGER_HAPPY + i - 10,
+ bytes[i / 8 + 3] & BIT(i));
+ }
+
+ /*
+ * Roll, pitch and yaw are signed. Throttle and any
+ * extra axes are unsigned. All values are full-range.
+ */
+ axis = bytes[11];
+ axis |= bytes[12] << 8;
+ input_report_abs(dev, ABS_X, (int16_t) axis);
+
+ axis = bytes[13];
+ axis |= bytes[14] << 8;
+ input_report_abs(dev, ABS_Y, (int16_t) axis);
+
+ axis = bytes[15];
+ axis |= bytes[16] << 8;
+ input_report_abs(dev, ABS_Z, (int16_t) axis);
+
+ axis = bytes[17];
+ axis |= bytes[18] << 8;
+ input_report_abs(dev, ABS_THROTTLE, axis);
+
+ for (i = 0; i < attachment->extra_axes && i < MAX_GIP_FLIGHT_STICK_AXES; i++) {
+ if (20 + i * 2 >= num_bytes)
+ return;
+
+ axis = bytes[19 + i * 2];
+ axis |= bytes[20 + i * 2] << 8;
+ input_report_abs(dev, gip_flight_stick_extra_axes[i], axis);
+ }
+}
+
static void gip_handle_wheel_report(struct gip_attachment *attachment,
struct input_dev *dev, const uint8_t *bytes, int num_bytes)
{
@@ -2508,6 +2620,9 @@ static int gip_handle_ll_input_report(struct gip_attachment *attachment,
case GIP_TYPE_ARCADE_STICK:
gip_handle_arcade_stick_report(attachment, dev, bytes, num_bytes);
break;
+ case GIP_TYPE_FLIGHT_STICK:
+ gip_handle_flight_stick_report(attachment, dev, bytes, num_bytes);
+ break;
case GIP_TYPE_WHEEL:
gip_handle_wheel_report(attachment, dev, bytes, num_bytes);
break;
@@ -2723,6 +2838,11 @@ static int gip_handle_message(struct gip_attachment *attachment,
num_bytes);
switch (header->message_type) {
+ case GIP_CMD_DEVICE_CAPABILITIES:
+ if (attachment->attachment_type == GIP_TYPE_FLIGHT_STICK)
+ return gip_handle_device_capabilities(attachment,
+ header, bytes, num_bytes);
+ break;
case GIP_CMD_RAW_REPORT:
if (attachment->features & GIP_FEATURE_ELITE_BUTTONS)
return gip_handle_command_raw_report(attachment,
--
2.50.1
^ permalink raw reply related
* [PATCH 5/6] Input: xbox_gip - Add wheel support
From: Vicki Pfau @ 2025-08-08 4:30 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
In-Reply-To: <20250808043017.1953101-1-vi@endrift.com>
This adds preliminary support for racing wheel support in xbox_gip,
exposing them mapped to the newly added axes.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/input/joystick/xbox_gip.c | 209 ++++++++++++++++++++++++++++--
1 file changed, 201 insertions(+), 8 deletions(-)
diff --git a/drivers/input/joystick/xbox_gip.c b/drivers/input/joystick/xbox_gip.c
index afb70311c064e..874705a9b6bfb 100644
--- a/drivers/input/joystick/xbox_gip.c
+++ b/drivers/input/joystick/xbox_gip.c
@@ -10,7 +10,7 @@
* - Event logging
* - Sending fragmented messages
* - Raw character device
- * - Wheel support
+ * - Wheel force feedback
* - Flight stick support
* - More arcade stick testing
* - Arcade stick extra buttons
@@ -219,6 +219,22 @@
#define GIP_EXTENDED_STATUS_ACCESS_DENIED 3
#define GIP_EXTENDED_STATUS_FAILED 4
+/* Wheel-specific flags */
+#define GIP_WHEEL_HAS_POWER BIT(3)
+#define GIP_WHEEL_HANDBRAKE_CONN BIT(4)
+#define GIP_WHEEL_CLUTCH_CONN BIT(5)
+#define GIP_WHEEL_BRAKE_CONN BIT(6)
+#define GIP_WHEEL_THROTTLE_CONN BIT(7)
+
+#define GIP_HSHIFTER_NONE 0
+#define GIP_HSHIFTER_2POS 1 /* 2 position, no neutral */
+#define GIP_HSHIFTER_2POS_N 2 /* 2 position, neutral */
+#define GIP_HSHIFTER_RTL_1TL 3 /* Reverse top left, first top left */
+#define GIP_HSHIFTER_RTL_1BL 4 /* Reverse top left, first bottom left */
+#define GIP_HSHIFTER_RBL 5 /* Reverse bottom left */
+#define GIP_HSHIFTER_RTR 6 /* Reverse top right */
+#define GIP_HSHIFTER_RBR 7 /* Reverse bottom right */
+
/* Internal constants, not part of protocol */
#define GIP_DEFAULT_IN_SYSTEM_MESSAGES 0x5e
#define GIP_DEFAULT_OUT_SYSTEM_MESSAGES 0x472
@@ -234,6 +250,8 @@
#define GIP_QUIRK_NO_HELLO BIT(0)
#define GIP_QUIRK_NO_IMPULSE_VIBRATION BIT(1)
+#define GIP_QUIRK_FORCE_GAMEPAD_SB BIT(2)
+#define GIP_QUIRK_WHEEL_FORCE_HANDBRAKE BIT(31)
#define GIP_LED_GUIDE_MAX_BRIGHTNESS 100 /* Spec says 47, but larger values work */
#define GIP_LED_GUIDE_INIT_BRIGHTNESS 20
@@ -276,6 +294,11 @@ enum gip_elite_button_format {
GIP_BTN_FMT_XBE2_5,
};
+enum gip_vendor_type {
+ GIP_VENDOR_NONE = 0,
+ GIP_VENDOR_LOGI_TRUE_FORCE_WHEEL = 1,
+};
+
static const guid_t guid_arcade_stick =
GUID_INIT(0x332054cc, 0xa34b, 0x41d5, 0xa3, 0x4a, 0xa6, 0xa6, 0x71, 0x1e, 0xc4, 0xb3);
static const guid_t guid_console_function_map =
@@ -354,6 +377,18 @@ static const struct gip_audio_format gip_audio_format_table[MAX_GIP_AUDIO_FORMAT
[GIP_AUDIO_FORMAT_48000HZ_8CH] = { .rate = 48000, .channels = 8 },
};
+struct gip_wheel_info {
+ uint8_t connections;
+ uint8_t shifter_type: 3;
+ uint8_t max_gear: 5;
+ uint16_t angle_setting;
+ uint16_t max_angle;
+ uint16_t max_throttle;
+ uint16_t max_brake;
+ uint16_t max_clutch;
+ uint8_t max_handbrake;
+};
+
struct gip_quirks {
uint16_t vendor_id;
uint16_t product_id;
@@ -370,6 +405,11 @@ struct gip_quirks {
};
static const struct gip_quirks quirks[] = {
+ /* Thrustmaster T128X GIP Racing Wheel */
+ { 0x044f, 0xb69c, 0,
+ .quirks = GIP_QUIRK_FORCE_GAMEPAD_SB | GIP_QUIRK_WHEEL_FORCE_HANDBRAKE,
+ .device_type = GIP_TYPE_WHEEL },
+
/* Xbox One Controller (model 1573) */
{ 0x045e, 0x02d1, 0, .override_name = "Xbox One Controller" },
@@ -497,11 +537,14 @@ struct gip_attachment {
enum gip_elite_button_format xbe_format;
uint32_t features;
uint32_t quirks;
+ enum gip_vendor_type vendor_type;
int extra_buttons;
int extra_axes;
bool dpad_as_buttons;
+ struct gip_wheel_info wheel;
+ int8_t logi_dial_state;
struct hid_device *hdev;
};
@@ -1573,10 +1616,44 @@ static int gip_setup_input_device(struct gip_attachment *attachment)
input_set_capability(input, EV_KEY, BTN_THUMBR);
input_set_capability(input, EV_KEY, BTN_THUMBL);
break;
- case GIP_TYPE_FLIGHT_STICK:
case GIP_TYPE_WHEEL:
+ input_set_abs_params(input, ABS_WHEEL,
+ -attachment->wheel.max_angle - 1,
+ attachment->wheel.max_angle, 0, 0);
+ input_abs_set_res(input, ABS_WHEEL, attachment->wheel.angle_setting);
+ if (attachment->wheel.max_throttle)
+ input_set_abs_params(input, ABS_GAS, 0,
+ attachment->wheel.max_throttle, 0, 0);
+
+ if (attachment->wheel.max_brake)
+ input_set_abs_params(input, ABS_BRAKE, 0,
+ attachment->wheel.max_brake, 0, 0);
+
+ if (attachment->wheel.max_clutch)
+ input_set_abs_params(input, ABS_CLUTCH, 0,
+ attachment->wheel.max_clutch, 0, 0);
+
+ if (attachment->wheel.max_handbrake)
+ input_set_abs_params(input, ABS_HANDBRAKE, 0,
+ attachment->wheel.max_handbrake, 0, 0);
+
+ if (attachment->wheel.shifter_type)
+ input_set_abs_params(input, ABS_SHIFTER, -1,
+ attachment->wheel.max_gear, 0, 0);
+
+
+ if (attachment->vendor_type == GIP_VENDOR_LOGI_TRUE_FORCE_WHEEL) {
+ input_set_capability(input, EV_KEY, BTN_THUMBL);
+ input_set_capability(input, EV_KEY, BTN_THUMBR);
+ input_set_capability(input, EV_KEY, KEY_KPPLUS);
+ input_set_capability(input, EV_KEY, KEY_KPMINUS);
+ input_set_capability(input, EV_KEY, KEY_KPENTER);
+ input_set_capability(input, EV_REL, REL_DIAL);
+ }
+ break;
case GIP_TYPE_UNKNOWN:
case GIP_TYPE_NAVIGATION_CONTROLLER:
+ case GIP_TYPE_FLIGHT_STICK:
break;
case GIP_TYPE_CHATPAD:
case GIP_TYPE_HEADSET:
@@ -1601,6 +1678,11 @@ static int gip_setup_input_device(struct gip_attachment *attachment)
if (attachment->vendor_id == 0x045e && attachment->product_id == 0x0b0a)
input_set_abs_params(input, ABS_PROFILE, 0, 3, 0, 0);
+ if (attachment->quirks & GIP_QUIRK_WHEEL_FORCE_HANDBRAKE) {
+ input_set_capability(input, EV_KEY, BTN_THUMBR);
+ input_set_capability(input, EV_KEY, BTN_THUMBL);
+ }
+
#ifdef CONFIG_JOYSTICK_XBOX_GIP_FF
if (attachment->features & GIP_FEATURE_MOTOR_CONTROL) {
input_set_capability(input, EV_FF, FF_RUMBLE);
@@ -1680,6 +1762,14 @@ static int gip_send_init_sequence(struct gip_attachment *attachment)
return rc;
}
+ if (attachment->attachment_type == GIP_TYPE_WHEEL) {
+ struct gip_initial_reports_request request = { 0 };
+
+ gip_send_vendor_message(attachment,
+ GIP_CMD_INITIAL_REPORTS_REQUEST, 0, &request,
+ sizeof(request));
+ }
+
usb_make_path(attachment->device->udev, attachment->phys,
sizeof(attachment->phys));
len = strlen(attachment->phys);
@@ -1688,7 +1778,8 @@ static int gip_send_init_sequence(struct gip_attachment *attachment)
sizeof(attachment->phys) - len, "/input%d",
attachment->attachment_index);
- if (gip_attachment_is_controller(attachment) && !attachment->input) {
+ if (gip_attachment_is_controller(attachment) && !attachment->input
+ && attachment->attachment_type != GIP_TYPE_WHEEL) {
rc = gip_setup_input_device(attachment);
if (rc == -ENODEV)
return 0;
@@ -2005,6 +2096,13 @@ static int gip_handle_command_metadata_respose(struct gip_attachment *attachment
expected_guid = &guid_headset;
break;
}
+
+ if (strcmp(type, "Logi.Xbox.Input.TrueForceWheel") == 0) {
+ attachment->attachment_type = GIP_TYPE_WHEEL;
+ attachment->vendor_type = GIP_VENDOR_LOGI_TRUE_FORCE_WHEEL;
+ expected_guid = &guid_logi_true_force_wheel;
+ break;
+ }
}
found_expected_guid = !expected_guid;
@@ -2307,13 +2405,87 @@ static void gip_handle_arcade_stick_report(struct gip_attachment *attachment,
}
}
+static void gip_handle_wheel_report(struct gip_attachment *attachment,
+ struct input_dev *dev, const uint8_t *bytes, int num_bytes)
+{
+ int32_t axis;
+
+ if (num_bytes < 16)
+ return;
+
+ axis = bytes[2];
+ axis |= bytes[3] << 8;
+ input_report_abs(dev, ABS_WHEEL, axis - 0x8000);
+
+ if (attachment->wheel.connections & GIP_WHEEL_THROTTLE_CONN) {
+ axis = bytes[4];
+ axis |= bytes[5] << 8;
+ input_report_abs(dev, ABS_GAS, axis);
+ }
+
+ if (attachment->wheel.connections & GIP_WHEEL_BRAKE_CONN) {
+ axis = bytes[6];
+ axis |= bytes[7] << 8;
+ input_report_abs(dev, ABS_BRAKE, axis);
+ }
+
+ if (attachment->wheel.connections & GIP_WHEEL_CLUTCH_CONN) {
+ axis = bytes[8];
+ axis |= bytes[9] << 8;
+ input_report_abs(dev, ABS_CLUTCH, axis);
+ }
+
+ if (attachment->wheel.connections & GIP_WHEEL_HANDBRAKE_CONN)
+ input_report_abs(dev, ABS_HANDBRAKE, bytes[10]);
+
+ if (attachment->wheel.shifter_type)
+ input_report_abs(dev, ABS_SHIFTER, (int8_t)bytes[12]);
+
+ if (attachment->vendor_type == GIP_VENDOR_LOGI_TRUE_FORCE_WHEEL && num_bytes >= 18) {
+ int dial = bytes[17] >> 5;
+
+ input_report_key(dev, BTN_THUMBL, bytes[17] & BIT(0));
+ input_report_key(dev, BTN_THUMBR, bytes[17] & BIT(1));
+ input_report_key(dev, KEY_KPPLUS, bytes[17] & BIT(2));
+ input_report_key(dev, KEY_KPMINUS, bytes[17] & BIT(3));
+ input_report_key(dev, KEY_KPENTER, bytes[17] & BIT(4));
+ if (dial == 0 && attachment->logi_dial_state == 7)
+ input_report_rel(dev, REL_DIAL, -1);
+ else if (dial == 7 && attachment->logi_dial_state == 0)
+ input_report_rel(dev, REL_DIAL, 1);
+ else
+ input_report_rel(dev, REL_DIAL,
+ attachment->logi_dial_state - dial);
+ attachment->logi_dial_state = dial;
+ }
+}
+
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 *dev = attachment->input;
- if (!dev)
- return -ENODEV;
+ if (!dev) {
+ if (attachment->attachment_type == GIP_TYPE_WHEEL) {
+ if (num_bytes < 17)
+ return -EINVAL;
+ attachment->wheel.max_gear = bytes[11] & 0x1F;
+ attachment->wheel.shifter_type = bytes[11] >> 5;
+ attachment->wheel.angle_setting = bytes[13];
+ attachment->wheel.angle_setting |= bytes[14] << 8;
+ attachment->wheel.connections = bytes[16];
+
+ if (attachment->quirks & GIP_QUIRK_WHEEL_FORCE_HANDBRAKE)
+ attachment->wheel.connections |= GIP_WHEEL_HANDBRAKE_CONN;
+
+ if (attachment->wheel.angle_setting && attachment->wheel.max_angle)
+ return gip_setup_input_device(attachment);
+ else
+ return 0;
+ } else {
+ return -ENODEV;
+ }
+ }
if (attachment->device_state != GIP_STATE_START) {
dev_dbg(GIP_DEV(attachment), "Discarding early input report\n");
@@ -2336,6 +2508,14 @@ static int gip_handle_ll_input_report(struct gip_attachment *attachment,
case GIP_TYPE_ARCADE_STICK:
gip_handle_arcade_stick_report(attachment, dev, bytes, num_bytes);
break;
+ case GIP_TYPE_WHEEL:
+ gip_handle_wheel_report(attachment, dev, bytes, num_bytes);
+ break;
+ }
+
+ if (attachment->quirks & GIP_QUIRK_FORCE_GAMEPAD_SB) {
+ input_report_key(dev, BTN_THUMBL, bytes[1] & BIT(6));
+ input_report_key(dev, BTN_THUMBR, bytes[1] & BIT(7));
}
if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) {
@@ -2413,9 +2593,22 @@ static int gip_handle_ll_input_report(struct gip_attachment *attachment,
static int gip_handle_ll_static_configuration(struct gip_attachment *attachment,
const struct gip_header *header, const uint8_t *bytes, int num_bytes)
{
- /* TODO */
- dev_dbg(GIP_DEV(attachment), "Unimplemented Static Configuration message\n");
- return -ENOTSUPP;
+ if (attachment->attachment_type == GIP_TYPE_WHEEL) {
+ if (num_bytes < 11)
+ return -EINVAL;
+ attachment->wheel.max_angle = BIT(bytes[0]) - 1;
+ attachment->wheel.max_throttle = BIT(bytes[1]) - 1;
+ attachment->wheel.max_brake = BIT(bytes[2]) - 1;
+ attachment->wheel.max_clutch = BIT(bytes[3]) - 1;
+ attachment->wheel.max_handbrake = BIT(bytes[4]) - 1;
+ if (attachment->wheel.angle_setting && attachment->wheel.max_angle)
+ return gip_setup_input_device(attachment);
+ } else {
+ /* TODO */
+ dev_dbg(GIP_DEV(attachment), "Unimplemented Static Configuration message\n");
+ return -ENOTSUPP;
+ }
+ return 0;
}
static int gip_handle_ll_button_info_report(struct gip_attachment *attachment,
--
2.50.1
^ permalink raw reply related
* [PATCH v3 RESEND 0/3] Initial work for Rust abstraction for HID device driver development
From: Rahul Rameshbabu @ 2025-08-08 6:13 UTC (permalink / raw)
To: linux-input, rust-for-linux, linux-kernel
Cc: Jiri Kosina, a.hindborg, alex.gaynor, aliceryhl, benno.lossin,
Benjamin Tissoires, bjorn3_gh, boqun.feng, dakr, db48x, gary,
ojeda, tmgross, peter.hutterer, Rahul Rameshbabu
Hello again,
Resending my patches for review.
https://lore.kernel.org/rust-for-linux/20250721020211.196394-2-sergeantsagara@protonmail.com/
I incorporated Danilo's and Miguel's feedback from my v2. Additionally, I
noticed I had basic formatting issues when running scripts/checkpatch.pl.
I made sure to check the generated rustdocs and that the Rust examples
compile as part of the kunit infrastructure. I dropped the kref bindings
as they are no longer needed for this series.
Link: https://lore.kernel.org/rust-for-linux/20250721020211.196394-2-sergeantsagara@protonmail.com/
Link: https://lore.kernel.org/rust-for-linux/20250713211012.101476-4-sergeantsagara@protonmail.com/
Link: https://lore.kernel.org/rust-for-linux/20250629045031.92358-2-sergeantsagara@protonmail.com/
Link: https://lore.kernel.org/rust-for-linux/20250313160220.6410-2-sergeantsagara@protonmail.com/
Link: https://binary-eater.github.io/tags/usb-monitor-control/
Rahul Rameshbabu (3):
HID: core: Change hid_driver to use a const char* for name
rust: core abstractions for HID drivers
rust: hid: Glorious PC Gaming Race Model O and O- mice reference
driver
MAINTAINERS | 16 +
drivers/hid/Kconfig | 16 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-glorious.c | 2 +
drivers/hid/hid_glorious_rust.rs | 60 ++++
include/linux/hid.h | 2 +-
rust/bindings/bindings_helper.h | 3 +
rust/kernel/hid.rs | 503 +++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 2 +
9 files changed, 604 insertions(+), 1 deletion(-)
create mode 100644 drivers/hid/hid_glorious_rust.rs
create mode 100644 rust/kernel/hid.rs
base-commit: 1523590203786bf4e1d29b7d08a7100c783f20ba
--
2.47.2
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox