* [PATCH WIP v3 02/11] Input: stmfts - Use dev struct directly
From: David Heidelberg via B4 Relay @ 2026-04-03 17:08 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260403-stmfts5-v3-0-5da768cfd201@ixit.cz>
From: David Heidelberg <david@ixit.cz>
Makes the code better readable and noticably shorter.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index def6bd0c8e059..7b1e975a85668 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -619,6 +619,7 @@ static int stmfts_enable_led(struct stmfts_data *sdata)
static int stmfts_probe(struct i2c_client *client)
{
+ struct device *dev = &client->dev;
int err;
struct stmfts_data *sdata;
@@ -627,7 +628,7 @@ static int stmfts_probe(struct i2c_client *client)
I2C_FUNC_SMBUS_I2C_BLOCK))
return -ENODEV;
- sdata = devm_kzalloc(&client->dev, sizeof(*sdata), GFP_KERNEL);
+ sdata = devm_kzalloc(dev, sizeof(*sdata), GFP_KERNEL);
if (!sdata)
return -ENOMEM;
@@ -639,13 +640,13 @@ static int stmfts_probe(struct i2c_client *client)
sdata->regulators[STMFTS_REGULATOR_VDD].supply = "vdd";
sdata->regulators[STMFTS_REGULATOR_AVDD].supply = "avdd";
- err = devm_regulator_bulk_get(&client->dev,
+ err = devm_regulator_bulk_get(dev,
ARRAY_SIZE(sdata->regulators),
sdata->regulators);
if (err)
return err;
- sdata->input = devm_input_allocate_device(&client->dev);
+ sdata->input = devm_input_allocate_device(dev);
if (!sdata->input)
return -ENOMEM;
@@ -664,7 +665,7 @@ static int stmfts_probe(struct i2c_client *client)
input_set_abs_params(sdata->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0);
- sdata->use_key = device_property_read_bool(&client->dev,
+ sdata->use_key = device_property_read_bool(dev,
"touch-key-connected");
if (sdata->use_key) {
input_set_capability(sdata->input, EV_KEY, KEY_MENU);
@@ -685,20 +686,20 @@ static int stmfts_probe(struct i2c_client *client)
* interrupts. To be on the safe side it's better to not enable
* the interrupts during their request.
*/
- err = devm_request_threaded_irq(&client->dev, client->irq,
+ err = devm_request_threaded_irq(dev, client->irq,
NULL, stmfts_irq_handler,
IRQF_ONESHOT | IRQF_NO_AUTOEN,
"stmfts_irq", sdata);
if (err)
return err;
- dev_dbg(&client->dev, "initializing ST-Microelectronics FTS...\n");
+ dev_dbg(dev, "initializing ST-Microelectronics FTS...\n");
err = stmfts_power_on(sdata);
if (err)
return err;
- err = devm_add_action_or_reset(&client->dev, stmfts_power_off, sdata);
+ err = devm_add_action_or_reset(dev, stmfts_power_off, sdata);
if (err)
return err;
@@ -715,13 +716,13 @@ static int stmfts_probe(struct i2c_client *client)
* without LEDs. The ledvdd regulator pointer will be
* used as a flag.
*/
- dev_warn(&client->dev, "unable to use touchkey leds\n");
+ dev_warn(dev, "unable to use touchkey leds\n");
sdata->ledvdd = NULL;
}
}
- pm_runtime_enable(&client->dev);
- device_enable_async_suspend(&client->dev);
+ pm_runtime_enable(dev);
+ device_enable_async_suspend(dev);
return 0;
}
--
2.53.0
^ permalink raw reply related
* [PATCH WIP v3 03/11] Input: stmfts - Switch to devm_regulator_bulk_get_const
From: David Heidelberg via B4 Relay @ 2026-04-03 17:08 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260403-stmfts5-v3-0-5da768cfd201@ixit.cz>
From: David Heidelberg <david@ixit.cz>
Switch to devm_regulator_bulk_get_const() to stop setting the supplies
list in probe(), and move the regulator_bulk_data struct in static const.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 25 ++++++++++++-------------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index 7b1e975a85668..ff884e04ad4c8 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -69,9 +69,9 @@
#define STMFTS_MAX_FINGERS 10
#define STMFTS_DEV_NAME "stmfts"
-enum stmfts_regulators {
- STMFTS_REGULATOR_VDD,
- STMFTS_REGULATOR_AVDD,
+static const struct regulator_bulk_data stmfts_supplies[] = {
+ { .supply = "vdd" },
+ { .supply = "avdd" },
};
struct stmfts_data {
@@ -82,7 +82,7 @@ struct stmfts_data {
struct touchscreen_properties prop;
- struct regulator_bulk_data regulators[2];
+ struct regulator_bulk_data *supplies;
/*
* Presence of ledvdd will be used also to check
@@ -523,8 +523,8 @@ static int stmfts_power_on(struct stmfts_data *sdata)
int err;
u8 reg[8];
- err = regulator_bulk_enable(ARRAY_SIZE(sdata->regulators),
- sdata->regulators);
+ err = regulator_bulk_enable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
if (err)
return err;
@@ -589,8 +589,8 @@ static void stmfts_power_off(void *data)
struct stmfts_data *sdata = data;
disable_irq(sdata->client->irq);
- regulator_bulk_disable(ARRAY_SIZE(sdata->regulators),
- sdata->regulators);
+ regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
}
static int stmfts_enable_led(struct stmfts_data *sdata)
@@ -638,11 +638,10 @@ static int stmfts_probe(struct i2c_client *client)
mutex_init(&sdata->mutex);
init_completion(&sdata->cmd_done);
- sdata->regulators[STMFTS_REGULATOR_VDD].supply = "vdd";
- sdata->regulators[STMFTS_REGULATOR_AVDD].supply = "avdd";
- err = devm_regulator_bulk_get(dev,
- ARRAY_SIZE(sdata->regulators),
- sdata->regulators);
+ err = devm_regulator_bulk_get_const(dev,
+ ARRAY_SIZE(stmfts_supplies),
+ stmfts_supplies,
+ &sdata->supplies);
if (err)
return err;
--
2.53.0
^ permalink raw reply related
* [PATCH WIP v3 04/11] Input: stmfts - abstract reading information from the firmware
From: David Heidelberg via B4 Relay @ 2026-04-03 17:08 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260403-stmfts5-v3-0-5da768cfd201@ixit.cz>
From: David Heidelberg <david@ixit.cz>
Improves readability and makes splitting power on function in following
commit easier.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 36 ++++++++++++++++++++++++------------
1 file changed, 24 insertions(+), 12 deletions(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index ff884e04ad4c8..71d9b747ccfc5 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -518,22 +518,11 @@ static struct attribute *stmfts_sysfs_attrs[] = {
};
ATTRIBUTE_GROUPS(stmfts_sysfs);
-static int stmfts_power_on(struct stmfts_data *sdata)
+static int stmfts_read_system_info(struct stmfts_data *sdata)
{
int err;
u8 reg[8];
- err = regulator_bulk_enable(ARRAY_SIZE(stmfts_supplies),
- sdata->supplies);
- if (err)
- return err;
-
- /*
- * The datasheet does not specify the power on time, but considering
- * that the reset time is < 10ms, I sleep 20ms to be sure
- */
- msleep(20);
-
err = i2c_smbus_read_i2c_block_data(sdata->client, STMFTS_READ_INFO,
sizeof(reg), reg);
if (err < 0)
@@ -547,6 +536,29 @@ static int stmfts_power_on(struct stmfts_data *sdata)
sdata->config_id = reg[4];
sdata->config_ver = reg[5];
+ return 0;
+}
+
+static int stmfts_power_on(struct stmfts_data *sdata)
+{
+ int err;
+
+ err = regulator_bulk_enable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
+ if (err)
+ return err;
+
+ /*
+ * The datasheet does not specify the power on time, but considering
+ * that the reset time is < 10ms, I sleep 20ms to be sure
+ */
+ msleep(20);
+
+
+ err = stmfts_read_system_info(sdata);
+ if (err)
+ return err;
+
enable_irq(sdata->client->irq);
msleep(50);
--
2.53.0
^ permalink raw reply related
* [PATCH WIP v3 05/11] Input: stmfts - disable regulators when power on fails
From: David Heidelberg via B4 Relay @ 2026-04-03 17:08 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260403-stmfts5-v3-0-5da768cfd201@ixit.cz>
From: David Heidelberg <david@ixit.cz>
We must power off regulators after failing at power on phase.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index 71d9b747ccfc5..a90528b76f52b 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -557,7 +557,7 @@ static int stmfts_power_on(struct stmfts_data *sdata)
err = stmfts_read_system_info(sdata);
if (err)
- return err;
+ goto power_off;
enable_irq(sdata->client->irq);
@@ -565,11 +565,11 @@ static int stmfts_power_on(struct stmfts_data *sdata)
err = stmfts_command(sdata, STMFTS_SYSTEM_RESET);
if (err)
- return err;
+ goto power_off;
err = stmfts_command(sdata, STMFTS_SLEEP_OUT);
if (err)
- return err;
+ goto power_off;
/* optional tuning */
err = stmfts_command(sdata, STMFTS_MS_CX_TUNING);
@@ -585,7 +585,7 @@ static int stmfts_power_on(struct stmfts_data *sdata)
err = stmfts_command(sdata, STMFTS_FULL_FORCE_CALIBRATION);
if (err)
- return err;
+ goto power_off;
/*
* At this point no one is using the touchscreen
@@ -594,6 +594,11 @@ static int stmfts_power_on(struct stmfts_data *sdata)
(void) i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
return 0;
+
+power_off:
+ regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
+ return err;
}
static void stmfts_power_off(void *data)
--
2.53.0
^ permalink raw reply related
* [PATCH WIP v3 06/11] Input: stmfts - use client to make future code cleaner
From: David Heidelberg via B4 Relay @ 2026-04-03 17:08 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260403-stmfts5-v3-0-5da768cfd201@ixit.cz>
From: Petr Hodina <petr.hodina@protonmail.com>
Make code cleaner, compiler will optimize it away anyway.
Preparation for FTM5 support, where more steps are needed.
Signed-off-by: Petr Hodina <petr.hodina@protonmail.com>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index a90528b76f52b..5f7de5e687da2 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -763,9 +763,10 @@ static int stmfts_runtime_suspend(struct device *dev)
static int stmfts_runtime_resume(struct device *dev)
{
struct stmfts_data *sdata = dev_get_drvdata(dev);
+ struct i2c_client *client = sdata->client;
int ret;
- ret = i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_OUT);
+ ret = i2c_smbus_write_byte(client, STMFTS_SLEEP_OUT);
if (ret)
dev_err(dev, "failed to resume device: %d\n", ret);
--
2.53.0
^ permalink raw reply related
* [PATCH WIP v3 08/11] Input: stmfts - add optional reset GPIO support
From: David Heidelberg via B4 Relay @ 2026-04-03 17:08 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260403-stmfts5-v3-0-5da768cfd201@ixit.cz>
From: Petr Hodina <petr.hodina@protonmail.com>
Add support for an optional "reset-gpios" property. If present, the
driver drives the reset line high at probe time and releases it during
power-on, after the regulators have been enabled.
Signed-off-by: Petr Hodina <petr.hodina@protonmail.com>
Co-developed-by: David Heidelberg <david@ixit.cz>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index 5f7de5e687da2..04110006f54a0 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -77,6 +77,7 @@ static const struct regulator_bulk_data stmfts_supplies[] = {
struct stmfts_data {
struct i2c_client *client;
struct input_dev *input;
+ struct gpio_desc *reset_gpio;
struct led_classdev led_cdev;
struct mutex mutex;
@@ -539,6 +540,15 @@ static int stmfts_read_system_info(struct stmfts_data *sdata)
return 0;
}
+static void stmfts_reset(struct stmfts_data *sdata)
+{
+ gpiod_set_value_cansleep(sdata->reset_gpio, 1);
+ msleep(20);
+
+ gpiod_set_value_cansleep(sdata->reset_gpio, 0);
+ msleep(50);
+}
+
static int stmfts_power_on(struct stmfts_data *sdata)
{
int err;
@@ -548,6 +558,9 @@ static int stmfts_power_on(struct stmfts_data *sdata)
if (err)
return err;
+ if (sdata->reset_gpio)
+ stmfts_reset(sdata);
+
/*
* The datasheet does not specify the power on time, but considering
* that the reset time is < 10ms, I sleep 20ms to be sure
@@ -606,6 +619,10 @@ static void stmfts_power_off(void *data)
struct stmfts_data *sdata = data;
disable_irq(sdata->client->irq);
+
+ if (sdata->reset_gpio)
+ gpiod_set_value_cansleep(sdata->reset_gpio, 1);
+
regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
sdata->supplies);
}
@@ -662,6 +679,12 @@ static int stmfts_probe(struct i2c_client *client)
if (err)
return err;
+ sdata->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(sdata->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(sdata->reset_gpio),
+ "Failed to get GPIO 'reset'\n");
+
sdata->input = devm_input_allocate_device(dev);
if (!sdata->input)
return -ENOMEM;
--
2.53.0
^ permalink raw reply related
* [PATCH WIP v3 07/11] dt-bindings: input: touchscreen: st,stmfts: Introduce reset GPIO
From: David Heidelberg via B4 Relay @ 2026-04-03 17:08 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260403-stmfts5-v3-0-5da768cfd201@ixit.cz>
From: David Heidelberg <david@ixit.cz>
FTS has associated reset GPIO, document it.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
index 12256ae7df90d..64c4f24ea3dd0 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
+++ b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
@@ -40,6 +40,10 @@ properties:
vdd-supply:
description: Power supply
+ reset-gpios:
+ description: Reset GPIO (active-low)
+ maxItems: 1
+
required:
- compatible
- reg
--
2.53.0
^ permalink raw reply related
* [PATCH WIP v3 09/11] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5
From: David Heidelberg via B4 Relay @ 2026-04-03 17:08 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260403-stmfts5-v3-0-5da768cfd201@ixit.cz>
From: David Heidelberg <david@ixit.cz>
Introduce more recent STM FTS5 touchscreen support.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
.../devicetree/bindings/input/touchscreen/st,stmfts.yaml | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
index 64c4f24ea3dd0..441fc92b9a4ed 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
+++ b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
@@ -16,10 +16,19 @@ description:
allOf:
- $ref: touchscreen.yaml#
+ - if:
+ properties:
+ compatible:
+ const: st,stmfts5
+ then:
+ required:
+ - mode-switch-gpios
properties:
compatible:
- const: st,stmfts
+ enum:
+ - st,stmfts
+ - st,stmfts5
reg:
maxItems: 1
@@ -40,6 +49,10 @@ properties:
vdd-supply:
description: Power supply
+ mode-switch-gpios:
+ description: Switch between touchscreen SLPI and AP mode.
+ maxItems: 1
+
reset-gpios:
description: Reset GPIO (active-low)
maxItems: 1
--
2.53.0
^ permalink raw reply related
* [PATCH WIP v3 10/11] Input: stmfts - support FTS5
From: David Heidelberg via B4 Relay @ 2026-04-03 17:08 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260403-stmfts5-v3-0-5da768cfd201@ixit.cz>
From: Petr Hodina <petr.hodina@protonmail.com>
Introduce basic FTS5 support.
FTS support SLPI and AP mode, introduce mode-switch GPIO to switch between
those two. Currently we can handle only full power AP mode, so we just
keep the AP on.
Useful for devices like Pixel 3 (blueline) and many others.
Signed-off-by: Petr Hodina <petr.hodina@protonmail.com>
Co-developed-by: David Heidelberg <david@ixit.cz>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 481 +++++++++++++++++++++++++++++++++++--
1 file changed, 460 insertions(+), 21 deletions(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index 04110006f54a0..e0fa1c4af1987 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -1,8 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
-// STMicroelectronics FTS Touchscreen device driver
-//
-// Copyright (c) 2017 Samsung Electronics Co., Ltd.
-// Copyright (c) 2017 Andi Shyti <andi@etezian.org>
+/* STMicroelectronics FTS Touchscreen device driver
+ *
+ * Copyright 2017 Samsung Electronics Co., Ltd.
+ * Copyright 2017 Andi Shyti <andi@etezian.org>
+ * Copyright David Heidelberg <david@ixit.cz>
+ * Copyright Petr Hodina <petr.hodina@protonmail.com>
+ */
#include <linux/delay.h>
#include <linux/i2c.h>
@@ -12,6 +15,7 @@
#include <linux/irq.h>
#include <linux/leds.h>
#include <linux/module.h>
+#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
@@ -34,6 +38,7 @@
#define STMFTS_FULL_FORCE_CALIBRATION 0xa2
#define STMFTS_MS_CX_TUNING 0xa3
#define STMFTS_SS_CX_TUNING 0xa4
+#define STMFTS5_SET_SCAN_MODE 0xa0
/* events */
#define STMFTS_EV_NO_EVENT 0x00
@@ -51,12 +56,32 @@
#define STMFTS_EV_STATUS 0x16
#define STMFTS_EV_DEBUG 0xdb
+/* events FTS5 */
+#define STMFTS5_EV_CONTROLLER_READY 0x03
+/* FTM5 event IDs (full byte, not masked) */
+#define STMFTS5_EV_MULTI_TOUCH_ENTER 0x13
+#define STMFTS5_EV_MULTI_TOUCH_MOTION 0x23
+#define STMFTS5_EV_MULTI_TOUCH_LEAVE 0x33
+#define STMFTS5_EV_STATUS_UPDATE 0x43
+#define STMFTS5_EV_USER_REPORT 0x53
+#define STMFTS5_EV_DEBUG 0xe3
+#define STMFTS5_EV_ERROR 0xf3
+
/* multi touch related event masks */
#define STMFTS_MASK_EVENT_ID 0x0f
#define STMFTS_MASK_TOUCH_ID 0xf0
#define STMFTS_MASK_LEFT_EVENT 0x0f
#define STMFTS_MASK_X_MSB 0x0f
#define STMFTS_MASK_Y_LSB 0xf0
+#define STMFTS5_MASK_TOUCH_TYPE 0x0f
+
+/* touch type classifications */
+#define STMFTS_TOUCH_TYPE_INVALID 0x00
+#define STMFTS_TOUCH_TYPE_FINGER 0x01
+#define STMFTS_TOUCH_TYPE_GLOVE 0x02
+#define STMFTS_TOUCH_TYPE_STYLUS 0x03
+#define STMFTS_TOUCH_TYPE_PALM 0x04
+#define STMFTS_TOUCH_TYPE_HOVER 0x05
/* key related event masks */
#define STMFTS_MASK_KEY_NO_TOUCH 0x00
@@ -75,9 +100,12 @@ static const struct regulator_bulk_data stmfts_supplies[] = {
};
struct stmfts_data {
+ const struct stmfts_chip_ops *ops;
+
struct i2c_client *client;
struct input_dev *input;
struct gpio_desc *reset_gpio;
+ struct gpio_desc *mode_switch_gpio;
struct led_classdev led_cdev;
struct mutex mutex;
@@ -101,12 +129,24 @@ struct stmfts_data {
struct completion cmd_done;
+ unsigned long touch_id;
+ unsigned long stylus_id;
+
+ bool is_fts5;
bool use_key;
bool led_status;
bool hover_enabled;
+ bool stylus_enabled;
bool running;
};
+struct stmfts_chip_ops {
+ int (*power_on)(struct stmfts_data *sdata);
+ int (*input_open)(struct input_dev *dev);
+ void (*input_close)(struct input_dev *dev);
+ void (*parse_events)(struct stmfts_data *sdata);
+};
+
static int stmfts_brightness_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
@@ -169,6 +209,7 @@ static int stmfts_read_events(struct stmfts_data *sdata)
return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
}
+/* FTS4 event handling functions */
static void stmfts_report_contact_event(struct stmfts_data *sdata,
const u8 event[])
{
@@ -204,6 +245,157 @@ static void stmfts_report_contact_release(struct stmfts_data *sdata,
input_sync(sdata->input);
}
+/* FTS5 event handling functions */
+static void stmfts5_report_contact_event(struct stmfts_data *sdata,
+ const u8 event[])
+{
+ u8 area;
+ u8 maj;
+ u8 min;
+ /* FTM5 event format:
+ * event[0] = event ID (0x13/0x23)
+ * event[1] = touch type (low 4 bits) | touch ID (high 4 bits)
+ * event[2] = X LSB
+ * event[3] = X MSB (low 4 bits) | Y MSB (high 4 bits)
+ * event[4] = Y LSB
+ * event[5] = pressure
+ * event[6] = major (low 4 bits) | minor (high 4 bits)
+ * event[7] = minor (high 2 bits)
+ */
+ u8 touch_id = (event[1] & STMFTS_MASK_TOUCH_ID) >> 4;
+ u8 touch_type = event[1] & STMFTS5_MASK_TOUCH_TYPE;
+ int x, y, distance;
+ unsigned int tool = MT_TOOL_FINGER;
+ bool touch_condition = true;
+
+ /* Parse coordinates with better precision */
+ x = (((int)event[3] & STMFTS_MASK_X_MSB) << 8) | event[2];
+ y = ((int)event[4] << 4) | ((event[3] & STMFTS_MASK_Y_LSB) >> 4);
+
+ /* Parse pressure - ensure non-zero for active touch */
+ area = event[5];
+ if (area <= 0 && touch_type != STMFTS_TOUCH_TYPE_HOVER) {
+ /* Should not happen for contact events. Set minimum pressure
+ * to prevent touch from being dropped
+ */
+ dev_warn_once(&sdata->client->dev,
+ "zero pressure on contact event, slot %d\n", touch_id);
+ area = 1;
+ }
+
+ /* Parse touch area with improved bit extraction */
+ maj = (((event[0] & 0x0C) << 2) | ((event[6] & 0xF0) >> 4));
+ min = (((event[7] & 0xC0) >> 2) | (event[6] & 0x0F));
+
+ /* Distance is 0 for touching, max for hovering */
+ distance = 0;
+
+ /* Classify touch type and set appropriate tool and parameters */
+ switch (touch_type) {
+ case STMFTS_TOUCH_TYPE_STYLUS:
+ if (sdata->stylus_enabled) {
+ tool = MT_TOOL_PEN;
+ __set_bit(touch_id, &sdata->stylus_id);
+ __clear_bit(touch_id, &sdata->touch_id);
+ break;
+ }
+ fallthrough; /* Report as finger if stylus not enabled */
+
+ case STMFTS_TOUCH_TYPE_FINGER:
+ case STMFTS_TOUCH_TYPE_GLOVE:
+ tool = MT_TOOL_FINGER;
+ __set_bit(touch_id, &sdata->touch_id);
+ __clear_bit(touch_id, &sdata->stylus_id);
+ break;
+
+ case STMFTS_TOUCH_TYPE_PALM:
+ /* Palm touch - report but can be filtered by userspace */
+ tool = MT_TOOL_PALM;
+ __set_bit(touch_id, &sdata->touch_id);
+ __clear_bit(touch_id, &sdata->stylus_id);
+ break;
+
+ case STMFTS_TOUCH_TYPE_HOVER:
+ tool = MT_TOOL_FINGER;
+ touch_condition = false;
+ area = 0;
+ distance = 255;
+ __set_bit(touch_id, &sdata->touch_id);
+ __clear_bit(touch_id, &sdata->stylus_id);
+ break;
+
+ case STMFTS_TOUCH_TYPE_INVALID:
+ default:
+ dev_warn(&sdata->client->dev,
+ "invalid touch type %d for slot %d\n",
+ touch_type, touch_id);
+ return;
+ }
+
+ /* Boundary check - some devices report max value, adjust */
+ if (x >= sdata->prop.max_x)
+ x = sdata->prop.max_x - 1;
+ if (y >= sdata->prop.max_y)
+ y = sdata->prop.max_y - 1;
+
+ input_mt_slot(sdata->input, touch_id);
+ input_report_key(sdata->input, BTN_TOUCH, touch_condition);
+ input_mt_report_slot_state(sdata->input, tool, true);
+
+ input_report_abs(sdata->input, ABS_MT_POSITION_X, x);
+ input_report_abs(sdata->input, ABS_MT_POSITION_Y, y);
+ input_report_abs(sdata->input, ABS_MT_TOUCH_MAJOR, maj);
+ input_report_abs(sdata->input, ABS_MT_TOUCH_MINOR, min);
+ input_report_abs(sdata->input, ABS_MT_PRESSURE, area);
+ input_report_abs(sdata->input, ABS_MT_DISTANCE, distance);
+
+ input_sync(sdata->input);
+}
+
+static void stmfts5_report_contact_release(struct stmfts_data *sdata,
+ const u8 event[])
+{
+ /* FTM5 format: touch ID is in high 4 bits of event[1] */
+ u8 touch_id = (event[1] & STMFTS_MASK_TOUCH_ID) >> 4;
+ u8 touch_type = event[1] & STMFTS5_MASK_TOUCH_TYPE;
+ unsigned int tool = MT_TOOL_FINGER;
+
+ /* Determine tool type based on touch classification */
+ switch (touch_type) {
+ case STMFTS_TOUCH_TYPE_STYLUS:
+ if (sdata->stylus_enabled) {
+ tool = MT_TOOL_PEN;
+ __clear_bit(touch_id, &sdata->stylus_id);
+ } else {
+ __clear_bit(touch_id, &sdata->touch_id);
+ }
+ break;
+
+ case STMFTS_TOUCH_TYPE_PALM:
+ tool = MT_TOOL_PALM;
+ __clear_bit(touch_id, &sdata->touch_id);
+ break;
+
+ case STMFTS_TOUCH_TYPE_FINGER:
+ case STMFTS_TOUCH_TYPE_GLOVE:
+ case STMFTS_TOUCH_TYPE_HOVER:
+ default:
+ tool = MT_TOOL_FINGER;
+ __clear_bit(touch_id, &sdata->touch_id);
+ break;
+ }
+
+ input_mt_slot(sdata->input, touch_id);
+ input_report_abs(sdata->input, ABS_MT_PRESSURE, 0);
+ input_mt_report_slot_state(sdata->input, tool, false);
+
+ /* Report BTN_TOUCH only if no touches remain */
+ if (!sdata->touch_id && !sdata->stylus_id)
+ input_report_key(sdata->input, BTN_TOUCH, 0);
+
+ input_sync(sdata->input);
+}
+
static void stmfts_report_hover_event(struct stmfts_data *sdata,
const u8 event[])
{
@@ -251,7 +443,6 @@ static void stmfts_parse_events(struct stmfts_data *sdata)
u8 *event = &sdata->data[i * STMFTS_EVENT_SIZE];
switch (event[0]) {
-
case STMFTS_EV_CONTROLLER_READY:
case STMFTS_EV_SLEEP_OUT_CONTROLLER_READY:
case STMFTS_EV_STATUS:
@@ -264,7 +455,6 @@ static void stmfts_parse_events(struct stmfts_data *sdata)
}
switch (event[0] & STMFTS_MASK_EVENT_ID) {
-
case STMFTS_EV_MULTI_TOUCH_ENTER:
case STMFTS_EV_MULTI_TOUCH_MOTION:
stmfts_report_contact_event(sdata, event);
@@ -298,6 +488,45 @@ static void stmfts_parse_events(struct stmfts_data *sdata)
}
}
+static void stmfts5_parse_events(struct stmfts_data *sdata)
+{
+ for (int i = 0; i < STMFTS_STACK_DEPTH; i++) {
+ u8 *event = &sdata->data[i * STMFTS_EVENT_SIZE];
+
+ switch (event[0]) {
+ case STMFTS5_EV_CONTROLLER_READY:
+ complete(&sdata->cmd_done);
+ fallthrough;
+
+ case STMFTS_EV_NO_EVENT:
+ case STMFTS5_EV_STATUS_UPDATE:
+ case STMFTS5_EV_USER_REPORT:
+ case STMFTS5_EV_DEBUG:
+ return;
+
+ case STMFTS5_EV_MULTI_TOUCH_ENTER:
+ case STMFTS5_EV_MULTI_TOUCH_MOTION:
+ stmfts5_report_contact_event(sdata, event);
+ break;
+
+ case STMFTS5_EV_MULTI_TOUCH_LEAVE:
+ stmfts5_report_contact_release(sdata, event);
+ break;
+
+ case STMFTS5_EV_ERROR:
+ dev_warn(&sdata->client->dev,
+ "error code: 0x%x%x%x%x%x%x",
+ event[6], event[5], event[4],
+ event[3], event[2], event[1]);
+ break;
+
+ default:
+ dev_err(&sdata->client->dev,
+ "unknown FTS5 event %#02x\n", event[0]);
+ }
+ }
+}
+
static irqreturn_t stmfts_irq_handler(int irq, void *dev)
{
struct stmfts_data *sdata = dev;
@@ -310,7 +539,7 @@ static irqreturn_t stmfts_irq_handler(int irq, void *dev)
dev_err(&sdata->client->dev,
"failed to read events: %d\n", err);
else
- stmfts_parse_events(sdata);
+ sdata->ops->parse_events(sdata);
return IRQ_HANDLED;
}
@@ -332,6 +561,25 @@ static int stmfts_command(struct stmfts_data *sdata, const u8 cmd)
return 0;
}
+static int stmfts5_set_scan_mode(struct stmfts_data *sdata, const u8 val)
+{
+ int err;
+
+ u8 scan_mode_cmd[3] = { STMFTS5_SET_SCAN_MODE, 0x00, val };
+ struct i2c_msg msg = {
+ .addr = sdata->client->addr,
+ .len = sizeof(scan_mode_cmd),
+ .buf = scan_mode_cmd,
+ };
+
+ err = i2c_transfer(sdata->client->adapter, &msg, 1);
+ if (err != 1)
+ return err < 0 ? err : -EIO;
+
+ return 0;
+
+}
+
static int stmfts_input_open(struct input_dev *dev)
{
struct stmfts_data *sdata = input_get_drvdata(dev);
@@ -371,6 +619,28 @@ static int stmfts_input_open(struct input_dev *dev)
return 0;
}
+static int stmfts5_input_open(struct input_dev *dev)
+{
+ struct stmfts_data *sdata = input_get_drvdata(dev);
+ int err;
+
+ err = pm_runtime_resume_and_get(&sdata->client->dev);
+ if (err)
+ return err;
+
+ mutex_lock(&sdata->mutex);
+ sdata->running = true;
+ mutex_unlock(&sdata->mutex);
+
+ err = stmfts5_set_scan_mode(sdata, 0xff);
+ if (err) {
+ pm_runtime_put_sync(&sdata->client->dev);
+ return err;
+ }
+
+ return 0;
+}
+
static void stmfts_input_close(struct input_dev *dev)
{
struct stmfts_data *sdata = input_get_drvdata(dev);
@@ -404,6 +674,23 @@ static void stmfts_input_close(struct input_dev *dev)
pm_runtime_put_sync(&sdata->client->dev);
}
+static void stmfts5_input_close(struct input_dev *dev)
+{
+ struct stmfts_data *sdata = input_get_drvdata(dev);
+ int err;
+
+ err = stmfts5_set_scan_mode(sdata, 0x00);
+ if (err)
+ dev_warn(&sdata->client->dev,
+ "failed to disable touchscreen: %d\n", err);
+
+ mutex_lock(&sdata->mutex);
+ sdata->running = false;
+ mutex_unlock(&sdata->mutex);
+
+ pm_runtime_put_sync(&sdata->client->dev);
+}
+
static ssize_t stmfts_sysfs_chip_id(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -484,7 +771,7 @@ static ssize_t stmfts_sysfs_hover_enable_write(struct device *dev,
guard(mutex)(&sdata->mutex);
if (hover != sdata->hover_enabled) {
- if (sdata->running) {
+ if (sdata->running && !sdata->is_fts5) {
err = i2c_smbus_write_byte(sdata->client,
value ? STMFTS_SS_HOVER_SENSE_ON :
STMFTS_SS_HOVER_SENSE_OFF);
@@ -614,6 +901,41 @@ static int stmfts_power_on(struct stmfts_data *sdata)
return err;
}
+static int stmfts5_power_on(struct stmfts_data *sdata)
+{
+ int err, ret;
+ u8 event[STMFTS_EVENT_SIZE];
+
+ err = regulator_bulk_enable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
+ if (err)
+ return err;
+
+ /* Power stabilization delay */
+ msleep(20);
+
+ if (sdata->reset_gpio)
+ stmfts_reset(sdata);
+
+ /* Verify I2C communication */
+ ret = i2c_smbus_read_i2c_block_data(sdata->client,
+ STMFTS_READ_ALL_EVENT,
+ sizeof(event), event);
+ if (ret < 0) {
+ err = ret;
+ goto power_off;
+ }
+
+ enable_irq(sdata->client->irq);
+
+ return 0;
+
+power_off:
+ regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
+ return err;
+}
+
static void stmfts_power_off(void *data)
{
struct stmfts_data *sdata = data;
@@ -623,6 +945,11 @@ static void stmfts_power_off(void *data)
if (sdata->reset_gpio)
gpiod_set_value_cansleep(sdata->reset_gpio, 1);
+ if (sdata->is_fts5) {
+ i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
+ msleep(20);
+ }
+
regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
sdata->supplies);
}
@@ -656,6 +983,7 @@ static int stmfts_probe(struct i2c_client *client)
struct device *dev = &client->dev;
int err;
struct stmfts_data *sdata;
+ const struct of_device_id *match;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
I2C_FUNC_SMBUS_BYTE_DATA |
@@ -672,6 +1000,13 @@ static int stmfts_probe(struct i2c_client *client)
mutex_init(&sdata->mutex);
init_completion(&sdata->cmd_done);
+ match = of_match_device(dev->driver->of_match_table, dev);
+ sdata->ops = of_device_get_match_data(dev);
+ if (match && of_device_is_compatible(dev->of_node, "st,stmfts5"))
+ sdata->is_fts5 = true;
+ else
+ sdata->is_fts5 = false;
+
err = devm_regulator_bulk_get_const(dev,
ARRAY_SIZE(stmfts_supplies),
stmfts_supplies,
@@ -685,34 +1020,80 @@ static int stmfts_probe(struct i2c_client *client)
return dev_err_probe(dev, PTR_ERR(sdata->reset_gpio),
"Failed to get GPIO 'reset'\n");
+ if (sdata->is_fts5) {
+ sdata->mode_switch_gpio = devm_gpiod_get_optional(&client->dev,
+ "mode-switch",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(sdata->mode_switch_gpio))
+ return dev_err_probe(dev, PTR_ERR(sdata->mode_switch_gpio),
+ "Failed to get GPIO 'switch'\n");
+
+ }
+
sdata->input = devm_input_allocate_device(dev);
if (!sdata->input)
return -ENOMEM;
sdata->input->name = STMFTS_DEV_NAME;
sdata->input->id.bustype = BUS_I2C;
- sdata->input->open = stmfts_input_open;
- sdata->input->close = stmfts_input_close;
+ sdata->input->open = sdata->ops->input_open;
+ sdata->input->close = sdata->ops->input_close;
+
+ /* FTS5-specific input properties */
+ if (sdata->is_fts5) {
+ /* Mark as direct input device for calibration support */
+ __set_bit(INPUT_PROP_DIRECT, sdata->input->propbit);
+
+ /* Set up basic touch capabilities */
+ input_set_capability(sdata->input, EV_KEY, BTN_TOUCH);
+ }
input_set_capability(sdata->input, EV_ABS, ABS_MT_POSITION_X);
input_set_capability(sdata->input, EV_ABS, ABS_MT_POSITION_Y);
touchscreen_parse_properties(sdata->input, true, &sdata->prop);
+ /* Set resolution for accurate calibration (FTS5) */
+ if (sdata->is_fts5 && !input_abs_get_res(sdata->input, ABS_MT_POSITION_X)) {
+ input_abs_set_res(sdata->input, ABS_MT_POSITION_X, 10);
+ input_abs_set_res(sdata->input, ABS_MT_POSITION_Y, 10);
+ }
+
+ /* Enhanced MT parameters */
input_set_abs_params(sdata->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
input_set_abs_params(sdata->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
- input_set_abs_params(sdata->input, ABS_MT_ORIENTATION, 0, 255, 0, 0);
input_set_abs_params(sdata->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
- input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0);
+
+ if (sdata->is_fts5) {
+ input_set_abs_params(sdata->input, ABS_MT_DISTANCE, 0, 255, 0, 0);
+
+ /* Enable stylus support if requested */
+ sdata->stylus_enabled = device_property_read_bool(dev,
+ "stylus-enabled");
+ } else {
+ input_set_abs_params(sdata->input, ABS_MT_ORIENTATION, 0, 255, 0, 0);
+ input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0);
+ }
sdata->use_key = device_property_read_bool(dev,
"touch-key-connected");
- if (sdata->use_key) {
+ if (sdata->use_key && !sdata->is_fts5) {
input_set_capability(sdata->input, EV_KEY, KEY_MENU);
input_set_capability(sdata->input, EV_KEY, KEY_BACK);
}
- err = input_mt_init_slots(sdata->input,
- STMFTS_MAX_FINGERS, INPUT_MT_DIRECT);
+ /* Initialize touch tracking bitmaps (FTS5) */
+ if (sdata->is_fts5) {
+ sdata->touch_id = 0;
+ sdata->stylus_id = 0;
+
+ /* Initialize MT slots with support for pen tool type */
+ err = input_mt_init_slots(sdata->input, STMFTS_MAX_FINGERS,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ } else {
+ err = input_mt_init_slots(sdata->input, STMFTS_MAX_FINGERS,
+ INPUT_MT_DIRECT);
+ }
+
if (err)
return err;
@@ -732,9 +1113,11 @@ static int stmfts_probe(struct i2c_client *client)
if (err)
return err;
- dev_dbg(dev, "initializing ST-Microelectronics FTS...\n");
+ dev_dbg(dev, "initializing ST-Microelectronics FTS%s...\n",
+ sdata->is_fts5 ? "5" : "");
+
- err = stmfts_power_on(sdata);
+ err = sdata->ops->power_on(sdata);
if (err)
return err;
@@ -746,7 +1129,7 @@ static int stmfts_probe(struct i2c_client *client)
if (err)
return err;
- if (sdata->use_key) {
+ if (sdata->use_key && !sdata->is_fts5) {
err = stmfts_enable_led(sdata);
if (err) {
/*
@@ -790,8 +1173,47 @@ static int stmfts_runtime_resume(struct device *dev)
int ret;
ret = i2c_smbus_write_byte(client, STMFTS_SLEEP_OUT);
- if (ret)
+ if (ret) {
dev_err(dev, "failed to resume device: %d\n", ret);
+ return ret;
+ }
+
+ if (sdata->is_fts5) {
+ msleep(20);
+
+ /* Perform capacitance tuning after wakeup */
+ ret = i2c_smbus_write_byte(client, STMFTS_MS_CX_TUNING);
+ if (ret)
+ dev_warn(dev, "MS_CX_TUNING failed: %d\n", ret);
+ msleep(20);
+
+ ret = i2c_smbus_write_byte(client, STMFTS_SS_CX_TUNING);
+ if (ret)
+ dev_warn(dev, "SS_CX_TUNING failed: %d\n", ret);
+ msleep(20);
+
+ /* Force calibration */
+ ret = i2c_smbus_write_byte(client, STMFTS_FULL_FORCE_CALIBRATION);
+ if (ret)
+ dev_warn(dev, "FORCE_CALIBRATION failed: %d\n", ret);
+ msleep(50);
+
+ /* Enable controller interrupts */
+ u8 int_enable_cmd[4] = {0xB6, 0x00, 0x2C, 0x01};
+ struct i2c_msg msg = {
+ .addr = client->addr,
+ .len = 4,
+ .buf = int_enable_cmd,
+ };
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret != 1)
+ return ret < 0 ? ret : -EIO;
+
+ msleep(20);
+
+ return 0;
+ }
return ret;
}
@@ -809,7 +1231,7 @@ static int stmfts_resume(struct device *dev)
{
struct stmfts_data *sdata = dev_get_drvdata(dev);
- return stmfts_power_on(sdata);
+ return sdata->ops->power_on(sdata);
}
static const struct dev_pm_ops stmfts_pm_ops = {
@@ -818,8 +1240,23 @@ static const struct dev_pm_ops stmfts_pm_ops = {
};
#ifdef CONFIG_OF
+static const struct stmfts_chip_ops stmfts4_ops = {
+ .power_on = stmfts_power_on,
+ .input_open = stmfts_input_open,
+ .input_close = stmfts_input_close,
+ .parse_events = stmfts_parse_events,
+};
+
+static const struct stmfts_chip_ops stmfts5_ops = {
+ .power_on = stmfts5_power_on,
+ .input_open = stmfts5_input_open,
+ .input_close = stmfts5_input_close,
+ .parse_events = stmfts5_parse_events,
+};
+
static const struct of_device_id stmfts_of_match[] = {
- { .compatible = "st,stmfts", },
+ { .compatible = "st,stmfts", .data = &stmfts4_ops },
+ { .compatible = "st,stmfts5", .data = &stmfts5_ops },
{ },
};
MODULE_DEVICE_TABLE(of, stmfts_of_match);
@@ -847,5 +1284,7 @@ static struct i2c_driver stmfts_driver = {
module_i2c_driver(stmfts_driver);
MODULE_AUTHOR("Andi Shyti <andi.shyti@samsung.com>");
+MODULE_AUTHOR("David Heidelberg <david@ixit.cz>");
+MODULE_AUTHOR("Petr Hodina <petr.hodina@protonmail.com>");
MODULE_DESCRIPTION("STMicroelectronics FTS Touch Screen");
MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH WIP v3 11/11] arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support
From: David Heidelberg via B4 Relay @ 2026-04-03 17:08 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260403-stmfts5-v3-0-5da768cfd201@ixit.cz>
From: Petr Hodina <petr.hodina@protonmail.com>
Basic touchscreen connected to second i2c bus.
Signed-off-by: Petr Hodina <petr.hodina@protonmail.com>
Co-developed-by: David Heidelberg <david@ixit.cz>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts | 19 ++++++++++++++++++-
arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi | 2 +-
2 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts b/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts
index fa89be500fb85..8fb988130b551 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts
+++ b/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts
@@ -26,7 +26,24 @@ &i2c2 {
status = "okay";
- /* ST,FTS @ 49 */
+ touchscreen@49 {
+ compatible = "st,stmfts5";
+ reg = <0x49>;
+
+ pinctrl-0 = <&touchscreen_irq_n>, <&touchscreen_reset>;
+ pinctrl-names = "default";
+
+ interrupts-extended = <&tlmm 125 IRQ_TYPE_LEVEL_LOW>;
+
+ mode-switch-gpios = <&tlmm 136 GPIO_ACTIVE_HIGH>;
+ reset-gpios = <&tlmm 99 GPIO_ACTIVE_LOW>;
+
+ avdd-supply = <&vreg_l14a_1p8>;
+ vdd-supply = <&vreg_l19a_3p3>;
+
+ touchscreen-size-x = <1080>;
+ touchscreen-size-y = <2160>;
+ };
};
&mdss_dsi0 {
diff --git a/arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi b/arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi
index 6930066857768..4653c63ec26d2 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi
@@ -466,7 +466,7 @@ touchscreen_reset: ts-reset-state {
bias-pull-up;
};
- touchscreen_pins: ts-pins-gpio-state {
+ touchscreen_irq_n: ts-irq-n-gpio-state {
pins = "gpio125";
function = "gpio";
drive-strength = <2>;
--
2.53.0
^ permalink raw reply related
* Re: [PATCH] xpad: remove stale TODO and changelog header
From: Dmitry Torokhov @ 2026-04-04 5:03 UTC (permalink / raw)
To: Elliot Tester; +Cc: linux-input, linux-kernel, Elliot Tester
In-Reply-To: <20260325221618.135833-1-elliotctester1@gmail.com>
On Wed, Mar 25, 2026 at 11:16:17PM +0100, Elliot Tester wrote:
> From: Elliot Tester <elliotctester@gmail.com>
>
> All items in the TODO block have since been addressed: axis tuning,
> analog button handling, rumble support, and dance pad USB IDs are all
> implemented. The manual changelog is also removed as history is tracked
> in git.
>
> Signed-off-by: Elliot Tester <elliotctester1@gmail.com>
Applied, thank you.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] xpad: Add RedOctane Games vendor id
From: Dmitry Torokhov @ 2026-04-04 5:06 UTC (permalink / raw)
To: Sanjay Govind
Cc: Vicki Pfau, Nilton Perim Neto, Mario Limonciello, Lode Willems,
Pierre-Loup A. Griffais, linux-input, linux-kernel
In-Reply-To: <20260311213106.271577-2-sanjay.govind9@gmail.com>
On Thu, Mar 12, 2026 at 10:31:04AM +1300, Sanjay Govind wrote:
> Add vendor ID for RedOctane Games to xpad
>
> Signed-off-by: Sanjay Govind <sanjay.govind9@gmail.com>
> ---
> drivers/input/joystick/xpad.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
> index bf4accf3f581..e97ff270d978 100644
> --- a/drivers/input/joystick/xpad.c
> +++ b/drivers/input/joystick/xpad.c
> @@ -585,6 +585,7 @@ static const struct usb_device_id xpad_table[] = {
> XPAD_XBOX360_VENDOR(0x3651), /* CRKD Controllers */
> XPAD_XBOXONE_VENDOR(0x366c), /* ByoWave controllers */
> XPAD_XBOX360_VENDOR(0x37d7), /* Flydigi Controllers */
> + XPAD_XBOX360_VENDOR(0x3958), /* RedOctane Games Controllers */
> XPAD_XBOX360_VENDOR(0x413d), /* Black Shark Green Ghost Controller */
> { }
> };
Don't we also need to add some entries to the xpad_device table?
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] xpad: Add RedOctane Games vendor id
From: Sanjay Govind @ 2026-04-04 5:13 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Vicki Pfau, Nilton Perim Neto, Mario Limonciello, Lode Willems,
Pierre-Loup A. Griffais, linux-input, linux-kernel
In-Reply-To: <adCb-7uNtq90McnN@google.com>
On Sat, Apr 4, 2026 at 6:07 PM Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
> Don't we also need to add some entries to the xpad_device table?
It's not strictly necessary, xpad will pick the devices up with just
the vendor id, since they have the correct class, subclass and
protocol needed for matching, you only really need to put them into
xpad_device if you want to customize the name or give it different
flags, and that isn't necessary for these devices. A few of these
devices are still in development, so we don't have PIDs assigned yet.
^ permalink raw reply
* Re: [PATCH] xpad: Overhaul device data for wireless devices
From: Dmitry Torokhov @ 2026-04-04 5:33 UTC (permalink / raw)
To: Sanjay Govind
Cc: Vicki Pfau, Nilton Perim Neto, Mario Limonciello,
Pierre-Loup A. Griffais, Antheas Kapenekakis, linux-input,
linux-kernel
In-Reply-To: <20260314075034.1488655-2-sanjay.govind9@gmail.com>
Hi Sanjay,
On Sat, Mar 14, 2026 at 08:50:32PM +1300, Sanjay Govind wrote:
> Xbox 360 wireless controllers expose information in the link and
> capabilities reports.
>
> Extract and use the vendor id for wireless controllers, and use
> the subtype to build a nicer device name and product id.
>
> Some xbox 360 controllers put a vid and pid into the stick capability
> data, so check if this was done, and pull the vid, pid and revision from
> there.
>
> Signed-off-by: Sanjay Govind <sanjay.govind9@gmail.com>
> ---
> drivers/input/joystick/xpad.c | 138 +++++++++++++++++++++++++++++++++-
> 1 file changed, 135 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
> index bf4accf3f581..2490eb21a534 100644
> --- a/drivers/input/joystick/xpad.c
> +++ b/drivers/input/joystick/xpad.c
> @@ -94,6 +94,22 @@
> #define XTYPE_XBOXONE 3
> #define XTYPE_UNKNOWN 4
>
> +#define FLAG_FORCE_FEEDBACK 0x01
> +
> +#define SUBTYPE_GAMEPAD 0x01
> +#define SUBTYPE_WHEEL 0x02
> +#define SUBTYPE_ARCADE_STICK 0x03
> +#define SUBTYPE_FLIGHT_SICK 0x04
> +#define SUBTYPE_DANCE_PAD 0x05
> +#define SUBTYPE_GUITAR 0x06
> +#define SUBTYPE_GUITAR_ALTERNATE 0x07
> +#define SUBTYPE_DRUM_KIT 0x08
> +#define SUBTYPE_GUITAR_BASS 0x0B
> +#define SUBTYPE_RB_KEYBOARD 0x0F
> +#define SUBTYPE_ARCADE_PAD 0x13
> +#define SUBTYPE_TURNTABLE 0x17
> +#define SUBTYPE_PRO_GUITAR 0x19
> +
> /* Send power-off packet to xpad360w after holding the mode button for this many
> * seconds
> */
> @@ -795,6 +811,9 @@ struct usb_xpad {
> int xtype; /* type of xbox device */
> int packet_type; /* type of the extended packet */
> int pad_nr; /* the order x360 pads were attached */
> + u8 sub_type;
> + u16 flags;
> + u16 wireless_vid;
> const char *name; /* name of the device */
> struct work_struct work; /* init/remove device from callback */
> time64_t mode_btn_down_ts;
> @@ -807,6 +826,8 @@ 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);
> +static int xpad_inquiry_pad_capabilities(struct usb_xpad *xpad);
> +
>
> /*
> * xpad_process_packet
> @@ -1026,12 +1047,46 @@ static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned cha
> if (data[0] & 0x08) {
> present = (data[1] & 0x80) != 0;
>
> - if (xpad->pad_present != present) {
> + /* delay prescence until after we get the link report */
s/prescence/presence
But I would say "Only handle no longer being present here. When
a controller appears mark it as present only after receiving link
report".
> + if (!present && xpad->pad_present) {
> xpad->pad_present = present;
> schedule_work(&xpad->work);
> }
> }
>
> + /* Link report */
> + if (data[0] == 0x00 && data[1] == 0x0F) {
> + xpad->sub_type = data[25] & 0x7f;
> +
> + /* Decode vendor id from link report */
> + xpad->wireless_vid = ((data[0x16] & 0xf) | data[0x18] << 4) << 8 | data[0x17];
> +
> + if ((data[25] & 0x80) != 0)
> + xpad->flags |= FLAG_FORCE_FEEDBACK;
> +
> + if (!xpad->pad_present) {
> + xpad->pad_present = true;
> + schedule_work(&xpad->work);
> + }
> + xpad_inquiry_pad_capabilities(xpad);
> + }
> +
> + /* Capabilities report */
> + if (data[0] == 0x00 && data[1] == 0x05 && data[5] == 0x12) {
> + xpad->flags |= data[20];
> + /*
> + * A bunch of vendors started putting vids and pids
> + * into capabilities data because they can't be
> + * retrieved by xinput easliy.
easily.
> + * Not all of them do though, so check the vids match
> + * before extracting that info.
> + */
> + if (((data[11] << 8) | data[10]) == xpad->wireless_vid) {
get_unaaligned_le16()
> + xpad->dev->id.product = (data[13] << 8) | data[12];
get_unaligned_le16()
> + xpad->dev->id.version = (data[15] << 8) | data[14];
get_unaligned_le16()
However you should not change product and version once input device is
registered. So you should delay not until after link packet is received,
but until after capabilities packet.
You might need an enum instead of bool for controller state.
> + }
> + }
> +
> /* Valid pad data */
> if (data[1] != 0x1)
> return;
> @@ -1495,6 +1550,31 @@ static int xpad_inquiry_pad_presence(struct usb_xpad *xpad)
> return xpad_try_sending_next_out_packet(xpad);
> }
>
> +static int xpad_inquiry_pad_capabilities(struct usb_xpad *xpad)
> +{
> + struct xpad_output_packet *packet =
> + &xpad->out_packets[XPAD_OUT_CMD_IDX];
> +
> + guard(spinlock_irqsave)(&xpad->odata_lock);
> +
> + packet->data[0] = 0x00;
> + packet->data[1] = 0x00;
> + packet->data[2] = 0x02;
> + packet->data[3] = 0x80;
> + packet->data[4] = 0x00;
> + packet->data[5] = 0x00;
> + packet->data[6] = 0x00;
> + packet->data[7] = 0x00;
> + packet->data[8] = 0x00;
> + packet->data[9] = 0x00;
> + packet->data[10] = 0x00;
> + packet->data[11] = 0x00;
> + packet->len = 12;
> + packet->pending = true;
> +
> + return xpad_try_sending_next_out_packet(xpad);
> +}
> +
> static int xpad_start_xbox_one(struct usb_xpad *xpad)
> {
> int error;
> @@ -1965,8 +2045,60 @@ static int xpad_init_input(struct usb_xpad *xpad)
> usb_to_input_id(xpad->udev, &input_dev->id);
>
> if (xpad->xtype == XTYPE_XBOX360W) {
> - /* x360w controllers and the receiver have different ids */
> - input_dev->id.product = 0x02a1;
> + /*
> + * x360w controllers on windows put the subtype into the product
> + * for wheels and gamepads, but it makes sense to do it for all
> + * subtypes
> + */
> + input_dev->id.product = 0x02a0 + xpad->sub_type;
Another option is to use version field...
> + /* If the Link report has provided a vid, it won't be set to 1 */
> + if (xpad->wireless_vid != 1)
> + input_dev->id.vendor = xpad->wireless_vid;
> + switch (xpad->sub_type) {
> + case SUBTYPE_GAMEPAD:
> + input_dev->name = "Xbox 360 Wireless Controller";
> + break;
> + case SUBTYPE_WHEEL:
> + input_dev->name = "Xbox 360 Wireless Wheel";
> + break;
> + case SUBTYPE_ARCADE_STICK:
> + input_dev->name = "Xbox 360 Wireless Arcade Stick";
> + break;
> + case SUBTYPE_FLIGHT_SICK:
> + input_dev->name = "Xbox 360 Wireless Flight Stick";
> + break;
> + case SUBTYPE_DANCE_PAD:
> + input_dev->name = "Xbox 360 Wireless Dance Pad";
> + break;
> + case SUBTYPE_GUITAR:
> + input_dev->name = "Xbox 360 Wireless Guitar";
> + break;
> + case SUBTYPE_GUITAR_ALTERNATE:
> + input_dev->name = "Xbox 360 Wireless Alternate Guitar";
> + break;
> + case SUBTYPE_GUITAR_BASS:
> + input_dev->name = "Xbox 360 Wireless Bass Guitar";
> + break;
> + case SUBTYPE_DRUM_KIT:
> + /* Vendors used force feedback flag to differentiate these */
> + if (xpad->flags & FLAG_FORCE_FEEDBACK)
> + input_dev->name = "Xbox 360 Wireless Guitar Hero Drum Kit";
> + else
> + input_dev->name = "Xbox 360 Wireless Rock Band Drum Kit";
> + break;
> + case SUBTYPE_RB_KEYBOARD:
> + input_dev->name = "Xbox 360 Wireless Rock Band Keyboard";
> + break;
> + case SUBTYPE_ARCADE_PAD:
> + input_dev->name = "Xbox 360 Wireless Arcade Pad";
> + break;
> + case SUBTYPE_TURNTABLE:
> + input_dev->name = "Xbox 360 Wireless DJ Hero Turntable";
> + break;
> + case SUBTYPE_PRO_GUITAR:
> + input_dev->name = "Xbox 360 Wireless Rock Band Pro Guitar";
> + break;
> + }
Maybe have an array of
[type] = "Name"
instead of this switch?
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] xpad: Add RedOctane Games vendor id
From: Dmitry Torokhov @ 2026-04-04 5:34 UTC (permalink / raw)
To: Sanjay Govind
Cc: Vicki Pfau, Nilton Perim Neto, Mario Limonciello, Lode Willems,
Pierre-Loup A. Griffais, linux-input, linux-kernel
In-Reply-To: <CALQgdA3AzKnjD-rooix=8K9-Vh_zZa7HaZdM-mHhgqg=tVPxYQ@mail.gmail.com>
On Sat, Apr 04, 2026 at 06:13:25PM +1300, Sanjay Govind wrote:
> On Sat, Apr 4, 2026 at 6:07 PM Dmitry Torokhov
> <dmitry.torokhov@gmail.com> wrote:
> > Don't we also need to add some entries to the xpad_device table?
>
> It's not strictly necessary, xpad will pick the devices up with just
> the vendor id, since they have the correct class, subclass and
> protocol needed for matching, you only really need to put them into
> xpad_device if you want to customize the name or give it different
> flags, and that isn't necessary for these devices. A few of these
> devices are still in development, so we don't have PIDs assigned yet.
OK, I'll queue it to the next release then.
Thanks.
--
Dmitry
^ permalink raw reply
* [git pull] Input updates for v7.0-rc6
From: Dmitry Torokhov @ 2026-04-04 6:02 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, linux-input
Hi Linus,
Please pull from:
git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git tags/input-for-v7.0-rc6
to receive updates for the input subsystem. You will get:
- new IDs for BETOP BTP-KP50B/C and Razer Wolverine V3 Pro added to
xpad controller driver
- another quirk for new TUXEDO InfinityBook added to i8042
- a small fixup for Synaptics RMI4 driver to properly unlock mutex when
encountering an error in F54
- an update to bcm5974 touch controller driver to reliably switch into
wellspring mode.
Changelog:
---------
Bart Van Assche (1):
Input: synaptics-rmi4 - fix a locking bug in an error path
Christoffer Sandberg (1):
Input: i8042 - add TUXEDO InfinityBook Max 16 Gen10 AMD to i8042 quirk table
Liam Mitchell (1):
Input: bcm5974 - recover from failed mode switch
Shengyu Qu (1):
Input: xpad - add support for BETOP BTP-KP50B/C controller's wireless mode
Zoltan Illes (1):
Input: xpad - add support for Razer Wolverine V3 Pro
Diffstat:
--------
drivers/input/joystick/xpad.c | 5 +++++
drivers/input/mouse/bcm5974.c | 42 ++++++++++++++++++++++++++++++++++-
drivers/input/rmi4/rmi_f54.c | 4 ++--
drivers/input/serio/i8042-acpipnpio.h | 7 ++++++
4 files changed, 55 insertions(+), 3 deletions(-)
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] xpad: Overhaul device data for wireless devices
From: Sanjay Govind @ 2026-04-04 6:35 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Vicki Pfau, Nilton Perim Neto, Mario Limonciello,
Pierre-Loup A. Griffais, Antheas Kapenekakis, linux-input,
linux-kernel
In-Reply-To: <adCc8OuHm7wwkysq@google.com>
> However you should not change product and version once input device is
> registered. So you should delay not until after link packet is received,
> but until after the capabilities packet.
Yeah i wanted to do that originally, but the main issue with that is
some gamepads don't actually send the capabilities data at all. An
Easy solution I could think of is to just start a timer and continue
the init if it expires and we haven't gotten the capabilities data.
> Another option is to use version field...
Yeah that is reasonable, I had just gone with putting it in the
product id like that as it made things easier for SDL, and microsoft
had done that for the wheel subtype already, so some apps already have
the 0x02a2 pid.
Happy to change that though if you'd prefer i use the version field.
> Maybe have an array of
>
> [type] = "Name"
>
> instead of this switch?
The main problem is there's a lot of gaps in the list of subtypes, and
this also isn't an exhaustive list, there are a few other subtypes out
there that were used for some bespoke controllers, and so using switch
at least means they can keep a generic name if they are used.
^ permalink raw reply
* Re: [PATCH] xpad: Overhaul device data for wireless devices
From: Dmitry Torokhov @ 2026-04-04 6:39 UTC (permalink / raw)
To: Sanjay Govind
Cc: Vicki Pfau, Nilton Perim Neto, Mario Limonciello,
Pierre-Loup A. Griffais, Antheas Kapenekakis, linux-input,
linux-kernel
In-Reply-To: <CALQgdA1soGo6U2_pKS0RZ6smOSS9Zd-g+eMD7KjwUwz-_pAK_w@mail.gmail.com>
On April 3, 2026 11:35:35 PM PDT, Sanjay Govind <sanjay.govind9@gmail.com> wrote:
>> However you should not change product and version once input device is
>> registered. So you should delay not until after link packet is received,
>> but until after the capabilities packet.
>Yeah i wanted to do that originally, but the main issue with that is
>some gamepads don't actually send the capabilities data at all. An
>Easy solution I could think of is to just start a timer and continue
>the init if it expires and we haven't gotten the capabilities data.
That sounds sensible.
>
>> Another option is to use version field...
>Yeah that is reasonable, I had just gone with putting it in the
>product id like that as it made things easier for SDL, and microsoft
>had done that for the wheel subtype already, so some apps already have
>the 0x02a2 pid.
>Happy to change that though if you'd prefer i use the version field.
No, this is fine too.
>
>> Maybe have an array of
>>
>> [type] = "Name"
>>
>> instead of this switch?
>The main problem is there's a lot of gaps in the list of subtypes, and
>this also isn't an exhaustive list, there are a few other subtypes out
>there that were used for some bespoke controllers, and so using switch
>at least means they can keep a generic name if they are used.
OK, then let's keep it as switch.
Thanks.
--
Dmitry
^ permalink raw reply
* [PATCH v2] xpad: Overhaul device data for wireless devices
From: Sanjay Govind @ 2026-04-04 8:39 UTC (permalink / raw)
To: Dmitry Torokhov, Vicki Pfau, Nilton Perim Neto, Sanjay Govind,
Mario Limonciello, Antheas Kapenekakis
Cc: Pierre-Loup A. Griffais, linux-input, linux-kernel
Xbox 360 wireless controllers expose information in the link and
capabilities reports.
Extract and use the vendor id for wireless controllers, and use
the subtype to build a nicer device name and product id.
Some xbox 360 controllers put a vid and pid into the stick capability
data, so check if this was done, and pull the vid, pid and revision from
there.
Signed-off-by: Sanjay Govind <sanjay.govind9@gmail.com>
---
v2: Delay marking device as present until after capabilities or timeout
drivers/input/joystick/xpad.c | 162 ++++++++++++++++++++++++++++++++--
1 file changed, 155 insertions(+), 7 deletions(-)
diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index bf4accf3f581..c35512c7c199 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c
@@ -68,6 +68,7 @@
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/module.h>
+#include <linux/unaligned.h>
#include <linux/usb/input.h>
#include <linux/usb/quirks.h>
@@ -94,6 +95,22 @@
#define XTYPE_XBOXONE 3
#define XTYPE_UNKNOWN 4
+#define FLAG_FORCE_FEEDBACK 0x01
+
+#define SUBTYPE_GAMEPAD 0x01
+#define SUBTYPE_WHEEL 0x02
+#define SUBTYPE_ARCADE_STICK 0x03
+#define SUBTYPE_FLIGHT_SICK 0x04
+#define SUBTYPE_DANCE_PAD 0x05
+#define SUBTYPE_GUITAR 0x06
+#define SUBTYPE_GUITAR_ALTERNATE 0x07
+#define SUBTYPE_DRUM_KIT 0x08
+#define SUBTYPE_GUITAR_BASS 0x0B
+#define SUBTYPE_RB_KEYBOARD 0x0F
+#define SUBTYPE_ARCADE_PAD 0x13
+#define SUBTYPE_TURNTABLE 0x17
+#define SUBTYPE_PRO_GUITAR 0x19
+
/* Send power-off packet to xpad360w after holding the mode button for this many
* seconds
*/
@@ -795,8 +812,13 @@ struct usb_xpad {
int xtype; /* type of xbox device */
int packet_type; /* type of the extended packet */
int pad_nr; /* the order x360 pads were attached */
+ u8 sub_type;
+ u16 flags;
+ u16 wireless_vid;
+ u16 wireless_pid;
+ u16 wireless_version;
const char *name; /* name of the device */
- struct work_struct work; /* init/remove device from callback */
+ struct delayed_work work; /* init/remove device from callback */
time64_t mode_btn_down_ts;
bool delay_init; /* init packets should be delayed */
bool delayed_init_done;
@@ -807,6 +829,8 @@ 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);
+static int xpad_inquiry_pad_capabilities(struct usb_xpad *xpad);
+
/*
* xpad_process_packet
@@ -980,7 +1004,7 @@ static void xpad360_process_packet(struct usb_xpad *xpad, struct input_dev *dev,
static void xpad_presence_work(struct work_struct *work)
{
- struct usb_xpad *xpad = container_of(work, struct usb_xpad, work);
+ struct usb_xpad *xpad = container_of(work, struct usb_xpad, work.work);
int error;
if (xpad->pad_present) {
@@ -1028,10 +1052,60 @@ static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned cha
if (xpad->pad_present != present) {
xpad->pad_present = present;
- schedule_work(&xpad->work);
+ if (present) {
+ /*
+ * Delay marking device as present, so we can make sure
+ * we have received all the information from the capabilities
+ * report. Some devices don't send one, so the delay
+ * guarantees that these devices are still initialized.
+ */
+ schedule_delayed_work(&xpad->work, msecs_to_jiffies(500));
+ } else {
+ schedule_delayed_work(&xpad->work, 0);
+ }
}
}
+ /* Link report */
+ if (data[0] == 0x00 && data[1] == 0x0F) {
+ xpad->sub_type = data[25] & 0x7f;
+
+ /* Decode vendor id from link report */
+ xpad->wireless_vid = ((data[0x16] & 0xf) | data[0x18] << 4) << 8 | data[0x17];
+ /*
+ * x360w controllers on windows put the subtype into the product
+ * for wheels and gamepads, but it makes sense to do it for all
+ * subtypes. This will be used if the capabilities report
+ * doesn't provide us with a product id later.
+ */
+ xpad->wireless_pid = 0x02a0 + xpad->sub_type;
+ xpad->wireless_version = 0;
+
+ if ((data[25] & 0x80) != 0)
+ xpad->flags |= FLAG_FORCE_FEEDBACK;
+
+ xpad_inquiry_pad_capabilities(xpad);
+ }
+
+ /* Capabilities report */
+ if (data[0] == 0x00 && data[1] == 0x05 && data[5] == 0x12) {
+ xpad->flags |= data[20];
+ /*
+ * A bunch of vendors started putting vids and pids
+ * into capabilities data because they can't be
+ * retrieved by xinput easliy.
+ * Not all of them do though, so check the vids match
+ * before extracting that info.
+ */
+ if (get_unaligned_le16(data + 10) == xpad->wireless_vid) {
+ xpad->wireless_pid = get_unaligned_le16(data + 12);
+ xpad->wireless_version = get_unaligned_le16(data + 14);
+ }
+ /* We got the capabilities report, so mark the device present now */
+ cancel_delayed_work(&xpad->work);
+ schedule_delayed_work(&xpad->work, 0);
+ }
+
/* Valid pad data */
if (data[1] != 0x1)
return;
@@ -1495,6 +1569,31 @@ static int xpad_inquiry_pad_presence(struct usb_xpad *xpad)
return xpad_try_sending_next_out_packet(xpad);
}
+static int xpad_inquiry_pad_capabilities(struct usb_xpad *xpad)
+{
+ struct xpad_output_packet *packet =
+ &xpad->out_packets[XPAD_OUT_CMD_IDX];
+
+ guard(spinlock_irqsave)(&xpad->odata_lock);
+
+ packet->data[0] = 0x00;
+ packet->data[1] = 0x00;
+ packet->data[2] = 0x02;
+ packet->data[3] = 0x80;
+ packet->data[4] = 0x00;
+ packet->data[5] = 0x00;
+ packet->data[6] = 0x00;
+ packet->data[7] = 0x00;
+ packet->data[8] = 0x00;
+ packet->data[9] = 0x00;
+ packet->data[10] = 0x00;
+ packet->data[11] = 0x00;
+ packet->len = 12;
+ packet->pending = true;
+
+ return xpad_try_sending_next_out_packet(xpad);
+}
+
static int xpad_start_xbox_one(struct usb_xpad *xpad)
{
int error;
@@ -1893,7 +1992,7 @@ static void xpad360w_stop_input(struct usb_xpad *xpad)
usb_kill_urb(xpad->irq_in);
/* Make sure we are done with presence work if it was scheduled */
- flush_work(&xpad->work);
+ flush_delayed_work(&xpad->work);
}
static int xpad_open(struct input_dev *dev)
@@ -1965,8 +2064,57 @@ static int xpad_init_input(struct usb_xpad *xpad)
usb_to_input_id(xpad->udev, &input_dev->id);
if (xpad->xtype == XTYPE_XBOX360W) {
- /* x360w controllers and the receiver have different ids */
- input_dev->id.product = 0x02a1;
+ /* If the Link report has provided a vid, it won't be set to 1 */
+ if (xpad->wireless_vid != 1)
+ input_dev->id.vendor = xpad->wireless_vid;
+ if (xpad->wireless_version)
+ input_dev->id.version = xpad->wireless_version;
+ input_dev->id.product = xpad->wireless_pid;
+ switch (xpad->sub_type) {
+ case SUBTYPE_GAMEPAD:
+ input_dev->name = "Xbox 360 Wireless Controller";
+ break;
+ case SUBTYPE_WHEEL:
+ input_dev->name = "Xbox 360 Wireless Wheel";
+ break;
+ case SUBTYPE_ARCADE_STICK:
+ input_dev->name = "Xbox 360 Wireless Arcade Stick";
+ break;
+ case SUBTYPE_FLIGHT_SICK:
+ input_dev->name = "Xbox 360 Wireless Flight Stick";
+ break;
+ case SUBTYPE_DANCE_PAD:
+ input_dev->name = "Xbox 360 Wireless Dance Pad";
+ break;
+ case SUBTYPE_GUITAR:
+ input_dev->name = "Xbox 360 Wireless Guitar";
+ break;
+ case SUBTYPE_GUITAR_ALTERNATE:
+ input_dev->name = "Xbox 360 Wireless Alternate Guitar";
+ break;
+ case SUBTYPE_GUITAR_BASS:
+ input_dev->name = "Xbox 360 Wireless Bass Guitar";
+ break;
+ case SUBTYPE_DRUM_KIT:
+ /* Vendors used force feedback flag to differentiate these */
+ if (xpad->flags & FLAG_FORCE_FEEDBACK)
+ input_dev->name = "Xbox 360 Wireless Guitar Hero Drum Kit";
+ else
+ input_dev->name = "Xbox 360 Wireless Rock Band Drum Kit";
+ break;
+ case SUBTYPE_RB_KEYBOARD:
+ input_dev->name = "Xbox 360 Wireless Rock Band Keyboard";
+ break;
+ case SUBTYPE_ARCADE_PAD:
+ input_dev->name = "Xbox 360 Wireless Arcade Pad";
+ break;
+ case SUBTYPE_TURNTABLE:
+ input_dev->name = "Xbox 360 Wireless DJ Hero Turntable";
+ break;
+ case SUBTYPE_PRO_GUITAR:
+ input_dev->name = "Xbox 360 Wireless Rock Band Pro Guitar";
+ break;
+ }
}
input_dev->dev.parent = &xpad->intf->dev;
@@ -2106,7 +2254,7 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
xpad->delay_init = true;
xpad->packet_type = PKT_XB;
- INIT_WORK(&xpad->work, xpad_presence_work);
+ INIT_DELAYED_WORK(&xpad->work, xpad_presence_work);
if (xpad->xtype == XTYPE_UNKNOWN) {
if (intf->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC) {
--
2.53.0
^ permalink raw reply related
* [PATCH] HID: apple: ensure the keyboard backlight is off if suspending
From: Aditya Garg @ 2026-04-04 9:44 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, stable, André Eikmeyer
Some users reported that upon suspending their keyboard backlight
remained on. Fix this by adding the missing LED_CORE_SUSPENDRESUME flag.
Cc: stable@vger.kernel.org
Fixes: 394ba612f941 ("HID: apple: Add support for magic keyboard backlight on T2 Macs")
Fixes: 9018eacbe623 ("HID: apple: Add support for keyboard backlight on certain T2 Macs.")
Reported-by: André Eikmeyer <andre.eikmeyer@gmail.com>
Tested-by: André Eikmeyer <andre.eikmeyer@gmail.com>
Signed-off-by: Aditya Garg <gargaditya08@live.com>
---
drivers/hid/hid-apple.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index fc5897a6b..2eb45fac8 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -858,6 +858,7 @@ static int apple_backlight_init(struct hid_device *hdev)
asc->backlight->cdev.name = "apple::kbd_backlight";
asc->backlight->cdev.max_brightness = rep->backlight_on_max;
asc->backlight->cdev.brightness_set_blocking = apple_backlight_led_set;
+ asc->backlight->cdev.flags = LED_CORE_SUSPENDRESUME;
ret = apple_backlight_set(hdev, 0, 0);
if (ret < 0) {
@@ -926,6 +927,7 @@ static int apple_magic_backlight_init(struct hid_device *hdev)
backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT;
backlight->cdev.max_brightness = backlight->brightness->field[0]->logical_maximum;
backlight->cdev.brightness_set_blocking = apple_magic_backlight_led_set;
+ backlight->cdev.flags = LED_CORE_SUSPENDRESUME;
apple_magic_backlight_set(backlight, 0, 0);
--
2.52.0
^ permalink raw reply related
* Re: [PATCH] HID: lg-g15: Add support for the Logitech G710/G710+ gaming keyboards
From: Xavier Bestel @ 2026-04-04 10:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires; +Cc: Hans de Goede, linux-input, linux-kernel
In-Reply-To: <20260402075239.3829699-1-xav@bes.tel>
Le jeudi 02 avril 2026 à 09:52 +0200, Xavier Bestel a écrit :
> Add support for the Logitech G710 and G710+ (USB ID 046d:c24d) gaming
> keyboards to the hid-lg-g15 driver.
Did that mail get through ?
Xav
^ permalink raw reply
* Re: [PATCH] HID: lg-g15: Add support for the Logitech G710/G710+ gaming keyboards
From: Hans de Goede @ 2026-04-04 10:07 UTC (permalink / raw)
To: Xavier Bestel, Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, linux-kernel
In-Reply-To: <f6f46cfe110053208e7dbb4dd3074eab7966e8e7.camel@free.fr>
Hi,
On 4-Apr-26 12:05, Xavier Bestel wrote:
> Le jeudi 02 avril 2026 à 09:52 +0200, Xavier Bestel a écrit :
>> Add support for the Logitech G710 and G710+ (USB ID 046d:c24d) gaming
>> keyboards to the hid-lg-g15 driver.
>
> Did that mail get through ?
Yes we've received your patch. most kernel maintainers have
a very high workload, so the HID maintainers will look at
this patch when they have time, which can take a while.
Regards,
Hans
^ permalink raw reply
* [PATCH v2 0/1] HID: add malicious HID device detection driver
From: Zubeyr Almaho @ 2026-04-04 13:37 UTC (permalink / raw)
To: Jiri Kosina
Cc: Zubeyr Almaho, Benjamin Tissoires, linux-input, linux-kernel,
security
Hi Jiri, Benjamin,
This series introduces hid-omg-detect, a passive HID monitor that scores
potentially malicious keyboard-like USB devices (BadUSB / O.MG style)
using:
- keystroke timing entropy,
- plug-and-type latency,
- USB descriptor fingerprinting.
When the configurable threshold is crossed, the module emits a warning
with a userspace mitigation hint (usbguard).
The driver does not block, delay, or modify HID input events.
Changes since v1:
- Replaced global list + mutex with per-device drvdata.
- Removed logging inside spinlock-held regions.
- Moved VID/PID lookup to probe() to avoid hot-path overhead.
- Switched logging to hid_{info,warn,err} helpers.
- Capped timing sample counter at MAX_TIMING_SAMPLES.
- Renamed file to hid-omg-detect.c for kernel naming conventions.
Thanks,
Zubeyr Almaho
---
drivers/hid/hid-omg-detect.c | 435 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 435 insertions(+)
^ permalink raw reply
* [PATCH v2 1/1] HID: add malicious HID device detection driver
From: Zubeyr Almaho @ 2026-04-04 13:37 UTC (permalink / raw)
To: Jiri Kosina
Cc: Zubeyr Almaho, Benjamin Tissoires, linux-input, linux-kernel,
security
In-Reply-To: <20260404133746.80914-1-zybo1000@gmail.com>
Add a passive HID driver that computes a suspicion score for keyboard-like
USB devices based on:
- low keystroke timing entropy,
- immediate post-enumeration typing,
- known suspicious VID/PID and descriptor anomalies.
If the score exceeds a tunable threshold, the driver emits a warning and
suggests userspace blocking (e.g. usbguard). The module never blocks or
modifies HID events.
Signed-off-by: Zubeyr Almaho <zybo1000@gmail.com>
---
drivers/hid/hid-omg-detect.c | 435 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 435 insertions(+)
create mode 100644 drivers/hid/hid-omg-detect.c
diff --git a/drivers/hid/hid-omg-detect.c b/drivers/hid/hid-omg-detect.c
new file mode 100644
--- /dev/null
+++ b/drivers/hid/hid-omg-detect.c
@@ -0,0 +1,435 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * hid-omg-detect.c - Malicious HID Device Detection Kernel Module
+ *
+ * Detects O.MG Cable and similar BadUSB devices by combining:
+ * 1. Keystroke timing entropy analysis (human vs machine rhythm)
+ * 2. Plug-and-type detection (typing immediately after connect)
+ * 3. USB descriptor fingerprinting (known bad VID/PIDs + anomalies)
+ *
+ * When suspicion score >= threshold, logs a kernel warning and suggests
+ * blocking the device with usbguard.
+ *
+ * The driver is purely passive — it does not drop, modify, or delay any
+ * HID events.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/hid.h>
+#include <linux/usb.h>
+#include <linux/ktime.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/math64.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Zübeyr Almaho <zybo1000@gmail.com>");
+MODULE_DESCRIPTION("O.MG Cable / Malicious HID Device Detector");
+
+/* ============================================================
+ * Tuneable parameters (pass as insmod args)
+ * ============================================================ */
+static int score_threshold = 70;
+module_param(score_threshold, int, 0644);
+MODULE_PARM_DESC(score_threshold,
+ "Suspicion score (0-100) to trigger alert (default: 70)");
+
+static int plug_type_ms = 500;
+module_param(plug_type_ms, int, 0644);
+MODULE_PARM_DESC(plug_type_ms,
+ "Max ms after connect before first key = plug-and-type (default: 500)");
+
+static int entropy_variance_low = 500;
+module_param(entropy_variance_low, int, 0644);
+MODULE_PARM_DESC(entropy_variance_low,
+ "Variance (us^2) below which typing is machine-like (default: 500)");
+
+/* ============================================================
+ * Constants
+ * ============================================================ */
+#define DRIVER_NAME "hid_omg_detect"
+#define MAX_TIMING_SAMPLES 64
+#define MIN_SAMPLES 10
+
+/* ============================================================
+ * Known suspicious VID/PID pairs (O.MG Cable, Rubber Ducky, etc.)
+ * ============================================================ */
+static const struct {
+ u16 vid;
+ u16 pid;
+ const char *name;
+} suspicious_ids[] = {
+ { 0x16c0, 0x27db, "O.MG Cable (classic)" },
+ { 0x1209, 0xa000, "O.MG Cable (Elite)" },
+ { 0x16c0, 0x27dc, "USB Rubber Ducky" },
+ { 0x1b4f, 0x9208, "LilyPad Arduino BadUSB" },
+};
+
+/* ============================================================
+ * Per-device state (allocated on probe, freed on remove)
+ * ============================================================ */
+struct omg_device_state {
+ struct hid_device *hdev;
+
+ /* --- connection timing --- */
+ ktime_t connect_time;
+ ktime_t first_key_time;
+ bool first_key_seen;
+
+ /* --- keystroke interval circular buffer --- */
+ ktime_t last_key_time;
+ s64 intervals_us[MAX_TIMING_SAMPLES];
+ unsigned int sample_count;
+ unsigned int sample_head;
+
+ /* --- USB info (immutable after probe) --- */
+ u16 vid;
+ u16 pid;
+ const char *vid_pid_match_name; /* non-NULL if VID/PID matched */
+ bool descriptor_anomaly;
+
+ /* --- verdict --- */
+ int score;
+ bool alerted;
+
+ spinlock_t lock;
+};
+
+/* ============================================================
+ * Math helpers
+ * ============================================================ */
+static void timing_stats(struct omg_device_state *s, s64 *mean, s64 *variance)
+{
+ unsigned int i, n;
+ s64 sum = 0, sq = 0, m;
+
+ n = min(s->sample_count, (unsigned int)MAX_TIMING_SAMPLES);
+
+ if (n < 2) {
+ *mean = 0;
+ *variance = 0;
+ return;
+ }
+
+ for (i = 0; i < n; i++)
+ sum += s->intervals_us[i];
+ m = div_s64(sum, n);
+
+ for (i = 0; i < n; i++) {
+ s64 d = s->intervals_us[i] - m;
+
+ sq += d * d;
+ }
+
+ *mean = m;
+ *variance = div_s64(sq, n);
+}
+
+/* ============================================================
+ * Signal 1: Timing entropy
+ *
+ * Humans: high variance (pauses, rhythm changes)
+ * Machines: low variance (constant clock)
+ *
+ * Also penalises extremely fast consistent typing (mean < 15 ms).
+ * ============================================================ */
+static int score_entropy(struct omg_device_state *s)
+{
+ s64 mean, var;
+ int pts = 0;
+
+ if (s->sample_count < MIN_SAMPLES)
+ return 0;
+
+ timing_stats(s, &mean, &var);
+
+ if (var < (s64)entropy_variance_low)
+ pts += 35;
+ else if (var < (s64)entropy_variance_low * 10)
+ pts += 20;
+ else if (var < (s64)entropy_variance_low * 40)
+ pts += 8;
+
+ if (mean > 0 && mean < 5000)
+ pts += 25;
+ else if (mean > 0 && mean < 15000)
+ pts += 10;
+
+ return min(pts, 60);
+}
+
+/* ============================================================
+ * Signal 2: Plug-and-type
+ *
+ * A legitimate keyboard sits idle for a while after connect.
+ * A script device starts injecting within milliseconds.
+ * ============================================================ */
+static int score_plug_type(struct omg_device_state *s)
+{
+ s64 delta_ms;
+
+ if (!s->first_key_seen)
+ return 0;
+
+ delta_ms = ktime_to_ms(ktime_sub(s->first_key_time, s->connect_time));
+
+ if (delta_ms < 100)
+ return 30;
+ if (delta_ms < (s64)plug_type_ms)
+ return 15;
+ if (delta_ms < 2000)
+ return 5;
+
+ return 0;
+}
+
+/* ============================================================
+ * Signal 3: USB descriptor fingerprint
+ * ============================================================ */
+static int score_descriptor(struct omg_device_state *s)
+{
+ int pts = 0;
+
+ if (s->vid_pid_match_name)
+ pts += 50;
+
+ if (s->descriptor_anomaly)
+ pts += 20;
+
+ return min(pts, 50);
+}
+
+/* ============================================================
+ * Combine all signals and return per-signal breakdown.
+ * Caller is responsible for alerting outside any lock.
+ * ============================================================ */
+struct omg_score_result {
+ int entropy;
+ int plug_type;
+ int descriptor;
+ int total;
+ bool newly_alerted;
+};
+
+static void compute_score(struct omg_device_state *s,
+ struct omg_score_result *res)
+{
+ res->entropy = score_entropy(s);
+ res->plug_type = score_plug_type(s);
+ res->descriptor = score_descriptor(s);
+ res->total = min(res->entropy + res->plug_type + res->descriptor,
+ 100);
+
+ s->score = res->total;
+ res->newly_alerted = false;
+
+ if (res->total >= score_threshold && !s->alerted) {
+ s->alerted = true;
+ res->newly_alerted = true;
+ }
+}
+
+/* Emit alert outside of spinlock */
+static void emit_alert(struct hid_device *hdev,
+ struct omg_device_state *s,
+ const struct omg_score_result *res)
+{
+ hid_warn(hdev, "=============================================\n");
+ hid_warn(hdev, "!! SUSPICIOUS HID DEVICE DETECTED !!\n");
+ hid_warn(hdev, "Device : %s\n", hdev->name);
+ hid_warn(hdev, "VID/PID: %04x:%04x\n", s->vid, s->pid);
+ if (s->vid_pid_match_name)
+ hid_warn(hdev, "Match : %s\n", s->vid_pid_match_name);
+ hid_warn(hdev, "Score : %d/100 (threshold=%d)\n",
+ res->total, score_threshold);
+ hid_warn(hdev, " Entropy : %d pts\n", res->entropy);
+ hid_warn(hdev, " Plug-and-type: %d pts\n", res->plug_type);
+ hid_warn(hdev, " Descriptor : %d pts\n", res->descriptor);
+ hid_warn(hdev, "Action : sudo usbguard block-device <id>\n");
+ hid_warn(hdev, "=============================================\n");
+}
+
+/* ============================================================
+ * HID raw_event hook — called for every incoming HID report
+ *
+ * This runs in softirq/BH context — no sleeping locks allowed.
+ * ============================================================ */
+static int omg_raw_event(struct hid_device *hdev,
+ struct hid_report *report,
+ u8 *data, int size)
+{
+ struct omg_device_state *s = hid_get_drvdata(hdev);
+ struct omg_score_result res;
+ ktime_t now;
+ unsigned long flags;
+ bool keystroke = false;
+ int i;
+
+ if (!s)
+ return 0;
+
+ if (report->type != HID_INPUT_REPORT)
+ return 0;
+
+ /* Byte 0 = modifier, byte 1 = reserved, bytes 2-7 = keycodes */
+ for (i = 2; i < size && i < 8; i++) {
+ if (data[i] != 0) {
+ keystroke = true;
+ break;
+ }
+ }
+ if (size > 0 && data[0] != 0)
+ keystroke = true;
+
+ if (!keystroke)
+ return 0;
+
+ now = ktime_get();
+
+ spin_lock_irqsave(&s->lock, flags);
+
+ if (!s->first_key_seen) {
+ s->first_key_seen = true;
+ s->first_key_time = now;
+ s->last_key_time = now;
+ } else {
+ s64 interval = ktime_to_us(ktime_sub(now, s->last_key_time));
+
+ /* ignore idle gaps > 10 s */
+ if (interval < 10000000LL) {
+ s->intervals_us[s->sample_head] = interval;
+ s->sample_head =
+ (s->sample_head + 1) % MAX_TIMING_SAMPLES;
+ if (s->sample_count < MAX_TIMING_SAMPLES)
+ s->sample_count++;
+ }
+ s->last_key_time = now;
+ }
+
+ compute_score(s, &res);
+
+ spin_unlock_irqrestore(&s->lock, flags);
+
+ /* Alert outside the spinlock — hid_warn may sleep */
+ if (res.newly_alerted)
+ emit_alert(hdev, s, &res);
+
+ return 0;
+}
+
+/* ============================================================
+ * HID probe — device connected
+ * ============================================================ */
+static int omg_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct omg_device_state *s;
+ struct usb_interface *intf;
+ struct usb_device *udev;
+ struct omg_score_result res;
+ int ret, i;
+
+ if (hdev->bus != BUS_USB)
+ return -ENODEV;
+
+ s = kzalloc(sizeof(*s), GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+
+ spin_lock_init(&s->lock);
+ s->hdev = hdev;
+ s->connect_time = ktime_get();
+
+ /* Extract USB descriptor info */
+ intf = to_usb_interface(hdev->dev.parent);
+ udev = interface_to_usbdev(intf);
+ s->vid = le16_to_cpu(udev->descriptor.idVendor);
+ s->pid = le16_to_cpu(udev->descriptor.idProduct);
+
+ /* Check known bad VID/PID table (once, at probe time) */
+ for (i = 0; i < ARRAY_SIZE(suspicious_ids); i++) {
+ if (s->vid == suspicious_ids[i].vid &&
+ s->pid == suspicious_ids[i].pid) {
+ s->vid_pid_match_name = suspicious_ids[i].name;
+ break;
+ }
+ }
+
+ /*
+ * Anomaly: legitimate HID keyboards use bDeviceClass = 0x00
+ * (class defined at interface level). Any other value here
+ * for a device presenting as a keyboard is suspicious.
+ */
+ s->descriptor_anomaly =
+ (udev->descriptor.bDeviceClass != 0x00 &&
+ udev->descriptor.bDeviceClass != 0x03);
+
+ hid_set_drvdata(hdev, s);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "hid_parse failed: %d\n", ret);
+ goto err_free;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret) {
+ hid_err(hdev, "hid_hw_start failed: %d\n", ret);
+ goto err_free;
+ }
+
+ hid_info(hdev, "Monitoring %s (VID=%04x PID=%04x%s%s)\n",
+ hdev->name, s->vid, s->pid,
+ s->vid_pid_match_name ? " KNOWN-BAD:" : "",
+ s->vid_pid_match_name ? s->vid_pid_match_name : "");
+
+ if (s->descriptor_anomaly)
+ hid_warn(hdev, "Descriptor anomaly: bDeviceClass=0x%02x\n",
+ udev->descriptor.bDeviceClass);
+
+ /* Run initial descriptor-only score */
+ compute_score(s, &res);
+ if (res.newly_alerted)
+ emit_alert(hdev, s, &res);
+
+ return 0;
+
+err_free:
+ hid_set_drvdata(hdev, NULL);
+ kfree(s);
+ return ret;
+}
+
+/* ============================================================
+ * HID remove — device disconnected
+ * ============================================================ */
+static void omg_remove(struct hid_device *hdev)
+{
+ struct omg_device_state *s = hid_get_drvdata(hdev);
+
+ hid_hw_stop(hdev);
+
+ if (s) {
+ hid_info(hdev, "Removed %s (final score: %d/100)\n",
+ hdev->name, s->score);
+ hid_set_drvdata(hdev, NULL);
+ kfree(s);
+ }
+}
+
+/* Match all USB HID devices — we inspect and score them all */
+static const struct hid_device_id omg_table[] = {
+ { HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, omg_table);
+
+static struct hid_driver omg_driver = {
+ .name = DRIVER_NAME,
+ .id_table = omg_table,
+ .probe = omg_probe,
+ .remove = omg_remove,
+ .raw_event = omg_raw_event,
+};
+
+module_hid_driver(omg_driver);
^ permalink raw reply
* Re: [git pull] Input updates for v7.0-rc6
From: pr-tracker-bot @ 2026-04-04 15:34 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Linus Torvalds, linux-kernel, linux-input
In-Reply-To: <adCpWQOyfvE6E6k0@google.com>
The pull request you sent on Fri, 3 Apr 2026 23:02:12 -0700:
> git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git tags/input-for-v7.0-rc6
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/3aae9383f42f687221c011d7ee87529398e826b3
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ 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