* [PATCH] Input: synaptics - support the touchpad LED on RMI function $30
@ 2026-10-30 7:08 ahmad baghereslami
2026-10-31 1:23 ` kernel test robot
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: ahmad baghereslami @ 2026-10-30 7:08 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: linux-input, linux-kernel, ahmad baghereslami
Some HP laptops have an LED in the top left corner of the Synaptics
touchpad that HP's Windows driver lights while the touchpad is disabled
(double tap on that corner). On the HP ZBook 17 G3 (SYN3040, TM2706-002)
the LED is not driven through the PS/2 deluxe LED command: the Windows
driver (SynTP.sys 19.0.19.68) switches the touchpad to RMI register
access over PS/2 with mode byte 0x7f, looks up function $30 (GPIO/LED)
in the page description table and sets the LED bit in its control
register:
register write: SetScale 2:1, SetRate <reg>, SetRate <value>
register read: SetScale 2:1, SetRate <reg>, GetInfo (first byte)
For touchpads in a new rmi_led_pnp_ids list, look up the F30 LED this
way when the touchpad is set up and register a
synaptics::touchpad_disabled LED class device. Setting it deactivates
the touchpad, writes the F30 registers and restores the mode byte, which
leaves RMI access. The LED is restored after reconnect.
On this touchpad F30 is on page 1 with control base 0x55 and four
GPIO/LED pins; the LED is bit 3 of control register 0x59 and its
brightness register is 0x5f.
Tested on an HP ZBook 17 G3 with Ubuntu's 7.0.0 kernel: the LED follows
writes to its brightness file, the touchpad and the pointing stick keep
working, and a userspace helper that turns the touchpad off on a double
tap in the corner keeps the LED in step with GNOME's touchpad setting.
Reading HP's Windows driver (Ghidra) and writing this patch were done
with the help of an AI coding assistant (Claude); I tested the LED and
the touchpad on the machine.
Signed-off-by: ahmad baghereslami <ahmad.b1995@gmail.com>
---
drivers/input/mouse/synaptics.c | 241 ++++++++++++++++++++++++++++++++
drivers/input/mouse/synaptics.h | 13 ++
2 files changed, 254 insertions(+)
diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index b007e642e..bf1c6c738 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -589,6 +589,236 @@ static int synaptics_set_advanced_gesture_mode(struct psmouse *psmouse)
return 0;
}
+/*
+ * Some HP touchpads have an LED in the top left corner that HP's Windows
+ * driver lights while the touchpad is disabled. It is wired to RMI function
+ * $30 (GPIO/LED), which these touchpads also expose over PS/2: mode byte 0x7f
+ * switches them to RMI register access, where SetScale 2:1 followed by two
+ * SetRate commands writes a register, and SetScale 2:1, SetRate and GetInfo
+ * read one. Setting the normal mode byte again leaves RMI access.
+ */
+static const char * const rmi_led_pnp_ids[] = {
+ "SYN3040", /* HP ZBook 17 G3 */
+ NULL
+};
+
+#define SYN_RMI_MODE 0x7f
+#define SYN_RMI_PAGE_SELECT 0xff
+#define SYN_RMI_PDT_START 0xee
+#define SYN_RMI_PDT_END 0x0b
+#define SYN_RMI_PDT_ENTRY_SIZE 6
+#define SYN_RMI_NUM_PAGES 4
+#define SYN_RMI_F30 0x30
+#define SYN_RMI_F30_HAS_LED BIT(2)
+#define SYN_RMI_F30_HAS_GPIO BIT(3)
+#define SYN_RMI_LED_BRIGHTNESS 0x0b /* as set by HP's Windows driver */
+
+static int synaptics_rmi_write(struct psmouse *psmouse, u8 reg, u8 val)
+{
+ struct ps2dev *ps2dev = &psmouse->ps2dev;
+ int error;
+
+ error = ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21);
+ if (error)
+ return error;
+
+ error = ps2_command(ps2dev, ®, PSMOUSE_CMD_SETRATE);
+ if (error)
+ return error;
+
+ return ps2_command(ps2dev, &val, PSMOUSE_CMD_SETRATE);
+}
+
+static int synaptics_rmi_read(struct psmouse *psmouse, u8 reg, u8 *val)
+{
+ struct ps2dev *ps2dev = &psmouse->ps2dev;
+ u8 param[3];
+ int error;
+
+ error = ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21);
+ if (error)
+ return error;
+
+ error = ps2_command(ps2dev, ®, PSMOUSE_CMD_SETRATE);
+ if (error)
+ return error;
+
+ error = ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO);
+ if (error)
+ return error;
+
+ *val = param[0];
+ return 0;
+}
+
+/* Finds the LED of RMI function $30. Leaves the touchpad in RMI mode. */
+static int synaptics_rmi_led_probe(struct psmouse *psmouse)
+{
+ struct synaptics_data *priv = psmouse->private;
+ u8 fn, query, ctrl, caps, count, gpios;
+ unsigned int page, zeros, i;
+ int addr, error;
+
+ error = synaptics_mode_cmd(psmouse, SYN_RMI_MODE);
+ if (error)
+ return error;
+
+ for (page = 0; page < SYN_RMI_NUM_PAGES; page++) {
+ error = synaptics_rmi_write(psmouse, SYN_RMI_PAGE_SELECT, page);
+ if (error)
+ return error;
+
+ for (addr = SYN_RMI_PDT_START; addr >= SYN_RMI_PDT_END;
+ addr -= SYN_RMI_PDT_ENTRY_SIZE) {
+ error = synaptics_rmi_read(psmouse, addr, &fn);
+ if (error)
+ return error;
+ if (!fn)
+ break;
+ if (fn != SYN_RMI_F30)
+ continue;
+
+ error = synaptics_rmi_read(psmouse, addr - 5, &query);
+ if (!error)
+ error = synaptics_rmi_read(psmouse, addr - 3, &ctrl);
+ if (!error)
+ error = synaptics_rmi_read(psmouse, query, &caps);
+ if (!error)
+ error = synaptics_rmi_read(psmouse, query + 1, &count);
+ if (error)
+ return error;
+
+ if (!(caps & SYN_RMI_F30_HAS_LED))
+ return -ENODEV;
+
+ priv->rmi_led_page = page;
+ if (!(caps & SYN_RMI_F30_HAS_GPIO)) {
+ priv->rmi_led_mask = BIT(0);
+ priv->rmi_led_ctrl = ctrl + 1;
+ priv->rmi_led_brightness = ctrl + 4;
+ return 0;
+ }
+
+ /* GPIO/LED select: the LED is the first set bit */
+ error = synaptics_rmi_read(psmouse, ctrl, &gpios);
+ if (error)
+ return error;
+
+ priv->rmi_led_mask = 0;
+ for (i = 0, zeros = 0; i < min(count, 8); i++) {
+ if (!(gpios & BIT(i)))
+ zeros++;
+ else if (!priv->rmi_led_mask)
+ priv->rmi_led_mask = BIT(i);
+ }
+ if (!priv->rmi_led_mask)
+ return -ENODEV;
+
+ priv->rmi_led_ctrl = ctrl + 4;
+ priv->rmi_led_brightness = ctrl + 7 + zeros;
+ return 0;
+ }
+ }
+
+ return -ENODEV;
+}
+
+/* Sets the LED. Leaves the touchpad in RMI mode. */
+static int __synaptics_rmi_led_set(struct psmouse *psmouse, bool on)
+{
+ struct synaptics_data *priv = psmouse->private;
+ u8 val;
+ int error;
+
+ error = synaptics_mode_cmd(psmouse, SYN_RMI_MODE);
+ if (error)
+ return error;
+
+ error = synaptics_rmi_write(psmouse, SYN_RMI_PAGE_SELECT, priv->rmi_led_page);
+ if (error)
+ return error;
+
+ if (on) {
+ error = synaptics_rmi_write(psmouse, priv->rmi_led_brightness,
+ SYN_RMI_LED_BRIGHTNESS);
+ if (error)
+ return error;
+ }
+
+ error = synaptics_rmi_read(psmouse, priv->rmi_led_ctrl, &val);
+ if (error)
+ return error;
+
+ val = on ? val | priv->rmi_led_mask : val & ~priv->rmi_led_mask;
+ return synaptics_rmi_write(psmouse, priv->rmi_led_ctrl, val);
+}
+
+static int synaptics_set_mode(struct psmouse *psmouse);
+
+static int synaptics_rmi_led_set(struct led_classdev *led,
+ enum led_brightness value)
+{
+ struct synaptics_data *priv = container_of(led, struct synaptics_data, rmi_led);
+ struct psmouse *psmouse = priv->psmouse;
+ int error, ret;
+
+ guard(mutex)(&priv->rmi_led_lock);
+
+ if (psmouse->state != PSMOUSE_ACTIVATED)
+ return -EBUSY;
+
+ error = psmouse_deactivate(psmouse);
+ if (error)
+ return error;
+
+ ret = __synaptics_rmi_led_set(psmouse, value != LED_OFF);
+
+ /* Setting the mode byte again leaves RMI access */
+ error = synaptics_set_mode(psmouse);
+ if (!ret)
+ ret = error;
+
+ error = psmouse_activate(psmouse);
+ return ret ?: error;
+}
+
+static void synaptics_rmi_led_init(struct psmouse *psmouse)
+{
+ struct synaptics_data *priv = psmouse->private;
+
+ if (!IS_REACHABLE(CONFIG_LEDS_CLASS) ||
+ !psmouse_matches_pnp_id(psmouse, rmi_led_pnp_ids))
+ return;
+
+ if (synaptics_rmi_led_probe(psmouse))
+ return;
+
+ psmouse_info(psmouse, "touchpad LED on RMI F30 (page %u, ctrl %#x, mask %#x)\n",
+ priv->rmi_led_page, priv->rmi_led_ctrl, priv->rmi_led_mask);
+ __synaptics_rmi_led_set(psmouse, false);
+ priv->has_rmi_led = true;
+}
+
+static void synaptics_rmi_led_register(struct psmouse *psmouse)
+{
+ struct synaptics_data *priv = psmouse->private;
+ int error;
+
+ if (!priv->has_rmi_led)
+ return;
+
+ mutex_init(&priv->rmi_led_lock);
+ priv->psmouse = psmouse;
+ priv->rmi_led.name = "synaptics::touchpad_disabled";
+ priv->rmi_led.max_brightness = 1;
+ priv->rmi_led.brightness_set_blocking = synaptics_rmi_led_set;
+ error = led_classdev_register(&psmouse->ps2dev.serio->dev, &priv->rmi_led);
+ if (error) {
+ psmouse_warn(psmouse, "failed to register touchpad LED: %d\n", error);
+ priv->has_rmi_led = false;
+ }
+}
+
static int synaptics_set_mode(struct psmouse *psmouse)
{
struct synaptics_data *priv = psmouse->private;
@@ -1444,6 +1674,9 @@ static void synaptics_disconnect(struct psmouse *psmouse)
{
struct synaptics_data *priv = psmouse->private;
+ if (priv->has_rmi_led)
+ led_classdev_unregister(&priv->rmi_led);
+
/*
* We might have left a breadcrumb when trying to
* set up SMbus companion.
@@ -1496,6 +1729,10 @@ static int synaptics_reconnect(struct psmouse *psmouse)
return error;
}
+ /* The reset turned the LED off; the mode byte below leaves RMI access. */
+ if (priv->has_rmi_led && priv->rmi_led.brightness)
+ __synaptics_rmi_led_set(psmouse, true);
+
error = synaptics_set_mode(psmouse);
if (error) {
psmouse_err(psmouse, "Unable to initialize device.\n");
@@ -1616,6 +1853,8 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
*/
priv->is_forcepad = psmouse_matches_pnp_id(psmouse, forcepad_pnp_ids);
+ synaptics_rmi_led_init(psmouse);
+
err = synaptics_set_mode(psmouse);
if (err) {
psmouse_err(psmouse, "Unable to initialize device.\n");
@@ -1693,6 +1932,8 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
}
}
+ synaptics_rmi_led_register(psmouse);
+
return 0;
init_fail:
diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h
index 3853165b6..1d36a207e 100644
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -6,6 +6,9 @@
#ifndef _SYNAPTICS_H
#define _SYNAPTICS_H
+#include <linux/leds.h>
+#include <linux/mutex.h>
+
/* synaptics queries */
#define SYN_QUE_IDENTIFY 0x00
#define SYN_QUE_MODES 0x01
@@ -202,6 +205,16 @@ struct synaptics_data {
bool press;
bool report_press;
bool is_forcepad;
+
+ /* Touchpad LED on RMI function $30, reached over PS/2 */
+ struct psmouse *psmouse;
+ struct led_classdev rmi_led;
+ struct mutex rmi_led_lock;
+ bool has_rmi_led;
+ u8 rmi_led_page;
+ u8 rmi_led_ctrl;
+ u8 rmi_led_mask;
+ u8 rmi_led_brightness;
};
void synaptics_module_init(void);
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] Input: synaptics - support the touchpad LED on RMI function $30
2026-10-30 7:08 [PATCH] Input: synaptics - support the touchpad LED on RMI function $30 ahmad baghereslami
@ 2026-10-31 1:23 ` kernel test robot
2026-10-31 2:29 ` kernel test robot
2026-11-06 9:56 ` [PATCH v2] " ahmad baghereslami
2 siblings, 0 replies; 4+ messages in thread
From: kernel test robot @ 2026-10-31 1:23 UTC (permalink / raw)
To: ahmad baghereslami, dmitry.torokhov
Cc: oe-kbuild-all, linux-input, linux-kernel, ahmad baghereslami
Hi ahmad,
kernel test robot noticed the following build errors:
[auto build test ERROR on dtor-input/next]
[also build test ERROR on dtor-input/for-linus hid/for-next linus/master v7.3 next-20261029]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/ahmad-baghereslami/Input-synaptics-support-the-touchpad-LED-on-RMI-function-30/20261030-103821
base: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
patch link: https://lore.kernel.org/r/20261030070821.1716595-1-ahmad.b1995%40gmail.com
patch subject: [PATCH] Input: synaptics - support the touchpad LED on RMI function $30
config: um-randconfig-1000-20261031 (https://download.01.org/0day-ci/archive/20261031/202610310804.T0iDq0C6-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.4.0-5) 12.4.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261031/202610310804.T0iDq0C6-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202610310804.T0iDq0C6-lkp@intel.com/
All errors (new ones prefixed by >>):
/usr/bin/ld: drivers/input/mouse/synaptics.o: in function `synaptics_disconnect':
>> synaptics.c:(.text+0x22dc): undefined reference to `led_classdev_unregister'
/usr/bin/ld: drivers/input/mouse/synaptics.o: in function `synaptics_init_ps2':
>> synaptics.c:(.text+0x406d): undefined reference to `led_classdev_register_ext'
collect2: error: ld returned 1 exit status
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] Input: synaptics - support the touchpad LED on RMI function $30
2026-10-30 7:08 [PATCH] Input: synaptics - support the touchpad LED on RMI function $30 ahmad baghereslami
2026-10-31 1:23 ` kernel test robot
@ 2026-10-31 2:29 ` kernel test robot
2026-11-06 9:56 ` [PATCH v2] " ahmad baghereslami
2 siblings, 0 replies; 4+ messages in thread
From: kernel test robot @ 2026-10-31 2:29 UTC (permalink / raw)
To: ahmad baghereslami, dmitry.torokhov
Cc: oe-kbuild-all, linux-input, linux-kernel, ahmad baghereslami
Hi ahmad,
kernel test robot noticed the following build errors:
[auto build test ERROR on dtor-input/next]
[also build test ERROR on dtor-input/for-linus hid/for-next linus/master v7.3 next-20261030]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/ahmad-baghereslami/Input-synaptics-support-the-touchpad-LED-on-RMI-function-30/20261030-103821
base: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
patch link: https://lore.kernel.org/r/20261030070821.1716595-1-ahmad.b1995%40gmail.com
patch subject: [PATCH] Input: synaptics - support the touchpad LED on RMI function $30
config: um-i386_defconfig (https://download.01.org/0day-ci/archive/20261031/202610311023.UR64jYrp-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261031/202610311023.UR64jYrp-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202610311023.UR64jYrp-lkp@intel.com/
All errors (new ones prefixed by >>):
/usr/bin/ld: drivers/input/mouse/synaptics.o: in function `synaptics_disconnect':
>> drivers/input/mouse/synaptics.c:1678:(.text+0x3ec): undefined reference to `led_classdev_unregister'
/usr/bin/ld: drivers/input/mouse/synaptics.o: in function `led_classdev_register':
>> include/linux/leds.h:274:(.text+0x12c9): undefined reference to `led_classdev_register_ext'
collect2: error: ld returned 1 exit status
vim +1678 drivers/input/mouse/synaptics.c
1668
1669 PSMOUSE_DEFINE_ATTR(disable_gesture, S_IWUSR | S_IRUGO, NULL,
1670 synaptics_show_disable_gesture,
1671 synaptics_set_disable_gesture);
1672
1673 static void synaptics_disconnect(struct psmouse *psmouse)
1674 {
1675 struct synaptics_data *priv = psmouse->private;
1676
1677 if (priv->has_rmi_led)
> 1678 led_classdev_unregister(&priv->rmi_led);
1679
1680 /*
1681 * We might have left a breadcrumb when trying to
1682 * set up SMbus companion.
1683 */
1684 psmouse_smbus_cleanup(psmouse);
1685
1686 if (!priv->absolute_mode &&
1687 SYN_ID_DISGEST_SUPPORTED(priv->info.identity))
1688 device_remove_file(&psmouse->ps2dev.serio->dev,
1689 &psmouse_attr_disable_gesture.dattr);
1690
1691 synaptics_reset(psmouse);
1692 kfree(priv);
1693 psmouse->private = NULL;
1694 }
1695
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2] Input: synaptics - support the touchpad LED on RMI function $30
2026-10-30 7:08 [PATCH] Input: synaptics - support the touchpad LED on RMI function $30 ahmad baghereslami
2026-10-31 1:23 ` kernel test robot
2026-10-31 2:29 ` kernel test robot
@ 2026-11-06 9:56 ` ahmad baghereslami
2 siblings, 0 replies; 4+ messages in thread
From: ahmad baghereslami @ 2026-11-06 9:56 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: linux-input, linux-kernel, ahmad baghereslami
Some HP laptops have an LED in the top left corner of the Synaptics
touchpad that HP's Windows driver lights while the touchpad is disabled
(double tap on that corner). On the HP ZBook 17 G3 (SYN3040, TM2706-002)
the LED is not driven through the PS/2 deluxe LED command: the Windows
driver (SynTP.sys 19.0.19.68) switches the touchpad to RMI register
access over PS/2 with mode byte 0x7f, looks up function $30 (GPIO/LED)
in the page description table and sets the LED bit in its control
register:
register write: SetScale 2:1, SetRate <reg>, SetRate <value>
register read: SetScale 2:1, SetRate <reg>, GetInfo (first byte)
For touchpads in a new rmi_led_pnp_ids list, look up the F30 LED this
way when the touchpad is set up and register a
synaptics::touchpad_disabled LED class device. Setting it queues a work
item that, with psmouse_mutex held like the protected psmouse
attributes, deactivates the touchpad, writes the F30 registers and
restores the mode byte, which leaves RMI access. synaptics_disconnect()
cancels the work with psmouse_mutex held, so the work only tries to take
the mutex and retries later when it is busy. psmouse_mutex is no longer
static so that synaptics.c can take it. The LED is restored after
reconnect.
On this touchpad F30 is on page 1 with control base 0x55 and four
GPIO/LED pins; the LED is bit 3 of control register 0x59 and its
brightness register is 0x5f.
Tested on an HP ZBook 17 G3 with Ubuntu's 7.0.0 kernel: the LED follows
writes to its brightness file, the touchpad and the pointing stick keep
working, and a userspace helper that turns the touchpad off on a double
tap in the corner keeps the LED in step with GNOME's touchpad setting.
Reading HP's Windows driver (Ghidra) and writing this patch were done
with the help of an AI coding assistant (Claude); I tested the LED and
the touchpad on the machine.
Signed-off-by: ahmad baghereslami <ahmad.b1995@gmail.com>
---
Notes:
v2:
- Fix the link failure reported by the kernel test robot when
CONFIG_LEDS_CLASS is not reachable: the register and unregister calls
are now behind IS_REACHABLE(CONFIG_LEDS_CLASS).
- Set the LED from a work item that holds psmouse_mutex instead of a
private mutex, so it cannot interleave with reconnect, resync or
attribute writes (sashiko review). The work uses mutex_trylock() and
retries, because synaptics_disconnect() cancels it with psmouse_mutex
held. If you would rather have this done another way, for example a
helper in psmouse-base.c instead of exporting psmouse_mutex to the
protocol code, I am happy to change it.
- Restore the mode with synaptics_mode_cmd(psmouse, priv->mode) instead of
synaptics_set_mode(), so SYN_BIT_FOUR_BYTE_CLIENT survives (sashiko).
- Set LED_RETAIN_AT_SHUTDOWN: synaptics_disconnect() resets the touchpad,
which turns the LED off, so unregistering no longer calls into the
driver with the touchpad in command mode (sashiko).
- Mask the F30 GPIO/LED count with 0x1f and give up on more than eight
pins (sashiko). The brightness register offset still counts every
GPIO pin, not only those below the LED: the brightness registers come
after one register per GPIO pin, which is how HP's driver computes it
and what this touchpad needs (three GPIO pins, brightness at 0x5f).
v1: https://lore.kernel.org/all/20261030070821.1716595-1-ahmad.b1995@gmail.com/
drivers/input/mouse/psmouse-base.c | 2 +-
drivers/input/mouse/psmouse.h | 3 +
drivers/input/mouse/synaptics.c | 282 +++++++++++++++++++++++++++++
drivers/input/mouse/synaptics.h | 13 ++
4 files changed, 299 insertions(+), 1 deletion(-)
diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
index 231646882..020821be1 100644
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -111,7 +111,7 @@ ATTRIBUTE_GROUPS(psmouse_dev);
* rarely more than one PS/2 mouse connected and since semaphore
* is taken in "slow" paths it is not worth it.
*/
-static DEFINE_MUTEX(psmouse_mutex);
+DEFINE_MUTEX(psmouse_mutex);
struct psmouse *psmouse_from_serio(struct serio *serio)
{
diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h
index 90ed8cd15..77c137a57 100644
--- a/drivers/input/mouse/psmouse.h
+++ b/drivers/input/mouse/psmouse.h
@@ -136,6 +136,9 @@ int psmouse_reset(struct psmouse *psmouse);
void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state);
void psmouse_set_resolution(struct psmouse *psmouse, unsigned int resolution);
psmouse_ret_t psmouse_process_byte(struct psmouse *psmouse);
+/* Serializes connecting, reconnecting and reconfiguring PS/2 mice */
+extern struct mutex psmouse_mutex;
+
int psmouse_activate(struct psmouse *psmouse);
int psmouse_deactivate(struct psmouse *psmouse);
bool psmouse_matches_pnp_id(struct psmouse *psmouse, const char * const ids[]);
diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index b007e642e..4186e4f53 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -589,6 +589,278 @@ static int synaptics_set_advanced_gesture_mode(struct psmouse *psmouse)
return 0;
}
+/*
+ * Some HP touchpads have an LED in the top left corner that HP's Windows
+ * driver lights while the touchpad is disabled. It is wired to RMI function
+ * $30 (GPIO/LED), which these touchpads also expose over PS/2: mode byte 0x7f
+ * switches them to RMI register access, where SetScale 2:1 followed by two
+ * SetRate commands writes a register, and SetScale 2:1, SetRate and GetInfo
+ * read one. Setting the normal mode byte again leaves RMI access.
+ */
+static const char * const rmi_led_pnp_ids[] = {
+ "SYN3040", /* HP ZBook 17 G3 */
+ NULL
+};
+
+#define SYN_RMI_MODE 0x7f
+#define SYN_RMI_PAGE_SELECT 0xff
+#define SYN_RMI_PDT_START 0xee
+#define SYN_RMI_PDT_END 0x0b
+#define SYN_RMI_PDT_ENTRY_SIZE 6
+#define SYN_RMI_NUM_PAGES 4
+#define SYN_RMI_F30 0x30
+#define SYN_RMI_F30_HAS_LED BIT(2)
+#define SYN_RMI_F30_HAS_GPIO BIT(3)
+#define SYN_RMI_F30_COUNT GENMASK(4, 0)
+#define SYN_RMI_LED_BRIGHTNESS 0x0b /* as set by HP's Windows driver */
+#define SYN_RMI_LED_RETRY_MS 50
+
+static int synaptics_rmi_write(struct psmouse *psmouse, u8 reg, u8 val)
+{
+ struct ps2dev *ps2dev = &psmouse->ps2dev;
+ int error;
+
+ error = ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21);
+ if (error)
+ return error;
+
+ error = ps2_command(ps2dev, ®, PSMOUSE_CMD_SETRATE);
+ if (error)
+ return error;
+
+ return ps2_command(ps2dev, &val, PSMOUSE_CMD_SETRATE);
+}
+
+static int synaptics_rmi_read(struct psmouse *psmouse, u8 reg, u8 *val)
+{
+ struct ps2dev *ps2dev = &psmouse->ps2dev;
+ u8 param[3];
+ int error;
+
+ error = ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21);
+ if (error)
+ return error;
+
+ error = ps2_command(ps2dev, ®, PSMOUSE_CMD_SETRATE);
+ if (error)
+ return error;
+
+ error = ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO);
+ if (error)
+ return error;
+
+ *val = param[0];
+ return 0;
+}
+
+/* Finds the LED of RMI function $30. Leaves the touchpad in RMI mode. */
+static int synaptics_rmi_led_probe(struct psmouse *psmouse)
+{
+ struct synaptics_data *priv = psmouse->private;
+ u8 fn, query, ctrl, caps, count, gpios;
+ unsigned int page, zeros, i;
+ int addr, error;
+
+ error = synaptics_mode_cmd(psmouse, SYN_RMI_MODE);
+ if (error)
+ return error;
+
+ for (page = 0; page < SYN_RMI_NUM_PAGES; page++) {
+ error = synaptics_rmi_write(psmouse, SYN_RMI_PAGE_SELECT, page);
+ if (error)
+ return error;
+
+ for (addr = SYN_RMI_PDT_START; addr >= SYN_RMI_PDT_END;
+ addr -= SYN_RMI_PDT_ENTRY_SIZE) {
+ error = synaptics_rmi_read(psmouse, addr, &fn);
+ if (error)
+ return error;
+ if (!fn)
+ break;
+ if (fn != SYN_RMI_F30)
+ continue;
+
+ error = synaptics_rmi_read(psmouse, addr - 5, &query);
+ if (!error)
+ error = synaptics_rmi_read(psmouse, addr - 3, &ctrl);
+ if (!error)
+ error = synaptics_rmi_read(psmouse, query, &caps);
+ if (!error)
+ error = synaptics_rmi_read(psmouse, query + 1, &count);
+ if (error)
+ return error;
+
+ count &= SYN_RMI_F30_COUNT;
+ if (!(caps & SYN_RMI_F30_HAS_LED) || count > 8)
+ return -ENODEV;
+
+ priv->rmi_led_page = page;
+ if (!(caps & SYN_RMI_F30_HAS_GPIO)) {
+ priv->rmi_led_mask = BIT(0);
+ priv->rmi_led_ctrl = ctrl + 1;
+ priv->rmi_led_brightness = ctrl + 4;
+ return 0;
+ }
+
+ /*
+ * GPIO/LED select: the LED is the first set bit. The
+ * brightness registers follow one register for each
+ * pin used as a GPIO, so count all of those.
+ */
+ error = synaptics_rmi_read(psmouse, ctrl, &gpios);
+ if (error)
+ return error;
+
+ priv->rmi_led_mask = 0;
+ for (i = 0, zeros = 0; i < count; i++) {
+ if (!(gpios & BIT(i)))
+ zeros++;
+ else if (!priv->rmi_led_mask)
+ priv->rmi_led_mask = BIT(i);
+ }
+ if (!priv->rmi_led_mask)
+ return -ENODEV;
+
+ priv->rmi_led_ctrl = ctrl + 4;
+ priv->rmi_led_brightness = ctrl + 7 + zeros;
+ return 0;
+ }
+ }
+
+ return -ENODEV;
+}
+
+/* Sets the LED. Leaves the touchpad in RMI mode. */
+static int __synaptics_rmi_led_set(struct psmouse *psmouse, bool on)
+{
+ struct synaptics_data *priv = psmouse->private;
+ u8 val;
+ int error;
+
+ error = synaptics_mode_cmd(psmouse, SYN_RMI_MODE);
+ if (error)
+ return error;
+
+ error = synaptics_rmi_write(psmouse, SYN_RMI_PAGE_SELECT, priv->rmi_led_page);
+ if (error)
+ return error;
+
+ if (on) {
+ error = synaptics_rmi_write(psmouse, priv->rmi_led_brightness,
+ SYN_RMI_LED_BRIGHTNESS);
+ if (error)
+ return error;
+ }
+
+ error = synaptics_rmi_read(psmouse, priv->rmi_led_ctrl, &val);
+ if (error)
+ return error;
+
+ val = on ? val | priv->rmi_led_mask : val & ~priv->rmi_led_mask;
+ return synaptics_rmi_write(psmouse, priv->rmi_led_ctrl, val);
+}
+
+/*
+ * Runs with psmouse_mutex held, like the protected psmouse attributes, so
+ * that it does not race with reconnect, resync or attribute changes.
+ * synaptics_disconnect() cancels it with psmouse_mutex held, so it must not
+ * wait for the mutex: when it is busy, try again a little later.
+ */
+static void synaptics_rmi_led_work(struct work_struct *work)
+{
+ struct synaptics_data *priv = container_of(to_delayed_work(work),
+ struct synaptics_data,
+ rmi_led_work);
+ struct psmouse *psmouse = priv->psmouse;
+ int error;
+
+ if (!mutex_trylock(&psmouse_mutex)) {
+ schedule_delayed_work(&priv->rmi_led_work,
+ msecs_to_jiffies(SYN_RMI_LED_RETRY_MS));
+ return;
+ }
+
+ if (psmouse->state == PSMOUSE_RESYNCING) {
+ schedule_delayed_work(&priv->rmi_led_work,
+ msecs_to_jiffies(SYN_RMI_LED_RETRY_MS));
+ } else if (psmouse->state == PSMOUSE_ACTIVATED) {
+ /* Otherwise the LED is restored on reconnect */
+ psmouse_deactivate(psmouse);
+
+ error = __synaptics_rmi_led_set(psmouse,
+ priv->rmi_led.brightness != LED_OFF);
+ if (error)
+ psmouse_warn(psmouse,
+ "failed to set touchpad LED: %d\n", error);
+
+ /* Setting the mode byte again leaves RMI access */
+ if (synaptics_mode_cmd(psmouse, priv->mode))
+ psmouse_warn(psmouse, "failed to restore mode\n");
+
+ psmouse_activate(psmouse);
+ }
+
+ mutex_unlock(&psmouse_mutex);
+}
+
+static void synaptics_rmi_led_set(struct led_classdev *led,
+ enum led_brightness value)
+{
+ struct synaptics_data *priv = container_of(led, struct synaptics_data, rmi_led);
+
+ mod_delayed_work(system_wq, &priv->rmi_led_work, 0);
+}
+
+static void synaptics_rmi_led_init(struct psmouse *psmouse)
+{
+ struct synaptics_data *priv = psmouse->private;
+
+ if (!IS_REACHABLE(CONFIG_LEDS_CLASS) ||
+ !psmouse_matches_pnp_id(psmouse, rmi_led_pnp_ids))
+ return;
+
+ if (synaptics_rmi_led_probe(psmouse))
+ return;
+
+ psmouse_info(psmouse, "touchpad LED on RMI F30 (page %u, ctrl %#x, mask %#x)\n",
+ priv->rmi_led_page, priv->rmi_led_ctrl, priv->rmi_led_mask);
+ __synaptics_rmi_led_set(psmouse, false);
+ priv->has_rmi_led = true;
+}
+
+static void synaptics_rmi_led_register(struct psmouse *psmouse)
+{
+ struct synaptics_data *priv = psmouse->private;
+ int error;
+
+ if (!IS_REACHABLE(CONFIG_LEDS_CLASS) || !priv->has_rmi_led)
+ return;
+
+ priv->psmouse = psmouse;
+ INIT_DELAYED_WORK(&priv->rmi_led_work, synaptics_rmi_led_work);
+ priv->rmi_led.name = "synaptics::touchpad_disabled";
+ priv->rmi_led.max_brightness = 1;
+ priv->rmi_led.brightness_set = synaptics_rmi_led_set;
+ /* synaptics_disconnect() resets the touchpad, which turns the LED off */
+ priv->rmi_led.flags = LED_RETAIN_AT_SHUTDOWN;
+ error = led_classdev_register(&psmouse->ps2dev.serio->dev, &priv->rmi_led);
+ if (error) {
+ psmouse_warn(psmouse, "failed to register touchpad LED: %d\n", error);
+ priv->has_rmi_led = false;
+ }
+}
+
+static void synaptics_rmi_led_unregister(struct psmouse *psmouse)
+{
+ struct synaptics_data *priv = psmouse->private;
+
+ if (!IS_REACHABLE(CONFIG_LEDS_CLASS) || !priv->has_rmi_led)
+ return;
+
+ led_classdev_unregister(&priv->rmi_led);
+ cancel_delayed_work_sync(&priv->rmi_led_work);
+}
+
static int synaptics_set_mode(struct psmouse *psmouse)
{
struct synaptics_data *priv = psmouse->private;
@@ -1444,6 +1716,8 @@ static void synaptics_disconnect(struct psmouse *psmouse)
{
struct synaptics_data *priv = psmouse->private;
+ synaptics_rmi_led_unregister(psmouse);
+
/*
* We might have left a breadcrumb when trying to
* set up SMbus companion.
@@ -1496,6 +1770,10 @@ static int synaptics_reconnect(struct psmouse *psmouse)
return error;
}
+ /* The reset turned the LED off; the mode byte below leaves RMI access. */
+ if (priv->has_rmi_led && priv->rmi_led.brightness)
+ __synaptics_rmi_led_set(psmouse, true);
+
error = synaptics_set_mode(psmouse);
if (error) {
psmouse_err(psmouse, "Unable to initialize device.\n");
@@ -1616,6 +1894,8 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
*/
priv->is_forcepad = psmouse_matches_pnp_id(psmouse, forcepad_pnp_ids);
+ synaptics_rmi_led_init(psmouse);
+
err = synaptics_set_mode(psmouse);
if (err) {
psmouse_err(psmouse, "Unable to initialize device.\n");
@@ -1693,6 +1973,8 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
}
}
+ synaptics_rmi_led_register(psmouse);
+
return 0;
init_fail:
diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h
index 3853165b6..b792ac98a 100644
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -6,6 +6,9 @@
#ifndef _SYNAPTICS_H
#define _SYNAPTICS_H
+#include <linux/leds.h>
+#include <linux/workqueue.h>
+
/* synaptics queries */
#define SYN_QUE_IDENTIFY 0x00
#define SYN_QUE_MODES 0x01
@@ -202,6 +205,16 @@ struct synaptics_data {
bool press;
bool report_press;
bool is_forcepad;
+
+ /* Touchpad LED on RMI function $30, reached over PS/2 */
+ struct psmouse *psmouse;
+ struct led_classdev rmi_led;
+ struct delayed_work rmi_led_work;
+ bool has_rmi_led;
+ u8 rmi_led_page;
+ u8 rmi_led_ctrl;
+ u8 rmi_led_mask;
+ u8 rmi_led_brightness;
};
void synaptics_module_init(void);
base-commit: 97a889cad5dd8ff83f40514f2d67c565c0dc54b1
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-06 9:57 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-30 7:08 [PATCH] Input: synaptics - support the touchpad LED on RMI function $30 ahmad baghereslami
2026-10-31 1:23 ` kernel test robot
2026-10-31 2:29 ` kernel test robot
2026-11-06 9:56 ` [PATCH v2] " ahmad baghereslami
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®