From 3668399ce9e7bba243728c3c02b2785565ea7b0b Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 22 Jun 2026 22:35:34 -0700 Subject: [PATCH 01/61] Input: mms114 - prefer GPL over GPL v2 for module license As explained in commit bf7fbeeae6db ("module: Cure the MODULE_LICENSE "GPL" vs. "GPL v2" bogosity"), "GPL" and "GPL v2" have identical semantics in the module loader, but "GPL" is preferred to avoid unnecessary confusion and maintain consistency across the kernel. Change MODULE_LICENSE("GPL v2") to MODULE_LICENSE("GPL"). Assisted-by: Antigravity:gemini-3.5-flash Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260616050912.1531241-2-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/mms114.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c index 53ad35d61d47..db23b51f4630 100644 --- a/drivers/input/touchscreen/mms114.c +++ b/drivers/input/touchscreen/mms114.c @@ -722,4 +722,4 @@ module_i2c_driver(mms114_driver); /* Module information */ MODULE_AUTHOR("Joonyoung Shim "); MODULE_DESCRIPTION("MELFAS mms114 Touchscreen driver"); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); From ce414fb127d9a0bf566502023a8030af564f66bb Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 22 Jun 2026 22:35:44 -0700 Subject: [PATCH 02/61] Input: mms114 - use appropriate register argument types The MMS114 I2C touch controller uses 8-bit register addresses (0x01 to 0xF2) and 8-bit single-register data values. The helper functions previously declared reg and val as 32-bit unsigned int, requiring explicit bitwise masking (& 0xff) to narrow the values down to u8 before populating the I2C transfer buffers. Update reg and val parameters to u8 across mms114_read_reg(), mms114_write_reg(), and __mms114_read_reg() to accurately reflect the hardware specification and eliminate the redundant & 0xff masking. Additionally, update the val buffer pointer in __mms114_read_reg() from u8 * to void * to allow callers to pass data structures directly without requiring explicit casting. Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260616050912.1531241-3-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/mms114.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c index db23b51f4630..c2e006ac1196 100644 --- a/drivers/input/touchscreen/mms114.c +++ b/drivers/input/touchscreen/mms114.c @@ -87,12 +87,12 @@ struct mms114_touch { u8 reserved[2]; } __packed; -static int __mms114_read_reg(struct mms114_data *data, unsigned int reg, - unsigned int len, u8 *val) +static int __mms114_read_reg(struct mms114_data *data, u8 reg, + unsigned int len, void *val) { struct i2c_client *client = data->client; struct i2c_msg xfer[2]; - u8 buf = reg & 0xff; + u8 buf = reg; int error; if (reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL) @@ -121,7 +121,7 @@ static int __mms114_read_reg(struct mms114_data *data, unsigned int reg, return 0; } -static int mms114_read_reg(struct mms114_data *data, unsigned int reg) +static int mms114_read_reg(struct mms114_data *data, u8 reg) { u8 val; int error; @@ -133,15 +133,14 @@ static int mms114_read_reg(struct mms114_data *data, unsigned int reg) return error < 0 ? error : val; } -static int mms114_write_reg(struct mms114_data *data, unsigned int reg, - unsigned int val) +static int mms114_write_reg(struct mms114_data *data, u8 reg, u8 val) { struct i2c_client *client = data->client; u8 buf[2]; int error; - buf[0] = reg & 0xff; - buf[1] = val & 0xff; + buf[0] = reg; + buf[1] = val; error = i2c_master_send(client, buf, 2); if (error != 2) { @@ -242,9 +241,8 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) touch_size = packet_size / event_size; - error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size, - (u8 *)touch); - if (error < 0) + error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size, touch); + if (error) goto out; for (index = 0; index < touch_size; index++) { From 144337eeefbec6bbc7e9b51a6d58551baad03d40 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 22 Jun 2026 22:36:01 -0700 Subject: [PATCH 03/61] Input: mms114 - replace udelay with usleep_range The driver currently uses udelay(MMS114_I2C_DELAY) (50us) to ensure a mandatory delay between I2C transfers in __mms114_read_reg() and mms114_write_reg(). Both functions invoke underlying I2C core operations (i2c_transfer, i2c_master_send) which acquire mutexes and sleep. Furthermore, the interrupt handler mms114_interrupt() is registered as a threaded IRQ handler. Since the entire execution path is fully sleepable, busy-waiting with udelay() for 50us unnecessarily wastes CPU cycles. Replace udelay() with usleep_range() to allow the CPU to enter low-power states or execute other tasks during the delay. Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260616050912.1531241-4-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/mms114.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c index c2e006ac1196..c59aec8f2feb 100644 --- a/drivers/input/touchscreen/mms114.c +++ b/drivers/input/touchscreen/mms114.c @@ -116,7 +116,7 @@ static int __mms114_read_reg(struct mms114_data *data, u8 reg, "%s: i2c transfer failed (%d)\n", __func__, error); return error < 0 ? error : -EIO; } - udelay(MMS114_I2C_DELAY); + usleep_range(MMS114_I2C_DELAY, MMS114_I2C_DELAY + 50); return 0; } @@ -148,7 +148,7 @@ static int mms114_write_reg(struct mms114_data *data, u8 reg, u8 val) "%s: i2c send failed (%d)\n", __func__, error); return error < 0 ? error : -EIO; } - udelay(MMS114_I2C_DELAY); + usleep_range(MMS114_I2C_DELAY, MMS114_I2C_DELAY + 50); if (reg == MMS114_MODE_CONTROL) data->cache_mode_control = val; From 55b109de8ec5eb9840a1ea5ee163e3fee9aa3ba6 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 22 Jun 2026 22:36:13 -0700 Subject: [PATCH 04/61] Input: mms114 - replace BUG() and fix alignment Avoid taking the machine down with BUG() if a caller ever requests a read spanning the write-only MODE_CONTROL register; warn and return -EINVAL so the driver can recover. Additionally, fix parameter alignment to match the open parenthesis in several functions to conform to the kernel coding style. Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260616050912.1531241-5-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/mms114.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c index c59aec8f2feb..bf01eee0560a 100644 --- a/drivers/input/touchscreen/mms114.c +++ b/drivers/input/touchscreen/mms114.c @@ -95,8 +95,8 @@ static int __mms114_read_reg(struct mms114_data *data, u8 reg, u8 buf = reg; int error; - if (reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL) - BUG(); + if (WARN_ON(reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL)) + return -EINVAL; /* Write register */ xfer[0].addr = client->addr; @@ -310,8 +310,7 @@ static int mms114_get_version(struct mms114_data *data) if (error) return error; - group = i2c_smbus_read_byte_data(data->client, - MMS152_COMPAT_GROUP); + group = i2c_smbus_read_byte_data(data->client, MMS152_COMPAT_GROUP); if (group < 0) return group; @@ -371,14 +370,14 @@ static int mms114_setup_regs(struct mms114_data *data) if (data->contact_threshold) { error = mms114_write_reg(data, MMS114_CONTACT_THRESHOLD, - data->contact_threshold); + data->contact_threshold); if (error < 0) return error; } if (data->moving_threshold) { error = mms114_write_reg(data, MMS114_MOVING_THRESHOLD, - data->moving_threshold); + data->moving_threshold); if (error < 0) return error; } @@ -464,9 +463,9 @@ static int mms114_parse_legacy_bindings(struct mms114_data *data) } device_property_read_u32(dev, "contact-threshold", - &data->contact_threshold); + &data->contact_threshold); device_property_read_u32(dev, "moving-threshold", - &data->moving_threshold); + &data->moving_threshold); if (device_property_read_bool(dev, "x-invert")) props->invert_x = true; @@ -519,7 +518,7 @@ static int mms114_probe(struct i2c_client *client) return data->num_keycodes; } else if (data->num_keycodes > MMS114_MAX_TOUCHKEYS) { dev_warn(&client->dev, - "Found %d linux,keycodes but max is %d, ignoring the rest\n", + "Found %d linux,keycodes but max is %d, ignoring the rest\n", data->num_keycodes, MMS114_MAX_TOUCHKEYS); data->num_keycodes = MMS114_MAX_TOUCHKEYS; } From 2718c726a0682e89e9fc1a11db928fad8781e268 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 22 Jun 2026 22:38:01 -0700 Subject: [PATCH 05/61] Input: mms114 - refactor chip variant handling using descriptors Instead of using an enum and conditional switch/if statements throughout the driver to handle differences between chip variants (MMS114, MMS134S, MMS136, MMS152, MMS345L), introduce a variant-specific descriptor structure that encapsulates variant-specific properties (name, event size, presence of configuration registers) and callbacks (such as get_version). Define descriptors for each supported chip and associate them with the matching entries in the OF and I2C device ID tables. This eliminates the need for variant checks in the driver logic, making it easier to support new chip variants in the future. Note that there is slight change in device names: MMS134S: "MELFAS MMS134 Touchscreen" -> "MELFAS MMS134S Touchscreen" MMS345L: "MELFAS MMS345 Touchscreen" -> "MELFAS MMS345L Touchscreen" Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260616050912.1531241-6-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/mms114.c | 204 ++++++++++++++++------------- 1 file changed, 112 insertions(+), 92 deletions(-) diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c index bf01eee0560a..006dded17eb8 100644 --- a/drivers/input/touchscreen/mms114.c +++ b/drivers/input/touchscreen/mms114.c @@ -52,21 +52,13 @@ #define MMS114_TYPE_TOUCHSCREEN 1 #define MMS114_TYPE_TOUCHKEY 2 -enum mms_type { - TYPE_MMS114 = 114, - TYPE_MMS134S = 134, - TYPE_MMS136 = 136, - TYPE_MMS152 = 152, - TYPE_MMS345L = 345, -}; - struct mms114_data { + const struct mms_chip *chip; struct i2c_client *client; struct input_dev *input_dev; struct regulator *core_reg; struct regulator *io_reg; struct touchscreen_properties props; - enum mms_type type; unsigned int contact_threshold; unsigned int moving_threshold; @@ -77,6 +69,13 @@ struct mms114_data { u8 cache_mode_control; }; +struct mms_chip { + const char *name; + int event_size; + bool has_config_regs; + int (*get_version)(struct mms114_data *data); +}; + struct mms114_touch { u8 id:4, reserved_bit4:1, type:2, pressed:1; u8 x_hi:4, y_hi:4; @@ -156,6 +155,91 @@ static int mms114_write_reg(struct mms114_data *data, u8 reg, u8 val) return 0; } +static int mms114_get_version(struct mms114_data *data) +{ + struct device *dev = &data->client->dev; + u8 buf[6]; + int error; + + error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf); + if (error) + return error; + + dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n", + buf[0], buf[1], buf[3]); + return 0; +} + +static int mms152_get_version(struct mms114_data *data) +{ + struct device *dev = &data->client->dev; + u8 buf[3]; + int group; + int error; + + error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); + if (error) + return error; + + group = i2c_smbus_read_byte_data(data->client, MMS152_COMPAT_GROUP); + if (group < 0) + return group; + + dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x, Compat group: %c\n", + buf[0], buf[1], buf[2], group); + return 0; +} + +static int mms345l_get_version(struct mms114_data *data) +{ + struct device *dev = &data->client->dev; + u8 buf[3]; + int error; + + error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); + if (error) + return error; + + dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n", + buf[0], buf[1], buf[2]); + return 0; +} + +static const struct mms_chip mms114_descriptor = { + .name = "MMS114", + .event_size = MMS114_EVENT_SIZE, + .has_config_regs = true, + .get_version = mms114_get_version, +}; + +static const struct mms_chip mms134s_descriptor = { + .name = "MMS134S", + .event_size = MMS136_EVENT_SIZE, + .has_config_regs = true, + .get_version = mms114_get_version, +}; + +static const struct mms_chip mms136_descriptor = { + .name = "MMS136", + .event_size = MMS136_EVENT_SIZE, + .has_config_regs = true, + .get_version = mms114_get_version, +}; + +static const struct mms_chip mms152_descriptor = { + .name = "MMS152", + .event_size = MMS114_EVENT_SIZE, + .has_config_regs = false, + .get_version = mms152_get_version, +}; + +static const struct mms_chip mms345l_descriptor = { + .name = "MMS345L", + .event_size = MMS114_EVENT_SIZE, + .has_config_regs = false, + .get_version = mms345l_get_version, +}; + static void mms114_process_mt(struct mms114_data *data, struct mms114_touch *touch) { struct i2c_client *client = data->client; @@ -217,8 +301,8 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) struct i2c_client *client = data->client; struct mms114_touch touch[MMS114_MAX_TOUCH]; struct mms114_touch *t; + int event_size = data->chip->event_size; int packet_size; - int event_size; int touch_size; int index; int error; @@ -233,12 +317,6 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) goto out; } - /* MMS136 has slightly different event size */ - if (data->type == TYPE_MMS134S || data->type == TYPE_MMS136) - event_size = MMS136_EVENT_SIZE; - else - event_size = MMS114_EVENT_SIZE; - touch_size = packet_size / event_size; error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size, touch); @@ -288,64 +366,17 @@ static int mms114_set_active(struct mms114_data *data, bool active) return mms114_write_reg(data, MMS114_MODE_CONTROL, val); } -static int mms114_get_version(struct mms114_data *data) -{ - struct device *dev = &data->client->dev; - u8 buf[6]; - int group; - int error; - - switch (data->type) { - case TYPE_MMS345L: - error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); - if (error) - return error; - - dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n", - buf[0], buf[1], buf[2]); - break; - - case TYPE_MMS152: - error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); - if (error) - return error; - - group = i2c_smbus_read_byte_data(data->client, MMS152_COMPAT_GROUP); - if (group < 0) - return group; - - dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x, Compat group: %c\n", - buf[0], buf[1], buf[2], group); - break; - - case TYPE_MMS114: - case TYPE_MMS134S: - case TYPE_MMS136: - error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf); - if (error) - return error; - - dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n", - buf[0], buf[1], buf[3]); - break; - } - - return 0; -} - static int mms114_setup_regs(struct mms114_data *data) { const struct touchscreen_properties *props = &data->props; int val; int error; - error = mms114_get_version(data); - if (error < 0) + error = data->chip->get_version(data); + if (error) return error; - /* MMS114, MMS134S and MMS136 have configuration and power on registers */ - if (data->type != TYPE_MMS114 && data->type != TYPE_MMS134S && - data->type != TYPE_MMS136) + if (!data->chip->has_config_regs) return 0; error = mms114_set_active(data, true); @@ -481,7 +512,6 @@ static int mms114_probe(struct i2c_client *client) { struct mms114_data *data; struct input_dev *input_dev; - const void *match_data; int error; int i; @@ -501,12 +531,10 @@ static int mms114_probe(struct i2c_client *client) data->client = client; data->input_dev = input_dev; - match_data = device_get_match_data(&client->dev); - if (!match_data) + data->chip = i2c_get_match_data(client); + if (!data->chip) return -EINVAL; - data->type = (enum mms_type)match_data; - data->num_keycodes = device_property_count_u32(&client->dev, "linux,keycodes"); if (data->num_keycodes == -EINVAL) { @@ -563,8 +591,7 @@ static int mms114_probe(struct i2c_client *client) 0, data->props.max_y, 0, 0); } - if (data->type == TYPE_MMS114 || data->type == TYPE_MMS134S || - data->type == TYPE_MMS136) { + if (data->chip->has_config_regs) { /* * The firmware handles movement and pressure fuzz, so * don't duplicate that in software. @@ -579,8 +606,8 @@ static int mms114_probe(struct i2c_client *client) } input_dev->name = devm_kasprintf(&client->dev, GFP_KERNEL, - "MELFAS MMS%d Touchscreen", - data->type); + "MELFAS %s Touchscreen", + data->chip->name); if (!input_dev->name) return -ENOMEM; @@ -676,29 +703,22 @@ static int mms114_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(mms114_pm_ops, mms114_suspend, mms114_resume); static const struct i2c_device_id mms114_id[] = { - { .name = "mms114" }, + { .name = "mms114", .driver_data = (kernel_ulong_t)&mms114_descriptor }, + { .name = "mms134s", .driver_data = (kernel_ulong_t)&mms134s_descriptor }, + { .name = "mms136", .driver_data = (kernel_ulong_t)&mms136_descriptor }, + { .name = "mms152", .driver_data = (kernel_ulong_t)&mms152_descriptor }, + { .name = "mms345l", .driver_data = (kernel_ulong_t)&mms345l_descriptor }, { } }; MODULE_DEVICE_TABLE(i2c, mms114_id); #ifdef CONFIG_OF static const struct of_device_id mms114_dt_match[] = { - { - .compatible = "melfas,mms114", - .data = (void *)TYPE_MMS114, - }, { - .compatible = "melfas,mms134s", - .data = (void *)TYPE_MMS134S, - }, { - .compatible = "melfas,mms136", - .data = (void *)TYPE_MMS136, - }, { - .compatible = "melfas,mms152", - .data = (void *)TYPE_MMS152, - }, { - .compatible = "melfas,mms345l", - .data = (void *)TYPE_MMS345L, - }, + { .compatible = "melfas,mms114", .data = &mms114_descriptor }, + { .compatible = "melfas,mms134s", .data = &mms134s_descriptor }, + { .compatible = "melfas,mms136", .data = &mms136_descriptor }, + { .compatible = "melfas,mms152", .data = &mms152_descriptor }, + { .compatible = "melfas,mms345l", .data = &mms345l_descriptor }, { } }; MODULE_DEVICE_TABLE(of, mms114_dt_match); From d3b78c9e1f79bc57d81ccac6b26a4bd6141a62b2 Mon Sep 17 00:00:00 2001 From: Jun Yan Date: Wed, 17 Jun 2026 23:02:40 +0800 Subject: [PATCH 06/61] Input: cap11xx - clean up duplicate log and add probe error logs Duplicated device detection log exists at line 537 and line 542, which brings redundant kernel print messages. Drop one redundant log entry to clean up dmesg output. Meanwhile add missing error logs when I2C communication fails during driver probe(), helping debug. Signed-off-by: Jun Yan Link: https://patch.msgid.link/20260617150318.753148-2-jerrysteve1101@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/cap11xx.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c index 2447c1ae2166..485d8ba97723 100644 --- a/drivers/input/keyboard/cap11xx.c +++ b/drivers/input/keyboard/cap11xx.c @@ -512,7 +512,7 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) error = regmap_read(priv->regmap, CAP11XX_REG_PRODUCT_ID, &val); if (error) - return error; + return dev_err_probe(dev, error, "Failed to read product ID\n"); if (val != cap->product_id) { dev_err(dev, "Product ID: Got 0x%02x, expected 0x%02x\n", @@ -522,7 +522,7 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) error = regmap_read(priv->regmap, CAP11XX_REG_MANUFACTURER_ID, &val); if (error) - return error; + return dev_err_probe(dev, error, "Failed to read manufacturer ID\n"); if (val != CAP11XX_MANUFACTURER_ID) { dev_err(dev, "Manufacturer ID: Got 0x%02x, expected 0x%02x\n", @@ -531,11 +531,8 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) } error = regmap_read(priv->regmap, CAP11XX_REG_REVISION, &rev); - if (error < 0) - return error; - - dev_info(dev, "CAP11XX detected, model %s, revision 0x%02x\n", - id->name, rev); + if (error) + return dev_err_probe(dev, error, "Failed to read revision\n"); priv->model = cap; From 32964d017e56e123e1faf8702c7f93255db8acda Mon Sep 17 00:00:00 2001 From: Jun Yan Date: Wed, 17 Jun 2026 23:02:41 +0800 Subject: [PATCH 07/61] Input: cap11xx - remove unused register macros Remove unused register address macros and their corresponding definitions in the cap11xx_reg_defaults array. This cleanup reduces code clutter and makes the driver easier to maintain without affecting functionality. Signed-off-by: Jun Yan Link: https://patch.msgid.link/20260617150318.753148-3-jerrysteve1101@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/cap11xx.c | 51 -------------------------------- 1 file changed, 51 deletions(-) diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c index 485d8ba97723..fae26f035186 100644 --- a/drivers/input/keyboard/cap11xx.c +++ b/drivers/input/keyboard/cap11xx.c @@ -20,53 +20,24 @@ #define CAP11XX_REG_MAIN_CONTROL_GAIN_SHIFT (6) #define CAP11XX_REG_MAIN_CONTROL_GAIN_MASK (0xc0) #define CAP11XX_REG_MAIN_CONTROL_DLSEEP BIT(4) -#define CAP11XX_REG_GENERAL_STATUS 0x02 #define CAP11XX_REG_SENSOR_INPUT 0x03 -#define CAP11XX_REG_NOISE_FLAG_STATUS 0x0a #define CAP11XX_REG_SENOR_DELTA(X) (0x10 + (X)) #define CAP11XX_REG_SENSITIVITY_CONTROL 0x1f #define CAP11XX_REG_SENSITIVITY_CONTROL_DELTA_SENSE_MASK 0x70 -#define CAP11XX_REG_CONFIG 0x20 -#define CAP11XX_REG_SENSOR_ENABLE 0x21 -#define CAP11XX_REG_SENSOR_CONFIG 0x22 -#define CAP11XX_REG_SENSOR_CONFIG2 0x23 -#define CAP11XX_REG_SAMPLING_CONFIG 0x24 -#define CAP11XX_REG_CALIBRATION 0x26 -#define CAP11XX_REG_INT_ENABLE 0x27 #define CAP11XX_REG_REPEAT_RATE 0x28 #define CAP11XX_REG_SIGNAL_GUARD_ENABLE 0x29 -#define CAP11XX_REG_MT_CONFIG 0x2a -#define CAP11XX_REG_MT_PATTERN_CONFIG 0x2b -#define CAP11XX_REG_MT_PATTERN 0x2d -#define CAP11XX_REG_RECALIB_CONFIG 0x2f #define CAP11XX_REG_SENSOR_THRESH(X) (0x30 + (X)) -#define CAP11XX_REG_SENSOR_NOISE_THRESH 0x38 -#define CAP11XX_REG_STANDBY_CHANNEL 0x40 -#define CAP11XX_REG_STANDBY_CONFIG 0x41 -#define CAP11XX_REG_STANDBY_SENSITIVITY 0x42 -#define CAP11XX_REG_STANDBY_THRESH 0x43 #define CAP11XX_REG_CONFIG2 0x44 #define CAP11XX_REG_CONFIG2_ALT_POL BIT(6) -#define CAP11XX_REG_SENSOR_BASE_CNT(X) (0x50 + (X)) -#define CAP11XX_REG_LED_POLARITY 0x73 #define CAP11XX_REG_LED_OUTPUT_CONTROL 0x74 #define CAP11XX_REG_CALIB_SENSITIVITY_CONFIG 0x80 #define CAP11XX_REG_CALIB_SENSITIVITY_CONFIG2 0x81 - -#define CAP11XX_REG_LED_DUTY_CYCLE_1 0x90 -#define CAP11XX_REG_LED_DUTY_CYCLE_2 0x91 -#define CAP11XX_REG_LED_DUTY_CYCLE_3 0x92 #define CAP11XX_REG_LED_DUTY_CYCLE_4 0x93 -#define CAP11XX_REG_LED_DUTY_MIN_MASK (0x0f) -#define CAP11XX_REG_LED_DUTY_MIN_MASK_SHIFT (0) #define CAP11XX_REG_LED_DUTY_MAX_MASK (0xf0) #define CAP11XX_REG_LED_DUTY_MAX_MASK_SHIFT (4) #define CAP11XX_REG_LED_DUTY_MAX_VALUE (15) -#define CAP11XX_REG_SENSOR_CALIB (0xb1 + (X)) -#define CAP11XX_REG_SENSOR_CALIB_LSB1 0xb9 -#define CAP11XX_REG_SENSOR_CALIB_LSB2 0xba #define CAP11XX_REG_PRODUCT_ID 0xfd #define CAP11XX_REG_MANUFACTURER_ID 0xfe #define CAP11XX_REG_REVISION 0xff @@ -111,37 +82,15 @@ struct cap11xx_hw_model { static const struct reg_default cap11xx_reg_defaults[] = { { CAP11XX_REG_MAIN_CONTROL, 0x00 }, - { CAP11XX_REG_GENERAL_STATUS, 0x00 }, - { CAP11XX_REG_SENSOR_INPUT, 0x00 }, - { CAP11XX_REG_NOISE_FLAG_STATUS, 0x00 }, { CAP11XX_REG_SENSITIVITY_CONTROL, 0x2f }, - { CAP11XX_REG_CONFIG, 0x20 }, - { CAP11XX_REG_SENSOR_ENABLE, 0x3f }, - { CAP11XX_REG_SENSOR_CONFIG, 0xa4 }, - { CAP11XX_REG_SENSOR_CONFIG2, 0x07 }, - { CAP11XX_REG_SAMPLING_CONFIG, 0x39 }, - { CAP11XX_REG_CALIBRATION, 0x00 }, - { CAP11XX_REG_INT_ENABLE, 0x3f }, { CAP11XX_REG_REPEAT_RATE, 0x3f }, - { CAP11XX_REG_MT_CONFIG, 0x80 }, - { CAP11XX_REG_MT_PATTERN_CONFIG, 0x00 }, - { CAP11XX_REG_MT_PATTERN, 0x3f }, - { CAP11XX_REG_RECALIB_CONFIG, 0x8a }, { CAP11XX_REG_SENSOR_THRESH(0), 0x40 }, { CAP11XX_REG_SENSOR_THRESH(1), 0x40 }, { CAP11XX_REG_SENSOR_THRESH(2), 0x40 }, { CAP11XX_REG_SENSOR_THRESH(3), 0x40 }, { CAP11XX_REG_SENSOR_THRESH(4), 0x40 }, { CAP11XX_REG_SENSOR_THRESH(5), 0x40 }, - { CAP11XX_REG_SENSOR_NOISE_THRESH, 0x01 }, - { CAP11XX_REG_STANDBY_CHANNEL, 0x00 }, - { CAP11XX_REG_STANDBY_CONFIG, 0x39 }, - { CAP11XX_REG_STANDBY_SENSITIVITY, 0x02 }, - { CAP11XX_REG_STANDBY_THRESH, 0x40 }, { CAP11XX_REG_CONFIG2, 0x40 }, - { CAP11XX_REG_LED_POLARITY, 0x00 }, - { CAP11XX_REG_SENSOR_CALIB_LSB1, 0x00 }, - { CAP11XX_REG_SENSOR_CALIB_LSB2, 0x00 }, }; static bool cap11xx_volatile_reg(struct device *dev, unsigned int reg) From 7e6ad2f135b4d796b2b95372dccfdd24e0b351c3 Mon Sep 17 00:00:00 2001 From: Jun Yan Date: Wed, 17 Jun 2026 23:02:42 +0800 Subject: [PATCH 08/61] dt-bindings: input: microchip,cap11xx: Update datasheet URL and LED reg range - Add datasheet links for all supported CAP11xx variants. - Update LED node regex and replace enum constraints with minimum/maximum for LED reg ranges in preparation for CAP1114 support. CAP1114 has 11 LED channels. minimum/maximum constraints are easier to maintain than long enum lists when expanding channel count later. Drop unnecessary led unit-address pattern. Signed-off-by: Jun Yan Acked-by: Conor Dooley Link: https://patch.msgid.link/20260617150318.753148-4-jerrysteve1101@gmail.com Signed-off-by: Dmitry Torokhov --- .../bindings/input/microchip,cap11xx.yaml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml b/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml index 7ade03f1b32b..eabf06a1163e 100644 --- a/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml +++ b/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml @@ -10,6 +10,15 @@ description: | The Microchip CAP1xxx Family of RightTouchTM multiple-channel capacitive touch controllers and LED drivers. The device communication via I2C only. + For more product information please see the links below: + CAP1106: https://ww1.microchip.com/downloads/en/DeviceDoc/00001624B.pdf + CAP1126: https://ww1.microchip.com/downloads/en/DeviceDoc/00001623B.pdf + CAP1188: https://ww1.microchip.com/downloads/en/DeviceDoc/00001620C.pdf + CAP1203: https://ww1.microchip.com/downloads/en/DeviceDoc/00001572B.pdf + CAP1206: https://ww1.microchip.com/downloads/en/DeviceDoc/00001567B.pdf + CAP1293: https://ww1.microchip.com/downloads/en/DeviceDoc/00001566B.pdf + CAP1298: https://ww1.microchip.com/downloads/en/DeviceDoc/00001571B.pdf + maintainers: - Rob Herring @@ -131,7 +140,9 @@ patternProperties: properties: reg: - enum: [0, 1, 2, 3, 4, 5, 6, 7] + description: LED channel number + minimum: 0 + maximum: 7 label: true @@ -158,7 +169,7 @@ allOf: - microchip,cap1298 then: patternProperties: - "^led@[0-7]$": false + "^led@": false - if: properties: From dc0d41bd7bd35cd1ac0efe7bbe9cd29cd2fd5022 Mon Sep 17 00:00:00 2001 From: Jun Yan Date: Wed, 17 Jun 2026 23:02:43 +0800 Subject: [PATCH 09/61] dt-bindings: input: microchip,cap11xx: Add microchip,cap1126 LED reg constraints Apply per-chip LED channel limits: - CAP1126: max 2 channels (0-1) - CAP1188: max 8 channels (0-7) - CAP1106, CAP12xx: no LED support Signed-off-by: Jun Yan Acked-by: Conor Dooley Link: https://patch.msgid.link/20260617150318.753148-5-jerrysteve1101@gmail.com Signed-off-by: Dmitry Torokhov --- .../bindings/input/microchip,cap11xx.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml b/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml index eabf06a1163e..798035e942af 100644 --- a/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml +++ b/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml @@ -171,6 +171,19 @@ allOf: patternProperties: "^led@": false + - if: + properties: + compatible: + contains: + enum: + - microchip,cap1126 + then: + patternProperties: + "^led@": + properties: + reg: + maximum: 1 + - if: properties: compatible: From 7c492b9eee50076da2717890236d4a08cd0109a5 Mon Sep 17 00:00:00 2001 From: Jun Yan Date: Wed, 17 Jun 2026 23:02:44 +0800 Subject: [PATCH 10/61] dt-bindings: input: microchip,cap11xx: Add reset-gpios property Add support for the optional reset-gpios property to describe the active-high reset pin for CAP1126/CAP1188 devices. Driving the GPIO high asserts reset and deep sleep, while driving it low releases reset for normal operation. Restrict this property to be available only on CAP1126 and CAP1188 chips, as other CAP11xx variants do not have a hardware reset pin. Signed-off-by: Jun Yan Acked-by: Conor Dooley Link: https://patch.msgid.link/20260617150318.753148-6-jerrysteve1101@gmail.com Signed-off-by: Dmitry Torokhov --- .../bindings/input/microchip,cap11xx.yaml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml b/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml index 798035e942af..b97e5b2735f1 100644 --- a/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml +++ b/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml @@ -49,6 +49,13 @@ properties: device's ALERT#/CM_IRQ# pin is connected to. The device only has one interrupt source. + reset-gpios: + description: | + GPIO connected to the active-high RESET pin of the chip; + driving it high asserts reset and deep sleep, while driving + it low releases reset for normal operation. + maxItems: 1 + autorepeat: description: | Enables the Linux input system's autorepeat feature on the input device. @@ -157,6 +164,20 @@ patternProperties: allOf: - $ref: input.yaml + - if: + properties: + compatible: + contains: + enum: + - microchip,cap1106 + - microchip,cap1203 + - microchip,cap1206 + - microchip,cap1293 + - microchip,cap1298 + then: + properties: + reset-gpios: false + - if: properties: compatible: @@ -207,6 +228,8 @@ additionalProperties: false examples: - | + #include + i2c { #address-cells = <1>; #size-cells = <0>; @@ -228,6 +251,8 @@ examples: <109>, /* KEY_PAGEDOWN */ <104>; /* KEY_PAGEUP */ + reset-gpios = <&gpio 17 GPIO_ACTIVE_HIGH>; + #address-cells = <1>; #size-cells = <0>; From e40bdf042d417c876ce2623f77215a7eb51a4caa Mon Sep 17 00:00:00 2001 From: Jun Yan Date: Wed, 17 Jun 2026 23:02:45 +0800 Subject: [PATCH 11/61] Input: cap11xx - add reset gpio support Some CAP11xx devices (CAP1126/CAP1188) have a dedicated RESET pin. Add hardware reset operation to improve device reliability and ensure proper initialization on probe. Signed-off-by: Jun Yan Link: https://patch.msgid.link/20260617150318.753148-7-jerrysteve1101@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/cap11xx.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c index fae26f035186..1db4a9090705 100644 --- a/drivers/input/keyboard/cap11xx.c +++ b/drivers/input/keyboard/cap11xx.c @@ -5,6 +5,7 @@ * (c) 2014 Daniel Mack */ +#include #include #include #include @@ -44,6 +45,9 @@ #define CAP11XX_MANUFACTURER_ID 0x5d +#define CAP11XX_T_RST_FILT_MIN_US 10000 +#define CAP11XX_T_RST_ON_MIN_MS 400 + #ifdef CONFIG_LEDS_CLASS struct cap11xx_led { struct cap11xx_priv *priv; @@ -56,6 +60,7 @@ struct cap11xx_priv { struct regmap *regmap; struct device *dev; struct input_dev *idev; + struct gpio_desc *reset_gpio; const struct cap11xx_hw_model *model; struct cap11xx_led *leds; @@ -459,6 +464,17 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) if (IS_ERR(priv->regmap)) return PTR_ERR(priv->regmap); + priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), + "Failed to get 'reset' GPIO\n"); + + if (priv->reset_gpio) { + usleep_range(CAP11XX_T_RST_FILT_MIN_US, CAP11XX_T_RST_FILT_MIN_US * 2); + gpiod_set_value_cansleep(priv->reset_gpio, 0); + msleep(CAP11XX_T_RST_ON_MIN_MS); + } + error = regmap_read(priv->regmap, CAP11XX_REG_PRODUCT_ID, &val); if (error) return dev_err_probe(dev, error, "Failed to read product ID\n"); From 6ddb3d0c90d9423ed84b4cd3213d73d54ad386ba Mon Sep 17 00:00:00 2001 From: Jun Yan Date: Mon, 22 Jun 2026 22:07:35 -0700 Subject: [PATCH 12/61] Input: cap11xx - refactor code for better CAP1114 support. Extend cap11xx_hw_model structure to support CAP1114 with different register offsets and hardware characteristics: - led_output_control_reg_base: different address on CAP1114 - sensor_input_reg_base: different address on CAP1114 - num_sensor_thresholds: separate value from num_channels for CAP1114 - has_repeat_en: repeat enable support, disabled by default on CAP1114 Include linux/bits.h, update the register operations related to LEDs. Signed-off-by: Jun Yan Link: https://patch.msgid.link/20260617150318.753148-8-jerrysteve1101@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/cap11xx.c | 95 ++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 23 deletions(-) diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c index 1db4a9090705..16edabcf6f2d 100644 --- a/drivers/input/keyboard/cap11xx.c +++ b/drivers/input/keyboard/cap11xx.c @@ -5,6 +5,7 @@ * (c) 2014 Daniel Mack */ +#include #include #include #include @@ -22,6 +23,7 @@ #define CAP11XX_REG_MAIN_CONTROL_GAIN_MASK (0xc0) #define CAP11XX_REG_MAIN_CONTROL_DLSEEP BIT(4) #define CAP11XX_REG_SENSOR_INPUT 0x03 +#define CAP1114_REG_BUTTON_STATUS2 0x04 #define CAP11XX_REG_SENOR_DELTA(X) (0x10 + (X)) #define CAP11XX_REG_SENSITIVITY_CONTROL 0x1f #define CAP11XX_REG_SENSITIVITY_CONTROL_DELTA_SENSE_MASK 0x70 @@ -36,7 +38,6 @@ #define CAP11XX_REG_LED_DUTY_CYCLE_4 0x93 #define CAP11XX_REG_LED_DUTY_MAX_MASK (0xf0) -#define CAP11XX_REG_LED_DUTY_MAX_MASK_SHIFT (4) #define CAP11XX_REG_LED_DUTY_MAX_VALUE (15) #define CAP11XX_REG_PRODUCT_ID 0xfd @@ -77,10 +78,14 @@ struct cap11xx_priv { struct cap11xx_hw_model { u8 product_id; + u8 led_output_control_reg_base; + u8 sensor_input_reg_base; unsigned int num_channels; unsigned int num_leds; + unsigned int num_sensor_thresholds; bool has_gain; bool has_irq_config; + bool has_repeat_en; bool has_sensitivity_control; bool has_signal_guard; }; @@ -103,6 +108,7 @@ static bool cap11xx_volatile_reg(struct device *dev, unsigned int reg) switch (reg) { case CAP11XX_REG_MAIN_CONTROL: case CAP11XX_REG_SENSOR_INPUT: + case CAP1114_REG_BUTTON_STATUS2: case CAP11XX_REG_SENOR_DELTA(0): case CAP11XX_REG_SENOR_DELTA(1): case CAP11XX_REG_SENOR_DELTA(2): @@ -211,8 +217,8 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) } if (!of_property_read_u32_array(node, "microchip,input-threshold", - priv->thresholds, priv->model->num_channels)) { - for (i = 0; i < priv->model->num_channels; i++) { + priv->thresholds, priv->model->num_sensor_thresholds)) { + for (i = 0; i < priv->model->num_sensor_thresholds; i++) { if (priv->thresholds[i] > 127) { dev_err(dev, "Invalid input-threshold value %u\n", priv->thresholds[i]); @@ -286,10 +292,12 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) of_property_read_u32_array(node, "linux,keycodes", priv->keycodes, priv->model->num_channels); - /* Disable autorepeat. The Linux input system has its own handling. */ - error = regmap_write(priv->regmap, CAP11XX_REG_REPEAT_RATE, 0); - if (error) - return error; + if (priv->model->has_repeat_en) { + /* Disable autorepeat. The Linux input system has its own handling. */ + error = regmap_write(priv->regmap, CAP11XX_REG_REPEAT_RATE, 0); + if (error) + return error; + } return 0; } @@ -308,7 +316,7 @@ static irqreturn_t cap11xx_thread_func(int irq_num, void *data) if (ret < 0) goto out; - ret = regmap_read(priv->regmap, CAP11XX_REG_SENSOR_INPUT, &status); + ret = regmap_read(priv->regmap, priv->model->sensor_input_reg_base, &status); if (ret < 0) goto out; @@ -361,10 +369,16 @@ static int cap11xx_led_set(struct led_classdev *cdev, * limitation. Brightness levels per LED are either * 0 (OFF) and 1 (ON). */ - return regmap_update_bits(priv->regmap, - CAP11XX_REG_LED_OUTPUT_CONTROL, - BIT(led->reg), - value ? BIT(led->reg) : 0); + if (led->reg >= 8) + return regmap_update_bits(priv->regmap, + priv->model->led_output_control_reg_base + 1, + BIT(led->reg - 8), + value ? BIT(led->reg - 8) : 0); + else + return regmap_update_bits(priv->regmap, + priv->model->led_output_control_reg_base, + BIT(led->reg), + value ? BIT(led->reg) : 0); } static int cap11xx_init_leds(struct device *dev, @@ -374,6 +388,7 @@ static int cap11xx_init_leds(struct device *dev, struct cap11xx_led *led; int cnt = of_get_child_count(node); int error; + u32 duty_val; if (!num_leds || !cnt) return 0; @@ -387,15 +402,26 @@ static int cap11xx_init_leds(struct device *dev, priv->leds = led; + /* Set all LEDs to off */ error = regmap_update_bits(priv->regmap, - CAP11XX_REG_LED_OUTPUT_CONTROL, 0xff, 0); + priv->model->led_output_control_reg_base, + GENMASK(min(num_leds, 8) - 1, 0), 0); if (error) return error; + if (num_leds > 8) { + error = regmap_update_bits(priv->regmap, + priv->model->led_output_control_reg_base + 1, + GENMASK(num_leds - 8 - 1, 0), 0); + if (error) + return error; + } + + duty_val = FIELD_PREP(CAP11XX_REG_LED_DUTY_MAX_MASK, + CAP11XX_REG_LED_DUTY_MAX_VALUE); + error = regmap_update_bits(priv->regmap, CAP11XX_REG_LED_DUTY_CYCLE_4, - CAP11XX_REG_LED_DUTY_MAX_MASK, - CAP11XX_REG_LED_DUTY_MAX_VALUE << - CAP11XX_REG_LED_DUTY_MAX_MASK_SHIFT); + CAP11XX_REG_LED_DUTY_MAX_MASK, duty_val); if (error) return error; @@ -561,41 +587,64 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) } static const struct cap11xx_hw_model cap1106_model = { - .product_id = 0x55, .num_channels = 6, .num_leds = 0, + .product_id = 0x55, + .num_channels = 6, .num_leds = 0, .num_sensor_thresholds = 6, + .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, .has_gain = true, .has_irq_config = true, + .has_repeat_en = true, }; static const struct cap11xx_hw_model cap1126_model = { - .product_id = 0x53, .num_channels = 6, .num_leds = 2, + .product_id = 0x53, + .num_channels = 6, .num_leds = 2, .num_sensor_thresholds = 6, + .led_output_control_reg_base = CAP11XX_REG_LED_OUTPUT_CONTROL, + .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, .has_gain = true, .has_irq_config = true, + .has_repeat_en = true, }; static const struct cap11xx_hw_model cap1188_model = { - .product_id = 0x50, .num_channels = 8, .num_leds = 8, + .product_id = 0x50, + .num_channels = 8, .num_leds = 8, .num_sensor_thresholds = 8, + .led_output_control_reg_base = CAP11XX_REG_LED_OUTPUT_CONTROL, + .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, .has_gain = true, .has_irq_config = true, + .has_repeat_en = true, }; static const struct cap11xx_hw_model cap1203_model = { - .product_id = 0x6d, .num_channels = 3, .num_leds = 0, + .product_id = 0x6d, + .num_channels = 3, .num_leds = 0, .num_sensor_thresholds = 3, + .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_repeat_en = true, }; static const struct cap11xx_hw_model cap1206_model = { - .product_id = 0x67, .num_channels = 6, .num_leds = 0, + .product_id = 0x67, + .num_channels = 6, .num_leds = 0, .num_sensor_thresholds = 6, + .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_repeat_en = true, }; static const struct cap11xx_hw_model cap1293_model = { - .product_id = 0x6f, .num_channels = 3, .num_leds = 0, + .product_id = 0x6f, + .num_channels = 3, .num_leds = 0, .num_sensor_thresholds = 3, + .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, .has_gain = true, + .has_repeat_en = true, .has_sensitivity_control = true, .has_signal_guard = true, }; static const struct cap11xx_hw_model cap1298_model = { - .product_id = 0x71, .num_channels = 8, .num_leds = 0, + .product_id = 0x71, + .num_channels = 8, .num_leds = 0, .num_sensor_thresholds = 8, + .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, .has_gain = true, + .has_repeat_en = true, .has_sensitivity_control = true, .has_signal_guard = true, }; From a9a7baeb3ab7a09eca0696c575b7a3f1058868d6 Mon Sep 17 00:00:00 2001 From: Jun Yan Date: Wed, 17 Jun 2026 23:02:47 +0800 Subject: [PATCH 13/61] Input: cap11xx - guard unsupported DT properties before parsing Check of_property_present() before parsing microchip,calib-sensitivity and microchip,signal-guard, so that models which do not support these properties (e.g. CAP1114) skip the parsing entirely. This prevents a potential buffer overflow in calib_sensitivities[8] and signal_guard_inputs_mask when a model with more than 8 channels (CAP1114 has 14) would otherwise call of_property_read_u32_array() with num_channels as the element count. Signed-off-by: Jun Yan Link: https://patch.msgid.link/20260617150318.753148-9-jerrysteve1101@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/cap11xx.c | 52 +++++++++++++++++--------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c index 16edabcf6f2d..5ee01edff581 100644 --- a/drivers/input/keyboard/cap11xx.c +++ b/drivers/input/keyboard/cap11xx.c @@ -233,10 +233,13 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) } } - if (!of_property_read_u32_array(node, "microchip,calib-sensitivity", - priv->calib_sensitivities, - priv->model->num_channels)) { - if (priv->model->has_sensitivity_control) { + if (of_property_present(node, "microchip,calib-sensitivity")) { + if (!priv->model->has_sensitivity_control) { + dev_warn(dev, + "This model doesn't support 'calib-sensitivity'\n"); + } else if (!of_property_read_u32_array(node, "microchip,calib-sensitivity", + priv->calib_sensitivities, + priv->model->num_channels)) { for (i = 0; i < priv->model->num_channels; i++) { if (!is_power_of_2(priv->calib_sensitivities[i]) || priv->calib_sensitivities[i] > 4) { @@ -256,32 +259,31 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) if (error) return error; } - } else { - dev_warn(dev, - "This model doesn't support 'calib-sensitivity'\n"); } } - for (i = 0; i < priv->model->num_channels; i++) { - if (!of_property_read_u32_index(node, "microchip,signal-guard", - i, &u32_val)) { - if (u32_val > 1) - return -EINVAL; - if (u32_val) - priv->signal_guard_inputs_mask |= 0x01 << i; - } - } - - if (priv->signal_guard_inputs_mask) { - if (priv->model->has_signal_guard) { - error = regmap_write(priv->regmap, - CAP11XX_REG_SIGNAL_GUARD_ENABLE, - priv->signal_guard_inputs_mask); - if (error) - return error; - } else { + if (of_property_present(node, "microchip,signal-guard")) { + if (!priv->model->has_signal_guard) { dev_warn(dev, "This model doesn't support 'signal-guard'\n"); + } else { + for (i = 0; i < priv->model->num_channels; i++) { + if (!of_property_read_u32_index(node, "microchip,signal-guard", + i, &u32_val)) { + if (u32_val > 1) + return -EINVAL; + if (u32_val) + priv->signal_guard_inputs_mask |= 0x01 << i; + } + } + + if (priv->signal_guard_inputs_mask) { + error = regmap_write(priv->regmap, + CAP11XX_REG_SIGNAL_GUARD_ENABLE, + priv->signal_guard_inputs_mask); + if (error) + return error; + } } } From ec0d4ef4f95b0e8ae9d26e83fd88b395f43f629e Mon Sep 17 00:00:00 2001 From: Jun Yan Date: Wed, 17 Jun 2026 23:02:48 +0800 Subject: [PATCH 14/61] dt-bindings: input: microchip,cap11xx: Add CAP1114 support CAP1114 is a 14-channel capacitive touch sensor with 11 LED outputs and hardware reset support. Add the compatible string for CAP1114, add its datasheet URL, update the maximum of LED channel reg, and add constraint for linux,keycodes. Previously, the LED reg property had a default maximum of 7 for CAP1188. With the addition of CAP1114, the default maximum is now 11. An if-then constraint is added to limit the LED count for CAP1188. Update description for microchip,input-threshold: CAP1114 only provides eight threshold entries, which does not match its total channel count. CAP1114 does not support microchip,signal-guard and microchip,calib-sensitivity. Add CAP1114 to the unsupported enum list. Signed-off-by: Jun Yan Reviewed-by: Conor Dooley Link: https://patch.msgid.link/20260617150318.753148-10-jerrysteve1101@gmail.com Signed-off-by: Dmitry Torokhov --- .../bindings/input/microchip,cap11xx.yaml | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml b/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml index b97e5b2735f1..2a37ac252c37 100644 --- a/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml +++ b/Documentation/devicetree/bindings/input/microchip,cap11xx.yaml @@ -12,6 +12,7 @@ description: | For more product information please see the links below: CAP1106: https://ww1.microchip.com/downloads/en/DeviceDoc/00001624B.pdf + CAP1114: https://ww1.microchip.com/downloads/en/DeviceDoc/00002444A.pdf CAP1126: https://ww1.microchip.com/downloads/en/DeviceDoc/00001623B.pdf CAP1188: https://ww1.microchip.com/downloads/en/DeviceDoc/00001620C.pdf CAP1203: https://ww1.microchip.com/downloads/en/DeviceDoc/00001572B.pdf @@ -26,6 +27,7 @@ properties: compatible: enum: - microchip,cap1106 + - microchip,cap1114 - microchip,cap1126 - microchip,cap1188 - microchip,cap1203 @@ -62,7 +64,7 @@ properties: linux,keycodes: minItems: 3 - maxItems: 8 + maxItems: 14 description: | Specifies an array of numeric keycode values to be used for the channels. If this property is @@ -122,6 +124,8 @@ properties: is required for a touch to be registered, making the touch sensor less sensitive. The number of entries must correspond to the number of channels. + CAP1114 is an exception where channels 8~14 reuse the eighth entry's + threshold, so counts differ. microchip,calib-sensitivity: $ref: /schemas/types.yaml#/definitions/uint32-array @@ -140,7 +144,7 @@ properties: The number of entries must correspond to the number of channels. patternProperties: - "^led@[0-7]$": + "^led@[0-9a]$": type: object description: CAP11xx LEDs $ref: /schemas/leds/common.yaml# @@ -149,7 +153,7 @@ patternProperties: reg: description: LED channel number minimum: 0 - maximum: 7 + maximum: 10 label: true @@ -178,6 +182,21 @@ allOf: properties: reset-gpios: false + - if: + properties: + compatible: + contains: + enum: + - microchip,cap1114 + then: + properties: + linux,keycodes: + minItems: 14 + else: + properties: + linux,keycodes: + maxItems: 8 + - if: properties: compatible: @@ -205,12 +224,26 @@ allOf: reg: maximum: 1 + - if: + properties: + compatible: + contains: + enum: + - microchip,cap1188 + then: + patternProperties: + "^led@": + properties: + reg: + maximum: 7 + - if: properties: compatible: contains: enum: - microchip,cap1106 + - microchip,cap1114 - microchip,cap1126 - microchip,cap1188 - microchip,cap1203 From 0c9245c455e809c8111cb60284328c79064df050 Mon Sep 17 00:00:00 2001 From: Jun Yan Date: Wed, 17 Jun 2026 23:02:49 +0800 Subject: [PATCH 15/61] Input: cap11xx - add support for CAP1114 CAP1114 is a 14-channel capacitive touch sensor with 11 LED outputs and hardware reset support. The CAP1114 uses two control registers for LED output management and requires two button status registers for touch input state reporting. By default, channels CS8~CS14 operate as a single grouped block. Set the corresponding register enable bit to enable these channels as independent touch inputs. Note these channels share the input threshold of the eighth entry, causing num_sensor_thresholds to differ from num_channels. Signed-off-by: Jun Yan Link: https://patch.msgid.link/20260617150318.753148-11-jerrysteve1101@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/cap11xx.c | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c index 5ee01edff581..25b6f7bd97ee 100644 --- a/drivers/input/keyboard/cap11xx.c +++ b/drivers/input/keyboard/cap11xx.c @@ -18,6 +18,12 @@ #include #include +#define CAP1114_REG_BUTTON_STATUS1 0x03 +#define CAP1114_REG_BUTTON_STATUS2 0x04 +#define CAP1114_REG_CONFIG2 0x40 +#define CAP1114_REG_CONFIG2_VOL_UP_DOWN BIT(1) +#define CAP1114_REG_LED_OUTPUT_CONTROL1 0x73 + #define CAP11XX_REG_MAIN_CONTROL 0x00 #define CAP11XX_REG_MAIN_CONTROL_GAIN_SHIFT (6) #define CAP11XX_REG_MAIN_CONTROL_GAIN_MASK (0xc0) @@ -84,6 +90,7 @@ struct cap11xx_hw_model { unsigned int num_leds; unsigned int num_sensor_thresholds; bool has_gain; + bool has_grouped_sensors; bool has_irq_config; bool has_repeat_en; bool has_sensitivity_control; @@ -100,6 +107,8 @@ static const struct reg_default cap11xx_reg_defaults[] = { { CAP11XX_REG_SENSOR_THRESH(3), 0x40 }, { CAP11XX_REG_SENSOR_THRESH(4), 0x40 }, { CAP11XX_REG_SENSOR_THRESH(5), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(6), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(7), 0x40 }, { CAP11XX_REG_CONFIG2, 0x40 }, }; @@ -108,6 +117,11 @@ static bool cap11xx_volatile_reg(struct device *dev, unsigned int reg) switch (reg) { case CAP11XX_REG_MAIN_CONTROL: case CAP11XX_REG_SENSOR_INPUT: + /* + * CAP1114_REG_BUTTON_STATUS1 (CAP11XX_REG_SENSOR_INPUT) and + * CAP1114_REG_BUTTON_STATUS2 is volatile for the CAP1114, + * which supports more than 8 touch channels. + */ case CAP1114_REG_BUTTON_STATUS2: case CAP11XX_REG_SENOR_DELTA(0): case CAP11XX_REG_SENOR_DELTA(1): @@ -294,6 +308,17 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) of_property_read_u32_array(node, "linux,keycodes", priv->keycodes, priv->model->num_channels); + /* + * CAP1114 needs dedicated configuration to split + * grouped sensors into independent inputs. + */ + if (priv->model->has_grouped_sensors) { + error = regmap_set_bits(priv->regmap, CAP1114_REG_CONFIG2, + CAP1114_REG_CONFIG2_VOL_UP_DOWN); + if (error) + return error; + } + if (priv->model->has_repeat_en) { /* Disable autorepeat. The Linux input system has its own handling. */ error = regmap_write(priv->regmap, CAP11XX_REG_REPEAT_RATE, 0); @@ -322,6 +347,21 @@ static irqreturn_t cap11xx_thread_func(int irq_num, void *data) if (ret < 0) goto out; + if (priv->model->num_channels > 8) { + unsigned int status2; + + ret = regmap_read(priv->regmap, priv->model->sensor_input_reg_base + 1, &status2); + if (ret < 0) + goto out; + + /* + * CAP1114 STATUS1 register only contains data for the first 6 channels. + * the remaining channels is stored in STATUS2. + */ + status &= GENMASK(5, 0); + status |= FIELD_PREP(GENMASK(13, 6), status2); + } + for (i = 0; i < priv->idev->keycodemax; i++) input_report_key(priv->idev, priv->keycodes[i], status & (1 << i)); @@ -597,6 +637,14 @@ static const struct cap11xx_hw_model cap1106_model = { .has_repeat_en = true, }; +static const struct cap11xx_hw_model cap1114_model = { + .product_id = 0x3a, + .num_channels = 14, .num_leds = 11, .num_sensor_thresholds = 8, + .led_output_control_reg_base = CAP1114_REG_LED_OUTPUT_CONTROL1, + .sensor_input_reg_base = CAP1114_REG_BUTTON_STATUS1, + .has_grouped_sensors = true, +}; + static const struct cap11xx_hw_model cap1126_model = { .product_id = 0x53, .num_channels = 6, .num_leds = 2, .num_sensor_thresholds = 6, @@ -653,6 +701,7 @@ static const struct cap11xx_hw_model cap1298_model = { static const struct of_device_id cap11xx_dt_ids[] = { { .compatible = "microchip,cap1106", .data = &cap1106_model }, + { .compatible = "microchip,cap1114", .data = &cap1114_model }, { .compatible = "microchip,cap1126", .data = &cap1126_model }, { .compatible = "microchip,cap1188", .data = &cap1188_model }, { .compatible = "microchip,cap1203", .data = &cap1203_model }, @@ -665,6 +714,7 @@ MODULE_DEVICE_TABLE(of, cap11xx_dt_ids); static const struct i2c_device_id cap11xx_i2c_ids[] = { { .name = "cap1106", .driver_data = (kernel_ulong_t)&cap1106_model }, + { .name = "cap1114", .driver_data = (kernel_ulong_t)&cap1114_model }, { .name = "cap1126", .driver_data = (kernel_ulong_t)&cap1126_model }, { .name = "cap1188", .driver_data = (kernel_ulong_t)&cap1188_model }, { .name = "cap1203", .driver_data = (kernel_ulong_t)&cap1203_model }, From f373f18a3c43afaad15b2a91880dd65347ed760f Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Sat, 23 May 2026 11:45:34 +0200 Subject: [PATCH 16/61] dt-bindings: input: syna,rmi4: Document syna,rmi4-s3706b Mostly irrelevant for authentic Synaptics touchscreens, but very important for applying workarounds to cheap TS knockoffs. These knockoffs work well with the downstream driver, and since the user has no way to distinguish them, later in this patch set, we introduce workarounds to ensure they function as well as possible. Acked-by: Krzysztof Kozlowski Signed-off-by: David Heidelberg Link: https://patch.msgid.link/20260523-synaptics-rmi4-dt-v2-1-0645122babdc@ixit.cz Signed-off-by: Dmitry Torokhov --- .../devicetree/bindings/input/syna,rmi4.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/input/syna,rmi4.yaml b/Documentation/devicetree/bindings/input/syna,rmi4.yaml index 8685ef4481f4..fb4804ac3544 100644 --- a/Documentation/devicetree/bindings/input/syna,rmi4.yaml +++ b/Documentation/devicetree/bindings/input/syna,rmi4.yaml @@ -18,9 +18,14 @@ description: | properties: compatible: - enum: - - syna,rmi4-i2c - - syna,rmi4-spi + oneOf: + - enum: + - syna,rmi4-i2c + - syna,rmi4-spi + - items: + - enum: + - syna,rmi4-s3706b # OnePlus 6/6T + - const: syna,rmi4-i2c reg: maxItems: 1 From 7890fd28fd12b321ccd9e4fdeadbdf9c5ea1be7a Mon Sep 17 00:00:00 2001 From: Shashwat Agrawal Date: Fri, 26 Jun 2026 18:30:51 +0530 Subject: [PATCH 17/61] Input: synaptics - enable InterTouch on Dell Inspiron 3521 The Synaptics touchpad on Dell Inspiron 3521 (PNP ID DLL0597) advertises InterTouch / SMBus support, but is not on the SMBus passlist, so the driver falls back to PS/2 and logs a hint to try psmouse.synaptics_intertouch=1. Add DLL0597 to smbus_pnp_ids so InterTouch is enabled automatically on this model (and other Dells that reuse the same PNP ID). Hardware: Dell Inc. Inspiron 3521 (board 06RYX8, BIOS A07), Synaptics fw 8.1 / board id 2382, firmware_id "PNP: DLL0597 PNP0f13". Signed-off-by: Shashwat Agrawal Link: https://patch.msgid.link/20260626130051.2574-1-shashwatagrawal473@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/synaptics.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index c70502e24031..2170bbe4c589 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -164,6 +164,7 @@ static const char * const topbuttonpad_pnp_ids[] = { #ifdef CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS static const char * const smbus_pnp_ids[] = { /* all of the topbuttonpad_pnp_ids are valid, we just add some extras */ + "DLL0597", /* Dell Inspiron 3521 */ "DLL060d", /* Dell Precision M3800 */ "LEN0048", /* X1 Carbon 3 */ "LEN0046", /* X250 */ From 66788475a4e8ac6bd7882e81133d0c3f7c983498 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Wed, 17 Jun 2026 10:05:26 +0300 Subject: [PATCH 18/61] dt-bindings: input: Document Imagis ISA1200 haptic motor driver Document the Imagis ISA1200 haptic motor driver, used primarily in mobile handheld devices and capable of supporting up to two motors. The exact datasheet for the ISA1200 is not available; all data was modeled based on available downstream kernel sources for various devices and fragments of information scattered across the internet. Signed-off-by: Svyatoslav Ryhel Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260617070528.35006-2-clamor95@gmail.com Signed-off-by: Dmitry Torokhov --- .../bindings/input/imagis,isa1200.yaml | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/imagis,isa1200.yaml diff --git a/Documentation/devicetree/bindings/input/imagis,isa1200.yaml b/Documentation/devicetree/bindings/input/imagis,isa1200.yaml new file mode 100644 index 000000000000..4bc8630edcdd --- /dev/null +++ b/Documentation/devicetree/bindings/input/imagis,isa1200.yaml @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/imagis,isa1200.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Imagis ISA1200 haptic motor driver + +maintainers: + - Svyatoslav Ryhel + - Linus Walleij + +description: + The ISA1200 is a high-performance enhanced haptic motor driver designed + for mobile hand-held devices. It supports various voltages for both ERM + (Eccentric Rotating Mass) and LRA (Linear Resonant Actuator) type + actuators. Thanks to an embedded LDO, battery power can be used directly + in handheld applications. + +properties: + compatible: + const: imagis,isa1200 + + reg: + maxItems: 1 + + control-gpios: + description: + One or two GPIOs flagged as active high linked to HEN and LEN pins + minItems: 1 + maxItems: 2 + + clocks: + maxItems: 1 + + pwms: + maxItems: 1 + + vdd-supply: + description: + Regulator for 2.4V - 5.5V power supply + + vddp-supply: + description: + Regulator for 2.4V - 3.6V IO power supply + + imagis,clk-div: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Divider for the external input clock/PWM + enum: [128, 256, 512, 1024] + default: 128 + + imagis,pll-div: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Divider for the internal PLL clock + minimum: 1 + maximum: 15 + default: 1 + + imagis,mode: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Defines the motor type isa1200 drives + 0 - LRA (Linear Resonant Actuator) + 1 - ERM (Eccentric Rotating Mass) + enum: [0, 1] + default: 0 + + imagis,period-ns: + description: + Period of the internal PWM channel in nanoseconds. + minimum: 10000 + maximum: 30000 + + imagis,duty-cycle-ns: + description: + Duty cycle of the external/internal PWM channel in nanoseconds, + defaults to 50% of the channel's period + + ldo: + $ref: /schemas/regulator/regulator.yaml# + type: object + description: + Embedded LDO regulator with voltage range 2.3V - 3.8V + unevaluatedProperties: false + + required: + - regulator-min-microvolt + - regulator-max-microvolt + +required: + - compatible + - reg + - ldo + +oneOf: + - required: + - clocks + - imagis,period-ns + - required: + - pwms + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + haptic-engine@49 { + compatible = "imagis,isa1200"; + reg = <0x49>; + + clocks = <&isa1200_refclk>; + + control-gpios = <&gpio 22 GPIO_ACTIVE_HIGH>, + <&gpio 23 GPIO_ACTIVE_HIGH>; + + vdd-supply = <&vdd_3v3_vbat>; + vddp-supply = <&vdd_2v8_vvib>; + + imagis,clk-div = <256>; + imagis,pll-div = <2>; + + imagis,mode = <0>; /* LRA_MODE */ + + imagis,period-ns = <13400>; + imagis,duty-cycle-ns = <100>; + + ldo { + regulator-name = "vdd_vib"; + regulator-min-microvolt = <2300000>; + regulator-max-microvolt = <2300000>; + }; + }; + }; From 8fc62e1d7429e39ac0420dd457021b5fe809e90d Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 17 Jun 2026 10:05:27 +0300 Subject: [PATCH 19/61] Input: isa1200 - new driver for Imagis ISA1200 The ISA1200 is a haptic feedback unit from Imagis Technology using two motors for haptic feedback in mobile phones. Used in many mobile devices c. 2012 including Samsung Galxy S Advance GT-I9070 (Janice), Samsung Beam GT-I8350 (Gavini), LG Optimus 4X P880 and LG Optimus Vu P895. The exact datasheet for the ISA1200 is not available; all data was modeled based on available downstream kernel sources for various devices and fragments of information scattered across the internet. Tested-by: Linus Walleij # GT-I9070 Janice Signed-off-by: Linus Walleij Co-developed-by: Svyatoslav Ryhel Signed-off-by: Svyatoslav Ryhel Link: https://patch.msgid.link/20260617070528.35006-3-clamor95@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/misc/Kconfig | 12 + drivers/input/misc/Makefile | 1 + drivers/input/misc/isa1200.c | 533 +++++++++++++++++++++++++++++++++++ 3 files changed, 546 insertions(+) create mode 100644 drivers/input/misc/isa1200.c diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 1f6c57dba030..7154eaf5a60b 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -842,6 +842,18 @@ config INPUT_IQS7222 To compile this driver as a module, choose M here: the module will be called iqs7222. +config INPUT_ISA1200_HAPTIC + tristate "Imagis ISA1200 haptic feedback unit" + depends on I2C + select INPUT_FF_MEMLESS + select REGMAP_I2C + help + Say Y to enable support for the Imagis ISA1200 haptic + feedback unit. + + To compile this driver as a module, choose M here: the + module will be called isa1200. + config INPUT_CMA3000 tristate "VTI CMA3000 Tri-axis accelerometer" help diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 2281d6803fce..e9f85ca20c33 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o obj-$(CONFIG_INPUT_IQS269A) += iqs269a.o obj-$(CONFIG_INPUT_IQS626A) += iqs626a.o obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o +obj-$(CONFIG_INPUT_ISA1200_HAPTIC) += isa1200.o obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o diff --git a/drivers/input/misc/isa1200.c b/drivers/input/misc/isa1200.c new file mode 100644 index 000000000000..926cffcd38d6 --- /dev/null +++ b/drivers/input/misc/isa1200.c @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * System control (LDO regulator) + * + * LDO voltage to register mapping is linear, but it is split in two parts: + * 2.3V - 3.0V map to 0x08 - 0x0f; 3.1V - 3.8V map to 0x00 - 0x7 + */ + +#define ISA1200_SCTRL 0x00 +#define ISA1200_LDO_VOLTAGE_BASE 0x08 +#define ISA1200_LDO_VOLTAGE_STEP 100000 +#define ISA1200_LDO_VOLTAGE_2V3 23 +#define ISA1200_LDO_VOLTAGE_3V1 31 +#define ISA1200_LDO_VOLTAGE_MIN 2300000 +#define ISA1200_LDO_VOLTAGE_MAX 3800000 + +/* + * The output frequency is calculated with this formula: + * + * base clock frequency + * fout = ----------------------------------------- + * (128 - PWM_FREQ) * 2 * PLLDIV * PWM_PERIOD + * + * The base clock frequency is the clock frequency provided on the + * clock input to the chip, divided by the value in HCTRL0 + * + * PWM_FREQ is configured in register HCTRL4, it is common to set this + * to 0 to get only two variables to calculate. + * + * PLLDIV is configured in register HCTRL3 (bits 7..4, so 0..15) + * PWM_PERIOD is configured in register HCTRL6 + * Further the duty cycle can be configured in HCTRL5 + */ + +/* + * HCTRL0 configures clock or PWM input and selects the divider for + * the clock input. + */ +#define ISA1200_HCTRL0 0x30 +#define ISA1200_HCTRL0_HAP_ENABLE BIT(7) +#define ISA1200_HCTRL0_PWM_GEN_MODE BIT(4) +#define ISA1200_HCTRL0_PWM_INPUT_MODE BIT(3) +#define ISA1200_HCTRL0_CLKDIV_128 128 + +/* + * HCTRL1 configures the motor type and clock sourse + */ +#define ISA1200_HCTRL1 0x31 +#define ISA1200_HCTRL1_EXT_CLOCK BIT(7) +#define ISA1200_HCTRL1_DAC_INVERT BIT(6) +#define ISA1200_HCTRL1_MODE(n) (((n) & 1) << 5) + +/* HCTRL2 controls software reset of the chip */ +#define ISA1200_HCTRL2 0x32 +#define ISA1200_HCTRL2_SW_RESET BIT(0) + +/* + * HCTRL3 controls the PLL divisor + * + * Bits [0,1] are always set to 1 (we don't know what they are + * used for) and bit 4 and upward control the PLL divisor. + */ +#define ISA1200_HCTRL3 0x33 +#define ISA1200_HCTRL3_DEFAULT 0x03 +#define ISA1200_HCTRL3_PLLDIV(n) (((n) & 0xf) << 4) + +/* HCTRL4 controls the PWM frequency of external channel */ +#define ISA1200_HCTRL4 0x34 + +/* HCTRL5 controls the PWM high duty cycle of internal channel */ +#define ISA1200_HCTRL5 0x35 + +/* HCTRL6 controls the PWM period of internal channel */ +#define ISA1200_HCTRL6 0x36 +#define ISA1200_HCTRL6_PERIOD_SCALE 100 + +/* The use for these registers is unknown but they exist */ +#define ISA1200_HCTRL7 0x37 +#define ISA1200_HCTRL8 0x38 +#define ISA1200_HCTRL9 0x39 +#define ISA1200_HCTRLA 0x3a +#define ISA1200_HCTRLB 0x3b +#define ISA1200_HCTRLC 0x3c +#define ISA1200_HCTRLD 0x3d + +#define ISA1200_EN_PINS_MAX 2 + +static const struct regulator_bulk_data isa1200_supplies[] = { + { .supply = "vdd" }, { .supply = "vddp" }, +}; + +struct isa1200_config { + u32 ldo_voltage; + u32 mode; + u32 clkdiv; + u32 plldiv; + u32 freq; + u32 period; + u32 duty; +}; + +struct isa1200 { + struct input_dev *input; + struct regmap *map; + + struct clk *clk; + struct pwm_device *pwm; + struct gpio_descs *enable_gpios; + struct regulator_bulk_data *supplies; + + struct work_struct play_work; + struct isa1200_config config; + + int level; + bool suspended; + bool active; +}; + +static const struct regmap_config isa1200_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ISA1200_HCTRLD, +}; + +static void isa1200_start(struct isa1200 *isa) +{ + struct isa1200_config *config = &isa->config; + struct device *dev = &isa->input->dev; + struct pwm_state state; + u8 hctrl0 = 0, hctrl1 = 0; + DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX); + int err; + + if (!isa->active) { + err = regulator_bulk_enable(ARRAY_SIZE(isa1200_supplies), + isa->supplies); + if (err) { + dev_err(dev, "failed to enable supplies (%d)\n", err); + return; + } + + err = clk_prepare_enable(isa->clk); + if (err) { + dev_err(dev, "failed to enable clock (%d)\n", err); + regulator_bulk_disable(ARRAY_SIZE(isa1200_supplies), + isa->supplies); + return; + } + + bitmap_fill(values, ISA1200_EN_PINS_MAX); + gpiod_multi_set_value_cansleep(isa->enable_gpios, values); + + usleep_range(200, 300); + } + + regmap_write(isa->map, ISA1200_SCTRL, config->ldo_voltage); + + if (isa->clk) { + hctrl0 = ISA1200_HCTRL0_PWM_GEN_MODE; + hctrl1 = ISA1200_HCTRL1_EXT_CLOCK; + } + + if (isa->pwm) { + hctrl0 = ISA1200_HCTRL0_PWM_INPUT_MODE; + hctrl1 = 0; + } + + hctrl0 |= __ffs(config->clkdiv / ISA1200_HCTRL0_CLKDIV_128); + hctrl1 |= ISA1200_HCTRL1_DAC_INVERT; + hctrl1 |= ISA1200_HCTRL1_MODE(config->mode); + + regmap_write(isa->map, ISA1200_HCTRL0, hctrl0); + regmap_write(isa->map, ISA1200_HCTRL1, hctrl1); + + /* Make sure to de-assert software reset */ + regmap_write(isa->map, ISA1200_HCTRL2, 0x00); + + /* PLL divisor */ + regmap_write(isa->map, ISA1200_HCTRL3, + ISA1200_HCTRL3_PLLDIV(config->plldiv) | + ISA1200_HCTRL3_DEFAULT); + + /* Frequency */ + regmap_write(isa->map, ISA1200_HCTRL4, config->freq); + /* Duty cycle */ + regmap_write(isa->map, ISA1200_HCTRL5, config->period >> 1); + /* Period */ + regmap_write(isa->map, ISA1200_HCTRL6, config->period); + + hctrl0 |= ISA1200_HCTRL0_HAP_ENABLE; + regmap_write(isa->map, ISA1200_HCTRL0, hctrl0); + + if (isa->clk) + regmap_write(isa->map, ISA1200_HCTRL5, config->duty); + + if (isa->pwm) { + pwm_get_state(isa->pwm, &state); + state.duty_cycle = config->duty; + state.enabled = true; + pwm_apply_might_sleep(isa->pwm, &state); + } + + isa->active = true; +} + +static void isa1200_stop(struct isa1200 *isa) +{ + struct pwm_state state; + DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX); + + if (!isa->active) + return; + + if (isa->pwm) { + pwm_get_state(isa->pwm, &state); + state.duty_cycle = 0; + state.enabled = false; + pwm_apply_might_sleep(isa->pwm, &state); + } + + regmap_write(isa->map, ISA1200_HCTRL0, 0x00); + + bitmap_zero(values, ISA1200_EN_PINS_MAX); + gpiod_multi_set_value_cansleep(isa->enable_gpios, values); + + clk_disable_unprepare(isa->clk); + regulator_bulk_disable(ARRAY_SIZE(isa1200_supplies), + isa->supplies); + + isa->active = false; +} + +static void isa1200_play_work(struct work_struct *work) +{ + struct isa1200 *isa = container_of(work, struct isa1200, play_work); + + if (!READ_ONCE(isa->suspended)) { + if (isa->level) + isa1200_start(isa); + else + isa1200_stop(isa); + } +} + +static int isa1200_vibrator_play_effect(struct input_dev *input, void *data, + struct ff_effect *effect) +{ + struct isa1200 *isa = input_get_drvdata(input); + int level; + + /* + * TODO: we currently only support rumble. + * The ISA1200 can control two motors and some devices + * also have two motors mounted. + */ + level = effect->u.rumble.strong_magnitude; + if (!level) + level = effect->u.rumble.weak_magnitude; + + dev_dbg(&input->dev, "FF effect type %d level %d\n", + effect->type, level); + + if (isa->level != level) { + isa->level = level; + if (!READ_ONCE(isa->suspended)) + schedule_work(&isa->play_work); + } + + return 0; +} + +static void isa1200_vibrator_close(struct input_dev *input) +{ + struct isa1200 *isa = input_get_drvdata(input); + + cancel_work_sync(&isa->play_work); + isa1200_stop(isa); + isa->level = 0; +} + +static int isa1200_of_probe(struct i2c_client *client) +{ + struct isa1200 *isa = i2c_get_clientdata(client); + struct isa1200_config *config = &isa->config; + struct device *dev = &client->dev; + struct fwnode_handle *ldo_node; + int err; + + isa->clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(isa->clk)) + return dev_err_probe(dev, PTR_ERR(isa->clk), + "failed to get clock\n"); + + isa->pwm = devm_pwm_get(dev, NULL); + if (IS_ERR(isa->pwm)) { + err = PTR_ERR(isa->pwm); + if (err == -ENODEV || err == -EINVAL) + isa->pwm = NULL; + else + return dev_err_probe(dev, err, "getting PWM\n"); + } + + if (!isa->clk && !isa->pwm) + return dev_err_probe(dev, -EINVAL, + "clock or PWM are required, none were provided\n"); + + err = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(isa1200_supplies), + isa1200_supplies, &isa->supplies); + if (err) + return dev_err_probe(dev, err, "failed to get supplies\n"); + + isa->enable_gpios = devm_gpiod_get_array_optional(dev, "control", + GPIOD_OUT_LOW); + if (IS_ERR(isa->enable_gpios)) + return dev_err_probe(dev, PTR_ERR(isa->enable_gpios), + "failed to get enable gpios\n"); + + if (isa->enable_gpios && isa->enable_gpios->ndescs > ISA1200_EN_PINS_MAX) + return dev_err_probe(dev, -EINVAL, "too many enable gpios\n"); + + ldo_node = device_get_named_child_node(dev, "ldo"); + if (!ldo_node) + return dev_err_probe(dev, -ENODEV, + "failed to get embedded LDO node\n"); + + err = fwnode_property_read_u32(ldo_node, "regulator-min-microvolt", + &config->ldo_voltage); + fwnode_handle_put(ldo_node); + if (err) + return dev_err_probe(dev, err, + "failed to get ldo voltage\n"); + + config->ldo_voltage = clamp(config->ldo_voltage, + ISA1200_LDO_VOLTAGE_MIN, + ISA1200_LDO_VOLTAGE_MAX); + + config->ldo_voltage /= ISA1200_LDO_VOLTAGE_STEP; + if (config->ldo_voltage < ISA1200_LDO_VOLTAGE_3V1) + config->ldo_voltage = config->ldo_voltage - + ISA1200_LDO_VOLTAGE_2V3 + + ISA1200_LDO_VOLTAGE_BASE; + else + config->ldo_voltage -= ISA1200_LDO_VOLTAGE_3V1; + + config->mode = 0; /* LRA_MODE */ + device_property_read_u32(dev, "imagis,mode", &config->mode); + + config->clkdiv = ISA1200_HCTRL0_CLKDIV_128; + device_property_read_u32(dev, "imagis,clk-div", &config->clkdiv); + if (!config->clkdiv) + return dev_err_probe(dev, -EINVAL, "clk-div cannot be zero\n"); + + config->clkdiv = clamp(config->clkdiv, ISA1200_HCTRL0_CLKDIV_128, + ISA1200_HCTRL0_CLKDIV_128 << 3); + + err = device_property_read_u32(dev, "imagis,pll-div", &config->plldiv); + if (err || !config->plldiv) + config->plldiv = 1; + + config->period = 0; + config->freq = 0; + config->duty = 0; + + if (isa->clk) { + err = device_property_read_u32(dev, "imagis,period-ns", + &config->period); + if (err) + return dev_err_probe(dev, err, + "failed to get period\n"); + + /* + * TODO: The scale value is arbitrary, but it fits observations + * quite well, and the exact conversion method is unknown. + * The period property value returned above is the HCTRL6 + * register value set by the vendor code, multiplied by 100. + */ + config->period /= ISA1200_HCTRL6_PERIOD_SCALE; + config->duty = config->period >> 1; + } + + if (isa->pwm) { + struct pwm_state state; + + pwm_init_state(isa->pwm, &state); + + if (!state.period) + return dev_err_probe(dev, -EINVAL, + "PWM period cannot be zero\n"); + + config->freq = div64_u64(NANO, state.period * config->clkdiv); + config->duty = state.period >> 1; + + err = pwm_apply_might_sleep(isa->pwm, &state); + if (err) + return dev_err_probe(dev, err, + "failed to apply initial PWM state\n"); + } + + /* + * TODO: If device is using a clock, this property should return the + * value written to the HCTRL5 register by downstrem code. It likely + * needs to be converted into a meaningful duty cycle value, though + * unfortunately the exact conversion mechanism is unknown. If the + * device uses PWM, this property will return the correct duty cycle + * in nanoseconds. + */ + device_property_read_u32(dev, "imagis,duty-cycle-ns", &config->duty); + + return 0; +} + +static int isa1200_probe(struct i2c_client *client) +{ + struct isa1200 *isa; + struct device *dev = &client->dev; + int err; + + isa = devm_kzalloc(dev, sizeof(*isa), GFP_KERNEL); + if (!isa) + return -ENOMEM; + + isa->input = devm_input_allocate_device(dev); + if (!isa->input) + return -ENOMEM; + + i2c_set_clientdata(client, isa); + + err = isa1200_of_probe(client); + if (err) + return err; + + isa->map = devm_regmap_init_i2c(client, &isa1200_regmap_config); + if (IS_ERR(isa->map)) + return dev_err_probe(dev, PTR_ERR(isa->map), + "failed to initialize register map\n"); + + INIT_WORK(&isa->play_work, isa1200_play_work); + + isa->input->name = "isa1200-haptic"; + isa->input->id.bustype = BUS_I2C; + isa->input->close = isa1200_vibrator_close; + + isa->active = false; + + input_set_drvdata(isa->input, isa); + + /* TODO: this hardware can likely support more than rumble */ + input_set_capability(isa->input, EV_FF, FF_RUMBLE); + + err = input_ff_create_memless(isa->input, NULL, + isa1200_vibrator_play_effect); + if (err) + return dev_err_probe(dev, err, "failed to create FF dev\n"); + + err = input_register_device(isa->input); + if (err) + return dev_err_probe(dev, err, "failed to register input dev\n"); + + return 0; +} + +static int isa1200_suspend(struct device *dev) +{ + struct isa1200 *isa = dev_get_drvdata(dev); + + guard(mutex)(&isa->input->mutex); + + if (input_device_enabled(isa->input)) { + WRITE_ONCE(isa->suspended, true); + cancel_work_sync(&isa->play_work); + isa1200_stop(isa); + } + + return 0; +} + +static int isa1200_resume(struct device *dev) +{ + struct isa1200 *isa = dev_get_drvdata(dev); + + guard(mutex)(&isa->input->mutex); + + if (input_device_enabled(isa->input)) { + WRITE_ONCE(isa->suspended, false); + if (isa->level) + schedule_work(&isa->play_work); + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(isa1200_pm_ops, isa1200_suspend, isa1200_resume); + +static const struct of_device_id isa1200_of_match[] = { + { .compatible = "imagis,isa1200" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, isa1200_of_match); + +static struct i2c_driver isa1200_i2c_driver = { + .driver = { + .name = "isa1200", + .of_match_table = isa1200_of_match, + .pm = pm_sleep_ptr(&isa1200_pm_ops), + }, + .probe = isa1200_probe, +}; +module_i2c_driver(isa1200_i2c_driver); + +MODULE_AUTHOR("Linus Walleij "); +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("Imagis ISA1200 haptic feedback unit"); +MODULE_LICENSE("GPL"); From c8f174900926d3b58cd048ac33b4cbb3de419bfe Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Jun 2026 00:08:47 +0100 Subject: [PATCH 20/61] Input: sur40 - fix MAX_CONTACTS value based on PixelSense specification The Samsung SUR40 with Microsoft PixelSense is offically specified to support 52 simultaneuous touch contacts, not 64. The value of 64 was an unverified guess as noted by the FIXME comment. Update MAX_CONTACTS to match the documented hardware specification and remove the FIXME. Signed-off-by: Oliver Link: https://patch.msgid.link/20260614230847.4938-1-oliverburns.kernel@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/sur40.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/input/touchscreen/sur40.c b/drivers/input/touchscreen/sur40.c index fe63d53d56db..77ec2c94b91f 100644 --- a/drivers/input/touchscreen/sur40.c +++ b/drivers/input/touchscreen/sur40.c @@ -128,8 +128,8 @@ struct sur40_image_header { /* polling interval (ms) */ #define POLL_INTERVAL 1 -/* maximum number of contacts FIXME: this is a guess? */ -#define MAX_CONTACTS 64 +/* maximum number of contacts */ +#define MAX_CONTACTS 52 /* control commands */ #define SUR40_GET_VERSION 0xb0 /* 12 bytes string */ From 136be950466b3ccf6c2256789db787259190f059 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 16:24:57 -0700 Subject: [PATCH 21/61] Input: ims-pcu - add missing MODULE_DEVICE_TABLE() The driver has a match table for the usb bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260704151730.42772-1-pengpeng@iscas.ac.cn Signed-off-by: Dmitry Torokhov --- drivers/input/misc/ims-pcu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c index b1ff8c70877f..b1a0edcc49b4 100644 --- a/drivers/input/misc/ims-pcu.c +++ b/drivers/input/misc/ims-pcu.c @@ -2234,6 +2234,7 @@ static const struct usb_device_id ims_pcu_id_table[] = { }, { } }; +MODULE_DEVICE_TABLE(usb, ims_pcu_id_table); static const struct attribute_group *ims_pcu_sysfs_groups[] = { &ims_pcu_attr_group, From 8760a10464be92bd8428f62f91a5d77eebe2ecdb Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 10 Jul 2026 23:19:54 +0200 Subject: [PATCH 22/61] Input: matrix_keyboard - remove linux/gpio.h inclusion linux/gpio.h is going away, so remove that since the driver already includes linux/gpio/consumer.h. Acked-by: Bartosz Golaszewski Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260710211954.1373336-10-arnd@kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/matrix_keypad.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/input/keyboard/matrix_keypad.c b/drivers/input/keyboard/matrix_keypad.c index e50a6fea9a60..8863b741d1a3 100644 --- a/drivers/input/keyboard/matrix_keypad.c +++ b/drivers/input/keyboard/matrix_keypad.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include From 72870cd641edd69b520997c3232d765085a460f2 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Fri, 10 Jul 2026 11:01:53 +0200 Subject: [PATCH 23/61] dt-bindings: input: gpio-charlieplex-keypad: add missing parenthesis Add missing '('. Signed-off-by: Manuel Ebner Acked-by: Hugo Villeneuve Link: https://patch.msgid.link/20260710090153.431170-2-manuelebner@mailbox.org Signed-off-by: Dmitry Torokhov --- .../devicetree/bindings/input/gpio-charlieplex-keypad.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/input/gpio-charlieplex-keypad.yaml b/Documentation/devicetree/bindings/input/gpio-charlieplex-keypad.yaml index c085de6dab85..c6842c017934 100644 --- a/Documentation/devicetree/bindings/input/gpio-charlieplex-keypad.yaml +++ b/Documentation/devicetree/bindings/input/gpio-charlieplex-keypad.yaml @@ -11,7 +11,7 @@ maintainers: - Hugo Villeneuve description: | - The charlieplex keypad supports N^2)-N different key combinations (where N is + The charlieplex keypad supports (N^2)-N different key combinations (where N is the number of I/O lines). Key presses and releases are detected by configuring only one line as output at a time, and reading other line states. This process is repeated for each line. Diodes are required to ensure current flows in only From 79d7a453c79e818b8059b1aa6d6a7ad53d9d4f72 Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Tue, 7 Jul 2026 01:04:19 -0500 Subject: [PATCH 24/61] Input: i8042 - replace strlcat() with seq_buf and scnprintf() In preparation for removing the strlcat() API[1], replace its uses in i8042-acpipnpio.h. i8042_pnp_id_to_string() accumulates a variable number of PNP ids in a loop, which is what seq_buf is for. The kbd and aux probe functions build a name from at most three parts that are all known up front, so the whole construction becomes a single scnprintf() there. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/akyW4xkvCCROM0SE@dev Signed-off-by: Dmitry Torokhov --- drivers/input/serio/i8042-acpipnpio.h | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h index 412f82d7a303..9ecb0eed48c4 100644 --- a/drivers/input/serio/i8042-acpipnpio.h +++ b/drivers/input/serio/i8042-acpipnpio.h @@ -3,6 +3,7 @@ #define _I8042_ACPIPNPIO_H #include +#include #ifdef CONFIG_X86 #include @@ -1479,17 +1480,21 @@ static char i8042_pnp_aux_name[32]; static void i8042_pnp_id_to_string(struct pnp_id *id, char *dst, int dst_size) { - strscpy(dst, "PNP:", dst_size); + struct seq_buf sb; + + seq_buf_init(&sb, dst, dst_size); + seq_buf_printf(&sb, "PNP:"); while (id) { - strlcat(dst, " ", dst_size); - strlcat(dst, id->id, dst_size); + seq_buf_printf(&sb, " %s", id->id); id = id->next; } } static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did) { + const char *name = pnp_dev_name(dev); + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) i8042_pnp_data_reg = pnp_port_start(dev,0); @@ -1499,11 +1504,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id * if (pnp_irq_valid(dev,0)) i8042_pnp_kbd_irq = pnp_irq(dev, 0); - strscpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name)); - if (strlen(pnp_dev_name(dev))) { - strlcat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name)); - strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name)); - } + scnprintf(i8042_pnp_kbd_name, sizeof(i8042_pnp_kbd_name), "%s%s%s", + did->id, strlen(name) ? ":" : "", name); i8042_pnp_id_to_string(dev->id, i8042_kbd_firmware_id, sizeof(i8042_kbd_firmware_id)); i8042_kbd_fwnode = dev_fwnode(&dev->dev); @@ -1517,6 +1519,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id * static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *did) { + const char *name = pnp_dev_name(dev); + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) i8042_pnp_data_reg = pnp_port_start(dev,0); @@ -1526,11 +1530,8 @@ static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id * if (pnp_irq_valid(dev, 0)) i8042_pnp_aux_irq = pnp_irq(dev, 0); - strscpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name)); - if (strlen(pnp_dev_name(dev))) { - strlcat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name)); - strlcat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name)); - } + scnprintf(i8042_pnp_aux_name, sizeof(i8042_pnp_aux_name), "%s%s%s", + did->id, strlen(name) ? ":" : "", name); i8042_pnp_id_to_string(dev->id, i8042_aux_firmware_id, sizeof(i8042_aux_firmware_id)); From 72fe16c61b1576176fddda0f84e04fc891f841ea Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 3 Jul 2026 23:01:13 -0700 Subject: [PATCH 25/61] Input: mms114 - fix endianness portability in I2C packet layout The driver defines the I2C packet layout using C bitfields in struct mms114_touch. This is not portable as the layout of bitfields within a byte is compiler-dependent and varies with endianness. On Big Endian systems, the fields will be parsed incorrectly. Fix this by redefining struct mms114_touch with plain u8 fields and introducing bitwise macros to extract the values portably. Reported-by: sashiko-bot@kernel.org Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260704060115.353049-2-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/mms114.c | 52 +++++++++++++++++++----------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c index 23e0283bc6b8..84afdadb3bcc 100644 --- a/drivers/input/touchscreen/mms114.c +++ b/drivers/input/touchscreen/mms114.c @@ -4,6 +4,8 @@ // Copyright (c) 2012 Samsung Electronics Co., Ltd. // Author: Joonyoung Shim +#include +#include #include #include #include @@ -76,9 +78,16 @@ struct mms_chip { int (*get_version)(struct mms114_data *data); }; +#define MMS114_FLAGS_ID_MASK GENMASK(3, 0) +#define MMS114_FLAGS_TYPE_MASK GENMASK(6, 5) +#define MMS114_FLAGS_PRESSED_MASK BIT(7) + +#define MMS114_XY_HI_X_MASK GENMASK(3, 0) +#define MMS114_XY_HI_Y_MASK GENMASK(7, 4) + struct mms114_touch { - u8 id:4, reserved_bit4:1, type:2, pressed:1; - u8 x_hi:4, y_hi:4; + u8 flags; + u8 xy_hi; u8 x_lo; u8 y_lo; u8 width; @@ -244,28 +253,30 @@ static void mms114_process_mt(struct mms114_data *data, struct mms114_touch *tou { struct i2c_client *client = data->client; struct input_dev *input_dev = data->input_dev; - unsigned int id; + unsigned int id = FIELD_GET(MMS114_FLAGS_ID_MASK, touch->flags); + unsigned int type = FIELD_GET(MMS114_FLAGS_TYPE_MASK, touch->flags); + bool pressed = FIELD_GET(MMS114_FLAGS_PRESSED_MASK, touch->flags); unsigned int x; unsigned int y; - if (touch->id == 0 || touch->id > MMS114_MAX_TOUCH) { - dev_err(&client->dev, "Wrong touch id (%d)\n", touch->id); + if (id == 0 || id > MMS114_MAX_TOUCH) { + dev_err(&client->dev, "Wrong touch id (%d)\n", id); return; } - id = touch->id - 1; - x = touch->x_lo | touch->x_hi << 8; - y = touch->y_lo | touch->y_hi << 8; + id--; + x = touch->x_lo | FIELD_GET(MMS114_XY_HI_X_MASK, touch->xy_hi) << 8; + y = touch->y_lo | FIELD_GET(MMS114_XY_HI_Y_MASK, touch->xy_hi) << 8; dev_dbg(&client->dev, "id: %d, type: %d, pressed: %d, x: %d, y: %d, width: %d, strength: %d\n", - id, touch->type, touch->pressed, + id, type, pressed, x, y, touch->width, touch->strength); input_mt_slot(input_dev, id); - input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, touch->pressed); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, pressed); - if (touch->pressed) { + if (pressed) { touchscreen_report_pos(input_dev, &data->props, x, y, true); input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, touch->width); input_report_abs(input_dev, ABS_MT_PRESSURE, touch->strength); @@ -278,21 +289,23 @@ static void mms114_process_touchkey(struct mms114_data *data, struct i2c_client *client = data->client; struct input_dev *input_dev = data->input_dev; unsigned int keycode_id; + unsigned int id = FIELD_GET(MMS114_FLAGS_ID_MASK, touch->flags); + bool pressed = FIELD_GET(MMS114_FLAGS_PRESSED_MASK, touch->flags); - if (touch->id == 0) + if (id == 0) return; - if (touch->id > data->num_keycodes) { + if (id > data->num_keycodes) { dev_err(&client->dev, "Wrong touch id for touchkey (%d)\n", - touch->id); + id); return; } - keycode_id = touch->id - 1; + keycode_id = id - 1; dev_dbg(&client->dev, "keycode id: %d, pressed: %d\n", keycode_id, - touch->pressed); + pressed); - input_report_key(input_dev, data->keycodes[keycode_id], touch->pressed); + input_report_key(input_dev, data->keycodes[keycode_id], pressed); } static irqreturn_t mms114_interrupt(int irq, void *dev_id) @@ -325,8 +338,9 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) for (index = 0; index < touch_size; index++) { t = (struct mms114_touch *)((u8 *)touch + index * event_size); + unsigned int type = FIELD_GET(MMS114_FLAGS_TYPE_MASK, t->flags); - switch (t->type) { + switch (type) { case MMS114_TYPE_TOUCHSCREEN: mms114_process_mt(data, t); break; @@ -337,7 +351,7 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) default: dev_err(&client->dev, "Wrong touch type (%d)\n", - t->type); + type); break; } } From c31398588d295a77e6b5b62d9a59caafffc797e4 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 3 Jul 2026 23:01:14 -0700 Subject: [PATCH 26/61] Input: mms114 - fix Y-resolution configuration In mms114_setup_regs(), the driver mistakenly uses props->max_x instead of props->max_y when configuring the low bits of the Y resolution (MMS114_Y_RESOLUTION). Fix this by using the correct property. Fixes: 07b8481d4aff ("Input: add MELFAS mms114 touchscreen driver") Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260704060115.353049-3-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/mms114.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c index 84afdadb3bcc..27911a9f4e9e 100644 --- a/drivers/input/touchscreen/mms114.c +++ b/drivers/input/touchscreen/mms114.c @@ -408,7 +408,7 @@ static int mms114_setup_regs(struct mms114_data *data) if (error < 0) return error; - val = props->max_x & 0xff; + val = props->max_y & 0xff; error = mms114_write_reg(data, MMS114_Y_RESOLUTION, val); if (error < 0) return error; From 1cd52a99c868b19b419485d2ae9b76ac3e717c2c Mon Sep 17 00:00:00 2001 From: Zhian Liang Date: Wed, 15 Jul 2026 13:51:35 -0700 Subject: [PATCH 27/61] Input: tca8418_keypad - enable overflow mode per datasheet (SCPS215G) The driver currently sets only the overflow interrupt enable bit (OVR_FLOW_IEN) in the configuration register, leaving the overflow mode bit (OVR_FLOW_M) at its default value of 0. According to the TCA8418 datasheet (SCPS215G, Section 8.6.4.1 "Overflow Errata - Description"), both OVR_FLOW_M (Bit_5) and OVR_FLOW_IEN (Bit_3) must be set high for the overflow interrupt to be generated. If only OVR_FLOW_IEN is set, FIFO overflow events are silently lost without notifying the host. Fix this by setting OVR_FLOW_M alongside OVR_FLOW_IEN in the configuration register. Signed-off-by: Zhian Liang Link: https://patch.msgid.link/20260529013900.43854-1-liangzhan5dev@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/tca8418_keypad.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/input/keyboard/tca8418_keypad.c b/drivers/input/keyboard/tca8418_keypad.c index eb5be644f236..4616afa0286c 100644 --- a/drivers/input/keyboard/tca8418_keypad.c +++ b/drivers/input/keyboard/tca8418_keypad.c @@ -254,7 +254,8 @@ static int tca8418_configure(struct tca8418_keypad *keypad_data, return error; error = tca8418_write_byte(keypad_data, REG_CFG, - CFG_INT_CFG | CFG_OVR_FLOW_IEN | CFG_KE_IEN); + CFG_INT_CFG | CFG_OVR_FLOW_IEN | + CFG_OVR_FLOW_M | CFG_KE_IEN); return error; } From 3ed4ba919eb35860ee1867e2851ee3e6f9fe37f5 Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Tue, 14 Jul 2026 20:22:41 -0500 Subject: [PATCH 28/61] Input: wacom_w8001 - replace strlcat() with a strscpy() helper In preparation for removing the strlcat() API[1], replace its five uses with a small append helper built on strnlen() and strscpy(). The five calls append device name fragments to a basename buffer that grows in place across the setup functions. The helper takes the same arguments as strlcat() and writes the same bytes, including when a fragment is truncated. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Link: https://patch.msgid.link/albg4Rv7QxvLJD05@dev Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/wacom_w8001.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/input/touchscreen/wacom_w8001.c b/drivers/input/touchscreen/wacom_w8001.c index 45930d731873..d8d1cdc3f09e 100644 --- a/drivers/input/touchscreen/wacom_w8001.c +++ b/drivers/input/touchscreen/wacom_w8001.c @@ -417,6 +417,13 @@ static int w8001_detect(struct w8001 *w8001) return 0; } +static void w8001_append_suffix(char *dest, const char *suffix, size_t dest_sz) +{ + size_t used = strnlen(dest, dest_sz); + + strscpy(dest + used, suffix, dest_sz - used); +} + static int w8001_setup_pen(struct w8001 *w8001, char *basename, size_t basename_sz) { @@ -453,7 +460,7 @@ static int w8001_setup_pen(struct w8001 *w8001, char *basename, } w8001->id = 0x90; - strlcat(basename, " Penabled", basename_sz); + w8001_append_suffix(basename, " Penabled", basename_sz); return 0; } @@ -503,14 +510,14 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, case 2: w8001->pktlen = W8001_PKTLEN_TOUCH93; w8001->id = 0x93; - strlcat(basename, " 1FG", basename_sz); + w8001_append_suffix(basename, " 1FG", basename_sz); break; case 1: case 3: case 4: w8001->pktlen = W8001_PKTLEN_TOUCH9A; - strlcat(basename, " 1FG", basename_sz); + w8001_append_suffix(basename, " 1FG", basename_sz); w8001->id = 0x9a; break; @@ -534,7 +541,7 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, input_abs_set_res(dev, ABS_MT_POSITION_X, touch.panel_res); input_abs_set_res(dev, ABS_MT_POSITION_Y, touch.panel_res); - strlcat(basename, " 2FG", basename_sz); + w8001_append_suffix(basename, " 2FG", basename_sz); if (w8001->max_pen_x && w8001->max_pen_y) w8001->id = 0xE3; else @@ -542,7 +549,7 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, break; } - strlcat(basename, " Touchscreen", basename_sz); + w8001_append_suffix(basename, " Touchscreen", basename_sz); return 0; } From 13b3dce7dab5cf515cbf43c1d9305269e891c5f4 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 11 Jun 2026 08:48:25 -0700 Subject: [PATCH 29/61] mfd: rohm-bd71828: Use software nodes for gpio-keys Refactor the rohm-bd71828 MFD driver to use software nodes for instantiating the gpio-keys child device, replacing the old platform_data mechanism. The power key's properties are now defined using software nodes and property entries. The IRQ is passed as a resource attached to the platform device. This will allow dropping support for using platform data for configuring gpio-keys in the future. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260611-rohm-software-nodes-v5-1-0244664a3b65@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/rohm-bd71828.c | 125 +++++++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 32 deletions(-) diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c index a79f354bf5cb..5fb6142cf087 100644 --- a/drivers/mfd/rohm-bd71828.c +++ b/drivers/mfd/rohm-bd71828.c @@ -5,7 +5,8 @@ * ROHM BD718[15/28/79] and BD72720 PMIC driver */ -#include +#include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include #include #include @@ -37,19 +39,6 @@ }, \ } -static struct gpio_keys_button button = { - .code = KEY_POWER, - .gpio = -1, - .type = EV_KEY, - .wakeup = 1, -}; - -static const struct gpio_keys_platform_data bd71828_powerkey_data = { - .buttons = &button, - .nbuttons = 1, - .name = "bd71828-pwrkey", -}; - static const struct resource bd71815_rtc_irqs[] = { DEFINE_RES_IRQ_NAMED(BD71815_INT_RTC0, "bd70528-rtc-alm-0"), DEFINE_RES_IRQ_NAMED(BD71815_INT_RTC1, "bd70528-rtc-alm-1"), @@ -174,10 +163,6 @@ static struct mfd_cell bd71828_mfd_cells[] = { .name = "bd71828-rtc", .resources = bd71828_rtc_irqs, .num_resources = ARRAY_SIZE(bd71828_rtc_irqs), - }, { - .name = "gpio-keys", - .platform_data = &bd71828_powerkey_data, - .pdata_size = sizeof(bd71828_powerkey_data), }, }; @@ -242,11 +227,8 @@ static const struct mfd_cell bd72720_mfd_cells[] = { .name = "bd72720-rtc", .resources = bd72720_rtc_irqs, .num_resources = ARRAY_SIZE(bd72720_rtc_irqs), - }, { - .name = "gpio-keys", - .platform_data = &bd71828_powerkey_data, - .pdata_size = sizeof(bd71828_powerkey_data), }, + /* Power button is registered separately */ }; static const struct regmap_range bd71815_volatile_ranges[] = { @@ -877,6 +859,84 @@ static int set_clk_mode(struct device *dev, struct regmap *regmap, OUT32K_MODE_CMOS); } +static const struct property_entry bd71828_powerkey_parent_props[] = { + PROPERTY_ENTRY_STRING("label", "bd71828-pwrkey"), + { } +}; + +static const struct property_entry bd71828_powerkey_props[] = { + PROPERTY_ENTRY_U32("linux,code", KEY_POWER), + PROPERTY_ENTRY_BOOL("wakeup-source"), + { } +}; + +#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ +#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ + +static int bd71828_i2c_register_swnodes(const struct software_node *nodes) +{ + const struct software_node * const node_group[] = { + &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL + }; + + return software_node_register_node_group(node_group); +} + +static void bd71828_i2c_unregister_swnodes(void *data) +{ + const struct software_node *nodes = data; + const struct software_node * const node_group[] = { + &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL + }; + + software_node_unregister_node_group(node_group); +} + +static int bd71828_i2c_register_pwrbutton(struct device *dev, int button_irq, + struct irq_domain *irq_domain) +{ + const struct resource res[] = { + DEFINE_RES_IRQ_NAMED(button_irq, "bd71828-pwrkey"), + }; + struct mfd_cell gpio_keys_cell = { + .name = "gpio-keys", + .resources = res, + .num_resources = ARRAY_SIZE(res), + }; + struct software_node *nodes; + int ret; + + nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); + if (!nodes) + return -ENOMEM; + + nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); + if (!nodes[GPIO_KEYS].name) + return -ENOMEM; + + nodes[GPIO_KEYS].properties = bd71828_powerkey_parent_props; + + nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; + nodes[PWRON_KEY].properties = bd71828_powerkey_props; + + ret = bd71828_i2c_register_swnodes(nodes); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, bd71828_i2c_unregister_swnodes, nodes); + if (ret) + return ret; + + gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; + + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, + NULL, 0, irq_domain); + if (ret) + return dev_err_probe(dev, ret, "Failed to register power-button"); + + return 0; +} + static struct i2c_client *bd71828_dev; static void bd71828_power_off(void) { @@ -929,6 +989,7 @@ static struct regmap *bd72720_do_regmaps(struct i2c_client *i2c) static int bd71828_i2c_probe(struct i2c_client *i2c) { struct regmap_irq_chip_data *irq_data; + struct irq_domain *irq_domain; int ret; struct regmap *regmap = NULL; const struct regmap_config *regmap_config; @@ -1022,23 +1083,23 @@ static int bd71828_i2c_probe(struct i2c_client *i2c) "Failed to enable main level IRQs\n"); } } - if (button_irq) { - ret = regmap_irq_get_virq(irq_data, button_irq); - if (ret < 0) - return dev_err_probe(&i2c->dev, ret, - "Failed to get the power-key IRQ\n"); - - button.irq = ret; - } ret = set_clk_mode(&i2c->dev, regmap, clkmode_reg); if (ret) return ret; + irq_domain = regmap_irq_get_domain(irq_data); + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, mfd, cells, - NULL, 0, regmap_irq_get_domain(irq_data)); + NULL, 0, irq_domain); if (ret) - return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); + return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); + + if (button_irq) { + ret = bd71828_i2c_register_pwrbutton(&i2c->dev, button_irq, irq_domain); + if (ret) + return ret; + } if (of_device_is_system_power_controller(i2c->dev.of_node) && chip_type == ROHM_CHIP_TYPE_BD71828) { From 6c0c972cacb5e78df02d44b5e89ed40779d26bba Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 11 Jun 2026 08:48:26 -0700 Subject: [PATCH 30/61] mfd: rohm-bd718x7: Use software nodes for gpio-keys Refactor the rohm-bd7182x7 MFD driver to use software nodes for instantiating the gpio-keys child device, replacing the old platform_data mechanism. The power key's properties are now defined using software nodes and property entries. The IRQ is passed as a resource attached to the platform device. This will allow dropping support for using platform data for configuring gpio-keys in the future. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260611-rohm-software-nodes-v5-2-0244664a3b65@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/rohm-bd718x7.c | 123 +++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 33 deletions(-) diff --git a/drivers/mfd/rohm-bd718x7.c b/drivers/mfd/rohm-bd718x7.c index ff714fd4f54d..be2acc429fe3 100644 --- a/drivers/mfd/rohm-bd718x7.c +++ b/drivers/mfd/rohm-bd718x7.c @@ -7,7 +7,8 @@ // Datasheet for BD71837MWV available from // https://www.rohm.com/datasheet/BD71837MWV/bd71837mwv-e -#include +#include +#include #include #include #include @@ -15,37 +16,16 @@ #include #include #include +#include #include #include -static struct gpio_keys_button button = { - .code = KEY_POWER, - .gpio = -1, - .type = EV_KEY, -}; - -static struct gpio_keys_platform_data bd718xx_powerkey_data = { - .buttons = &button, - .nbuttons = 1, - .name = "bd718xx-pwrkey", -}; - static struct mfd_cell bd71837_mfd_cells[] = { - { - .name = "gpio-keys", - .platform_data = &bd718xx_powerkey_data, - .pdata_size = sizeof(bd718xx_powerkey_data), - }, { .name = "bd71837-clk", }, { .name = "bd71837-pmic", }, }; static struct mfd_cell bd71847_mfd_cells[] = { - { - .name = "gpio-keys", - .platform_data = &bd718xx_powerkey_data, - .pdata_size = sizeof(bd718xx_powerkey_data), - }, { .name = "bd71847-clk", }, { .name = "bd71847-pmic", }, }; @@ -125,10 +105,89 @@ static int bd718xx_init_press_duration(struct regmap *regmap, return 0; } +static const struct property_entry bd718xx_powerkey_parent_props[] = { + PROPERTY_ENTRY_STRING("label", "bd718xx-pwrkey"), + { } +}; + +static const struct property_entry bd718xx_powerkey_props[] = { + PROPERTY_ENTRY_U32("linux,code", KEY_POWER), + { } +}; + +static const struct resource bd718xx_powerkey_resources[] = { + DEFINE_RES_IRQ_NAMED(BD718XX_INT_PWRBTN_S, "bd718xx-pwrkey"), +}; + +#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ +#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ + +static int bd718xx_i2c_register_swnodes(const struct software_node *nodes) +{ + const struct software_node * const node_group[] = { + &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL + }; + + return software_node_register_node_group(node_group); +} + +static void bd718xx_i2c_unregister_swnodes(void *data) +{ + const struct software_node *nodes = data; + const struct software_node * const node_group[] = { + &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL + }; + + software_node_unregister_node_group(node_group); +} + +static int bd718xx_i2c_register_pwrbutton(struct device *dev, + struct irq_domain *irq_domain) +{ + struct mfd_cell gpio_keys_cell = { + .name = "gpio-keys", + .resources = bd718xx_powerkey_resources, + .num_resources = ARRAY_SIZE(bd718xx_powerkey_resources), + }; + struct software_node *nodes; + int ret; + + nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); + if (!nodes) + return -ENOMEM; + + nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); + if (!nodes[GPIO_KEYS].name) + return -ENOMEM; + + nodes[GPIO_KEYS].properties = bd718xx_powerkey_parent_props; + + nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; + nodes[PWRON_KEY].properties = bd718xx_powerkey_props; + + ret = bd718xx_i2c_register_swnodes(nodes); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, bd718xx_i2c_unregister_swnodes, nodes); + if (ret) + return ret; + + gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; + + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, + NULL, 0, irq_domain); + if (ret) + return dev_err_probe(dev, ret, "Failed to register power-button"); + + return 0; +} + static int bd718xx_i2c_probe(struct i2c_client *i2c) { struct regmap *regmap; struct regmap_irq_chip_data *irq_data; + struct irq_domain *irq_domain; int ret; unsigned int chip_type; struct mfd_cell *mfd; @@ -169,20 +228,18 @@ static int bd718xx_i2c_probe(struct i2c_client *i2c) if (ret) return ret; - ret = regmap_irq_get_virq(irq_data, BD718XX_INT_PWRBTN_S); - - if (ret < 0) - return dev_err_probe(&i2c->dev, ret, "Failed to get the IRQ\n"); - - button.irq = ret; + irq_domain = regmap_irq_get_domain(irq_data); ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, - mfd, cells, NULL, 0, - regmap_irq_get_domain(irq_data)); + mfd, cells, NULL, 0, irq_domain); if (ret) - dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); + return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); - return ret; + ret = bd718xx_i2c_register_pwrbutton(&i2c->dev, irq_domain); + if (ret) + return ret; + + return 0; } static const struct of_device_id bd718xx_of_match[] = { From a1445ec0be7411f4c76458873ef7456b02688f63 Mon Sep 17 00:00:00 2001 From: Joy Zou Date: Wed, 15 Jul 2026 17:33:51 +0800 Subject: [PATCH 31/61] Input: snvs_pwrkey - make use of dev_err_probe() Add dev_err_probe() at return path of probe() to support users to identify issues easier. Reviewed-by: Frank Li Signed-off-by: Joy Zou Link: https://patch.msgid.link/20260715-b4-pwrkey-v5-1-07e7353c319e@oss.nxp.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/snvs_pwrkey.c | 44 ++++++++++------------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/drivers/input/keyboard/snvs_pwrkey.c b/drivers/input/keyboard/snvs_pwrkey.c index 954055aaf6e2..8cc6863d26ed 100644 --- a/drivers/input/keyboard/snvs_pwrkey.c +++ b/drivers/input/keyboard/snvs_pwrkey.c @@ -124,17 +124,15 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) /* Get SNVS register Page */ np = pdev->dev.of_node; if (!np) - return -ENODEV; + return dev_err_probe(&pdev->dev, -ENODEV, "Device tree node not found\n"); pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; pdata->snvs = syscon_regmap_lookup_by_phandle(np, "regmap"); - if (IS_ERR(pdata->snvs)) { - dev_err(&pdev->dev, "Can't get snvs syscon\n"); - return PTR_ERR(pdata->snvs); - } + if (IS_ERR(pdata->snvs)) + return dev_err_probe(&pdev->dev, PTR_ERR(pdata->snvs), "Can't get snvs syscon\n"); if (of_property_read_u32(np, "linux,keycode", &pdata->keycode)) { pdata->keycode = KEY_POWER; @@ -142,10 +140,9 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) } clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); - if (IS_ERR(clk)) { - dev_err(&pdev->dev, "Failed to get snvs clock (%pe)\n", clk); - return PTR_ERR(clk); - } + if (IS_ERR(clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(clk), + "Failed to get snvs clock (%pe)\n", clk); pdata->wakeup = of_property_read_bool(np, "wakeup-source"); @@ -165,9 +162,8 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) bpt = (val / 5) - 1; break; default: - dev_err(&pdev->dev, - "power-off-time-sec %d out of range\n", val); - return -EINVAL; + return dev_err_probe(&pdev->dev, -EINVAL, + "power-off-time-sec %d out of range\n", val); } regmap_update_bits(pdata->snvs, SNVS_LPCR_REG, SNVS_LPCR_BPT_MASK, @@ -185,10 +181,8 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) timer_setup(&pdata->check_timer, imx_imx_snvs_check_for_events, 0); input = devm_input_allocate_device(&pdev->dev); - if (!input) { - dev_err(&pdev->dev, "failed to allocate the input device\n"); - return -ENOMEM; - } + if (!input) + return dev_err_probe(&pdev->dev, -ENOMEM, "failed to allocate the input device\n"); input->name = pdev->name; input->phys = "snvs-pwrkey/input0"; @@ -198,10 +192,8 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) /* input customer action to cancel release timer */ error = devm_add_action(&pdev->dev, imx_snvs_pwrkey_act, pdata); - if (error) { - dev_err(&pdev->dev, "failed to register remove action\n"); - return error; - } + if (error) + return dev_err_probe(&pdev->dev, error, "failed to register remove action\n"); pdata->input = input; platform_set_drvdata(pdev, pdata); @@ -209,16 +201,12 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) error = devm_request_irq(&pdev->dev, pdata->irq, imx_snvs_pwrkey_interrupt, 0, pdev->name, pdev); - if (error) { - dev_err(&pdev->dev, "interrupt not available.\n"); - return error; - } + if (error) + return dev_err_probe(&pdev->dev, error, "interrupt not available.\n"); error = input_register_device(input); - if (error < 0) { - dev_err(&pdev->dev, "failed to register input device\n"); - return error; - } + if (error < 0) + return dev_err_probe(&pdev->dev, error, "failed to register input device\n"); device_init_wakeup(&pdev->dev, pdata->wakeup); error = dev_pm_set_wake_irq(&pdev->dev, pdata->irq); From 7ef54727f5a2deef5c59623f0685056b2e8a1676 Mon Sep 17 00:00:00 2001 From: Joy Zou Date: Wed, 15 Jul 2026 17:33:52 +0800 Subject: [PATCH 32/61] Input: snvs_pwrkey - propagate error code of platform_get_irq() Hardcoding -EINVAL discards the actual error code, which breaks probe deferral (-EPROBE_DEFER) and loses critical diagnostic information needed for proper kernel error handling. Reviewed-by: Frank Li Signed-off-by: Joy Zou Link: https://patch.msgid.link/20260715-b4-pwrkey-v5-2-07e7353c319e@oss.nxp.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/snvs_pwrkey.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/keyboard/snvs_pwrkey.c b/drivers/input/keyboard/snvs_pwrkey.c index 8cc6863d26ed..d58bbbe9fd58 100644 --- a/drivers/input/keyboard/snvs_pwrkey.c +++ b/drivers/input/keyboard/snvs_pwrkey.c @@ -148,7 +148,7 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) pdata->irq = platform_get_irq(pdev, 0); if (pdata->irq < 0) - return -EINVAL; + return pdata->irq; error = of_property_read_u32(np, "power-off-time-sec", &val); if (!error) { From 5a040cd37f397060e6ec8d6894ed0075859ff5cd Mon Sep 17 00:00:00 2001 From: Joy Zou Date: Wed, 15 Jul 2026 17:33:53 +0800 Subject: [PATCH 33/61] Input: snvs_pwrkey - use local device pointer to simple code Use local struct device pointer to avoid reference the platform_device pointer every time. No functional change. Reviewed-by: Frank Li Signed-off-by: Joy Zou Link: https://patch.msgid.link/20260715-b4-pwrkey-v5-3-07e7353c319e@oss.nxp.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/snvs_pwrkey.c | 41 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/drivers/input/keyboard/snvs_pwrkey.c b/drivers/input/keyboard/snvs_pwrkey.c index d58bbbe9fd58..cbe44a38d2b3 100644 --- a/drivers/input/keyboard/snvs_pwrkey.c +++ b/drivers/input/keyboard/snvs_pwrkey.c @@ -112,6 +112,7 @@ static void imx_snvs_pwrkey_act(void *pdata) static int imx_snvs_pwrkey_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct pwrkey_drv_data *pdata; struct input_dev *input; struct device_node *np; @@ -122,26 +123,26 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) u32 vid; /* Get SNVS register Page */ - np = pdev->dev.of_node; + np = dev->of_node; if (!np) - return dev_err_probe(&pdev->dev, -ENODEV, "Device tree node not found\n"); + return dev_err_probe(dev, -ENODEV, "Device tree node not found\n"); - pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; pdata->snvs = syscon_regmap_lookup_by_phandle(np, "regmap"); if (IS_ERR(pdata->snvs)) - return dev_err_probe(&pdev->dev, PTR_ERR(pdata->snvs), "Can't get snvs syscon\n"); + return dev_err_probe(dev, PTR_ERR(pdata->snvs), "Can't get snvs syscon\n"); if (of_property_read_u32(np, "linux,keycode", &pdata->keycode)) { pdata->keycode = KEY_POWER; - dev_warn(&pdev->dev, "KEY_POWER without setting in dts\n"); + dev_warn(dev, "KEY_POWER without setting in dts\n"); } - clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); + clk = devm_clk_get_optional_enabled(dev, NULL); if (IS_ERR(clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(clk), + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get snvs clock (%pe)\n", clk); pdata->wakeup = of_property_read_bool(np, "wakeup-source"); @@ -162,7 +163,7 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) bpt = (val / 5) - 1; break; default: - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "power-off-time-sec %d out of range\n", val); } @@ -180,9 +181,9 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) timer_setup(&pdata->check_timer, imx_imx_snvs_check_for_events, 0); - input = devm_input_allocate_device(&pdev->dev); + input = devm_input_allocate_device(dev); if (!input) - return dev_err_probe(&pdev->dev, -ENOMEM, "failed to allocate the input device\n"); + return dev_err_probe(dev, -ENOMEM, "failed to allocate the input device\n"); input->name = pdev->name; input->phys = "snvs-pwrkey/input0"; @@ -191,27 +192,27 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) input_set_capability(input, EV_KEY, pdata->keycode); /* input customer action to cancel release timer */ - error = devm_add_action(&pdev->dev, imx_snvs_pwrkey_act, pdata); + error = devm_add_action(dev, imx_snvs_pwrkey_act, pdata); if (error) - return dev_err_probe(&pdev->dev, error, "failed to register remove action\n"); + return dev_err_probe(dev, error, "failed to register remove action\n"); pdata->input = input; platform_set_drvdata(pdev, pdata); - error = devm_request_irq(&pdev->dev, pdata->irq, - imx_snvs_pwrkey_interrupt, - 0, pdev->name, pdev); + error = devm_request_irq(dev, pdata->irq, + imx_snvs_pwrkey_interrupt, + 0, pdev->name, pdev); if (error) - return dev_err_probe(&pdev->dev, error, "interrupt not available.\n"); + return dev_err_probe(dev, error, "interrupt not available.\n"); error = input_register_device(input); if (error < 0) - return dev_err_probe(&pdev->dev, error, "failed to register input device\n"); + return dev_err_probe(dev, error, "failed to register input device\n"); - device_init_wakeup(&pdev->dev, pdata->wakeup); - error = dev_pm_set_wake_irq(&pdev->dev, pdata->irq); + device_init_wakeup(dev, pdata->wakeup); + error = dev_pm_set_wake_irq(dev, pdata->irq); if (error) - dev_err(&pdev->dev, "irq wake enable failed.\n"); + dev_err(dev, "irq wake enable failed.\n"); return 0; } From 29fb42d56f1d8c10dd26e3f8410a825b6905dbc3 Mon Sep 17 00:00:00 2001 From: Joy Zou Date: Wed, 15 Jul 2026 17:33:54 +0800 Subject: [PATCH 34/61] Input: snvs_pwrkey - add press event reporting to avoid event loss during suspend The driver implements debounce protection using a timer-based mechanism: when a key interrupt occurs, a timer is scheduled to verify the key state after DEBOUNCE_TIME before reporting the event. This works well during normal operation. However, key press events can be lost during system resume on platforms like i.MX8MQ-EVK because: 1. During the no_irq resume phase, PCIe driver restoration can take up to 200ms with IRQs disabled. 2. The power key interrupt remains pending during the no_irq phase. 3. If the key is released before IRQs are re-enabled, the timer eventually runs but sees the key as released and skips reporting the event. To prevent event loss during system suspend, set a pending_press flag in the interrupt handler and report the press event from the timer callback when the flag is set. This avoids out-of-order event delivery and keeps the existing timer-based debounce mechanism for normal operation. Signed-off-by: Joy Zou Reviewed-by: Frank Li Link: https://patch.msgid.link/20260715-b4-pwrkey-v5-4-07e7353c319e@oss.nxp.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/snvs_pwrkey.c | 72 ++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/drivers/input/keyboard/snvs_pwrkey.c b/drivers/input/keyboard/snvs_pwrkey.c index cbe44a38d2b3..9d51c7ff1ebb 100644 --- a/drivers/input/keyboard/snvs_pwrkey.c +++ b/drivers/input/keyboard/snvs_pwrkey.c @@ -39,6 +39,9 @@ struct pwrkey_drv_data { int keycode; int keystate; /* 1:pressed */ int wakeup; + bool suspended; /* Track suspend state */ + bool pending_press; /* Key pressed during suspend, report from timer callback */ + spinlock_t lock; /* Protects keystate, suspended and pending_press */ struct timer_list check_timer; struct input_dev *input; u8 minor_rev; @@ -49,14 +52,38 @@ static void imx_imx_snvs_check_for_events(struct timer_list *t) struct pwrkey_drv_data *pdata = timer_container_of(pdata, t, check_timer); struct input_dev *input = pdata->input; + bool state_changed = false; + bool pending_press; u32 state; regmap_read(pdata->snvs, SNVS_HPSR_REG, &state); state = state & SNVS_HPSR_BTN ? 1 : 0; - /* only report new event if status changed */ - if (state ^ pdata->keystate) { - pdata->keystate = state; + scoped_guard(spinlock_irqsave, &pdata->lock) { + pending_press = pdata->pending_press; + if (pending_press) { + pdata->pending_press = false; + pdata->keystate = 1; + } + /* only report new event if status changed */ + if (state ^ pdata->keystate) { + pdata->keystate = state; + state_changed = true; + } + } + + /* + * Report a press event latched during suspend. If the key is still + * held, state_changed will be 0 (keystate already set to 1 above), + * so no duplicate press is reported. If already released, + * state_changed will fire next to report the release. + */ + if (pending_press) { + input_report_key(input, pdata->keycode, 1); + input_sync(input); + } + + if (state_changed) { input_event(input, EV_KEY, pdata->keycode, state); input_sync(input); pm_relax(pdata->input->dev.parent); @@ -92,8 +119,17 @@ static irqreturn_t imx_snvs_pwrkey_interrupt(int irq, void *dev_id) input_sync(input); pm_relax(input->dev.parent); } else { + /* + * If the key is pressed during suspend, latch it so + * the timer callback can report the press event in + * softirq context, avoiding out-of-order events. + */ + scoped_guard(spinlock_irqsave, &pdata->lock) { + if (pdata->suspended) + pdata->pending_press = true; + } mod_timer(&pdata->check_timer, - jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); + jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); } } @@ -151,6 +187,7 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) if (pdata->irq < 0) return pdata->irq; + spin_lock_init(&pdata->lock); error = of_property_read_u32(np, "power-off-time-sec", &val); if (!error) { switch (val) { @@ -217,6 +254,32 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) return 0; } +static int imx_snvs_pwrkey_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); + + guard(spinlock_irq)(&pdata->lock); + pdata->suspended = true; + + return 0; +} + +static int imx_snvs_pwrkey_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); + + guard(spinlock_irq)(&pdata->lock); + pdata->suspended = false; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(imx_snvs_pwrkey_pm_ops, + imx_snvs_pwrkey_suspend, + imx_snvs_pwrkey_resume); + static const struct of_device_id imx_snvs_pwrkey_ids[] = { { .compatible = "fsl,sec-v4.0-pwrkey" }, { /* sentinel */ } @@ -227,6 +290,7 @@ static struct platform_driver imx_snvs_pwrkey_driver = { .driver = { .name = "snvs_pwrkey", .of_match_table = imx_snvs_pwrkey_ids, + .pm = pm_ptr(&imx_snvs_pwrkey_pm_ops), }, .probe = imx_snvs_pwrkey_probe, }; From d6b0c1c2f3e8d8bd7b6f60b02cc7a37bff117fa6 Mon Sep 17 00:00:00 2001 From: Pradyot Kumar Nayak Date: Fri, 17 Jul 2026 17:28:34 +0530 Subject: [PATCH 35/61] dt-bindings: input: focaltech,ft8112: Add focaltech,ft3d81 compatible The Focaltech ft3d81 is fully compatible with the ft8112 i.e. it uses the same I2C-HID protocol and the same power-on/reset sequencing, DT nodes for boards carrying an ft3d81,can therefore bind to the existing ft8112 driver without any additional changes. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Pradyot Kumar Nayak Link: https://patch.msgid.link/20260717-add_focaltech_ft3d81_touchscreen_support-v4-1-5dd091e25801@oss.qualcomm.com Signed-off-by: Dmitry Torokhov --- .../devicetree/bindings/input/focaltech,ft8112.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/input/focaltech,ft8112.yaml b/Documentation/devicetree/bindings/input/focaltech,ft8112.yaml index 197f30b14d45..5ffa1246aba1 100644 --- a/Documentation/devicetree/bindings/input/focaltech,ft8112.yaml +++ b/Documentation/devicetree/bindings/input/focaltech,ft8112.yaml @@ -18,8 +18,13 @@ allOf: properties: compatible: - enum: - - focaltech,ft8112 + oneOf: + - items: + - enum: + - focaltech,ft3d81 + - const: focaltech,ft8112 + - enum: + - focaltech,ft8112 reg: maxItems: 1 From 0aa7c205e901ad75f4785f786b32a2b01b896a9b Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 15 Jul 2026 16:38:50 +0800 Subject: [PATCH 36/61] Input: iqs5xx - validate firmware record destination span The firmware record parser checks that the record address starts within the programmable map, but does not check that the complete record data fits in that map. A record near the end of the map can therefore make the copy to pmap exceed its destination span. Check the record length against the remaining programmable map range before copying the record data. Fixes: 7b5bb55d0dad ("Input: add support for Azoteq IQS550/572/525") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260715083850.32155-1-pengpeng@iscas.ac.cn Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/iqs5xx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/input/touchscreen/iqs5xx.c b/drivers/input/touchscreen/iqs5xx.c index c3cc37274335..88dcf72618df 100644 --- a/drivers/input/touchscreen/iqs5xx.c +++ b/drivers/input/touchscreen/iqs5xx.c @@ -785,7 +785,8 @@ static int iqs5xx_fw_file_parse(struct i2c_client *client, switch (rec_type) { case IQS5XX_REC_TYPE_DATA: if (rec_addr < IQS5XX_CHKSM || - rec_addr > IQS5XX_PMAP_END) { + rec_addr > IQS5XX_PMAP_END || + rec_len > IQS5XX_PMAP_END + 1 - rec_addr) { dev_err(&client->dev, "Invalid address at record %u\n", rec_num); From e640910640676b88883db616c78bd26dc9eab428 Mon Sep 17 00:00:00 2001 From: Surendra Singh Chouhan Date: Thu, 23 Jul 2026 07:59:43 +0530 Subject: [PATCH 37/61] Input: charlieplex_keypad - check gpiod_direction_output() return value charlieplex_keypad_scan_line() currently ignores the return value of gpiod_direction_output() when setting the active output line for scanning. If setting the GPIO direction fails (e.g. on I2C/SPI GPIO expanders or hardware errors), the function continues to sleep and read input values from an improperly configured GPIO line. Fix this by capturing the return value of gpiod_direction_output() and returning the error code immediately if it fails. Fixes: 2ca45e57ea02 ("Input: charlieplex_keypad - add GPIO charlieplex keypad") Signed-off-by: Surendra Singh Chouhan Link: https://patch.msgid.link/20260723022943.9337-1-kr494167@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/charlieplex_keypad.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/input/keyboard/charlieplex_keypad.c b/drivers/input/keyboard/charlieplex_keypad.c index d222b622c820..d46a2298e6da 100644 --- a/drivers/input/keyboard/charlieplex_keypad.c +++ b/drivers/input/keyboard/charlieplex_keypad.c @@ -77,7 +77,9 @@ static int charlieplex_keypad_scan_line(struct charlieplex_keypad *keypad, int err; /* Activate only one line as output at a time. */ - gpiod_direction_output(line_gpios->desc[oline], 1); + err = gpiod_direction_output(line_gpios->desc[oline], 1); + if (err) + return err; if (keypad->settling_time_us) fsleep(keypad->settling_time_us); From 691f40e482dc853c4548cbdf4cab1aa666d20aab Mon Sep 17 00:00:00 2001 From: Eduard Bostina Date: Thu, 23 Jul 2026 10:06:01 +0000 Subject: [PATCH 38/61] dt-bindings: input: Convert TI TPS65217 power button to DT schema Convert the Texas Instruments TPS65217 and TPS65218 Power Button bindings to DT schema. Signed-off-by: Eduard Bostina Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260723100605.628882-4-egbostina@gmail.com Signed-off-by: Dmitry Torokhov --- .../bindings/input/ti,tps65217-pwrbutton.yaml | 42 +++++++++++++++++++ .../bindings/input/tps65218-pwrbutton.txt | 30 ------------- 2 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 Documentation/devicetree/bindings/input/ti,tps65217-pwrbutton.yaml delete mode 100644 Documentation/devicetree/bindings/input/tps65218-pwrbutton.txt diff --git a/Documentation/devicetree/bindings/input/ti,tps65217-pwrbutton.yaml b/Documentation/devicetree/bindings/input/ti,tps65217-pwrbutton.yaml new file mode 100644 index 000000000000..3526d8b045fd --- /dev/null +++ b/Documentation/devicetree/bindings/input/ti,tps65217-pwrbutton.yaml @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/ti,tps65217-pwrbutton.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments TPS65217 and TPS65218 Power Button + +maintainers: + - Eduard Bostina + +description: + This module is part of the TPS65217/TPS65218 PMIC. It provides a simple + power button event via an interrupt. + +properties: + compatible: + enum: + - ti,tps65217-pwrbutton + - ti,tps65218-pwrbutton + + interrupts: + maxItems: 1 + +required: + - compatible + - interrupts + +additionalProperties: false + +examples: + - | + #include + pmic { + #address-cells = <1>; + #size-cells = <0>; + + power-button { + compatible = "ti,tps65218-pwrbutton"; + interrupts = <3 IRQ_TYPE_EDGE_BOTH>; + }; + }; diff --git a/Documentation/devicetree/bindings/input/tps65218-pwrbutton.txt b/Documentation/devicetree/bindings/input/tps65218-pwrbutton.txt deleted file mode 100644 index 8682ab6d4a50..000000000000 --- a/Documentation/devicetree/bindings/input/tps65218-pwrbutton.txt +++ /dev/null @@ -1,30 +0,0 @@ -Texas Instruments TPS65217 and TPS65218 power button - -This module is part of the TPS65217/TPS65218. For more details about the whole -TPS65217 chip see Documentation/devicetree/bindings/regulator/tps65217.txt. - -This driver provides a simple power button event via an Interrupt. - -Required properties: -- compatible: should be "ti,tps65217-pwrbutton" or "ti,tps65218-pwrbutton" - -Required properties: -- interrupts: should be one of the following - - <2>: For controllers compatible with tps65217 - - <3 IRQ_TYPE_EDGE_BOTH>: For controllers compatible with tps65218 - -Examples: - -&tps { - tps65217-pwrbutton { - compatible = "ti,tps65217-pwrbutton"; - interrupts = <2>; - }; -}; - -&tps { - power-button { - compatible = "ti,tps65218-pwrbutton"; - interrupts = <3 IRQ_TYPE_EDGE_BOTH>; - }; -}; From aa7ab8c6f5bffcc502d444ba3f3c510fc6f00d4f Mon Sep 17 00:00:00 2001 From: Bivash Kumar Singh Date: Sat, 25 Jul 2026 17:06:38 +0530 Subject: [PATCH 39/61] Input: elo - fix coding style issues in elo_setup_10() Fix two checkpatch warnings in elo_setup_10(): - Add missing space around '-' operator in array index expression - Add missing 'const' qualifier to elo_types pointer array, since the array is never modified after initialization Signed-off-by: Bivash Kumar Singh Link: https://patch.msgid.link/20260725113638.5147-1-bivashraj750@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/elo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/input/touchscreen/elo.c b/drivers/input/touchscreen/elo.c index 6814d5789b6f..97aeec7ef709 100644 --- a/drivers/input/touchscreen/elo.c +++ b/drivers/input/touchscreen/elo.c @@ -257,7 +257,7 @@ static int elo_command_10(struct elo *elo, unsigned char *packet) static int elo_setup_10(struct elo *elo) { - static const char *elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; + static const char * const elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; struct input_dev *dev = elo->dev; unsigned char packet[ELO10_PACKET_LEN] = { ELO10_ID_CMD }; @@ -273,7 +273,7 @@ static int elo_setup_10(struct elo *elo) dev_info(&elo->serio->dev, "%sTouch touchscreen, fw: %02x.%02x, features: 0x%02x, controller: 0x%02x\n", - elo_types[(packet[1] -'0') & 0x03], + elo_types[(packet[1] - '0') & 0x03], packet[5], packet[4], packet[3], packet[7]); return 0; From 337e5910dcbc38a656e446a6c8a7b37bd496f476 Mon Sep 17 00:00:00 2001 From: Bivash Kumar Singh Date: Sat, 25 Jul 2026 18:38:03 +0530 Subject: [PATCH 40/61] Input: inexio - replace printk with dev_dbg and fix missing space Replace printk(KERN_DEBUG) with dev_dbg() using the serio device, which is the correct logging style for driver code. Also fix missing space after comma in the function argument, and remove the redundant 'inexio.c:' filename prefix from the message. Signed-off-by: Bivash Kumar Singh Link: https://patch.msgid.link/20260725130803.6763-1-bivashraj750@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/inexio.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/input/touchscreen/inexio.c b/drivers/input/touchscreen/inexio.c index ac3836ff2a30..2fa04eaec292 100644 --- a/drivers/input/touchscreen/inexio.c +++ b/drivers/input/touchscreen/inexio.c @@ -81,7 +81,9 @@ static irqreturn_t inexio_interrupt(struct serio *serio, if (INEXIO_RESPONSE_BEGIN_BYTE&pinexio->data[0]) inexio_process_data(pinexio); else - printk(KERN_DEBUG "inexio.c: unknown/unsynchronized data from device, byte %x\n",pinexio->data[0]); + dev_dbg(&serio->dev, + "unknown/unsynchronized data from device, byte %x\n", + pinexio->data[0]); return IRQ_HANDLED; } From 116087d9db1502de054fb0453fdb002c0562019d Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 13 Jul 2026 22:39:37 -0700 Subject: [PATCH 41/61] Input: samsung-keypad - clean up wakeup configuration logic When checking if the device can wake the system, we should pull the device_may_wakeup() check to the caller instead of repeating it inside the toggle_wakeup() handler. Furthermore, when configuring the wakeup, we should safely ensure we write to the registers in the correct order: configure the interrupt receiver before enabling the peripheral's wake functionality, and vice-versa. Assisted-by: Antigravity:gemini-3.1-pro Link: https://patch.msgid.link/20260713-samsung-kp-irq-v2-1-acc84b6daf9a@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/samsung-keypad.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/input/keyboard/samsung-keypad.c b/drivers/input/keyboard/samsung-keypad.c index 17127269e3f0..a578f429d100 100644 --- a/drivers/input/keyboard/samsung-keypad.c +++ b/drivers/input/keyboard/samsung-keypad.c @@ -492,15 +492,14 @@ static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad, val = readl(keypad->base + SAMSUNG_KEYIFCON); if (enable) { + enable_irq_wake(keypad->irq); val |= SAMSUNG_KEYIFCON_WAKEUPEN; - if (device_may_wakeup(&keypad->pdev->dev)) - enable_irq_wake(keypad->irq); + writel(val, keypad->base + SAMSUNG_KEYIFCON); } else { val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; - if (device_may_wakeup(&keypad->pdev->dev)) - disable_irq_wake(keypad->irq); + writel(val, keypad->base + SAMSUNG_KEYIFCON); + disable_irq_wake(keypad->irq); } - writel(val, keypad->base + SAMSUNG_KEYIFCON); clk_disable(keypad->clk); } @@ -516,7 +515,8 @@ static int samsung_keypad_suspend(struct device *dev) if (input_device_enabled(input_dev)) samsung_keypad_stop(keypad); - samsung_keypad_toggle_wakeup(keypad, true); + if (device_may_wakeup(dev)) + samsung_keypad_toggle_wakeup(keypad, true); return 0; } @@ -529,7 +529,8 @@ static int samsung_keypad_resume(struct device *dev) guard(mutex)(&input_dev->mutex); - samsung_keypad_toggle_wakeup(keypad, false); + if (device_may_wakeup(dev)) + samsung_keypad_toggle_wakeup(keypad, false); if (input_device_enabled(input_dev)) samsung_keypad_start(keypad); From 9ce8270e30f567a10a9f98083084938e02de957d Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 13 Jul 2026 22:39:38 -0700 Subject: [PATCH 42/61] Input: samsung-keypad - keep interrupt disabled while closed The driver requests the interrupt during probe, which by default enables it. If the bootloader left the keypad interrupts enabled, or if a spurious interrupt fires early before the driver is fully initialized and clocks are enabled, the interrupt handler will attempt to read registers and may cause a synchronous external abort. Fix this by requesting the interrupt with IRQF_NO_AUTOEN, keeping it disabled during probe. Enable the interrupt in samsung_keypad_start() when the device is opened and ready, and disable it in samsung_keypad_stop() when the device is closed. Remove the redundant re-enabling of the interrupt at the end of samsung_keypad_stop(). Additionally, manually clear the pending interrupt status during system resume when the device is closed to avoid immediate resume. Fixes: 0fffed27f92d ("Input: samsung-keypad - Add samsung keypad driver") Assisted-by: Antigravity:gemini-3.1-pro Link: https://patch.msgid.link/20260713-samsung-kp-irq-v2-2-acc84b6daf9a@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/samsung-keypad.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/input/keyboard/samsung-keypad.c b/drivers/input/keyboard/samsung-keypad.c index a578f429d100..a51f0f639e9e 100644 --- a/drivers/input/keyboard/samsung-keypad.c +++ b/drivers/input/keyboard/samsung-keypad.c @@ -183,6 +183,8 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) writel(0, keypad->base + SAMSUNG_KEYIFCOL); pm_runtime_put(&keypad->pdev->dev); + + enable_irq(keypad->irq); } static void samsung_keypad_stop(struct samsung_keypad *keypad) @@ -206,12 +208,6 @@ static void samsung_keypad_stop(struct samsung_keypad *keypad) clk_disable(keypad->clk); - /* - * Now that chip should not generate interrupts we can safely - * re-enable the handler. - */ - enable_irq(keypad->irq); - pm_runtime_put(&keypad->pdev->dev); } @@ -412,7 +408,8 @@ static int samsung_keypad_probe(struct platform_device *pdev) } error = devm_request_threaded_irq(&pdev->dev, keypad->irq, NULL, - samsung_keypad_irq, IRQF_ONESHOT, + samsung_keypad_irq, + IRQF_ONESHOT | IRQF_NO_AUTOEN, dev_name(&pdev->dev), keypad); if (error) { dev_err(&pdev->dev, "failed to register keypad interrupt\n"); @@ -499,6 +496,9 @@ static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad, val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; writel(val, keypad->base + SAMSUNG_KEYIFCON); disable_irq_wake(keypad->irq); + + if (!input_device_enabled(keypad->input_dev)) + writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR); } clk_disable(keypad->clk); From 50411cada028f54d859d4b2361c1d87168562c49 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 13 Jul 2026 22:39:39 -0700 Subject: [PATCH 43/61] Input: samsung-keypad - use pm_runtime_active guard Simplify the driver by using the block-scope guard(pm_runtime_active) instead of manually invoking pm_runtime_get_sync() and pm_runtime_put(). Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260713-samsung-kp-irq-v2-3-acc84b6daf9a@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/samsung-keypad.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/input/keyboard/samsung-keypad.c b/drivers/input/keyboard/samsung-keypad.c index a51f0f639e9e..deffa98749c0 100644 --- a/drivers/input/keyboard/samsung-keypad.c +++ b/drivers/input/keyboard/samsung-keypad.c @@ -142,7 +142,7 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) unsigned int row_state[SAMSUNG_MAX_COLS]; bool key_down; - pm_runtime_get_sync(&keypad->pdev->dev); + guard(pm_runtime_active)(&keypad->pdev->dev); do { readl(keypad->base + SAMSUNG_KEYIFSTSCLR); @@ -158,8 +158,6 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) } while (key_down && !keypad->stopped); - pm_runtime_put(&keypad->pdev->dev); - return IRQ_HANDLED; } @@ -167,7 +165,7 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) { unsigned int val; - pm_runtime_get_sync(&keypad->pdev->dev); + guard(pm_runtime_active)(&keypad->pdev->dev); /* Tell IRQ thread that it may poll the device. */ keypad->stopped = false; @@ -182,8 +180,6 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) /* KEYIFCOL reg clear. */ writel(0, keypad->base + SAMSUNG_KEYIFCOL); - pm_runtime_put(&keypad->pdev->dev); - enable_irq(keypad->irq); } @@ -191,7 +187,7 @@ static void samsung_keypad_stop(struct samsung_keypad *keypad) { unsigned int val; - pm_runtime_get_sync(&keypad->pdev->dev); + guard(pm_runtime_active)(&keypad->pdev->dev); /* Signal IRQ thread to stop polling and disable the handler. */ keypad->stopped = true; @@ -207,8 +203,6 @@ static void samsung_keypad_stop(struct samsung_keypad *keypad) writel(val, keypad->base + SAMSUNG_KEYIFCON); clk_disable(keypad->clk); - - pm_runtime_put(&keypad->pdev->dev); } static int samsung_keypad_open(struct input_dev *input_dev) From 761c2040a7d4466c11fb59f3cab94d4078e6da29 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 26 Jul 2026 22:07:58 -0700 Subject: [PATCH 44/61] Input: psmouse - fix use-after-free during protocol disconnect When a PS/2 mouse is disconnected or unbound, psmouse_disconnect() calls the protocol disconnect handler (psmouse->disconnect()). During this time, stray bytes arriving from the physical controller can still be passed to psmouse_handle_byte(), which will invoke psmouse->protocol_handler(). This creates an asynchronous race condition with vendor disconnect handlers (such as synaptics_disconnect()), which free vendor-specific private structures (psmouse->private). If a byte arrives while the structures are being freed, it leads to a use-after-free or NULL pointer dereference in the protocol handler. Fix this by explicitly setting psmouse->protocol_handler to NULL safely wrapped in scoped_guard(serio_pause_rx, serio) immediately before calling the vendor disconnect handler. We also add an unlikely check in psmouse_handle_byte() to safely drop incoming bytes if the protocol handler is NULL. Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260727050803.1269941-1-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/psmouse-base.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index 6ab5f1d96eae..108591b7ebf3 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -267,7 +267,15 @@ void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) */ static int psmouse_handle_byte(struct psmouse *psmouse) { - psmouse_ret_t rc = psmouse->protocol_handler(psmouse); + psmouse_ret_t rc; + + /* protocol_handler is NULL when device is being disconnected */ + if (unlikely(!psmouse->protocol_handler)) { + psmouse->pktcnt = 0; + return 0; + } + + rc = psmouse->protocol_handler(psmouse); switch (rc) { case PSMOUSE_BAD_DATA: @@ -1466,6 +1474,9 @@ static void psmouse_disconnect(struct serio *serio) psmouse_deactivate(parent); } + scoped_guard(serio_pause_rx, serio) + psmouse->protocol_handler = NULL; + if (psmouse->disconnect) psmouse->disconnect(psmouse); From ad8d3b91e48e1d9b7f94a5cc46cd6e4fd58dc6f5 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 26 Jul 2026 22:07:59 -0700 Subject: [PATCH 45/61] Input: psmouse - clean up locking around disable_work_sync() In the past, psmouse_disconnect() used cancel_work_sync(). Because cancel_work_sync() must be called with the psmouse_mutex dropped, and we needed to prevent psmouse_receive_byte() from re-queueing the work behind our back, the code transitioned the device to PSMOUSE_CMD_MODE while holding the mutex, then dropped the mutex and cancelled the work. When cancel_work_sync() was replaced with disable_work_sync() in this path, the mutex juggling remained. However, disable_work_sync() inherently prevents the work from being executed or re-queued, making the mutex juggling unnecessary. Clean this up by moving disable_work_sync() to the very top of psmouse_disconnect(), before we acquire psmouse_mutex. Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260727050803.1269941-2-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/psmouse-base.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index 108591b7ebf3..668a6a4fbe82 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -1460,15 +1460,12 @@ static void psmouse_disconnect(struct serio *serio) struct psmouse *psmouse = psmouse_from_serio(serio); struct psmouse *parent = NULL; + disable_work_sync(&psmouse->resync_work); + mutex_lock(&psmouse_mutex); psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); - /* make sure we don't have a resync in progress */ - mutex_unlock(&psmouse_mutex); - disable_work_sync(&psmouse->resync_work); - mutex_lock(&psmouse_mutex); - if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { parent = psmouse_from_serio(serio->parent); psmouse_deactivate(parent); From c1df7e4e4951ee786c2e5eec002ac3a56848ea1f Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 26 Jul 2026 22:08:00 -0700 Subject: [PATCH 46/61] Input: psmouse - modernize PNP ID parsing Rewrite psmouse_matches_pnp_id() to parse and match the space-separated PNP ID string directly in place without dynamic memory allocation. Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260727050803.1269941-3-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/psmouse-base.c | 41 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index 668a6a4fbe82..d87fefded859 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -492,13 +492,16 @@ static int psmouse_poll(struct psmouse *psmouse) PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)); } -static bool psmouse_check_pnp_id(const char *id, const char * const ids[]) +static bool psmouse_check_pnp_id(const char *p, const char * const ids[]) { - int i; + const char * const *id; + size_t len; - for (i = 0; ids[i]; i++) - if (!strcasecmp(id, ids[i])) + for (id = ids; *id; id++) { + len = strlen(*id); + if (!strncasecmp(p, *id, len) && (p[len] == ' ' || p[len] == '\0')) return true; + } return false; } @@ -509,28 +512,26 @@ static bool psmouse_check_pnp_id(const char *id, const char * const ids[]) bool psmouse_matches_pnp_id(struct psmouse *psmouse, const char * const ids[]) { struct serio *serio = psmouse->ps2dev.serio; - char *p, *fw_id_copy, *save_ptr; - bool found = false; + const char *p = serio->firmware_id; - if (strncmp(serio->firmware_id, "PNP: ", 5)) + if (!strstarts(p, "PNP: ")) return false; - fw_id_copy = kstrndup(&serio->firmware_id[5], - sizeof(serio->firmware_id) - 5, - GFP_KERNEL); - if (!fw_id_copy) - return false; - - save_ptr = fw_id_copy; - while ((p = strsep(&fw_id_copy, " ")) != NULL) { - if (psmouse_check_pnp_id(p, ids)) { - found = true; + p += 5; + while (*p) { + p = skip_spaces(p); + if (!*p) + break; + + if (psmouse_check_pnp_id(p, ids)) + return true; + + p = strchr(p, ' '); + if (!p) break; - } } - kfree(save_ptr); - return found; + return false; } /* From fbe47f041262590f4dc1267f90fa97a0506acb28 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 26 Jul 2026 22:08:01 -0700 Subject: [PATCH 47/61] Input: psmouse - use guard() for resource management Replace manual serialization with guard(mutex) and guard(serio_pause_rx) where appropriate. This eliminates the need for explicit goto-based error paths. Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260727050803.1269941-4-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/psmouse-base.c | 31 ++++++++++++------------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index d87fefded859..23164688210d 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -256,9 +256,8 @@ static inline void __psmouse_set_state(struct psmouse *psmouse, enum psmouse_sta */ void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) { - serio_pause_rx(psmouse->ps2dev.serio); + guard(serio_pause_rx)(psmouse->ps2dev.serio); __psmouse_set_state(psmouse, new_state); - serio_continue_rx(psmouse->ps2dev.serio); } /* @@ -1320,10 +1319,10 @@ static void psmouse_resync(struct work_struct *work) bool failed = false, enabled = false; int i; - mutex_lock(&psmouse_mutex); + guard(mutex)(&psmouse_mutex); if (psmouse->state != PSMOUSE_RESYNCING) - goto out; + return; if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { parent = psmouse_from_serio(serio->parent); @@ -1401,8 +1400,6 @@ static void psmouse_resync(struct work_struct *work) if (parent) psmouse_activate(parent); - out: - mutex_unlock(&psmouse_mutex); } /* @@ -1413,7 +1410,7 @@ static void psmouse_cleanup(struct serio *serio) struct psmouse *psmouse = psmouse_from_serio(serio); struct psmouse *parent = NULL; - mutex_lock(&psmouse_mutex); + guard(mutex)(&psmouse_mutex); if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { parent = psmouse_from_serio(serio->parent); @@ -1449,8 +1446,6 @@ static void psmouse_cleanup(struct serio *serio) psmouse_activate(parent); } - - mutex_unlock(&psmouse_mutex); } /* @@ -1463,7 +1458,7 @@ static void psmouse_disconnect(struct serio *serio) disable_work_sync(&psmouse->resync_work); - mutex_lock(&psmouse_mutex); + guard(mutex)(&psmouse_mutex); psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); @@ -1493,8 +1488,6 @@ static void psmouse_disconnect(struct serio *serio) if (parent) psmouse_activate(parent); - - mutex_unlock(&psmouse_mutex); } static int psmouse_switch_protocol(struct psmouse *psmouse, @@ -1663,14 +1656,12 @@ static int __psmouse_reconnect(struct serio *serio, bool fast_reconnect) enum psmouse_type type; int rc = -1; - mutex_lock(&psmouse_mutex); + lockdep_assert_held(&psmouse_mutex); if (fast_reconnect) { reconnect_handler = psmouse->fast_reconnect; - if (!reconnect_handler) { - rc = -ENOENT; - goto out_unlock; - } + if (!reconnect_handler) + return -ENOENT; } else { reconnect_handler = psmouse->reconnect; } @@ -1722,18 +1713,20 @@ static int __psmouse_reconnect(struct serio *serio, bool fast_reconnect) if (parent) psmouse_activate(parent); -out_unlock: - mutex_unlock(&psmouse_mutex); return rc; } static int psmouse_reconnect(struct serio *serio) { + guard(mutex)(&psmouse_mutex); + return __psmouse_reconnect(serio, false); } static int psmouse_fast_reconnect(struct serio *serio) { + guard(mutex)(&psmouse_mutex); + return __psmouse_reconnect(serio, true); } From 7f9c8c6716a97e55ab52426df98a3b4007174757 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 3 Aug 2026 22:09:54 -0700 Subject: [PATCH 48/61] Input: focaltech - use signed coordinates to prevent underflow focaltech_finger_state stores finger coordinates x and y as unsigned int. When processing relative packets, negative deltas can cause unsigned integer underflow if the finger moves past the left or bottom boundary of the touchpad, wrapping the coordinates to values near UINT_MAX. When clamping the coordinates in focaltech_report_state(), these underflowed values are clamped against priv->x_max / priv->y_max instead of 0, causing the cursor to jump erratically to the opposite edge of the touchpad. Change the coordinate variables and limits to signed int so that negative values resulting from relative movements clamp correctly to 0, and write the clamped values back to state in focaltech_report_state() to prevent coordinate wind-up accumulation at the touchpad boundaries. Fixes: 05be1d079ec0 ("Input: psmouse - support for the FocalTech PS/2 protocol extensions") Reported-by: sashiko-bot@kernel.org Link: https://patch.msgid.link/am_tH_F938rK6ask@google.com Assisted-by: Antigravity:gemini-3.6-flash Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/focaltech.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/input/mouse/focaltech.c b/drivers/input/mouse/focaltech.c index 43f9939b7c63..4f636ed20b54 100644 --- a/drivers/input/mouse/focaltech.c +++ b/drivers/input/mouse/focaltech.c @@ -78,8 +78,8 @@ struct focaltech_finger_state { * Absolute position (from the bottom left corner) of the * finger. */ - unsigned int x; - unsigned int y; + int x; + int y; }; /* @@ -108,7 +108,7 @@ struct focaltech_hw_state { }; struct focaltech_data { - unsigned int x_max, y_max; + int x_max, y_max; struct focaltech_hw_state state; }; @@ -126,17 +126,16 @@ static void focaltech_report_state(struct psmouse *psmouse) input_mt_slot(dev, i); input_mt_report_slot_state(dev, MT_TOOL_FINGER, active); if (active) { - unsigned int clamped_x, clamped_y; /* * The touchpad might report invalid data, so we clamp * the resulting values so that we do not confuse - * userspace. + * userspace or accumulate coordinate wind-up. */ - clamped_x = clamp(finger->x, 0U, priv->x_max); - clamped_y = clamp(finger->y, 0U, priv->y_max); - input_report_abs(dev, ABS_MT_POSITION_X, clamped_x); + finger->x = clamp(finger->x, 0, priv->x_max); + finger->y = clamp(finger->y, 0, priv->y_max); + input_report_abs(dev, ABS_MT_POSITION_X, finger->x); input_report_abs(dev, ABS_MT_POSITION_Y, - priv->y_max - clamped_y); + priv->y_max - finger->y); input_report_abs(dev, ABS_TOOL_WIDTH, state->width); } } From f6efbdcecdeeba4bf81fcebb3397a817f5e3ae7b Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Thu, 30 Jul 2026 02:09:33 +0900 Subject: [PATCH 49/61] Input: pmic8xxx-keypad - remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Link: https://patch.msgid.link/20260729171001.260698-2-ekffu200098@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/pmic8xxx-keypad.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/input/keyboard/pmic8xxx-keypad.c b/drivers/input/keyboard/pmic8xxx-keypad.c index 35d1aa2a22a5..c916e80e2f58 100644 --- a/drivers/input/keyboard/pmic8xxx-keypad.c +++ b/drivers/input/keyboard/pmic8xxx-keypad.c @@ -462,15 +462,9 @@ static int pmic8xxx_kp_enable(struct pmic8xxx_kp *kp) static int pmic8xxx_kp_disable(struct pmic8xxx_kp *kp) { - int rc; - kp->ctrl_reg &= ~KEYP_CTRL_KEYP_EN; - rc = regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg); - if (rc < 0) - return rc; - - return rc; + return regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg); } static int pmic8xxx_kp_open(struct input_dev *dev) From 5005fa144501d866c392f7fc3d84ba7c2939ee20 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Thu, 30 Jul 2026 02:09:34 +0900 Subject: [PATCH 50/61] Input: rmi_smbus - remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Link: https://patch.msgid.link/20260729171001.260698-3-ekffu200098@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_smbus.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c index 6de68c602558..3160714a514a 100644 --- a/drivers/input/rmi4/rmi_smbus.c +++ b/drivers/input/rmi4/rmi_smbus.c @@ -177,13 +177,8 @@ static int smb_block_read(struct rmi_transport_dev *xport, struct rmi_smb_xport *rmi_smb = container_of(xport, struct rmi_smb_xport, xport); struct i2c_client *client = rmi_smb->client; - int retval; - retval = i2c_smbus_read_block_data(client, commandcode, buf); - if (retval < 0) - return retval; - - return retval; + return i2c_smbus_read_block_data(client, commandcode, buf); } static int rmi_smb_read_block(struct rmi_transport_dev *xport, u16 rmiaddr, From f523729aa10bb721eced4784e20d11223255faf8 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Thu, 30 Jul 2026 02:09:35 +0900 Subject: [PATCH 51/61] Input: synaptics_i2c - return 0 explicitly on success error is always zero at the last return in synaptics_i2c_reg_set(). Explicitly return 0 on the success path instead of returning error, which is the preferred way when there are multiple failure points. No functional change. Signed-off-by: Sang-Heon Jeon Link: https://patch.msgid.link/20260729171001.260698-4-ekffu200098@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/synaptics_i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/mouse/synaptics_i2c.c b/drivers/input/mouse/synaptics_i2c.c index d4cf982f1263..66e833974c6d 100644 --- a/drivers/input/mouse/synaptics_i2c.c +++ b/drivers/input/mouse/synaptics_i2c.c @@ -261,7 +261,7 @@ static s32 synaptics_i2c_reg_set(struct i2c_client *client, u16 reg, u8 val) if (error) return error; - return error; + return 0; } static s32 synaptics_i2c_word_get(struct i2c_client *client, u16 reg) From e07c509ad6eae086e330db359a32663aabe2b0d3 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 5 Aug 2026 14:12:30 -0700 Subject: [PATCH 52/61] Input: gscps2 - supply PA-RISC keyboard keymap via device property Instead of hardcoding PA-RISC specific keycode tables into atkbd via compile-time inclusion, have the gscps2 PS/2 port driver attach a linux,keymap software node device property to the serio device when a keyboard port is registered. This allows atkbd to dynamically fetch and apply the custom keymap when probing the port using generic firmware property helpers, removing architecture-specific hacks from generic keyboard driver code. Co-locate the keymap definitions with the serio port driver by moving hpps2atkbd.h from drivers/input/keyboard/ to drivers/input/serio/. To handle the five conflicting keys on RDI PrecisionBook laptops without runtime model string checks or duplicate keymap tables in memory, add CONFIG_SERIO_GSCPS2_RDI_KEYCODES to drivers/input/serio/Kconfig and resolve the conflicting keycodes at compile time via preprocessor macros. Link: https://patch.msgid.link/am_9BvmZu9g4RlUM@google.com Acked-by: Helge Deller Tested-by: Helge Deller Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/Kconfig | 38 ------- drivers/input/keyboard/atkbd.c | 8 -- drivers/input/keyboard/hpps2atkbd.h | 110 -------------------- drivers/input/serio/Kconfig | 27 +++++ drivers/input/serio/gscps2.c | 155 ++++++++++++++++++++++++++-- 5 files changed, 172 insertions(+), 166 deletions(-) delete mode 100644 drivers/input/keyboard/hpps2atkbd.h diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 9d1019ba0245..d8c64f333462 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -119,44 +119,6 @@ config KEYBOARD_ATKBD To compile this driver as a module, choose M here: the module will be called atkbd. -config KEYBOARD_ATKBD_HP_KEYCODES - bool "Use HP keyboard scancodes" - depends on PARISC && KEYBOARD_ATKBD - default y - help - Say Y here if you have a PA-RISC machine and want to use an AT or - PS/2 keyboard, and your keyboard uses keycodes that are specific to - PA-RISC keyboards. - - Say N if you use a standard keyboard. - -config KEYBOARD_ATKBD_RDI_KEYCODES - bool "Use PrecisionBook keyboard scancodes" - depends on KEYBOARD_ATKBD_HP_KEYCODES - default n - help - If you have an RDI PrecisionBook, say Y here if you want to use its - built-in keyboard (as opposed to an external keyboard). - - The PrecisionBook has five keys that conflict with those used by most - AT and PS/2 keyboards. These are as follows: - - PrecisionBook Standard AT or PS/2 - - F1 F12 - Left Ctrl Left Alt - Caps Lock Left Ctrl - Right Ctrl Caps Lock - Left 102nd key (the key to the right of Left Shift) - - If you say N here, and use the PrecisionBook keyboard, then each key - in the left-hand column will be interpreted as the corresponding key - in the right-hand column. - - If you say Y here, and use an external keyboard, then each key in the - right-hand column will be interpreted as the key shown in the - left-hand column. - config KEYBOARD_QT1050 tristate "Microchip AT42QT1050 Touch Sensor Chip" depends on I2C diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index 8cb4dc6fb165..3509b58e6492 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -73,13 +73,6 @@ MODULE_PARM_DESC(terminal, "Enable break codes on an IBM Terminal keyboard conne #define ATKBD_KEYMAP_SIZE 512 static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = { -#ifdef CONFIG_KEYBOARD_ATKBD_HP_KEYCODES - -/* XXX: need a more general approach */ - -#include "hpps2atkbd.h" /* include the keyboard scancodes */ - -#else 0, 67, 65, 63, 61, 59, 60, 88,183, 68, 66, 64, 62, 15, 41,117, 184, 56, 42, 93, 29, 16, 2, 0,185, 0, 44, 31, 30, 17, 3, 0, 186, 46, 45, 32, 18, 5, 4, 95,187, 57, 47, 33, 20, 19, 6,183, @@ -99,7 +92,6 @@ static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = { 110,111,108,112,106,103, 0,119, 0,118,109, 0, 99,104,119, 0, 0, 0, 0, 65, 99, -#endif }; static const unsigned short atkbd_set3_keycode[ATKBD_KEYMAP_SIZE] = { diff --git a/drivers/input/keyboard/hpps2atkbd.h b/drivers/input/keyboard/hpps2atkbd.h deleted file mode 100644 index dc33f6945222..000000000000 --- a/drivers/input/keyboard/hpps2atkbd.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * drivers/input/keyboard/hpps2atkbd.h - * - * Copyright (c) 2004 Helge Deller - * Copyright (c) 2002 Laurent Canet - * Copyright (c) 2002 Thibaut Varene - * Copyright (c) 2000 Xavier Debacker - * - * HP PS/2 AT-compatible Keyboard, found in PA/RISC Workstations & Laptops - * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file "COPYING" in the main directory of this archive - * for more details. - */ - - -/* Is the keyboard an RDI PrecisionBook? */ -#ifndef CONFIG_KEYBOARD_ATKBD_RDI_KEYCODES -# define CONFLICT(x,y) x -#else -# define CONFLICT(x,y) y -#endif - -/* sadly RDI (Tadpole) decided to ship a different keyboard layout - than HP for their PS/2 laptop keyboard which leads to conflicting - keycodes between a normal HP PS/2 keyboard and a RDI Precisionbook. - HP: RDI: */ -#define C_07 CONFLICT( KEY_F12, KEY_F1 ) -#define C_11 CONFLICT( KEY_LEFTALT, KEY_LEFTCTRL ) -#define C_14 CONFLICT( KEY_LEFTCTRL, KEY_CAPSLOCK ) -#define C_58 CONFLICT( KEY_CAPSLOCK, KEY_RIGHTCTRL ) -#define C_61 CONFLICT( KEY_102ND, KEY_LEFT ) - -/* Raw SET 2 scancode table */ - -/* 00 */ KEY_RESERVED, KEY_F9, KEY_RESERVED, KEY_F5, KEY_F3, KEY_F1, KEY_F2, C_07, -/* 08 */ KEY_ESC, KEY_F10, KEY_F8, KEY_F6, KEY_F4, KEY_TAB, KEY_GRAVE, KEY_F2, -/* 10 */ KEY_RESERVED, C_11, KEY_LEFTSHIFT, KEY_RESERVED, C_14, KEY_Q, KEY_1, KEY_F3, -/* 18 */ KEY_RESERVED, KEY_LEFTALT, KEY_Z, KEY_S, KEY_A, KEY_W, KEY_2, KEY_F4, -/* 20 */ KEY_RESERVED, KEY_C, KEY_X, KEY_D, KEY_E, KEY_4, KEY_3, KEY_F5, -/* 28 */ KEY_RESERVED, KEY_SPACE, KEY_V, KEY_F, KEY_T, KEY_R, KEY_5, KEY_F6, -/* 30 */ KEY_RESERVED, KEY_N, KEY_B, KEY_H, KEY_G, KEY_Y, KEY_6, KEY_F7, -/* 38 */ KEY_RESERVED, KEY_RIGHTALT, KEY_M, KEY_J, KEY_U, KEY_7, KEY_8, KEY_F8, -/* 40 */ KEY_RESERVED, KEY_COMMA, KEY_K, KEY_I, KEY_O, KEY_0, KEY_9, KEY_F9, -/* 48 */ KEY_RESERVED, KEY_DOT, KEY_SLASH, KEY_L, KEY_SEMICOLON, KEY_P, KEY_MINUS, KEY_F10, -/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_APOSTROPHE,KEY_RESERVED, KEY_LEFTBRACE, KEY_EQUAL, KEY_F11, KEY_SYSRQ, -/* 58 */ C_58, KEY_RIGHTSHIFT,KEY_ENTER, KEY_RIGHTBRACE,KEY_BACKSLASH, KEY_BACKSLASH,KEY_F12, KEY_SCROLLLOCK, -/* 60 */ KEY_DOWN, C_61, KEY_PAUSE, KEY_UP, KEY_DELETE, KEY_END, KEY_BACKSPACE, KEY_INSERT, -/* 68 */ KEY_RESERVED, KEY_KP1, KEY_RIGHT, KEY_KP4, KEY_KP7, KEY_PAGEDOWN, KEY_HOME, KEY_PAGEUP, -/* 70 */ KEY_KP0, KEY_KPDOT, KEY_KP2, KEY_KP5, KEY_KP6, KEY_KP8, KEY_ESC, KEY_NUMLOCK, -/* 78 */ KEY_F11, KEY_KPPLUS, KEY_KP3, KEY_KPMINUS, KEY_KPASTERISK,KEY_KP9, KEY_SCROLLLOCK,KEY_102ND, -/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 90 */ KEY_RESERVED, KEY_RIGHTALT, 255, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_CAPSLOCK, KEY_RESERVED, KEY_LEFTMETA, -/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTMETA, -/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_COMPOSE, -/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPSLASH, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPENTER, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* e8 */ KEY_RESERVED, KEY_END, KEY_RESERVED, KEY_LEFT, KEY_HOME, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* f0 */ KEY_INSERT, KEY_DELETE, KEY_DOWN, KEY_RESERVED, KEY_RIGHT, KEY_UP, KEY_RESERVED, KEY_PAUSE, -/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_PAGEDOWN, KEY_RESERVED, KEY_SYSRQ, KEY_PAGEUP, KEY_RESERVED, KEY_RESERVED, - -/* These are offset for escaped keycodes: */ - -/* 00 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_F7, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 08 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 10 */ KEY_RESERVED, KEY_RIGHTALT, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 18 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 20 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 28 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 30 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 38 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 40 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 48 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 58 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 60 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 68 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 70 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 78 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 90 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* e8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* f0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, -/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED - -#undef CONFLICT -#undef C_07 -#undef C_11 -#undef C_14 -#undef C_58 -#undef C_61 - diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig index bacab1f58400..087f0203fff9 100644 --- a/drivers/input/serio/Kconfig +++ b/drivers/input/serio/Kconfig @@ -106,6 +106,33 @@ config SERIO_GSCPS2 To compile this driver as a module, choose M here: the module will be called gscps2. +config SERIO_GSCPS2_RDI_KEYCODES + bool "Use PrecisionBook keyboard scancodes" + depends on SERIO_GSCPS2 + default n + help + If you have an RDI PrecisionBook, say Y here if you want to use its + built-in keyboard (as opposed to an external keyboard). + + The PrecisionBook has five keys that conflict with those used by most + AT and PS/2 keyboards. These are as follows: + + PrecisionBook Standard AT or PS/2 + + F1 F12 + Left Ctrl Left Alt + Caps Lock Left Ctrl + Right Ctrl Caps Lock + Left 102nd key (the key to the right of Left Shift) + + If you say N here, and use the PrecisionBook keyboard, then each key + in the left-hand column will be interpreted as the corresponding key + in the right-hand column. + + If you say Y here, and use an external keyboard, then each key in the + right-hand column will be interpreted as the key shown in the + left-hand column. + config HP_SDC tristate "HP System Device Controller i8042 Support" depends on (GSC || HP300) && SERIO diff --git a/drivers/input/serio/gscps2.c b/drivers/input/serio/gscps2.c index bf9b993f5733..43453ec533b2 100644 --- a/drivers/input/serio/gscps2.c +++ b/drivers/input/serio/gscps2.c @@ -22,18 +22,15 @@ * was usable/enabled ?) */ -#include -#include -#include -#include +#include #include #include -#include -#include +#include #include +#include +#include #include -#include #include MODULE_AUTHOR("Laurent Canet , Thibaut Varene , Helge Deller "); @@ -80,6 +77,116 @@ MODULE_LICENSE("GPL"); #define GSC_ID_KEYBOARD 0 /* device ID values */ #define GSC_ID_MOUSE 1 +#ifndef CONFIG_SERIO_GSCPS2_RDI_KEYCODES +# define CONFLICT(x, y) x +#else +# define CONFLICT(x, y) y +#endif + +/* + * Sadly RDI (Tadpole) decided to ship a different keyboard layout + * than HP for their PS/2 laptop keyboard which leads to conflicting + * keycodes between a normal HP PS/2 keyboard and a RDI PrecisionBook. + * HP: RDI: + */ +#define C_07 CONFLICT(KEY_F12, KEY_F1) +#define C_11 CONFLICT(KEY_LEFTALT, KEY_LEFTCTRL) +#define C_14 CONFLICT(KEY_LEFTCTRL, KEY_CAPSLOCK) +#define C_58 CONFLICT(KEY_CAPSLOCK, KEY_RIGHTCTRL) +#define C_61 CONFLICT(KEY_102ND, KEY_LEFT) + +/* + * Special keycode value recognized by atkbd (ATKBD_KEY_NULL) to silently + * discard scancodes without generating input events or "unknown key" warnings. + */ +#define KEY_NULL 255 + +#define KEYMAP_ENTRY(scancode, keycode) (((scancode) << 16) | (keycode)) + +static const u32 gscps2_keymap[] = { + KEYMAP_ENTRY(0x01, KEY_F9), KEYMAP_ENTRY(0x03, KEY_F5), + KEYMAP_ENTRY(0x04, KEY_F3), KEYMAP_ENTRY(0x05, KEY_F1), + KEYMAP_ENTRY(0x06, KEY_F2), KEYMAP_ENTRY(0x07, C_07), + KEYMAP_ENTRY(0x08, KEY_ESC), KEYMAP_ENTRY(0x09, KEY_F10), + KEYMAP_ENTRY(0x0a, KEY_F8), KEYMAP_ENTRY(0x0b, KEY_F6), + KEYMAP_ENTRY(0x0c, KEY_F4), KEYMAP_ENTRY(0x0d, KEY_TAB), + KEYMAP_ENTRY(0x0e, KEY_GRAVE), KEYMAP_ENTRY(0x0f, KEY_F2), + KEYMAP_ENTRY(0x11, C_11), KEYMAP_ENTRY(0x12, KEY_LEFTSHIFT), + KEYMAP_ENTRY(0x14, C_14), KEYMAP_ENTRY(0x15, KEY_Q), + KEYMAP_ENTRY(0x16, KEY_1), KEYMAP_ENTRY(0x17, KEY_F3), + KEYMAP_ENTRY(0x19, KEY_LEFTALT), KEYMAP_ENTRY(0x1a, KEY_Z), + KEYMAP_ENTRY(0x1b, KEY_S), KEYMAP_ENTRY(0x1c, KEY_A), + KEYMAP_ENTRY(0x1d, KEY_W), KEYMAP_ENTRY(0x1e, KEY_2), + KEYMAP_ENTRY(0x1f, KEY_F4), KEYMAP_ENTRY(0x21, KEY_C), + KEYMAP_ENTRY(0x22, KEY_X), KEYMAP_ENTRY(0x23, KEY_D), + KEYMAP_ENTRY(0x24, KEY_E), KEYMAP_ENTRY(0x25, KEY_4), + KEYMAP_ENTRY(0x26, KEY_3), KEYMAP_ENTRY(0x27, KEY_F5), + KEYMAP_ENTRY(0x29, KEY_SPACE), KEYMAP_ENTRY(0x2a, KEY_V), + KEYMAP_ENTRY(0x2b, KEY_F), KEYMAP_ENTRY(0x2c, KEY_T), + KEYMAP_ENTRY(0x2d, KEY_R), KEYMAP_ENTRY(0x2e, KEY_5), + KEYMAP_ENTRY(0x2f, KEY_F6), KEYMAP_ENTRY(0x31, KEY_N), + KEYMAP_ENTRY(0x32, KEY_B), KEYMAP_ENTRY(0x33, KEY_H), + KEYMAP_ENTRY(0x34, KEY_G), KEYMAP_ENTRY(0x35, KEY_Y), + KEYMAP_ENTRY(0x36, KEY_6), KEYMAP_ENTRY(0x37, KEY_F7), + KEYMAP_ENTRY(0x39, KEY_RIGHTALT), KEYMAP_ENTRY(0x3a, KEY_M), + KEYMAP_ENTRY(0x3b, KEY_J), KEYMAP_ENTRY(0x3c, KEY_U), + KEYMAP_ENTRY(0x3d, KEY_7), KEYMAP_ENTRY(0x3e, KEY_8), + KEYMAP_ENTRY(0x3f, KEY_F8), KEYMAP_ENTRY(0x41, KEY_COMMA), + KEYMAP_ENTRY(0x42, KEY_K), KEYMAP_ENTRY(0x43, KEY_I), + KEYMAP_ENTRY(0x44, KEY_O), KEYMAP_ENTRY(0x45, KEY_0), + KEYMAP_ENTRY(0x46, KEY_9), KEYMAP_ENTRY(0x47, KEY_F9), + KEYMAP_ENTRY(0x49, KEY_DOT), KEYMAP_ENTRY(0x4a, KEY_SLASH), + KEYMAP_ENTRY(0x4b, KEY_L), KEYMAP_ENTRY(0x4c, KEY_SEMICOLON), + KEYMAP_ENTRY(0x4d, KEY_P), KEYMAP_ENTRY(0x4e, KEY_MINUS), + KEYMAP_ENTRY(0x4f, KEY_F10), KEYMAP_ENTRY(0x52, KEY_APOSTROPHE), + KEYMAP_ENTRY(0x54, KEY_LEFTBRACE), KEYMAP_ENTRY(0x55, KEY_EQUAL), + KEYMAP_ENTRY(0x56, KEY_F11), KEYMAP_ENTRY(0x57, KEY_SYSRQ), + KEYMAP_ENTRY(0x58, C_58), KEYMAP_ENTRY(0x59, KEY_RIGHTSHIFT), + KEYMAP_ENTRY(0x5a, KEY_ENTER), KEYMAP_ENTRY(0x5b, KEY_RIGHTBRACE), + KEYMAP_ENTRY(0x5c, KEY_BACKSLASH), KEYMAP_ENTRY(0x5d, KEY_BACKSLASH), + KEYMAP_ENTRY(0x5e, KEY_F12), KEYMAP_ENTRY(0x5f, KEY_SCROLLLOCK), + KEYMAP_ENTRY(0x60, KEY_DOWN), KEYMAP_ENTRY(0x61, C_61), + KEYMAP_ENTRY(0x62, KEY_PAUSE), KEYMAP_ENTRY(0x63, KEY_UP), + KEYMAP_ENTRY(0x64, KEY_DELETE), KEYMAP_ENTRY(0x65, KEY_END), + KEYMAP_ENTRY(0x66, KEY_BACKSPACE), KEYMAP_ENTRY(0x67, KEY_INSERT), + KEYMAP_ENTRY(0x69, KEY_KP1), KEYMAP_ENTRY(0x6a, KEY_RIGHT), + KEYMAP_ENTRY(0x6b, KEY_KP4), KEYMAP_ENTRY(0x6c, KEY_KP7), + KEYMAP_ENTRY(0x6d, KEY_PAGEDOWN), KEYMAP_ENTRY(0x6e, KEY_HOME), + KEYMAP_ENTRY(0x6f, KEY_PAGEUP), KEYMAP_ENTRY(0x70, KEY_KP0), + KEYMAP_ENTRY(0x71, KEY_KPDOT), KEYMAP_ENTRY(0x72, KEY_KP2), + KEYMAP_ENTRY(0x73, KEY_KP5), KEYMAP_ENTRY(0x74, KEY_KP6), + KEYMAP_ENTRY(0x75, KEY_KP8), KEYMAP_ENTRY(0x76, KEY_ESC), + KEYMAP_ENTRY(0x77, KEY_NUMLOCK), KEYMAP_ENTRY(0x78, KEY_F11), + KEYMAP_ENTRY(0x79, KEY_KPPLUS), KEYMAP_ENTRY(0x7a, KEY_KP3), + KEYMAP_ENTRY(0x7b, KEY_KPMINUS), KEYMAP_ENTRY(0x7c, KEY_KPASTERISK), + KEYMAP_ENTRY(0x7d, KEY_KP9), KEYMAP_ENTRY(0x7e, KEY_SCROLLLOCK), + KEYMAP_ENTRY(0x7f, KEY_102ND), KEYMAP_ENTRY(0x91, KEY_RIGHTALT), + KEYMAP_ENTRY(0x92, KEY_NULL), KEYMAP_ENTRY(0x94, KEY_RIGHTCTRL), + KEYMAP_ENTRY(0x9d, KEY_CAPSLOCK), KEYMAP_ENTRY(0x9f, KEY_LEFTMETA), + KEYMAP_ENTRY(0xa7, KEY_RIGHTMETA), KEYMAP_ENTRY(0xaf, KEY_COMPOSE), + KEYMAP_ENTRY(0xca, KEY_KPSLASH), KEYMAP_ENTRY(0xda, KEY_KPENTER), + KEYMAP_ENTRY(0xe9, KEY_END), KEYMAP_ENTRY(0xeb, KEY_LEFT), + KEYMAP_ENTRY(0xec, KEY_HOME), KEYMAP_ENTRY(0xf0, KEY_INSERT), + KEYMAP_ENTRY(0xf1, KEY_DELETE), KEYMAP_ENTRY(0xf2, KEY_DOWN), + KEYMAP_ENTRY(0xf4, KEY_RIGHT), KEYMAP_ENTRY(0xf5, KEY_UP), + KEYMAP_ENTRY(0xf7, KEY_PAUSE), KEYMAP_ENTRY(0xfa, KEY_PAGEDOWN), + KEYMAP_ENTRY(0xfc, KEY_SYSRQ), KEYMAP_ENTRY(0xfd, KEY_PAGEUP), + + /* Escaped keycodes */ + KEYMAP_ENTRY(0x103, KEY_F7), KEYMAP_ENTRY(0x10b, KEY_LEFTMETA), + KEYMAP_ENTRY(0x10c, KEY_RIGHTMETA), KEYMAP_ENTRY(0x111, KEY_RIGHTALT), + KEYMAP_ENTRY(0x114, KEY_RIGHTCTRL), +}; + +static const struct property_entry gscps2_props[] = { + PROPERTY_ENTRY_U32_ARRAY("linux,keymap", gscps2_keymap), + { } +}; + +static const struct software_node gscps2_keyboard_node = { + .name = "gscps2-keyboard", + .properties = gscps2_props, +}; static irqreturn_t gscps2_interrupt(int irq, void *dev); @@ -398,6 +505,17 @@ static int __init gscps2_probe(struct parisc_device *dev) goto fail; #endif + if (ps2port->id == GSC_ID_KEYBOARD) { + ret = device_add_software_node(&serio->dev, + &gscps2_keyboard_node); + if (ret) { + dev_err(&dev->dev, + "failed to add software node for keyboard: %d\n", + ret); + goto fail; + } + } + pr_info("serio: %s port at 0x%08lx irq %d @ %s\n", ps2port->port->name, hpa, @@ -411,11 +529,16 @@ static int __init gscps2_probe(struct parisc_device *dev) return 0; fail: + if (ps2port->id == GSC_ID_KEYBOARD) + device_remove_software_node(&serio->dev); + free_irq(dev->irq, ps2port); fail_miserably: iounmap(ps2port->addr); +#if 0 release_mem_region(dev->hpa.start, GSC_STATUS + 4); +#endif fail_nomem: kfree(ps2port); @@ -434,6 +557,9 @@ static void __exit gscps2_remove(struct parisc_device *dev) { struct gscps2port *ps2port = dev_get_drvdata(&dev->dev); + if (ps2port->id == GSC_ID_KEYBOARD) + device_remove_software_node(&ps2port->port->dev); + serio_unregister_port(ps2port->port); free_irq(dev->irq, ps2port); gscps2_flush(ps2port); @@ -465,16 +591,25 @@ static struct parisc_driver parisc_ps2_driver __refdata = { static int __init gscps2_init(void) { - register_parisc_driver(&parisc_ps2_driver); - return 0; + int error; + + error = software_node_register(&gscps2_keyboard_node); + if (error) + return error; + + error = register_parisc_driver(&parisc_ps2_driver); + if (error) + software_node_unregister(&gscps2_keyboard_node); + + return error; } static void __exit gscps2_exit(void) { unregister_parisc_driver(&parisc_ps2_driver); + software_node_unregister(&gscps2_keyboard_node); } module_init(gscps2_init); module_exit(gscps2_exit); - From 35f0a0dceddce3a6008a24716bd02766e511ae49 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 2 Aug 2026 17:52:01 -0700 Subject: [PATCH 53/61] Input: ensure device is ready before delivering events When a device is opened via input_open_device(), the driver's open() callback is invoked. Some drivers, like cm109, submit URBs or perform other hardware initialization in their open() callbacks. However, the input core does not prevent dev->event() from being called concurrently during the driver's open() execution. For instance, if a console beep occurs, the kbd handler might inject an EV_SND event. This can lead to double list_add BUGs if the driver submits the same URB in both open() and event() paths without adequate synchronization. To fix this, introduce a ready flag in the input_dev structure. For complex devices (where dev->open is defined), this flag is set to true only after the driver's open() method successfully completes. The core now checks ready in input_event_dispose() and input_dev_toggle() to prevent events from reaching the hardware before it is fully prepared. For simple devices (no open callback), events are delivered immediately. We also replay the logical state in input_open_device() by calling input_dev_toggle() right after marking the device ready, ensuring no events are permanently lost. In the inhibit path, we ensure that physical feedback (LEDs/sounds) is turned off before the device is closed, and we synchronize the inhibited state transition under the event lock to prevent races with incoming events. Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260803005210.1251102-1-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/input.c | 103 +++++++++++++++++++++++++++--------------- include/linux/input.h | 12 +++-- 2 files changed, 74 insertions(+), 41 deletions(-) diff --git a/drivers/input/input.c b/drivers/input/input.c index cf6fecea79b8..e57d1023d262 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -318,7 +318,7 @@ static int input_get_disposition(struct input_dev *dev, static void input_event_dispose(struct input_dev *dev, int disposition, unsigned int type, unsigned int code, int value) { - if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) + if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event && dev->ready) dev->event(dev, type, code, value); if (disposition & INPUT_PASS_TO_HANDLERS) { @@ -568,6 +568,48 @@ void input_release_device(struct input_handle *handle) } EXPORT_SYMBOL(input_release_device); +#define INPUT_DO_TOGGLE(dev, type, bits, on) \ + do { \ + int i; \ + bool active; \ + \ + if (!test_bit(EV_##type, dev->evbit)) \ + break; \ + \ + for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ + active = test_bit(i, dev->bits); \ + if (!active && !on) \ + continue; \ + \ + dev->event(dev, EV_##type, i, on ? active : 0); \ + } \ + } while (0) + +/* + * Iterate through the logical state of the input device (LEDs, sounds, + * auto-repeat) and explicitly push that state down to the hardware + * via dev->event() to match the current logical state (if activate is true), + * or forcibly turn off all feedback like LEDs and sounds during teardown + * or suspend (if activate is false). + * + * Primarily used as a state-replay mechanism after a device is opened + * or uninhibited, as events might have been dropped by the core while the + * hardware was not marked as ready. + */ +static void input_dev_toggle(struct input_dev *dev, bool activate) +{ + if (!dev->event || !dev->ready) + return; + + INPUT_DO_TOGGLE(dev, LED, led, activate); + INPUT_DO_TOGGLE(dev, SND, snd, activate); + + if (activate && test_bit(EV_REP, dev->evbit)) { + dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); + dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); + } +} + /** * input_open_device - open input device * @handle: handle through which device is being accessed @@ -611,6 +653,11 @@ int input_open_device(struct input_handle *handle) } } + scoped_guard(spinlock_irq, &dev->event_lock) { + dev->ready = true; + input_dev_toggle(dev, true); + } + if (dev->poller) input_dev_poller_start(dev->poller); } @@ -651,6 +698,12 @@ void input_close_device(struct input_handle *handle) if (!--dev->users && !dev->inhibited) { if (dev->poller) input_dev_poller_stop(dev->poller); + + scoped_guard(spinlock_irq, &dev->event_lock) { + input_dev_toggle(dev, false); + dev->ready = false; + } + if (dev->close) dev->close(dev); } @@ -1702,37 +1755,6 @@ static int input_dev_uevent(const struct device *device, struct kobj_uevent_env return 0; } -#define INPUT_DO_TOGGLE(dev, type, bits, on) \ - do { \ - int i; \ - bool active; \ - \ - if (!test_bit(EV_##type, dev->evbit)) \ - break; \ - \ - for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ - active = test_bit(i, dev->bits); \ - if (!active && !on) \ - continue; \ - \ - dev->event(dev, EV_##type, i, on ? active : 0); \ - } \ - } while (0) - -static void input_dev_toggle(struct input_dev *dev, bool activate) -{ - if (!dev->event) - return; - - INPUT_DO_TOGGLE(dev, LED, led, activate); - INPUT_DO_TOGGLE(dev, SND, snd, activate); - - if (activate && test_bit(EV_REP, dev->evbit)) { - dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); - dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); - } -} - /** * input_reset_device() - reset/restore the state of input device * @dev: input device whose state needs to be reset @@ -1760,21 +1782,25 @@ static int input_inhibit_device(struct input_dev *dev) return 0; if (dev->users) { - if (dev->close) - dev->close(dev); if (dev->poller) input_dev_poller_stop(dev->poller); + + scoped_guard(spinlock_irq, &dev->event_lock) { + input_dev_toggle(dev, false); + dev->ready = false; + } + + if (dev->close) + dev->close(dev); } scoped_guard(spinlock_irq, &dev->event_lock) { input_mt_release_slots(dev); input_dev_release_keys(dev); input_handle_event(dev, EV_SYN, SYN_REPORT, 1); - input_dev_toggle(dev, false); + dev->inhibited = true; } - dev->inhibited = true; - return 0; } @@ -1793,6 +1819,9 @@ static int input_uninhibit_device(struct input_dev *dev) if (error) return error; } + scoped_guard(spinlock_irq, &dev->event_lock) + dev->ready = true; + if (dev->poller) input_dev_poller_start(dev->poller); } diff --git a/include/linux/input.h b/include/linux/input.h index 76f7aa226202..f147d27e6d1d 100644 --- a/include/linux/input.h +++ b/include/linux/input.h @@ -128,11 +128,14 @@ enum input_clock_type { * @devres_managed: indicates that devices is managed with devres framework * and needs not be explicitly unregistered or freed. * @timestamp: storage for a timestamp set by input_set_timestamp called - * by a driver + * by a driver * @inhibited: indicates that the input device is inhibited. If that is - * the case then input core ignores any events generated by the device. - * Device's close() is called when it is being inhibited and its open() - * is called when it is being uninhibited. + * the case then input core ignores any events generated by the device. + * Device's close() is called when it is being inhibited and its open() + * is called when it is being uninhibited. + * @ready: indicates that the device has been successfully opened and is + * prepared to process events (like LEDs or sounds) sent from the + * input core. */ struct input_dev { const char *name; @@ -209,6 +212,7 @@ struct input_dev { ktime_t timestamp[INPUT_CLK_MAX]; bool inhibited; + bool ready; }; #define to_input_dev(d) container_of(d, struct input_dev, dev) From 34135f0540b480d63d76f9ca82c032a92e1f7fa6 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 2 Aug 2026 17:52:02 -0700 Subject: [PATCH 54/61] Input: clear inhibited flag before re-opening device on uninhibit When uninhibiting a device, we previously called dev->open() and started the poller before clearing dev->inhibited. Since drivers (like gpio_keys) often report initial state during open(), and pollers report events immediately upon starting, these initial events were dropped by input_get_disposition() because dev->inhibited was still true. Fix this by clearing dev->inhibited before calling dev->open(), ensuring initial events are delivered to handlers, and restoring dev->inhibited = true if dev->open() fails. Fixes: a181616487db ("Input: Add "inhibited" property") Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260803005210.1251102-2-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/input.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/input/input.c b/drivers/input/input.c index e57d1023d262..e4f8c2067b84 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -1813,24 +1813,26 @@ static int input_uninhibit_device(struct input_dev *dev) if (!dev->inhibited) return 0; + dev->inhibited = false; + if (dev->users) { if (dev->open) { error = dev->open(dev); - if (error) + if (error) { + dev->inhibited = true; return error; + } } scoped_guard(spinlock_irq, &dev->event_lock) dev->ready = true; - - if (dev->poller) - input_dev_poller_start(dev->poller); } - dev->inhibited = false; - scoped_guard(spinlock_irq, &dev->event_lock) input_dev_toggle(dev, true); + if (dev->users && dev->poller) + input_dev_poller_start(dev->poller); + return 0; } From ceda733d49b8e94f2e7eac9b74853e635749c320 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 2 Aug 2026 17:52:03 -0700 Subject: [PATCH 55/61] Input: call handler->start() when uninhibiting device When an input device is inhibited via input_inhibit_device(), the driver is closed and physical feedback (like LEDs and sounds) is toggled off. However, from the input core's perspective, the handles remain open. When the device is later uninhibited, the driver is re-opened. While the core restores simple LED states via input_dev_toggle(), complex handlers (such as vt/keyboard) may need to re-synchronize their broader logical state with the hardware. Fixes: a181616487db ("Input: Add "inhibited" property") Assisted-by: Antigravity:gemini-3.5-flash Link: https://patch.msgid.link/20260803005210.1251102-3-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/input.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/input/input.c b/drivers/input/input.c index e4f8c2067b84..47886a394c6b 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -1806,6 +1806,7 @@ static int input_inhibit_device(struct input_dev *dev) static int input_uninhibit_device(struct input_dev *dev) { + struct input_handle *handle; int error; guard(mutex)(&dev->mutex); @@ -1833,6 +1834,11 @@ static int input_uninhibit_device(struct input_dev *dev) if (dev->users && dev->poller) input_dev_poller_start(dev->poller); + list_for_each_entry(handle, &dev->h_list, d_node) { + if (handle->open && handle->handler->start) + handle->handler->start(handle); + } + return 0; } From 876848ad2203d225e927a5f3373900bcbb73c9c5 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 2 Aug 2026 17:52:04 -0700 Subject: [PATCH 56/61] Input: defer handler's start() until device is opened When registering an input handle, handler->start() is currently called immediately. However, the input device might not be fully opened or ready to process events at this stage, meaning any state synchronization events (like setting LED states) injected by the handler's start method might be dropped. Move the handler->start() invocation to input_open_device(). If it is the first handle opening the device, start() is called after the driver's open() method has successfully completed and the device is fully prepared. To facilitate this, factor out the device startup logic (calling driver's open and starting polling) into input_start_device(). For passive observer handlers, their start() method is also deferred until the handle is opened. Since opening a passive observer handle does not start the underlying hardware device, their start() method is called immediately upon opening, regardless of whether the device is active. Fixes: c7e8dc6ee6d5 ("Input: add start() method to input handlers") Link: https://patch.msgid.link/20260803005210.1251102-4-dmitry.torokhov@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/input.c | 55 ++++++++++++++++++++++++------------------- include/linux/input.h | 5 ++-- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/drivers/input/input.c b/drivers/input/input.c index 47886a394c6b..c9f480629099 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -610,6 +610,33 @@ static void input_dev_toggle(struct input_dev *dev, bool activate) } } +static int input_start_device(struct input_dev *dev) +{ + int error; + + lockdep_assert_held(&dev->mutex); + + if (dev->users++ == 0 && !dev->inhibited) { + if (dev->open) { + error = dev->open(dev); + if (error) { + dev->users--; + return error; + } + } + + scoped_guard(spinlock_irq, &dev->event_lock) { + dev->ready = true; + input_dev_toggle(dev, true); + } + + if (dev->poller) + input_dev_poller_start(dev->poller); + } + + return 0; +} + /** * input_open_device - open input device * @handle: handle through which device is being accessed @@ -628,21 +655,9 @@ int input_open_device(struct input_handle *handle) handle->open++; - if (handle->handler->passive_observer) - return 0; - - if (dev->users++ || dev->inhibited) { - /* - * Device is already opened and/or inhibited, - * so we can exit immediately and report success. - */ - return 0; - } - - if (dev->open) { - error = dev->open(dev); + if (!handle->handler->passive_observer) { + error = input_start_device(dev); if (error) { - dev->users--; handle->open--; /* * Make sure we are not delivering any more @@ -653,13 +668,8 @@ int input_open_device(struct input_handle *handle) } } - scoped_guard(spinlock_irq, &dev->event_lock) { - dev->ready = true; - input_dev_toggle(dev, true); - } - - if (dev->poller) - input_dev_poller_start(dev->poller); + if (handle->open == 1 && handle->handler->start) + handle->handler->start(handle); } return 0; @@ -2692,9 +2702,6 @@ int input_register_handle(struct input_handle *handle) */ list_add_tail_rcu(&handle->h_node, &handler->h_list); - if (handler->start) - handler->start(handle); - return 0; } EXPORT_SYMBOL(input_register_handle); diff --git a/include/linux/input.h b/include/linux/input.h index f147d27e6d1d..0ee5f32de08a 100644 --- a/include/linux/input.h +++ b/include/linux/input.h @@ -288,8 +288,9 @@ struct input_handle; * @connect: called when attaching a handler to an input device * @disconnect: disconnects a handler from input device * @start: starts handler for given handle. This function is called by - * input core right after connect() method and also when a process - * that "grabbed" a device releases it + * input core when device is open and ready to process events, + * and also when device is uninhibited or when a process that "grabbed" + * a device releases it * @passive_observer: set to %true by drivers only interested in observing * data stream from devices if there are other users present. Such * drivers will not result in starting underlying hardware device From 8c3ff3164b6ec28f2977f71645a5e6d7fde06924 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 3 Aug 2026 16:48:11 -0700 Subject: [PATCH 57/61] Input: reject inhibit and uninhibit requests on unregistering devices When an input device is being unregistered via input_unregister_device(), input_disconnect_device() sets dev->going_away = true under dev->mutex and releases the mutex. If a concurrent sysfs write to the inhibited attribute executes input_inhibit_device() or input_uninhibit_device(), it acquires dev->mutex. Because neither function checks dev->going_away (unlike input_open_device()), input_uninhibit_device() proceeds to call dev->open() and start polling on a device that is in the middle of being unregistered and torn down. Fix this by checking dev->going_away in input_inhibit_device() and input_uninhibit_device() under dev->mutex and returning -ENODEV if the device is going away. Fixes: a181616487db ("Input: Add "inhibited" property") Reported-by: sashiko-bot@kernel.org Assisted-by: Antigravity:gemini-3.6-flash Link: https://patch.msgid.link/anEolqA35rGei9ql@google.com Signed-off-by: Dmitry Torokhov --- drivers/input/input.c | 6 ++++++ include/linux/input.h | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/input/input.c b/drivers/input/input.c index c9f480629099..78c10eea7328 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -1788,6 +1788,9 @@ static int input_inhibit_device(struct input_dev *dev) { guard(mutex)(&dev->mutex); + if (dev->going_away) + return -ENODEV; + if (dev->inhibited) return 0; @@ -1821,6 +1824,9 @@ static int input_uninhibit_device(struct input_dev *dev) guard(mutex)(&dev->mutex); + if (dev->going_away) + return -ENODEV; + if (!dev->inhibited) return 0; diff --git a/include/linux/input.h b/include/linux/input.h index 0ee5f32de08a..3381608127f7 100644 --- a/include/linux/input.h +++ b/include/linux/input.h @@ -117,7 +117,8 @@ enum input_clock_type { * user opens device and dev->close() is called when the very * last user closes the device * @going_away: marks devices that are in a middle of unregistering and - * causes input_open_device*() fail with -ENODEV. + * causes input_open_device() and input_inhibit/uninhibit_device() + * to fail with -ENODEV. * @dev: driver model's view of this device * @h_list: list of input handles associated with the device. When * accessing the list dev->mutex must be held From 785a490556b549493a6a25409f16026ce0959b02 Mon Sep 17 00:00:00 2001 From: Longlong Xia Date: Sun, 9 Aug 2026 22:29:28 +0800 Subject: [PATCH 58/61] Input: elan_i2c - use device-id/acpi.h for ACPI IDs elan-i2c-ids.h only needs struct acpi_device_id from the ACPI device ID definitions. The MODULE_DEVICE_TABLE() user already includes . Include instead of the broader header. Assisted-by: Codex:GPT-5 Signed-off-by: Longlong Xia Link: https://patch.msgid.link/20260809142928.4031270-1-xialonglong2025@163.com Signed-off-by: Dmitry Torokhov --- include/linux/input/elan-i2c-ids.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/input/elan-i2c-ids.h b/include/linux/input/elan-i2c-ids.h index 51cca17ee94c..874bf0ab500c 100644 --- a/include/linux/input/elan-i2c-ids.h +++ b/include/linux/input/elan-i2c-ids.h @@ -18,7 +18,7 @@ #ifndef __ELAN_I2C_IDS_H #define __ELAN_I2C_IDS_H -#include +#include static const struct acpi_device_id elan_acpi_id[] = { { "ELAN0000", 0 }, From ad255410cbfbbd8fb7ab3f86b9946e2b9fa840d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jingle=20Wu=20=E5=90=B3=E9=87=91=E5=9C=8B?= Date: Wed, 29 Jul 2026 06:19:19 +0000 Subject: [PATCH 59/61] Input: elan_i2c - optimize update speed for IC Type 0x19. Reduce update time by optimizing the update sequence and removing unnecessary delays. Signed-off-by: jingle.wu@emc.com.tw Link: https://patch.msgid.link/KL1PR01MB5116A253A126179473EDB7ACDCCA2@KL1PR01MB5116.apcprd01.prod.exchangelabs.com Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/elan_i2c.h | 3 ++- drivers/input/mouse/elan_i2c_core.c | 20 +++++++++++++++----- drivers/input/mouse/elan_i2c_i2c.c | 5 +++-- drivers/input/mouse/elan_i2c_smbus.c | 3 ++- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/drivers/input/mouse/elan_i2c.h b/drivers/input/mouse/elan_i2c.h index 3c84deefa327..555824e57743 100644 --- a/drivers/input/mouse/elan_i2c.h +++ b/drivers/input/mouse/elan_i2c.h @@ -102,7 +102,8 @@ struct elan_transport_ops { int (*prepare_fw_update)(struct i2c_client *client, u16 ic_type, u8 iap_version, u16 fw_page_size); int (*write_fw_block)(struct i2c_client *client, u16 fw_page_size, - const u8 *page, u16 checksum, int idx); + u16 fw_page_delay, const u8 *page, u16 checksum, + int idx); int (*finish_fw_update)(struct i2c_client *client, struct completion *reset_done); diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c index f93dd545d66b..46421687fb35 100644 --- a/drivers/input/mouse/elan_i2c_core.c +++ b/drivers/input/mouse/elan_i2c_core.c @@ -86,6 +86,7 @@ struct elan_tp_data { u16 ic_type; u16 fw_validpage_count; u16 fw_page_size; + u16 fw_page_delay; u32 fw_signature_address; u8 min_baseline; @@ -127,8 +128,11 @@ static u32 elan_i2c_lookup_quirks(u16 ic_type, u16 product_id) } static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, - u32 *signature_address, u16 *page_size) + u32 *signature_address, u16 *page_size, + u16 *page_delay) { + *page_delay = 30; + switch (ic_type) { case 0x00: case 0x06: @@ -164,6 +168,7 @@ static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, break; case 0x19: *validpage_count = 2032; + *page_delay = 10; break; default: /* unknown ic type clear value */ @@ -179,6 +184,7 @@ static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, if ((ic_type == 0x14 || ic_type == 0x15) && iap_version >= 2) { *validpage_count /= 8; *page_size = ETP_FW_PAGE_SIZE_512; + *page_delay = 50; } else if (ic_type >= 0x0D && iap_version >= 1) { *validpage_count /= 2; *page_size = ETP_FW_PAGE_SIZE_128; @@ -368,7 +374,8 @@ static int elan_query_device_info(struct elan_tp_data *data) error = elan_get_fwinfo(data->ic_type, data->iap_version, &data->fw_validpage_count, &data->fw_signature_address, - &data->fw_page_size); + &data->fw_page_size, + &data->fw_page_delay); if (error) dev_warn(&data->client->dev, "unexpected iap version %#04x (ic type: %#04x), firmware update will not work\n", @@ -479,14 +486,16 @@ static int elan_query_device_parameters(struct elan_tp_data *data) ********************************************************** */ static int elan_write_fw_block(struct elan_tp_data *data, u16 page_size, - const u8 *page, u16 checksum, int idx) + u16 page_delay, const u8 *page, u16 checksum, + int idx) { int retry = ETP_RETRY_COUNT; int error; do { error = data->ops->write_fw_block(data->client, page_size, - page, checksum, idx); + page_delay, page, checksum, + idx); if (!error) return 0; @@ -525,7 +534,8 @@ static int __elan_update_firmware(struct elan_tp_data *data, checksum += ((page[j + 1] << 8) | page[j]); error = elan_write_fw_block(data, data->fw_page_size, - page, checksum, i); + data->fw_page_delay, page, checksum, + i); if (error) { dev_err(dev, "write page %d fail: %d\n", i, error); return error; diff --git a/drivers/input/mouse/elan_i2c_i2c.c b/drivers/input/mouse/elan_i2c_i2c.c index 88d4070d4b44..56a745c0555b 100644 --- a/drivers/input/mouse/elan_i2c_i2c.c +++ b/drivers/input/mouse/elan_i2c_i2c.c @@ -625,7 +625,8 @@ static int elan_i2c_prepare_fw_update(struct i2c_client *client, u16 ic_type, } static int elan_i2c_write_fw_block(struct i2c_client *client, u16 fw_page_size, - const u8 *page, u16 checksum, int idx) + u16 fw_page_delay, const u8 *page, u16 checksum, + int idx) { struct device *dev = &client->dev; u8 val[3]; @@ -650,7 +651,7 @@ static int elan_i2c_write_fw_block(struct i2c_client *client, u16 fw_page_size, } /* Wait for F/W to update one page ROM data. */ - msleep(fw_page_size == ETP_FW_PAGE_SIZE_512 ? 50 : 35); + msleep(fw_page_delay); error = elan_i2c_read_cmd(client, ETP_I2C_IAP_CTRL_CMD, val); if (error) { diff --git a/drivers/input/mouse/elan_i2c_smbus.c b/drivers/input/mouse/elan_i2c_smbus.c index 6dc148b9d959..0287441cda46 100644 --- a/drivers/input/mouse/elan_i2c_smbus.c +++ b/drivers/input/mouse/elan_i2c_smbus.c @@ -416,7 +416,8 @@ static int elan_smbus_prepare_fw_update(struct i2c_client *client, u16 ic_type, static int elan_smbus_write_fw_block(struct i2c_client *client, u16 fw_page_size, - const u8 *page, u16 checksum, int idx) + u16 fw_page_delay, const u8 *page, u16 checksum, + int idx) { struct device *dev = &client->dev; int error; From c91d080c4e567562107bcb91c2f72556b317b0a8 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 11 Aug 2026 20:20:02 +0800 Subject: [PATCH 60/61] Input: elan_i2c - sort include statements Sort the include statements before adding new ones in the next change. Reviewed-by: Andy Shevchenko Signed-off-by: Chen-Yu Tsai Link: https://patch.msgid.link/20260811122011.3539250-3-wenst@chromium.org Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/elan_i2c_core.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c index 46421687fb35..f5e505edbc33 100644 --- a/drivers/input/mouse/elan_i2c_core.c +++ b/drivers/input/mouse/elan_i2c_core.c @@ -16,27 +16,27 @@ */ #include +#include #include #include #include #include #include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include #include #include #include #include +#include +#include +#include +#include #include #include "elan_i2c.h" From 9a29ee801f525bcad71fea021bfe2a030885c8df Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 6 Aug 2026 19:17:46 +0200 Subject: [PATCH 61/61] Input: rmi4 - use platform data instead of query, when available Platform data may define touchscreen-x-mm and touchscreen-y-mm, but these were quietly overridden by data provided by sensor. Signed-off-by: David Heidelberg Link: https://patch.msgid.link/20260731-respect-x-y-mm-v1-0-3e85a4bec745@ixit.cz Link: https://patch.msgid.link/20260806-respect-x-y-mm-v2-1-e0681ed3d63c@ixit.cz Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_f12.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/input/rmi4/rmi_f12.c b/drivers/input/rmi4/rmi_f12.c index 88c28089de99..841884d967a3 100644 --- a/drivers/input/rmi4/rmi_f12.c +++ b/drivers/input/rmi4/rmi_f12.c @@ -155,6 +155,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) offset += 4; } + /* When platform data are provided, we're done */ + if (sensor->x_mm && sensor->y_mm) + return 0; + /* * Use the Query DPM feature when the resolution query register * exists. @@ -171,8 +175,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) } dpm_resolution = buf[0]; - sensor->x_mm = sensor->max_x / dpm_resolution; - sensor->y_mm = sensor->max_y / dpm_resolution; + if (!sensor->x_mm) + sensor->x_mm = sensor->max_x / dpm_resolution; + if (!sensor->y_mm) + sensor->y_mm = sensor->max_y / dpm_resolution; } else { if (rmi_register_desc_has_subpacket(item, 3)) { rx_receivers = buf[offset]; @@ -184,8 +190,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) if (rmi_register_desc_has_subpacket(item, 4)) offset += 1; - sensor->x_mm = (pitch_x * rx_receivers) >> 12; - sensor->y_mm = (pitch_y * tx_receivers) >> 12; + if (!sensor->x_mm) + sensor->x_mm = (pitch_x * rx_receivers) >> 12; + if (!sensor->y_mm) + sensor->y_mm = (pitch_y * tx_receivers) >> 12; } rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s: x_mm: %d y_mm: %d\n", __func__,