linux-doc.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/11] HID: Implement haptic touchpad support
@ 2025-08-04 14:11 Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 01/11] HID: add haptics page defines Jonathan Denose
                   ` (10 more replies)
  0 siblings, 11 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

Hello,

This is an updated implementation of the interface for controlling haptic
touchpads.

Below is an updated design proposal for the userspace and HID interfaces,
modified from what one of my colleagues submitted in 2019 [0].

We would appreciate any feedback you might have.

Thank you,

Jonathan Denose
Chromium OS Team

Background
==========

There are multiple independent projects to develop a touchpad with force sensors
and haptic actuators, instead of a traditional button.  These haptic touchpads
have several advantages and potential uses; they allow clicking across the
entire touchpad surface, adjusting the force requirement for clicks, haptic
feedback initiated by UI, etc. Supporting these features will potentially
require two new communication channels at the kernel level:
* Control of haptic motor by the host
* Force sensor data from device to host

This document includes two related proposals:
1. HID design proposal, that hardware makers would need to implement
2. Kernel design proposal

Objective
==========

Develop a standard protocol to allow userspace applications to communicate with
haptic touchpads, and minimize duplicated code and effort.

Requirements:
1. Support UI-initiated haptic feedback.
2. Allow userspace to control when button press and button release haptic
   effects are triggered. (Useful when detecting a false click, changing force
   thresholds, or sending context-dependent effects).
3. Reveal force sensor readings to userspace applications.
4. Only allow OS-controlled haptic feedback for those systems which support it.

Proposal
========

In order to minimize duplicated effort, we propose standardized haptic touchpad
support in the linux kernel.

HID API
-------

Modes
.....

The haptic touchpad should be able to operate under two different modes.

1. Device-controlled mode

The haptic touchpad should start up in "device-controlled mode"
(HID_HAPTIC_MODE_DEVICE), meaning it acts as a normal touchpad. This means it
should perform the press and release haptic feedback autonomously at predefined
force thresholds, and send the appropriate BTN_* events.

2. Host-controlled mode

Once the touchpad has been confirmed as supporting haptics (described in more
detail in the the "Click and release control" section below), the device should
enter "host-controlled mode" (HID_HAPTIC_MODE_HOST). In this mode userspace
should take control. From here, userspace will take control over
press/release haptic feedback, relying on the effects sent by the kernel.

Multitouch
..........

The HID API for multitouch reports should follow the Microsoft precision
touchpad spec [1], with the following changes:
* A tip pressure field [2] should be used to report the force. The physical unit
  Type (Newtons or grams), exponent, and limits should be reported in the
  report descriptor for the force field.
* The device will always report the button state according to its predefined
  force thresholds, even when not in device-controlled mode.
* The device must expose a "simple haptic controller" logical collection
  alongside the touchpad collection.

Haptic control
..............

The HID protocol described in HUTRR63[3] must be used.

The following waveforms should be supported:

| WAVEFORMNONE             | Implicit waveforms required by protocol           |
| WAVEFORMSTOP             |                                                   |
| ------------------------ | ------------------------------------------------- |
| WAVEFORMPRESS            | To be used to simulate button press. In device-   |
|                          | controlled mode, it will also be used to simulate |
|                          | button release.                                   |
| ------------------------ | ------------------------------------------------- |
| WAVEFORMRELEASE          | To be used to simulate button release.            |

All waveforms will have an associated duration; continuous waveforms will be
ignored by the kernel.

Triggers & Mode switching
.........................

The “auto trigger waveform” should be set to WAVEFORM_PRESS by default, and the
button from the touchpad collection should be set as the “auto trigger
associated control”.

The kernel can trigger the different modes in the following ways:
* Device-controlled mode can be enabled by setting the “auto trigger waveform” to
  WAVEFORM_PRESS.
* Host-controlled mode can be enabled by setting the "auto trigger waveform" to
  WAVEFORM_STOP.

The device must also support manual triggering. If intensity modification for
waveforms is supported by the device, the intensity control should be included
in the manual trigger output report. This allows modification of the intensity
on a per-waveform basis. Retriggering does not need to be supported by the
device.

Userspace API
-------------

Multitouch protocol
...................

ABS_MT_PRESSURE will be used to report force. The resolution of ABS_MT_PRESSURE
should also be defined and reported in force units of grams or Newtons.
ABS_PRESSURE should be reported as the total force applied to the touchpad.
When the kernel is in host-controlled mode, it should always forward the button
press and release events to userspace.

Use Force Feedback protocol to request pre-defined effects
..........................................................

The force feedback protocol [4] should be used to control predefined effects.

Typical use of the force feedback protocol requires loading effects to the
driver by describing the output waveform, and then requesting those effects
using an ID provided by the driver. However, for haptic touchpads we do not want
to describe the output waveform explicitly, but use a set of predefined effects,
which are identified by HID usage.

The force feedback protocol will need to be extended to allow requests for HID
haptic effects. This requires a new feedback effect type:

/**
 * struct ff_hid_effect
 * @hid_usage: hid_usage according to Haptics page (WAVEFORM_CLICK, etc.)
 * @vendor_id: the waveform vendor ID if hid_usage is in the vendor-defined
 * range
 * @vendor_id: the vendor waveform page if hid_usage is in the vendor-defined
 * range
 * @intensity: strength of the effect
 * @repeat_count: number of times to retrigger effect
 * @retrigger_period: time before effect is retriggered (in ms)
 */
struct ff_hid_effect {
        __u16 hid_usage;
        __u16 vendor_id;
        __u8  vendor_waveform_page;
        __s16 intensity;
        __u16 repeat_count;
        __u16 retrigger_period;
}

Since the standard waveform id namespace does not overlap with the vendor
waveform id namespace, the vendor id and page can be ignored for standard
waveforms.

Click and release control
.........................

Haptic functionality shall be gated behind the HID_MULTITOUCH_HAPTIC kernel
configuration option, and this kernel configuration option should only be
enabled if userspace will support haptic capabilities. Haptic functionality will
only be initialized and used if HID_MULTITOUCH_HAPTIC is enabled, and if the
following conditions have been met:
* ABS_MT_PRESSURE is defined and reporting force units of Newtons or grams.
* The device supports haptic effects according to the hid protocol defined in
  HUTRR63 [3].
These checks will happen when the driver probes and initializes the multitouch
device.

In the case when the kernel configuration option has been set and the device
reports pressure and haptic effects as defined above, the kernel will initialize
the haptic device and configure the haptic driver to signal that the touchpad is
haptic-compatible. To signal to userspace that the touchpad is haptic-compatible
the kernel will mark INPUT_PROP_HAPTIC_TOUCHPAD.

With userspace willing and able to take control, the kernel will signal to the
device to exit device-controlled mode once a WAVEFORMPRESS or WAVEFORMRELEASE
event is uploaded. From here, userspace will take control over press/release
haptic feedback, relying on the effects sent by the kernel.

In all other cases, the driver will take no action to enable haptic
functionality.

Summary of normal use-case
1. The kernel waits for userspace to upload WAVEFORMPRESS or
   WAVEFORMRELEASE.
2. Userspace determines when a click has been performed based on its own
   criteria and tells the touchpad to perform a haptic effect.
3. When userspace erases the WAVEFORMPRESS or WAVEFORMRELEASE effect, signal the
   device to return to device-controlled mode.

[0]: https://www.spinics.net/lists/linux-input/msg60938.html
[1]: https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/touchpad-devices
[2]: Usage ID 0x30 of HID usage table 0x0D. See chapter 16:
     https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
[3]: https://www.usb.org/sites/default/files/hutrr63b_-_haptics_page_redline_0.pdf
[4]: https://www.kernel.org/doc/html/v4.20/input/ff.html

Signed-off-by: Jonathan Denose <jdenose@google.com>
---
Changes in v2:
- change drivers/hid/Makefile to only build hid-haptic when
  CONFIG_MULTITOUCH_HAPTIC is defined and not CONFIG_HID_HAPTIC to
  resolve build error.
- change cover letter subject from forcepad to touchpad
- Link to v1: https://lore.kernel.org/r/20250714-support-forcepads-v1-0-71c7c05748c9@google.com

---
Angela Czubak (11):
      HID: add haptics page defines
      Input: add FF_HID effect type
      Input: add INPUT_PROP_HAPTIC_TOUCHPAD
      HID: haptic: introduce hid_haptic_device
      HID: input: allow mapping of haptic output
      HID: haptic: initialize haptic device
      HID: input: calculate resolution for pressure
      HID: haptic: add functions handling events
      Input: MT - add INPUT_MT_TOTAL_FORCE flags
      HID: haptic: add hid_haptic_switch_mode
      HID: multitouch: add haptic multitouch support

 Documentation/input/event-codes.rst    |  14 +
 drivers/hid/Kconfig                    |  20 ++
 drivers/hid/Makefile                   |   1 +
 drivers/hid/hid-haptic.c               | 580 +++++++++++++++++++++++++++++++++
 drivers/hid/hid-haptic.h               | 131 ++++++++
 drivers/hid/hid-input.c                |  18 +-
 drivers/hid/hid-multitouch.c           | 136 +++++++-
 drivers/input/input-mt.c               |  14 +-
 include/linux/hid.h                    |  29 ++
 include/linux/input/mt.h               |   1 +
 include/uapi/linux/input-event-codes.h |   1 +
 include/uapi/linux/input.h             |  22 +-
 12 files changed, 959 insertions(+), 8 deletions(-)
---
base-commit: 86731a2a651e58953fc949573895f2fa6d456841
change-id: 20250625-support-forcepads-0b4f74fd3d0a

Best regards,
-- 
Jonathan Denose <jdenose@google.com>


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 01/11] HID: add haptics page defines
  2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
@ 2025-08-04 14:11 ` Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 02/11] Input: add FF_HID effect type Jonathan Denose
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

Introduce haptic usages as defined in HID Usage Tables specification.
Add HID units for newton and gram.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 include/linux/hid.h | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/include/linux/hid.h b/include/linux/hid.h
index 568a9d8c749bc5547ff78d5abe6db7bce2f62d2b..344ab0e40f29afe55575e5a7544496b7bb48a266 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -156,6 +156,7 @@ struct hid_item {
 #define HID_UP_TELEPHONY	0x000b0000
 #define HID_UP_CONSUMER		0x000c0000
 #define HID_UP_DIGITIZER	0x000d0000
+#define HID_UP_HAPTIC		0x000e0000
 #define HID_UP_PID		0x000f0000
 #define HID_UP_BATTERY		0x00850000
 #define HID_UP_CAMERA		0x00900000
@@ -316,6 +317,28 @@ struct hid_item {
 #define HID_DG_TOOLSERIALNUMBER	0x000d005b
 #define HID_DG_LATENCYMODE	0x000d0060
 
+#define HID_HP_SIMPLECONTROLLER	0x000e0001
+#define HID_HP_WAVEFORMLIST	0x000e0010
+#define HID_HP_DURATIONLIST	0x000e0011
+#define HID_HP_AUTOTRIGGER	0x000e0020
+#define HID_HP_MANUALTRIGGER	0x000e0021
+#define HID_HP_AUTOTRIGGERASSOCIATEDCONTROL 0x000e0022
+#define HID_HP_INTENSITY	0x000e0023
+#define HID_HP_REPEATCOUNT	0x000e0024
+#define HID_HP_RETRIGGERPERIOD	0x000e0025
+#define HID_HP_WAVEFORMVENDORPAGE	0x000e0026
+#define HID_HP_WAVEFORMVENDORID	0x000e0027
+#define HID_HP_WAVEFORMCUTOFFTIME	0x000e0028
+#define HID_HP_WAVEFORMNONE	0x000e1001
+#define HID_HP_WAVEFORMSTOP	0x000e1002
+#define HID_HP_WAVEFORMCLICK	0x000e1003
+#define HID_HP_WAVEFORMBUZZCONTINUOUS	0x000e1004
+#define HID_HP_WAVEFORMRUMBLECONTINUOUS	0x000e1005
+#define HID_HP_WAVEFORMPRESS	0x000e1006
+#define HID_HP_WAVEFORMRELEASE	0x000e1007
+#define HID_HP_VENDORWAVEFORMMIN	0x000e2001
+#define HID_HP_VENDORWAVEFORMMAX	0x000e2fff
+
 #define HID_BAT_ABSOLUTESTATEOFCHARGE	0x00850065
 #define HID_BAT_CHARGING		0x00850044
 
@@ -423,6 +446,12 @@ struct hid_item {
 #define HID_REPORT_PROTOCOL	1
 #define HID_BOOT_PROTOCOL	0
 
+/*
+ * HID units
+ */
+#define HID_UNIT_GRAM		0x0101
+#define HID_UNIT_NEWTON		0xe111
+
 /*
  * This is the global environment of the parser. This information is
  * persistent for main-items. The global environment can be saved and

-- 
2.50.1.565.gc32cd1483b-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 02/11] Input: add FF_HID effect type
  2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 01/11] HID: add haptics page defines Jonathan Denose
@ 2025-08-04 14:11 ` Jonathan Denose
  2025-08-04 20:34   ` tomasz.pakula.oficjalny
  2025-08-04 14:11 ` [PATCH v2 03/11] Input: add INPUT_PROP_HAPTIC_TOUCHPAD Jonathan Denose
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

FF_HID effect type can be used to trigger haptic feedback with HID simple
haptic usages.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 include/uapi/linux/input.h | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
index 2557eb7b056178b2b8be98d9cea855eba1bd5aaf..3ea7c826c6fb2034e46f95cb95b84ef6f5b866df 100644
--- a/include/uapi/linux/input.h
+++ b/include/uapi/linux/input.h
@@ -428,6 +428,24 @@ struct ff_rumble_effect {
 	__u16 weak_magnitude;
 };
 
+/**
+ * struct ff_hid_effect
+ * @hid_usage: hid_usage according to Haptics page (WAVEFORM_CLICK, etc.)
+ * @vendor_id: the waveform vendor ID if hid_usage is in the vendor-defined range
+ * @vendor_waveform_page: the vendor waveform page if hid_usage is in the vendor-defined range
+ * @intensity: strength of the effect as percentage
+ * @repeat_count: number of times to retrigger effect
+ * @retrigger_period: time before effect is retriggered (in ms)
+ */
+struct ff_hid_effect {
+	__u16 hid_usage;
+	__u16 vendor_id;
+	__u8  vendor_waveform_page;
+	__u16 intensity;
+	__u16 repeat_count;
+	__u16 retrigger_period;
+};
+
 /**
  * struct ff_effect - defines force feedback effect
  * @type: type of the effect (FF_CONSTANT, FF_PERIODIC, FF_RAMP, FF_SPRING,
@@ -464,6 +482,7 @@ struct ff_effect {
 		struct ff_periodic_effect periodic;
 		struct ff_condition_effect condition[2]; /* One for each axis */
 		struct ff_rumble_effect rumble;
+		struct ff_hid_effect hid;
 	} u;
 };
 
@@ -471,6 +490,7 @@ struct ff_effect {
  * Force feedback effect types
  */
 
+#define FF_HID		0x4f
 #define FF_RUMBLE	0x50
 #define FF_PERIODIC	0x51
 #define FF_CONSTANT	0x52
@@ -480,7 +500,7 @@ struct ff_effect {
 #define FF_INERTIA	0x56
 #define FF_RAMP		0x57
 
-#define FF_EFFECT_MIN	FF_RUMBLE
+#define FF_EFFECT_MIN	FF_HID
 #define FF_EFFECT_MAX	FF_RAMP
 
 /*

-- 
2.50.1.565.gc32cd1483b-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 03/11] Input: add INPUT_PROP_HAPTIC_TOUCHPAD
  2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 01/11] HID: add haptics page defines Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 02/11] Input: add FF_HID effect type Jonathan Denose
@ 2025-08-04 14:11 ` Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 04/11] HID: haptic: introduce hid_haptic_device Jonathan Denose
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

INPUT_PROP_HAPTIC_TOUCHPAD property is to be set for a device with simple
haptic capabilities.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 Documentation/input/event-codes.rst    | 14 ++++++++++++++
 include/uapi/linux/input-event-codes.h |  1 +
 2 files changed, 15 insertions(+)

diff --git a/Documentation/input/event-codes.rst b/Documentation/input/event-codes.rst
index b4557462edd7b3fef9e9cd6c2c3cb2d05bb531ab..6f7aa9e8207c4aa825d9694ad891f4d105fe8196 100644
--- a/Documentation/input/event-codes.rst
+++ b/Documentation/input/event-codes.rst
@@ -400,6 +400,20 @@ can report through the rotational axes (absolute and/or relative rx, ry, rz).
 All other axes retain their meaning. A device must not mix
 regular directional axes and accelerometer axes on the same event node.
 
+INPUT_PROP_HAPTIC_TOUCHPAD
+--------------------------
+
+The INPUT_PROP_HAPTIC_TOUCHPAD property indicates that device:
+- supports simple haptic auto and manual triggering
+- can differentiate between at least 5 fingers
+- uses correct resolution for the X/Y (units and value)
+- report correct force per touch, and correct units for them (newtons or grams)
+- follows the MT protocol type B
+
+Summing up, such devices follow the MS spec for input devices in
+Win8 and Win8.1, and in addition support the Simple haptic controller HID table,
+and report correct units for the pressure.
+
 Guidelines
 ==========
 
diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
index 3b2524e4b667d1e7cc02ff5cb674e7c2ac069a66..efe8c36d4ee9a938ffb1b0dd0ddd0ec6a3fcb8fe 100644
--- a/include/uapi/linux/input-event-codes.h
+++ b/include/uapi/linux/input-event-codes.h
@@ -27,6 +27,7 @@
 #define INPUT_PROP_TOPBUTTONPAD		0x04	/* softbuttons at top of pad */
 #define INPUT_PROP_POINTING_STICK	0x05	/* is a pointing stick */
 #define INPUT_PROP_ACCELEROMETER	0x06	/* has accelerometer */
+#define INPUT_PROP_HAPTIC_TOUCHPAD	0x07	/* is a haptic touchpad */
 
 #define INPUT_PROP_MAX			0x1f
 #define INPUT_PROP_CNT			(INPUT_PROP_MAX + 1)

-- 
2.50.1.565.gc32cd1483b-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 04/11] HID: haptic: introduce hid_haptic_device
  2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
                   ` (2 preceding siblings ...)
  2025-08-04 14:11 ` [PATCH v2 03/11] Input: add INPUT_PROP_HAPTIC_TOUCHPAD Jonathan Denose
@ 2025-08-04 14:11 ` Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 05/11] HID: input: allow mapping of haptic output Jonathan Denose
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

Define a new structure that contains simple haptic device configuration
as well as current state.
Add functions that recognize auto trigger and manual trigger reports
as well as save their addresses.
Verify that the pressure unit is either grams or newtons.
Mark the input device as a haptic touchpad if the unit is correct and
the reports are found.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 drivers/hid/Kconfig      |  9 ++++++
 drivers/hid/Makefile     |  1 +
 drivers/hid/hid-haptic.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/hid/hid-haptic.h | 74 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 156 insertions(+)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 43859fc757470caf6ad43bd5f72f119e9c36aea7..ad6bcc4248cc111705d7cfde2b1481b46353e2d7 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -92,6 +92,15 @@ config HID_GENERIC
 
 	If unsure, say Y.
 
+config HID_HAPTIC
+	bool "Haptic touchpad support"
+	default n
+	help
+	Support for touchpads with force sensors and haptic actuators instead of a
+	traditional button.
+
+	If unsure, say N.
+
 menu "Special HID drivers"
 
 config HID_A4TECH
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 10ae5dedbd84708d988ea1f594d409ccebd85ebb..361a7daedeb85454114def8afb5f58caeab58a00 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -4,6 +4,7 @@
 #
 hid-y			:= hid-core.o hid-input.o hid-quirks.o
 hid-$(CONFIG_DEBUG_FS)		+= hid-debug.o
+hid-$(CONFIG_HID_HAPTIC)	+= hid-haptic.o
 
 obj-$(CONFIG_HID_BPF)		+= bpf/
 
diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c
new file mode 100644
index 0000000000000000000000000000000000000000..d659a430c1a6b06ded31d49efe4bded909671cb6
--- /dev/null
+++ b/drivers/hid/hid-haptic.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  HID Haptic support for Linux
+ *
+ *  Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
+ */
+
+#include "hid-haptic.h"
+
+void hid_haptic_feature_mapping(struct hid_device *hdev,
+				struct hid_haptic_device *haptic,
+				struct hid_field *field, struct hid_usage *usage)
+{
+	if (usage->hid == HID_HP_AUTOTRIGGER) {
+		if (usage->usage_index >= field->report_count) {
+			dev_err(&hdev->dev,
+				"HID_HP_AUTOTRIGGER out of range\n");
+			return;
+		}
+
+		hid_device_io_start(hdev);
+		hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT);
+		hid_hw_wait(hdev);
+		hid_device_io_stop(hdev);
+		haptic->default_auto_trigger =
+			field->value[usage->usage_index];
+		haptic->auto_trigger_report = field->report;
+	}
+}
+EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping);
+
+bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
+				    struct hid_input *hi, struct hid_field *field)
+{
+	if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON)
+		return true;
+	return false;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_check_pressure_unit);
+
+int hid_haptic_input_mapping(struct hid_device *hdev,
+			     struct hid_haptic_device *haptic,
+			     struct hid_input *hi,
+			     struct hid_field *field, struct hid_usage *usage,
+			     unsigned long **bit, int *max)
+{
+	if (usage->hid == HID_HP_MANUALTRIGGER) {
+		haptic->manual_trigger_report = field->report;
+		/* we don't really want to map these fields */
+		return -1;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_input_mapping);
+
+int hid_haptic_input_configured(struct hid_device *hdev,
+				struct hid_haptic_device *haptic,
+				struct hid_input *hi)
+{
+
+	if (hi->application == HID_DG_TOUCHPAD) {
+		if (haptic->auto_trigger_report &&
+		    haptic->manual_trigger_report) {
+			__set_bit(INPUT_PROP_HAPTIC_TOUCHPAD, hi->input->propbit);
+			return 1;
+		}
+		return 0;
+	}
+	return -1;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_input_configured);
diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h
new file mode 100644
index 0000000000000000000000000000000000000000..fc8979772d00e8b3238b26060c5541065a61811d
--- /dev/null
+++ b/drivers/hid/hid-haptic.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *  HID Haptic support for Linux
+ *
+ *  Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
+ */
+
+/*
+ */
+
+
+#include <linux/hid.h>
+
+#define HID_HAPTIC_ORDINAL_WAVEFORMNONE 1
+#define HID_HAPTIC_ORDINAL_WAVEFORMSTOP 2
+
+#define HID_HAPTIC_MODE_DEVICE 0
+#define HID_HAPTIC_MODE_HOST 1
+
+struct hid_haptic_effect {
+	u8 *report_buf;
+	struct input_dev *input_dev;
+	struct work_struct work;
+	struct list_head control;
+	struct mutex control_mutex;
+};
+
+struct hid_haptic_effect_node {
+	struct list_head node;
+	struct file *file;
+};
+
+struct hid_haptic_device {
+	struct input_dev *input_dev;
+	struct hid_device *hdev;
+	struct hid_report *auto_trigger_report;
+	struct mutex auto_trigger_mutex;
+	struct workqueue_struct *wq;
+	struct hid_report *manual_trigger_report;
+	struct mutex manual_trigger_mutex;
+	size_t manual_trigger_report_len;
+	int pressed_state;
+	s32 pressure_sum;
+	s32 force_logical_minimum;
+	s32 force_physical_minimum;
+	s32 force_resolution;
+	u32 mode;
+	u32 default_auto_trigger;
+	u32 vendor_page;
+	u32 vendor_id;
+	u32 max_waveform_id;
+	u32 max_duration_id;
+	u16 *hid_usage_map;
+	u32 *duration_map;
+	u16 press_ordinal;
+	u16 release_ordinal;
+	struct hid_haptic_effect *effect;
+	struct hid_haptic_effect stop_effect;
+};
+
+void hid_haptic_feature_mapping(struct hid_device *hdev,
+				struct hid_haptic_device *haptic,
+				struct hid_field *field, struct hid_usage
+				*usage);
+bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
+				    struct hid_input *hi, struct hid_field *field);
+int hid_haptic_input_mapping(struct hid_device *hdev,
+			     struct hid_haptic_device *haptic,
+			     struct hid_input *hi,
+			     struct hid_field *field, struct hid_usage *usage,
+			     unsigned long **bit, int *max);
+int hid_haptic_input_configured(struct hid_device *hdev,
+				struct hid_haptic_device *haptic,
+				struct hid_input *hi);

-- 
2.50.1.565.gc32cd1483b-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 05/11] HID: input: allow mapping of haptic output
  2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
                   ` (3 preceding siblings ...)
  2025-08-04 14:11 ` [PATCH v2 04/11] HID: haptic: introduce hid_haptic_device Jonathan Denose
@ 2025-08-04 14:11 ` Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 06/11] HID: haptic: initialize haptic device Jonathan Denose
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

This change makes it possible to parse output reports by input mapping
functions by HID drivers.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 drivers/hid/hid-input.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 9d80635a91ebd8d8bdafaac07b5f85693b179cb4..d42c1fbd20a1cc01c04f93cf10f1d1c18043929c 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -682,9 +682,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
 	if (field->report_count < 1)
 		goto ignore;
 
-	/* only LED usages are supported in output fields */
+	/* only LED and HAPTIC usages are supported in output fields */
 	if (field->report_type == HID_OUTPUT_REPORT &&
-			(usage->hid & HID_USAGE_PAGE) != HID_UP_LED) {
+	    (usage->hid & HID_USAGE_PAGE) != HID_UP_LED &&
+	    (usage->hid & HID_USAGE_PAGE) != HID_UP_HAPTIC) {
 		goto ignore;
 	}
 

-- 
2.50.1.565.gc32cd1483b-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 06/11] HID: haptic: initialize haptic device
  2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
                   ` (4 preceding siblings ...)
  2025-08-04 14:11 ` [PATCH v2 05/11] HID: input: allow mapping of haptic output Jonathan Denose
@ 2025-08-04 14:11 ` Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 07/11] HID: input: calculate resolution for pressure Jonathan Denose
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

Add hid_haptic_init(). Parse autotrigger report to retrieve ordinals for
press and release waveforms.
Implement force feedback functions.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 drivers/hid/hid-haptic.c | 438 +++++++++++++++++++++++++++++++++++++++++++++++
 drivers/hid/hid-haptic.h |   1 +
 2 files changed, 439 insertions(+)

diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c
index d659a430c1a6b06ded31d49efe4bded909671cb6..923b685f0e1e81b7f95567b11209af264c522373 100644
--- a/drivers/hid/hid-haptic.c
+++ b/drivers/hid/hid-haptic.c
@@ -5,12 +5,16 @@
  *  Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
  */
 
+#include <linux/module.h>
+
 #include "hid-haptic.h"
 
 void hid_haptic_feature_mapping(struct hid_device *hdev,
 				struct hid_haptic_device *haptic,
 				struct hid_field *field, struct hid_usage *usage)
 {
+	u16 usage_hid;
+
 	if (usage->hid == HID_HP_AUTOTRIGGER) {
 		if (usage->usage_index >= field->report_count) {
 			dev_err(&hdev->dev,
@@ -25,6 +29,20 @@ void hid_haptic_feature_mapping(struct hid_device *hdev,
 		haptic->default_auto_trigger =
 			field->value[usage->usage_index];
 		haptic->auto_trigger_report = field->report;
+	} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ORDINAL) {
+		usage_hid = usage->hid & HID_USAGE;
+		switch (field->logical) {
+		case HID_HP_WAVEFORMLIST:
+			if (usage_hid > haptic->max_waveform_id)
+				haptic->max_waveform_id = usage_hid;
+			break;
+		case HID_HP_DURATIONLIST:
+			if (usage_hid > haptic->max_duration_id)
+				haptic->max_duration_id = usage_hid;
+			break;
+		default:
+			break;
+		}
 	}
 }
 EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping);
@@ -70,3 +88,423 @@ int hid_haptic_input_configured(struct hid_device *hdev,
 	return -1;
 }
 EXPORT_SYMBOL_GPL(hid_haptic_input_configured);
+
+static void parse_auto_trigger_field(struct hid_haptic_device *haptic,
+				     struct hid_field *field)
+{
+	int count = field->report_count;
+	int n;
+	u16 usage_hid;
+
+	for (n = 0; n < count; n++) {
+		switch (field->usage[n].hid & HID_USAGE_PAGE) {
+		case HID_UP_ORDINAL:
+			usage_hid = field->usage[n].hid & HID_USAGE;
+			switch (field->logical) {
+			case HID_HP_WAVEFORMLIST:
+				haptic->hid_usage_map[usage_hid] = field->value[n];
+				if (field->value[n] ==
+				    (HID_HP_WAVEFORMPRESS & HID_USAGE)) {
+					haptic->press_ordinal = usage_hid;
+				} else if (field->value[n] ==
+					   (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
+					haptic->release_ordinal = usage_hid;
+				}
+				break;
+			case HID_HP_DURATIONLIST:
+				haptic->duration_map[usage_hid] =
+					field->value[n];
+				break;
+			default:
+				break;
+			}
+			break;
+		case HID_UP_HAPTIC:
+			switch (field->usage[n].hid) {
+			case HID_HP_WAVEFORMVENDORID:
+				haptic->vendor_id = field->value[n];
+				break;
+			case HID_HP_WAVEFORMVENDORPAGE:
+				haptic->vendor_page = field->value[n];
+				break;
+			default:
+				break;
+			}
+			break;
+		default:
+			/* Should not really happen */
+			break;
+		}
+	}
+}
+
+static void fill_effect_buf(struct hid_haptic_device *haptic,
+			    struct ff_hid_effect *effect,
+			    struct hid_haptic_effect *haptic_effect,
+			    int waveform_ordinal)
+{
+	struct hid_report *rep = haptic->manual_trigger_report;
+	struct hid_usage *usage;
+	struct hid_field *field;
+	s32 value;
+	int i, j;
+	u8 *buf = haptic_effect->report_buf;
+
+	mutex_lock(&haptic->manual_trigger_mutex);
+	for (i = 0; i < rep->maxfield; i++) {
+		field = rep->field[i];
+		/* Ignore if report count is out of bounds. */
+		if (field->report_count < 1)
+			continue;
+
+		for (j = 0; j < field->maxusage; j++) {
+			usage = &field->usage[j];
+
+			switch (usage->hid) {
+			case HID_HP_INTENSITY:
+				if (effect->intensity > 100) {
+					value = field->logical_maximum;
+				} else {
+					value = field->logical_minimum +
+						effect->intensity *
+						(field->logical_maximum -
+						 field->logical_minimum) / 100;
+				}
+				break;
+			case HID_HP_REPEATCOUNT:
+				value = effect->repeat_count;
+				break;
+			case HID_HP_RETRIGGERPERIOD:
+				value = effect->retrigger_period;
+				break;
+			case HID_HP_MANUALTRIGGER:
+				value = waveform_ordinal;
+				break;
+			default:
+				break;
+			}
+
+			field->value[j] = value;
+		}
+	}
+
+	hid_output_report(rep, buf);
+	mutex_unlock(&haptic->manual_trigger_mutex);
+}
+
+static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect,
+				    struct ff_effect *old)
+{
+	struct ff_device *ff = dev->ff;
+	struct hid_haptic_device *haptic = ff->private;
+	int i, ordinal = 0;
+
+	/* If vendor range, check vendor id and page */
+	if (effect->u.hid.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) &&
+	    effect->u.hid.hid_usage <= (HID_HP_VENDORWAVEFORMMAX & HID_USAGE) &&
+	    (effect->u.hid.vendor_id != haptic->vendor_id ||
+	     effect->u.hid.vendor_waveform_page != haptic->vendor_page))
+		return -EINVAL;
+
+	/* Check hid_usage */
+	for (i = 1; i <= haptic->max_waveform_id; i++) {
+		if (haptic->hid_usage_map[i] == effect->u.hid.hid_usage) {
+			ordinal = i;
+			break;
+		}
+	}
+	if (ordinal < 1)
+		return -EINVAL;
+
+	/* Fill the buffer for the effect id */
+	fill_effect_buf(haptic, &effect->u.hid, &haptic->effect[effect->id],
+			ordinal);
+
+	return 0;
+}
+
+static int play_effect(struct hid_device *hdev, struct hid_haptic_device *haptic,
+		       struct hid_haptic_effect *effect)
+{
+	int ret;
+
+	ret = hid_hw_output_report(hdev, effect->report_buf,
+				   haptic->manual_trigger_report_len);
+	if (ret < 0) {
+		ret = hid_hw_raw_request(hdev,
+					 haptic->manual_trigger_report->id,
+					 effect->report_buf,
+					 haptic->manual_trigger_report_len,
+					 HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+	}
+
+	return ret;
+}
+
+static void haptic_work_handler(struct work_struct *work)
+{
+
+	struct hid_haptic_effect *effect = container_of(work,
+							struct hid_haptic_effect,
+							work);
+	struct input_dev *dev = effect->input_dev;
+	struct hid_device *hdev = input_get_drvdata(dev);
+	struct hid_haptic_device *haptic = dev->ff->private;
+
+	mutex_lock(&haptic->manual_trigger_mutex);
+	if (effect != &haptic->stop_effect)
+		play_effect(hdev, haptic, &haptic->stop_effect);
+
+	play_effect(hdev, haptic, effect);
+	mutex_unlock(&haptic->manual_trigger_mutex);
+
+}
+
+static int hid_haptic_playback(struct input_dev *dev, int effect_id, int value)
+{
+	struct hid_haptic_device *haptic = dev->ff->private;
+
+	if (value)
+		queue_work(haptic->wq, &haptic->effect[effect_id].work);
+	else
+		queue_work(haptic->wq, &haptic->stop_effect.work);
+
+	return 0;
+}
+
+static void effect_set_default(struct ff_effect *effect)
+{
+	effect->type = FF_HID;
+	effect->id = -1;
+	effect->u.hid.hid_usage = HID_HP_WAVEFORMNONE & HID_USAGE;
+	effect->u.hid.intensity = 100;
+	effect->u.hid.retrigger_period = 0;
+	effect->u.hid.repeat_count = 0;
+}
+
+static int hid_haptic_erase(struct input_dev *dev, int effect_id)
+{
+	struct hid_haptic_device *haptic = dev->ff->private;
+	struct ff_effect effect;
+	int ordinal;
+
+	effect_set_default(&effect);
+
+	if (effect.u.hid.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
+		ordinal = haptic->release_ordinal;
+		if (!ordinal)
+			ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
+		else
+			effect.u.hid.hid_usage = HID_HP_WAVEFORMRELEASE &
+				HID_USAGE;
+		fill_effect_buf(haptic, &effect.u.hid, &haptic->effect[effect_id],
+				ordinal);
+	} else if (effect.u.hid.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE)) {
+		ordinal = haptic->press_ordinal;
+		if (!ordinal)
+			ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
+		else
+			effect.u.hid.hid_usage = HID_HP_WAVEFORMPRESS &
+				HID_USAGE;
+		fill_effect_buf(haptic, &effect.u.hid, &haptic->effect[effect_id],
+				ordinal);
+	}
+
+	return 0;
+}
+
+static void hid_haptic_destroy(struct ff_device *ff)
+{
+	struct hid_haptic_device *haptic = ff->private;
+	struct hid_device *hdev = haptic->hdev;
+	int r;
+
+	if (hdev)
+		put_device(&hdev->dev);
+
+	kfree(haptic->stop_effect.report_buf);
+	haptic->stop_effect.report_buf = NULL;
+
+	if (haptic->effect) {
+		for (r = 0; r < ff->max_effects; r++)
+			kfree(haptic->effect[r].report_buf);
+		kfree(haptic->effect);
+	}
+	haptic->effect = NULL;
+
+	destroy_workqueue(haptic->wq);
+	haptic->wq = NULL;
+
+	kfree(haptic->duration_map);
+	haptic->duration_map = NULL;
+
+	kfree(haptic->hid_usage_map);
+	haptic->hid_usage_map = NULL;
+
+	module_put(THIS_MODULE);
+}
+
+int hid_haptic_init(struct hid_device *hdev,
+		    struct hid_haptic_device **haptic_ptr)
+{
+	struct hid_haptic_device *haptic = *haptic_ptr;
+	struct input_dev *dev = NULL;
+	struct hid_input *hidinput;
+	struct ff_device *ff;
+	int ret = 0, r;
+	struct ff_hid_effect stop_effect = {
+		.hid_usage = HID_HP_WAVEFORMSTOP & HID_USAGE,
+	};
+	const char *prefix = "hid-haptic";
+	char *name;
+	int (*flush)(struct input_dev *dev, struct file *file);
+	int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
+
+	haptic->hdev = hdev;
+	haptic->max_waveform_id = max(2u, haptic->max_waveform_id);
+	haptic->max_duration_id = max(2u, haptic->max_duration_id);
+
+	haptic->hid_usage_map = kcalloc(haptic->max_waveform_id + 1,
+					sizeof(u16), GFP_KERNEL);
+	if (!haptic->hid_usage_map) {
+		ret = -ENOMEM;
+		goto exit;
+	}
+	haptic->duration_map = kcalloc(haptic->max_duration_id + 1,
+				       sizeof(u32), GFP_KERNEL);
+	if (!haptic->duration_map) {
+		ret = -ENOMEM;
+		goto usage_map;
+	}
+
+	if (haptic->max_waveform_id != haptic->max_duration_id)
+		dev_warn(&hdev->dev,
+			 "Haptic duration and waveform lists have different max id (%u and %u).\n",
+			 haptic->max_duration_id, haptic->max_waveform_id);
+
+	haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMNONE] =
+		HID_HP_WAVEFORMNONE & HID_USAGE;
+	haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMSTOP] =
+		HID_HP_WAVEFORMSTOP & HID_USAGE;
+
+	for (r = 0; r < haptic->auto_trigger_report->maxfield; r++)
+		parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]);
+
+	list_for_each_entry(hidinput, &hdev->inputs, list) {
+		if (hidinput->application == HID_DG_TOUCHPAD) {
+			dev = hidinput->input;
+			break;
+		}
+	}
+
+	if (!dev) {
+		dev_err(&hdev->dev, "Failed to find the input device\n");
+		ret = -ENODEV;
+		goto duration_map;
+	}
+
+	haptic->input_dev = dev;
+	haptic->manual_trigger_report_len =
+		hid_report_len(haptic->manual_trigger_report);
+	mutex_init(&haptic->manual_trigger_mutex);
+	name = kmalloc(strlen(prefix) + strlen(hdev->name) + 2, GFP_KERNEL);
+	if (name) {
+		sprintf(name, "%s %s", prefix, hdev->name);
+		haptic->wq = create_singlethread_workqueue(name);
+		kfree(name);
+	}
+	if (!haptic->wq) {
+		ret = -ENOMEM;
+		goto duration_map;
+	}
+	haptic->effect = kcalloc(FF_MAX_EFFECTS,
+				 sizeof(struct hid_haptic_effect), GFP_KERNEL);
+	if (!haptic->effect) {
+		ret = -ENOMEM;
+		goto output_queue;
+	}
+	for (r = 0; r < FF_MAX_EFFECTS; r++) {
+		haptic->effect[r].report_buf =
+			hid_alloc_report_buf(haptic->manual_trigger_report,
+					     GFP_KERNEL);
+		if (!haptic->effect[r].report_buf) {
+			dev_err(&hdev->dev,
+				"Failed to allocate a buffer for an effect.\n");
+			ret = -ENOMEM;
+			goto buffer_free;
+		}
+		haptic->effect[r].input_dev = dev;
+		INIT_WORK(&haptic->effect[r].work, haptic_work_handler);
+	}
+	haptic->stop_effect.report_buf =
+		hid_alloc_report_buf(haptic->manual_trigger_report,
+				     GFP_KERNEL);
+	if (!haptic->stop_effect.report_buf) {
+		dev_err(&hdev->dev,
+			"Failed to allocate a buffer for stop effect.\n");
+		ret = -ENOMEM;
+		goto buffer_free;
+	}
+	haptic->stop_effect.input_dev = dev;
+	INIT_WORK(&haptic->stop_effect.work, haptic_work_handler);
+	fill_effect_buf(haptic, &stop_effect, &haptic->stop_effect,
+			HID_HAPTIC_ORDINAL_WAVEFORMSTOP);
+
+	input_set_capability(dev, EV_FF, FF_HID);
+
+	flush = dev->flush;
+	event = dev->event;
+	ret = input_ff_create(dev, FF_MAX_EFFECTS);
+	if (ret) {
+		dev_err(&hdev->dev, "Failed to create ff device.\n");
+		goto stop_buffer_free;
+	}
+
+	ff = dev->ff;
+	ff->private = haptic;
+	ff->upload = hid_haptic_upload_effect;
+	ff->playback = hid_haptic_playback;
+	ff->erase = hid_haptic_erase;
+	ff->destroy = hid_haptic_destroy;
+	if (!try_module_get(THIS_MODULE)) {
+		dev_err(&hdev->dev, "Failed to increase module count.\n");
+		goto input_free;
+	}
+	if (!get_device(&hdev->dev)) {
+		dev_err(&hdev->dev, "Failed to get hdev device.\n");
+		module_put(THIS_MODULE);
+		goto input_free;
+	}
+	return 0;
+
+input_free:
+	input_ff_destroy(dev);
+	/* Do not let double free happen, input_ff_destroy will call
+	 * hid_haptic_destroy.
+	 */
+	*haptic_ptr = NULL;
+	/* Restore dev flush and event */
+	dev->flush = flush;
+	dev->event = event;
+	return ret;
+stop_buffer_free:
+	kfree(haptic->stop_effect.report_buf);
+	haptic->stop_effect.report_buf = NULL;
+buffer_free:
+	while (--r >= 0)
+		kfree(haptic->effect[r].report_buf);
+	kfree(haptic->effect);
+	haptic->effect = NULL;
+output_queue:
+	destroy_workqueue(haptic->wq);
+	haptic->wq = NULL;
+duration_map:
+	kfree(haptic->duration_map);
+	haptic->duration_map = NULL;
+usage_map:
+	kfree(haptic->hid_usage_map);
+	haptic->hid_usage_map = NULL;
+exit:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_init);
diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h
index fc8979772d00e8b3238b26060c5541065a61811d..9aa910579d09206a2a882a5f708efd8620428f78 100644
--- a/drivers/hid/hid-haptic.h
+++ b/drivers/hid/hid-haptic.h
@@ -72,3 +72,4 @@ int hid_haptic_input_mapping(struct hid_device *hdev,
 int hid_haptic_input_configured(struct hid_device *hdev,
 				struct hid_haptic_device *haptic,
 				struct hid_input *hi);
+int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr);

-- 
2.50.1.565.gc32cd1483b-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 07/11] HID: input: calculate resolution for pressure
  2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
                   ` (5 preceding siblings ...)
  2025-08-04 14:11 ` [PATCH v2 06/11] HID: haptic: initialize haptic device Jonathan Denose
@ 2025-08-04 14:11 ` Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 08/11] HID: haptic: add functions handling events Jonathan Denose
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

Assume that if the pressure is given in newtons it should be normalized
to grams. If the pressure has no unit do not calculate resolution.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 drivers/hid/hid-input.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index d42c1fbd20a1cc01c04f93cf10f1d1c18043929c..1d59787bd0c0e251698e2a2944dae1c4a96adefe 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -303,6 +303,19 @@ __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code)
 		}
 		break;
 
+	case ABS_PRESSURE:
+	case ABS_MT_PRESSURE:
+		if (field->unit == HID_UNIT_NEWTON) {
+			/* Convert to grams, 1 newton is 101.97 grams */
+			prev = physical_extents;
+			physical_extents *= 10197;
+			if (physical_extents < prev)
+				return 0;
+			unit_exponent -= 2;
+		} else if (field->unit != HID_UNIT_GRAM) {
+			return 0;
+		}
+		break;
 	default:
 		return 0;
 	}

-- 
2.50.1.565.gc32cd1483b-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 08/11] HID: haptic: add functions handling events
  2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
                   ` (6 preceding siblings ...)
  2025-08-04 14:11 ` [PATCH v2 07/11] HID: input: calculate resolution for pressure Jonathan Denose
@ 2025-08-04 14:11 ` Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 09/11] Input: MT - add INPUT_MT_TOTAL_FORCE flags Jonathan Denose
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

Implement hid_haptic_handle_press_release() which generates haptic feedback
as well as saves the pressed state of the haptic device.
Add functions to increase and reset the state of the pressure detected by
the device.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 drivers/hid/hid-haptic.c | 20 +++++++++++++++++++-
 drivers/hid/hid-haptic.h |  4 ++++
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c
index 923b685f0e1e81b7f95567b11209af264c522373..760dd1d70583489c07e199943ebba361d347bfa4 100644
--- a/drivers/hid/hid-haptic.c
+++ b/drivers/hid/hid-haptic.c
@@ -50,8 +50,13 @@ EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping);
 bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
 				    struct hid_input *hi, struct hid_field *field)
 {
-	if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON)
+	if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON) {
+		haptic->force_logical_minimum = field->logical_minimum;
+		haptic->force_physical_minimum = field->physical_minimum;
+		haptic->force_resolution = input_abs_get_res(hi->input,
+							     ABS_MT_PRESSURE);
 		return true;
+	}
 	return false;
 }
 EXPORT_SYMBOL_GPL(hid_haptic_check_pressure_unit);
@@ -508,3 +513,16 @@ int hid_haptic_init(struct hid_device *hdev,
 	return ret;
 }
 EXPORT_SYMBOL_GPL(hid_haptic_init);
+
+void hid_haptic_pressure_reset(struct hid_haptic_device *haptic)
+{
+	haptic->pressure_sum = 0;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_pressure_reset);
+
+void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
+				 __s32 pressure)
+{
+	haptic->pressure_sum += pressure;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_pressure_increase);
diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h
index 9aa910579d09206a2a882a5f708efd8620428f78..0a34b0c6d706a985630962acc41f7a8eb73cd343 100644
--- a/drivers/hid/hid-haptic.h
+++ b/drivers/hid/hid-haptic.h
@@ -73,3 +73,7 @@ int hid_haptic_input_configured(struct hid_device *hdev,
 				struct hid_haptic_device *haptic,
 				struct hid_input *hi);
 int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr);
+void hid_haptic_handle_press_release(struct hid_haptic_device *haptic);
+void hid_haptic_pressure_reset(struct hid_haptic_device *haptic);
+void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
+				  __s32 pressure);

-- 
2.50.1.565.gc32cd1483b-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 09/11] Input: MT - add INPUT_MT_TOTAL_FORCE flags
  2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
                   ` (7 preceding siblings ...)
  2025-08-04 14:11 ` [PATCH v2 08/11] HID: haptic: add functions handling events Jonathan Denose
@ 2025-08-04 14:11 ` Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 10/11] HID: haptic: add hid_haptic_switch_mode Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 11/11] HID: multitouch: add haptic multitouch support Jonathan Denose
  10 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

Add a flag to generate ABS_PRESSURE as sum of ABS_MT_PRESSURE across
all slots.
This flag should be set if one knows a device reports true force and would
like to report total force to the userspace.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 drivers/input/input-mt.c | 14 ++++++++++----
 include/linux/input/mt.h |  1 +
 2 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/drivers/input/input-mt.c b/drivers/input/input-mt.c
index 337006dd9dcf72ef2eeb8580e4dd83babf8100be..09f518897d4a71a4a7625367dc2c652ee6035d98 100644
--- a/drivers/input/input-mt.c
+++ b/drivers/input/input-mt.c
@@ -198,6 +198,7 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
 	struct input_mt *mt = dev->mt;
 	struct input_mt_slot *oldest;
 	int oldid, count, i;
+	int p, reported_p = 0;
 
 	if (!mt)
 		return;
@@ -216,6 +217,13 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
 			oldest = ps;
 			oldid = id;
 		}
+		if (test_bit(ABS_MT_PRESSURE, dev->absbit)) {
+			p = input_mt_get_value(ps, ABS_MT_PRESSURE);
+			if (mt->flags & INPUT_MT_TOTAL_FORCE)
+				reported_p += p;
+			else if (oldid == id)
+				reported_p = p;
+		}
 		count++;
 	}
 
@@ -245,10 +253,8 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
 		input_event(dev, EV_ABS, ABS_X, x);
 		input_event(dev, EV_ABS, ABS_Y, y);
 
-		if (test_bit(ABS_MT_PRESSURE, dev->absbit)) {
-			int p = input_mt_get_value(oldest, ABS_MT_PRESSURE);
-			input_event(dev, EV_ABS, ABS_PRESSURE, p);
-		}
+		if (test_bit(ABS_MT_PRESSURE, dev->absbit))
+			input_event(dev, EV_ABS, ABS_PRESSURE, reported_p);
 	} else {
 		if (test_bit(ABS_MT_PRESSURE, dev->absbit))
 			input_event(dev, EV_ABS, ABS_PRESSURE, 0);
diff --git a/include/linux/input/mt.h b/include/linux/input/mt.h
index 2cf89a538b18bbc7c99c8705c2d22bdc95065238..d30286298a00a356bc9db954ae362f034cdd359b 100644
--- a/include/linux/input/mt.h
+++ b/include/linux/input/mt.h
@@ -17,6 +17,7 @@
 #define INPUT_MT_DROP_UNUSED	0x0004	/* drop contacts not seen in frame */
 #define INPUT_MT_TRACK		0x0008	/* use in-kernel tracking */
 #define INPUT_MT_SEMI_MT	0x0010	/* semi-mt device, finger count handled manually */
+#define INPUT_MT_TOTAL_FORCE	0x0020	/* calculate total force from slots pressure */
 
 /**
  * struct input_mt_slot - represents the state of an input MT slot

-- 
2.50.1.565.gc32cd1483b-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 10/11] HID: haptic: add hid_haptic_switch_mode
  2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
                   ` (8 preceding siblings ...)
  2025-08-04 14:11 ` [PATCH v2 09/11] Input: MT - add INPUT_MT_TOTAL_FORCE flags Jonathan Denose
@ 2025-08-04 14:11 ` Jonathan Denose
  2025-08-04 14:11 ` [PATCH v2 11/11] HID: multitouch: add haptic multitouch support Jonathan Denose
  10 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

Function hid_haptic_switch_mode() can be used to switch between
device-controlled mode and host-controlled mode. Uploading a
WAVEFORMPRESS or WAVEFORMRELEASE effect triggers host-controlled mode if
the device is in device-controlled mode.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 drivers/hid/hid-haptic.c | 66 +++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 59 insertions(+), 7 deletions(-)

diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c
index 760dd1d70583489c07e199943ebba361d347bfa4..e83363cad6febb5d3fcd786b76e05bc16a7e4e94 100644
--- a/drivers/hid/hid-haptic.c
+++ b/drivers/hid/hid-haptic.c
@@ -5,6 +5,7 @@
  *  Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
  */
 
+#include <linux/input/mt.h>
 #include <linux/module.h>
 
 #include "hid-haptic.h"
@@ -197,12 +198,46 @@ static void fill_effect_buf(struct hid_haptic_device *haptic,
 	mutex_unlock(&haptic->manual_trigger_mutex);
 }
 
+static void switch_mode(struct hid_device *hdev, struct hid_haptic_device *haptic,
+			int mode)
+{
+	struct hid_report *rep = haptic->auto_trigger_report;
+	struct hid_field *field;
+	s32 value;
+	int i, j;
+
+	if (mode == HID_HAPTIC_MODE_HOST)
+		value = HID_HAPTIC_ORDINAL_WAVEFORMSTOP;
+	else
+		value = haptic->default_auto_trigger;
+
+	mutex_lock(&haptic->auto_trigger_mutex);
+	for (i = 0; i < rep->maxfield; i++) {
+		field = rep->field[i];
+		/* Ignore if report count is out of bounds. */
+		if (field->report_count < 1)
+			continue;
+
+		for (j = 0; j < field->maxusage; j++) {
+			if (field->usage[j].hid == HID_HP_AUTOTRIGGER)
+				field->value[j] = value;
+		}
+	}
+
+	/* send the report */
+	hid_hw_request(hdev, rep, HID_REQ_SET_REPORT);
+	mutex_unlock(&haptic->auto_trigger_mutex);
+	haptic->mode = mode;
+}
+
 static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect,
 				    struct ff_effect *old)
 {
+	struct hid_device *hdev = input_get_drvdata(dev);
 	struct ff_device *ff = dev->ff;
 	struct hid_haptic_device *haptic = ff->private;
 	int i, ordinal = 0;
+	bool switch_modes = false;
 
 	/* If vendor range, check vendor id and page */
 	if (effect->u.hid.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) &&
@@ -225,6 +260,16 @@ static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *eff
 	fill_effect_buf(haptic, &effect->u.hid, &haptic->effect[effect->id],
 			ordinal);
 
+	if (effect->u.hid.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE) ||
+			effect->u.hid.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE))
+		switch_modes = true;
+
+	/* If device is in autonomous mode, and the uploaded effect signals userspace
+	 * wants control of the device, change modes
+	 */
+	if (switch_modes && haptic->mode == HID_HAPTIC_MODE_DEVICE)
+		switch_mode(hdev, haptic, HID_HAPTIC_MODE_HOST);
+
 	return 0;
 }
 
@@ -290,6 +335,7 @@ static void effect_set_default(struct ff_effect *effect)
 static int hid_haptic_erase(struct input_dev *dev, int effect_id)
 {
 	struct hid_haptic_device *haptic = dev->ff->private;
+	struct hid_device *hdev = input_get_drvdata(dev);
 	struct ff_effect effect;
 	int ordinal;
 
@@ -297,20 +343,25 @@ static int hid_haptic_erase(struct input_dev *dev, int effect_id)
 
 	if (effect.u.hid.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
 		ordinal = haptic->release_ordinal;
-		if (!ordinal)
+		if (!ordinal) {
 			ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
-		else
-			effect.u.hid.hid_usage = HID_HP_WAVEFORMRELEASE &
-				HID_USAGE;
+			if (haptic->mode == HID_HAPTIC_MODE_HOST)
+				switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
+		} else {
+			effect.u.hid.hid_usage = HID_HP_WAVEFORMRELEASE & HID_USAGE;
+		}
 		fill_effect_buf(haptic, &effect.u.hid, &haptic->effect[effect_id],
 				ordinal);
 	} else if (effect.u.hid.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE)) {
 		ordinal = haptic->press_ordinal;
-		if (!ordinal)
+		if (!ordinal) {
 			ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
+			if (haptic->mode == HID_HAPTIC_MODE_HOST)
+				switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
+		}
 		else
-			effect.u.hid.hid_usage = HID_HP_WAVEFORMPRESS &
-				HID_USAGE;
+			effect.u.hid.hid_usage = HID_HP_WAVEFORMPRESS & HID_USAGE;
+
 		fill_effect_buf(haptic, &effect.u.hid, &haptic->effect[effect_id],
 				ordinal);
 	}
@@ -392,6 +443,7 @@ int hid_haptic_init(struct hid_device *hdev,
 	haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMSTOP] =
 		HID_HP_WAVEFORMSTOP & HID_USAGE;
 
+	mutex_init(&haptic->auto_trigger_mutex);
 	for (r = 0; r < haptic->auto_trigger_report->maxfield; r++)
 		parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]);
 

-- 
2.50.1.565.gc32cd1483b-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 11/11] HID: multitouch: add haptic multitouch support
  2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
                   ` (9 preceding siblings ...)
  2025-08-04 14:11 ` [PATCH v2 10/11] HID: haptic: add hid_haptic_switch_mode Jonathan Denose
@ 2025-08-04 14:11 ` Jonathan Denose
  2025-08-13  9:22   ` Benjamin Tissoires
  10 siblings, 1 reply; 17+ messages in thread
From: Jonathan Denose @ 2025-08-04 14:11 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

Add new option (MULTITOUCH_HAPTIC) to mark whether hid-multitouch
should try and configure simple haptic device.
Once this option is configured, and the device is recognized to have simple
haptic capabilities, check input frames for pressure and handle it using
hid_haptic_* API.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 drivers/hid/Kconfig          |  11 ++++
 drivers/hid/Makefile         |   2 +-
 drivers/hid/hid-haptic.h     |  52 +++++++++++++++++
 drivers/hid/hid-multitouch.c | 136 ++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 199 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index ad6bcc4248cc111705d7cfde2b1481b46353e2d7..b7452f11a4f914f92af582ed054d42ecbcd6cb9e 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -817,6 +817,17 @@ config HID_MULTITOUCH
 	  To compile this driver as a module, choose M here: the
 	  module will be called hid-multitouch.
 
+config MULTITOUCH_HAPTIC
+	bool "Simple haptic multitouch support"
+	depends on HID_MULTITOUCH
+	select HID_HAPTIC
+	default n
+	help
+	Support for simple multitouch haptic devices.
+	Adds extra parsing and FF device for the hid multitouch driver.
+	It can be used for Elan 2703 haptic touchpad.
+	To enable, say Y.
+
 config HID_NINTENDO
 	tristate "Nintendo Joy-Con, NSO, and Pro Controller support"
 	depends on NEW_LEDS
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 361a7daedeb85454114def8afb5f58caeab58a00..be09b4f13b2058a0a1d7eab79f35def758120fc4 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -4,7 +4,7 @@
 #
 hid-y			:= hid-core.o hid-input.o hid-quirks.o
 hid-$(CONFIG_DEBUG_FS)		+= hid-debug.o
-hid-$(CONFIG_HID_HAPTIC)	+= hid-haptic.o
+hid-$(CONFIG_MULTITOUCH_HAPTIC)	+= hid-haptic.o
 
 obj-$(CONFIG_HID_BPF)		+= bpf/
 
diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h
index 0a34b0c6d706a985630962acc41f7a8eb73cd343..808cec0b4e51eba1f58b839f3e552493655b7899 100644
--- a/drivers/hid/hid-haptic.h
+++ b/drivers/hid/hid-haptic.h
@@ -58,6 +58,7 @@ struct hid_haptic_device {
 	struct hid_haptic_effect stop_effect;
 };
 
+#ifdef CONFIG_MULTITOUCH_HAPTIC
 void hid_haptic_feature_mapping(struct hid_device *hdev,
 				struct hid_haptic_device *haptic,
 				struct hid_field *field, struct hid_usage
@@ -77,3 +78,54 @@ void hid_haptic_handle_press_release(struct hid_haptic_device *haptic);
 void hid_haptic_pressure_reset(struct hid_haptic_device *haptic);
 void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
 				  __s32 pressure);
+#else
+static inline
+void hid_haptic_feature_mapping(struct hid_device *hdev,
+				struct hid_haptic_device *haptic,
+				struct hid_field *field, struct hid_usage
+				*usage)
+{}
+static inline
+bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
+				    struct hid_input *hi, struct hid_field *field)
+{
+	return false;
+}
+static inline
+int hid_haptic_input_mapping(struct hid_device *hdev,
+			     struct hid_haptic_device *haptic,
+			     struct hid_input *hi,
+			     struct hid_field *field, struct hid_usage *usage,
+			     unsigned long **bit, int *max)
+{
+	return 0;
+}
+static inline
+int hid_haptic_input_configured(struct hid_device *hdev,
+				struct hid_haptic_device *haptic,
+				struct hid_input *hi)
+{
+	return 0;
+}
+static inline
+void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic)
+{}
+static inline
+int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr)
+{
+	return 0;
+}
+static inline
+void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) {}
+static inline
+bool hid_haptic_handle_input(struct hid_haptic_device *haptic)
+{
+	return false;
+}
+static inline
+void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) {}
+static inline
+void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
+				  __s32 pressure)
+{}
+#endif
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index b41001e02da7e02d492bd85743b359ed7ec16e7f..4ff9ac5022b13a0739dbc7ae5f6ebd84f0114a73 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -49,6 +49,8 @@ MODULE_LICENSE("GPL");
 
 #include "hid-ids.h"
 
+#include "hid-haptic.h"
+
 /* quirks to control the device */
 #define MT_QUIRK_NOT_SEEN_MEANS_UP	BIT(0)
 #define MT_QUIRK_SLOT_IS_CONTACTID	BIT(1)
@@ -167,11 +169,13 @@ struct mt_report_data {
 struct mt_device {
 	struct mt_class mtclass;	/* our mt device class */
 	struct timer_list release_timer;	/* to release sticky fingers */
+	struct hid_haptic_device *haptic;	/* haptic related configuration */
 	struct hid_device *hdev;	/* hid_device we're attached to */
 	unsigned long mt_io_flags;	/* mt flags (MT_IO_FLAGS_*) */
 	__u8 inputmode_value;	/* InputMode HID feature value */
 	__u8 maxcontacts;
 	bool is_buttonpad;	/* is this device a button pad? */
+	bool is_haptic_touchpad;	/* is this device a haptic touchpad? */
 	bool serial_maybe;	/* need to check for serial protocol */
 
 	struct list_head applications;
@@ -490,6 +494,95 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report)
 	kfree(buf);
 }
 
+#if defined(CONFIG_MULTITOUCH_HAPTIC)
+static int mt_haptic_init(struct hid_device *hdev,
+				struct hid_haptic_device **haptic_ptr)
+{
+	return hid_haptic_init(hdev, haptic_ptr);
+}
+
+static void mt_haptic_feature_mapping(struct hid_device *hdev,
+				struct hid_haptic_device *haptic,
+				struct hid_field *field, struct hid_usage *usage)
+{
+	return hid_haptic_feature_mapping(hdev, haptic, field, usage);
+}
+
+static bool mt_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
+				    struct hid_input *hi, struct hid_field *field)
+{
+	return hid_haptic_check_pressure_unit(haptic, hi, field);
+}
+
+static void mt_haptic_pressure_reset(struct hid_haptic_device *haptic)
+{
+	return hid_haptic_pressure_reset(haptic);
+}
+
+static void mt_haptic_pressure_increase(struct hid_haptic_device *haptic,
+				 __s32 pressure)
+{
+	return hid_haptic_pressure_increase(haptic, pressure);
+}
+
+static int mt_haptic_input_mapping(struct hid_device *hdev,
+			     struct hid_haptic_device *haptic,
+			     struct hid_input *hi,
+			     struct hid_field *field, struct hid_usage *usage,
+			     unsigned long **bit, int *max)
+{
+	return hid_haptic_input_mapping(hdev, haptic, hi, field, usage, bit, max);
+}
+
+static int mt_haptic_input_configured(struct hid_device *hdev,
+				struct hid_haptic_device *haptic,
+				struct hid_input *hi)
+{
+	return hid_haptic_input_configured(hdev, haptic, hi);
+}
+#else
+static int mt_haptic_init(struct hid_device *hdev,
+				struct hid_haptic_device **haptic_ptr)
+{
+	return 0;
+}
+
+static void mt_haptic_feature_mapping(struct hid_device *hdev,
+				struct hid_haptic_device *haptic,
+				struct hid_field *field, struct hid_usage *usage)
+{}
+
+static bool mt_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
+				    struct hid_input *hi, struct hid_field *field)
+{
+	return 0;
+}
+
+static void mt_haptic_pressure_reset(struct hid_haptic_device *haptic)
+{}
+
+static void mt_haptic_pressure_increase(struct hid_haptic_device *haptic,
+				 __s32 pressure)
+{}
+
+static int mt_haptic_input_mapping(struct hid_device *hdev,
+			     struct hid_haptic_device *haptic,
+			     struct hid_input *hi,
+			     struct hid_field *field, struct hid_usage *usage,
+			     unsigned long **bit, int *max)
+{
+	return 0;
+}
+
+static int mt_haptic_input_configured(struct hid_device *hdev,
+				struct hid_haptic_device *haptic,
+				struct hid_input *hi)
+{
+	return 0;
+}
+#endif
+
+
 static void mt_feature_mapping(struct hid_device *hdev,
 		struct hid_field *field, struct hid_usage *usage)
 {
@@ -525,6 +618,8 @@ static void mt_feature_mapping(struct hid_device *hdev,
 			mt_get_feature(hdev, field->report);
 		break;
 	}
+
+	mt_haptic_feature_mapping(hdev, td->haptic, field, usage);
 }
 
 static void set_abs(struct input_dev *input, unsigned int code,
@@ -856,6 +951,9 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
 		case HID_DG_TIPPRESSURE:
 			set_abs(hi->input, ABS_MT_PRESSURE, field,
 				cls->sn_pressure);
+			td->is_haptic_touchpad =
+				mt_haptic_check_pressure_unit(td->haptic,
+							       hi, field);
 			MT_STORE_FIELD(p);
 			return 1;
 		case HID_DG_SCANTIME:
@@ -980,6 +1078,8 @@ static void mt_sync_frame(struct mt_device *td, struct mt_application *app,
 
 	app->num_received = 0;
 	app->left_button_state = 0;
+	if (td->is_haptic_touchpad)
+		mt_haptic_pressure_reset(td->haptic);
 
 	if (test_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags))
 		set_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags);
@@ -1137,6 +1237,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
 			minor = minor >> 1;
 		}
 
+		if (td->is_haptic_touchpad)
+			mt_haptic_pressure_increase(td->haptic, *slot->p);
+
 		x = hdev->quirks & HID_QUIRK_X_INVERT ?
 			input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
 			*slot->x;
@@ -1324,6 +1427,9 @@ static int mt_touch_input_configured(struct hid_device *hdev,
 	if (cls->is_indirect)
 		app->mt_flags |= INPUT_MT_POINTER;
 
+	if (td->is_haptic_touchpad)
+		app->mt_flags |= INPUT_MT_TOTAL_FORCE;
+
 	if (app->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
 		app->mt_flags |= INPUT_MT_DROP_UNUSED;
 
@@ -1359,6 +1465,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
 	struct mt_device *td = hid_get_drvdata(hdev);
 	struct mt_application *application;
 	struct mt_report_data *rdata;
+	int ret;
 
 	rdata = mt_find_report_data(td, field->report);
 	if (!rdata) {
@@ -1421,6 +1528,11 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
 	if (field->physical == HID_DG_STYLUS)
 		hi->application = HID_DG_STYLUS;
 
+	ret = mt_haptic_input_mapping(hdev, td->haptic, hi, field, usage, bit,
+				       max);
+	if (ret != 0)
+		return ret;
+
 	/* let hid-core decide for the others */
 	return 0;
 }
@@ -1635,6 +1747,14 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
 	struct hid_report *report;
 	int ret;
 
+	if (td->is_haptic_touchpad && (td->mtclass.name == MT_CLS_WIN_8 ||
+	    td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT)) {
+		if (mt_haptic_input_configured(hdev, td->haptic, hi) == 0)
+			td->is_haptic_touchpad = false;
+	} else {
+		td->is_haptic_touchpad = false;
+	}
+
 	list_for_each_entry(report, &hi->reports, hidinput_list) {
 		rdata = mt_find_report_data(td, report);
 		if (!rdata) {
@@ -1764,7 +1884,6 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	int ret, i;
 	struct mt_device *td;
 	const struct mt_class *mtclass = mt_classes; /* MT_CLS_DEFAULT */
-
 	for (i = 0; mt_classes[i].name ; i++) {
 		if (id->driver_data == mt_classes[i].name) {
 			mtclass = &(mt_classes[i]);
@@ -1777,6 +1896,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		dev_err(&hdev->dev, "cannot allocate multitouch data\n");
 		return -ENOMEM;
 	}
+	td->haptic = kzalloc(sizeof(*(td->haptic)), GFP_KERNEL);
+	if (!td->haptic)
+		return -ENOMEM;
+	td->haptic->hdev = hdev;
 	td->hdev = hdev;
 	td->mtclass = *mtclass;
 	td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN;
@@ -1840,6 +1963,17 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
 
 	mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
 
+	if (td->is_haptic_touchpad) {
+		if (mt_haptic_init(hdev, &td->haptic)) {
+			dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
+				 hdev->name);
+			td->is_haptic_touchpad = false;
+			kfree(td->haptic);
+		}
+	} else {
+		kfree(td->haptic);
+	}
+
 	return 0;
 }
 

-- 
2.50.1.565.gc32cd1483b-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 02/11] Input: add FF_HID effect type
  2025-08-04 14:11 ` [PATCH v2 02/11] Input: add FF_HID effect type Jonathan Denose
@ 2025-08-04 20:34   ` tomasz.pakula.oficjalny
  2025-08-11 15:26     ` Jonathan Denose
  0 siblings, 1 reply; 17+ messages in thread
From: tomasz.pakula.oficjalny @ 2025-08-04 20:34 UTC (permalink / raw)
  To: Jonathan Denose, Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov,
	Jonathan Corbet, Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien

On Mon, 2025-08-04 at 14:11 +0000, Jonathan Denose wrote:
> From: Angela Czubak <aczubak@google.com>
> 
> FF_HID effect type can be used to trigger haptic feedback with HID simple
> haptic usages.
> 
> Signed-off-by: Angela Czubak <aczubak@google.com>
> Co-developed-by: Jonathan Denose <jdenose@google.com>
> Signed-off-by: Jonathan Denose <jdenose@google.com>
> ---
>  include/uapi/linux/input.h | 22 +++++++++++++++++++++-
>  1 file changed, 21 insertions(+), 1 deletion(-)
> 
> diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
> index 2557eb7b056178b2b8be98d9cea855eba1bd5aaf..3ea7c826c6fb2034e46f95cb95b84ef6f5b866df 100644
> --- a/include/uapi/linux/input.h
> +++ b/include/uapi/linux/input.h
> @@ -428,6 +428,24 @@ struct ff_rumble_effect {
>  	__u16 weak_magnitude;
>  };
>  
> +/**
> + * struct ff_hid_effect
> + * @hid_usage: hid_usage according to Haptics page (WAVEFORM_CLICK, etc.)
> + * @vendor_id: the waveform vendor ID if hid_usage is in the vendor-defined range
> + * @vendor_waveform_page: the vendor waveform page if hid_usage is in the vendor-defined range
> + * @intensity: strength of the effect as percentage
> + * @repeat_count: number of times to retrigger effect
> + * @retrigger_period: time before effect is retriggered (in ms)
> + */
> +struct ff_hid_effect {
> +	__u16 hid_usage;
> +	__u16 vendor_id;
> +	__u8  vendor_waveform_page;
> +	__u16 intensity;
> +	__u16 repeat_count;
> +	__u16 retrigger_period;
> +};

Wouldn't it make more sense to call this new effect ff_haptic_effect?
hid_effect sound generic, too generic. One could say, all ff effect are
hid effects because most ff apis (linux' included) are based on USB PID
spec.

> +
>  /**
>   * struct ff_effect - defines force feedback effect
>   * @type: type of the effect (FF_CONSTANT, FF_PERIODIC, FF_RAMP, FF_SPRING,
> @@ -464,6 +482,7 @@ struct ff_effect {
>  		struct ff_periodic_effect periodic;
>  		struct ff_condition_effect condition[2]; /* One for each axis */
>  		struct ff_rumble_effect rumble;
> +		struct ff_hid_effect hid;
>  	} u;
>  };
>  
> @@ -471,6 +490,7 @@ struct ff_effect {
>   * Force feedback effect types
>   */
>  
> +#define FF_HID		0x4f

Again here, FF_HID sounds confusing without having the broader context.
Constant, Sine, Inertia, Spring are way more descriptive. FF_HAPTIC
would be a great name to distinguish such an effect. Or maybe FF_TACTILE
with ff_tactile_effect?

>  #define FF_RUMBLE	0x50
>  #define FF_PERIODIC	0x51
>  #define FF_CONSTANT	0x52
> @@ -480,7 +500,7 @@ struct ff_effect {
>  #define FF_INERTIA	0x56
>  #define FF_RAMP		0x57
>  
> -#define FF_EFFECT_MIN	FF_RUMBLE
> +#define FF_EFFECT_MIN	FF_HID
>  #define FF_EFFECT_MAX	FF_RAMP
>  
>  /*

Overall, I'll keep an eye on this as I'm slowly working towards a
proposal for a revamped and extended ff api on Linux that would make it
fully featured (we're lacking things like device control and querying
effects and their status, arbitrary number of axes and arbitrary axes
themselves).

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 02/11] Input: add FF_HID effect type
  2025-08-04 20:34   ` tomasz.pakula.oficjalny
@ 2025-08-11 15:26     ` Jonathan Denose
  0 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-11 15:26 UTC (permalink / raw)
  To: tomasz.pakula.oficjalny
  Cc: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg, linux-input, linux-kernel, linux-doc,
	Angela Czubak, Sean O'Brien

On Mon, Aug 4, 2025 at 3:34 PM <tomasz.pakula.oficjalny@gmail.com> wrote:
>
> On Mon, 2025-08-04 at 14:11 +0000, Jonathan Denose wrote:
> > From: Angela Czubak <aczubak@google.com>
> >
> > FF_HID effect type can be used to trigger haptic feedback with HID simple
> > haptic usages.
> >
> > Signed-off-by: Angela Czubak <aczubak@google.com>
> > Co-developed-by: Jonathan Denose <jdenose@google.com>
> > Signed-off-by: Jonathan Denose <jdenose@google.com>
> > ---
> >  include/uapi/linux/input.h | 22 +++++++++++++++++++++-
> >  1 file changed, 21 insertions(+), 1 deletion(-)
> >
> > diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
> > index 2557eb7b056178b2b8be98d9cea855eba1bd5aaf..3ea7c826c6fb2034e46f95cb95b84ef6f5b866df 100644
> > --- a/include/uapi/linux/input.h
> > +++ b/include/uapi/linux/input.h
> > @@ -428,6 +428,24 @@ struct ff_rumble_effect {
> >       __u16 weak_magnitude;
> >  };
> >
> > +/**
> > + * struct ff_hid_effect
> > + * @hid_usage: hid_usage according to Haptics page (WAVEFORM_CLICK, etc.)
> > + * @vendor_id: the waveform vendor ID if hid_usage is in the vendor-defined range
> > + * @vendor_waveform_page: the vendor waveform page if hid_usage is in the vendor-defined range
> > + * @intensity: strength of the effect as percentage
> > + * @repeat_count: number of times to retrigger effect
> > + * @retrigger_period: time before effect is retriggered (in ms)
> > + */
> > +struct ff_hid_effect {
> > +     __u16 hid_usage;
> > +     __u16 vendor_id;
> > +     __u8  vendor_waveform_page;
> > +     __u16 intensity;
> > +     __u16 repeat_count;
> > +     __u16 retrigger_period;
> > +};
>
> Wouldn't it make more sense to call this new effect ff_haptic_effect?
> hid_effect sound generic, too generic. One could say, all ff effect are
> hid effects because most ff apis (linux' included) are based on USB PID
> spec.
>
> > +
> >  /**
> >   * struct ff_effect - defines force feedback effect
> >   * @type: type of the effect (FF_CONSTANT, FF_PERIODIC, FF_RAMP, FF_SPRING,
> > @@ -464,6 +482,7 @@ struct ff_effect {
> >               struct ff_periodic_effect periodic;
> >               struct ff_condition_effect condition[2]; /* One for each axis */
> >               struct ff_rumble_effect rumble;
> > +             struct ff_hid_effect hid;
> >       } u;
> >  };
> >
> > @@ -471,6 +490,7 @@ struct ff_effect {
> >   * Force feedback effect types
> >   */
> >
> > +#define FF_HID               0x4f
>
> Again here, FF_HID sounds confusing without having the broader context.
> Constant, Sine, Inertia, Spring are way more descriptive. FF_HAPTIC
> would be a great name to distinguish such an effect. Or maybe FF_TACTILE
> with ff_tactile_effect?
>
> >  #define FF_RUMBLE    0x50
> >  #define FF_PERIODIC  0x51
> >  #define FF_CONSTANT  0x52
> > @@ -480,7 +500,7 @@ struct ff_effect {
> >  #define FF_INERTIA   0x56
> >  #define FF_RAMP              0x57
> >
> > -#define FF_EFFECT_MIN        FF_RUMBLE
> > +#define FF_EFFECT_MIN        FF_HID
> >  #define FF_EFFECT_MAX        FF_RAMP
> >
> >  /*
>
> Overall, I'll keep an eye on this as I'm slowly working towards a
> proposal for a revamped and extended ff api on Linux that would make it
> fully featured (we're lacking things like device control and querying
> effects and their status, arbitrary number of axes and arbitrary axes
> themselves).

Thanks for your review, your comments make sense to me!

I'll change ff_hid_effect to ff_haptic_effect and FF_HID to FF_HAPTIC
and upload a new version of this series.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 11/11] HID: multitouch: add haptic multitouch support
  2025-08-04 14:11 ` [PATCH v2 11/11] HID: multitouch: add haptic multitouch support Jonathan Denose
@ 2025-08-13  9:22   ` Benjamin Tissoires
  2025-08-13 15:52     ` Jonathan Denose
  0 siblings, 1 reply; 17+ messages in thread
From: Benjamin Tissoires @ 2025-08-13  9:22 UTC (permalink / raw)
  To: Jonathan Denose
  Cc: Jiri Kosina, Dmitry Torokhov, Jonathan Corbet, Henrik Rydberg,
	linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien

On Aug 04 2025, Jonathan Denose wrote:
> From: Angela Czubak <aczubak@google.com>
> 
> Add new option (MULTITOUCH_HAPTIC) to mark whether hid-multitouch
> should try and configure simple haptic device.
> Once this option is configured, and the device is recognized to have simple
> haptic capabilities, check input frames for pressure and handle it using
> hid_haptic_* API.

Why creating a new option? It seems it'll add unwanted work from
distributions when we should have something that "just works" no?

It makes sense to depend on FF, but adding a new option is probably
useless IMO.


> 
> Signed-off-by: Angela Czubak <aczubak@google.com>
> Co-developed-by: Jonathan Denose <jdenose@google.com>
> Signed-off-by: Jonathan Denose <jdenose@google.com>
> ---
>  drivers/hid/Kconfig          |  11 ++++
>  drivers/hid/Makefile         |   2 +-
>  drivers/hid/hid-haptic.h     |  52 +++++++++++++++++
>  drivers/hid/hid-multitouch.c | 136 ++++++++++++++++++++++++++++++++++++++++++-
>  4 files changed, 199 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index ad6bcc4248cc111705d7cfde2b1481b46353e2d7..b7452f11a4f914f92af582ed054d42ecbcd6cb9e 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -817,6 +817,17 @@ config HID_MULTITOUCH
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called hid-multitouch.
>  
> +config MULTITOUCH_HAPTIC
> +	bool "Simple haptic multitouch support"
> +	depends on HID_MULTITOUCH
> +	select HID_HAPTIC
> +	default n
> +	help
> +	Support for simple multitouch haptic devices.
> +	Adds extra parsing and FF device for the hid multitouch driver.
> +	It can be used for Elan 2703 haptic touchpad.
> +	To enable, say Y.
> +
>  config HID_NINTENDO
>  	tristate "Nintendo Joy-Con, NSO, and Pro Controller support"
>  	depends on NEW_LEDS
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index 361a7daedeb85454114def8afb5f58caeab58a00..be09b4f13b2058a0a1d7eab79f35def758120fc4 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -4,7 +4,7 @@
>  #
>  hid-y			:= hid-core.o hid-input.o hid-quirks.o
>  hid-$(CONFIG_DEBUG_FS)		+= hid-debug.o
> -hid-$(CONFIG_HID_HAPTIC)	+= hid-haptic.o
> +hid-$(CONFIG_MULTITOUCH_HAPTIC)	+= hid-haptic.o
>  
>  obj-$(CONFIG_HID_BPF)		+= bpf/
>  
> diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h
> index 0a34b0c6d706a985630962acc41f7a8eb73cd343..808cec0b4e51eba1f58b839f3e552493655b7899 100644
> --- a/drivers/hid/hid-haptic.h
> +++ b/drivers/hid/hid-haptic.h
> @@ -58,6 +58,7 @@ struct hid_haptic_device {
>  	struct hid_haptic_effect stop_effect;
>  };
>  
> +#ifdef CONFIG_MULTITOUCH_HAPTIC

There is something wrong with your ifdef usages:
- here, you define the functions below conditionally to
	CONFIG_MULTITOUCH_HAPTIC, which is fine
- but in hid-multitouch, you also check for CONFIG_MULTITOUCH_HAPTIC
	before calling the same set of functions.

Either only define the haptic functions when CONFIG_MULTITOUCH_HAPTIC is
set, and in multitouch check for that define, or define it conditionally
and remove the checks in hid-multitouch (but probably add a comment).

>  void hid_haptic_feature_mapping(struct hid_device *hdev,
>  				struct hid_haptic_device *haptic,
>  				struct hid_field *field, struct hid_usage
> @@ -77,3 +78,54 @@ void hid_haptic_handle_press_release(struct hid_haptic_device *haptic);
>  void hid_haptic_pressure_reset(struct hid_haptic_device *haptic);
>  void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
>  				  __s32 pressure);
> +#else
> +static inline
> +void hid_haptic_feature_mapping(struct hid_device *hdev,
> +				struct hid_haptic_device *haptic,
> +				struct hid_field *field, struct hid_usage
> +				*usage)
> +{}
> +static inline
> +bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
> +				    struct hid_input *hi, struct hid_field *field)
> +{
> +	return false;
> +}
> +static inline
> +int hid_haptic_input_mapping(struct hid_device *hdev,
> +			     struct hid_haptic_device *haptic,
> +			     struct hid_input *hi,
> +			     struct hid_field *field, struct hid_usage *usage,
> +			     unsigned long **bit, int *max)
> +{
> +	return 0;
> +}
> +static inline
> +int hid_haptic_input_configured(struct hid_device *hdev,
> +				struct hid_haptic_device *haptic,
> +				struct hid_input *hi)
> +{
> +	return 0;
> +}
> +static inline
> +void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic)
> +{}
> +static inline
> +int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr)
> +{
> +	return 0;
> +}
> +static inline
> +void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) {}
> +static inline
> +bool hid_haptic_handle_input(struct hid_haptic_device *haptic)
> +{
> +	return false;
> +}
> +static inline
> +void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) {}
> +static inline
> +void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
> +				  __s32 pressure)
> +{}
> +#endif
> diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
> index b41001e02da7e02d492bd85743b359ed7ec16e7f..4ff9ac5022b13a0739dbc7ae5f6ebd84f0114a73 100644
> --- a/drivers/hid/hid-multitouch.c
> +++ b/drivers/hid/hid-multitouch.c
> @@ -49,6 +49,8 @@ MODULE_LICENSE("GPL");
>  
>  #include "hid-ids.h"
>  
> +#include "hid-haptic.h"
> +
>  /* quirks to control the device */
>  #define MT_QUIRK_NOT_SEEN_MEANS_UP	BIT(0)
>  #define MT_QUIRK_SLOT_IS_CONTACTID	BIT(1)
> @@ -167,11 +169,13 @@ struct mt_report_data {
>  struct mt_device {
>  	struct mt_class mtclass;	/* our mt device class */
>  	struct timer_list release_timer;	/* to release sticky fingers */
> +	struct hid_haptic_device *haptic;	/* haptic related configuration */
>  	struct hid_device *hdev;	/* hid_device we're attached to */
>  	unsigned long mt_io_flags;	/* mt flags (MT_IO_FLAGS_*) */
>  	__u8 inputmode_value;	/* InputMode HID feature value */
>  	__u8 maxcontacts;
>  	bool is_buttonpad;	/* is this device a button pad? */
> +	bool is_haptic_touchpad;	/* is this device a haptic touchpad? */
>  	bool serial_maybe;	/* need to check for serial protocol */
>  
>  	struct list_head applications;
> @@ -490,6 +494,95 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report)
>  	kfree(buf);
>  }
>  
> +#if defined(CONFIG_MULTITOUCH_HAPTIC)
> +static int mt_haptic_init(struct hid_device *hdev,
> +				struct hid_haptic_device **haptic_ptr)
> +{
> +	return hid_haptic_init(hdev, haptic_ptr);
> +}
> +
> +static void mt_haptic_feature_mapping(struct hid_device *hdev,
> +				struct hid_haptic_device *haptic,
> +				struct hid_field *field, struct hid_usage *usage)
> +{
> +	return hid_haptic_feature_mapping(hdev, haptic, field, usage);
> +}
> +
> +static bool mt_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
> +				    struct hid_input *hi, struct hid_field *field)
> +{
> +	return hid_haptic_check_pressure_unit(haptic, hi, field);
> +}
> +
> +static void mt_haptic_pressure_reset(struct hid_haptic_device *haptic)
> +{
> +	return hid_haptic_pressure_reset(haptic);
> +}
> +
> +static void mt_haptic_pressure_increase(struct hid_haptic_device *haptic,
> +				 __s32 pressure)
> +{
> +	return hid_haptic_pressure_increase(haptic, pressure);
> +}
> +
> +static int mt_haptic_input_mapping(struct hid_device *hdev,
> +			     struct hid_haptic_device *haptic,
> +			     struct hid_input *hi,
> +			     struct hid_field *field, struct hid_usage *usage,
> +			     unsigned long **bit, int *max)
> +{
> +	return hid_haptic_input_mapping(hdev, haptic, hi, field, usage, bit, max);
> +}
> +
> +static int mt_haptic_input_configured(struct hid_device *hdev,
> +				struct hid_haptic_device *haptic,
> +				struct hid_input *hi)
> +{
> +	return hid_haptic_input_configured(hdev, haptic, hi);
> +}
> +#else
> +static int mt_haptic_init(struct hid_device *hdev,
> +				struct hid_haptic_device **haptic_ptr)
> +{
> +	return 0;
> +}
> +
> +static void mt_haptic_feature_mapping(struct hid_device *hdev,
> +				struct hid_haptic_device *haptic,
> +				struct hid_field *field, struct hid_usage *usage)
> +{}
> +
> +static bool mt_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
> +				    struct hid_input *hi, struct hid_field *field)
> +{
> +	return 0;
> +}
> +
> +static void mt_haptic_pressure_reset(struct hid_haptic_device *haptic)
> +{}
> +
> +static void mt_haptic_pressure_increase(struct hid_haptic_device *haptic,
> +				 __s32 pressure)
> +{}
> +
> +static int mt_haptic_input_mapping(struct hid_device *hdev,
> +			     struct hid_haptic_device *haptic,
> +			     struct hid_input *hi,
> +			     struct hid_field *field, struct hid_usage *usage,
> +			     unsigned long **bit, int *max)
> +{
> +	return 0;
> +}
> +
> +static int mt_haptic_input_configured(struct hid_device *hdev,
> +				struct hid_haptic_device *haptic,
> +				struct hid_input *hi)
> +{
> +	return 0;
> +}
> +#endif
> +
> +
>  static void mt_feature_mapping(struct hid_device *hdev,
>  		struct hid_field *field, struct hid_usage *usage)
>  {
> @@ -525,6 +618,8 @@ static void mt_feature_mapping(struct hid_device *hdev,
>  			mt_get_feature(hdev, field->report);
>  		break;
>  	}
> +
> +	mt_haptic_feature_mapping(hdev, td->haptic, field, usage);
>  }
>  
>  static void set_abs(struct input_dev *input, unsigned int code,
> @@ -856,6 +951,9 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
>  		case HID_DG_TIPPRESSURE:
>  			set_abs(hi->input, ABS_MT_PRESSURE, field,
>  				cls->sn_pressure);
> +			td->is_haptic_touchpad =
> +				mt_haptic_check_pressure_unit(td->haptic,
> +							       hi, field);
>  			MT_STORE_FIELD(p);
>  			return 1;
>  		case HID_DG_SCANTIME:
> @@ -980,6 +1078,8 @@ static void mt_sync_frame(struct mt_device *td, struct mt_application *app,
>  
>  	app->num_received = 0;
>  	app->left_button_state = 0;
> +	if (td->is_haptic_touchpad)
> +		mt_haptic_pressure_reset(td->haptic);
>  
>  	if (test_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags))
>  		set_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags);
> @@ -1137,6 +1237,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
>  			minor = minor >> 1;
>  		}
>  
> +		if (td->is_haptic_touchpad)
> +			mt_haptic_pressure_increase(td->haptic, *slot->p);
> +
>  		x = hdev->quirks & HID_QUIRK_X_INVERT ?
>  			input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
>  			*slot->x;
> @@ -1324,6 +1427,9 @@ static int mt_touch_input_configured(struct hid_device *hdev,
>  	if (cls->is_indirect)
>  		app->mt_flags |= INPUT_MT_POINTER;
>  
> +	if (td->is_haptic_touchpad)
> +		app->mt_flags |= INPUT_MT_TOTAL_FORCE;
> +
>  	if (app->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
>  		app->mt_flags |= INPUT_MT_DROP_UNUSED;
>  
> @@ -1359,6 +1465,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
>  	struct mt_device *td = hid_get_drvdata(hdev);
>  	struct mt_application *application;
>  	struct mt_report_data *rdata;
> +	int ret;
>  
>  	rdata = mt_find_report_data(td, field->report);
>  	if (!rdata) {
> @@ -1421,6 +1528,11 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
>  	if (field->physical == HID_DG_STYLUS)
>  		hi->application = HID_DG_STYLUS;
>  
> +	ret = mt_haptic_input_mapping(hdev, td->haptic, hi, field, usage, bit,
> +				       max);
> +	if (ret != 0)
> +		return ret;
> +
>  	/* let hid-core decide for the others */
>  	return 0;
>  }
> @@ -1635,6 +1747,14 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
>  	struct hid_report *report;
>  	int ret;
>  
> +	if (td->is_haptic_touchpad && (td->mtclass.name == MT_CLS_WIN_8 ||
> +	    td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT)) {
> +		if (mt_haptic_input_configured(hdev, td->haptic, hi) == 0)
> +			td->is_haptic_touchpad = false;
> +	} else {
> +		td->is_haptic_touchpad = false;
> +	}
> +
>  	list_for_each_entry(report, &hi->reports, hidinput_list) {
>  		rdata = mt_find_report_data(td, report);
>  		if (!rdata) {
> @@ -1764,7 +1884,6 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
>  	int ret, i;
>  	struct mt_device *td;
>  	const struct mt_class *mtclass = mt_classes; /* MT_CLS_DEFAULT */
> -

unrelated change (line removed).

>  	for (i = 0; mt_classes[i].name ; i++) {
>  		if (id->driver_data == mt_classes[i].name) {
>  			mtclass = &(mt_classes[i]);
> @@ -1777,6 +1896,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
>  		dev_err(&hdev->dev, "cannot allocate multitouch data\n");
>  		return -ENOMEM;
>  	}
> +	td->haptic = kzalloc(sizeof(*(td->haptic)), GFP_KERNEL);

Please make use of the devm api, you are leaking the allocated memory in
the regular case (AFAICT).

> +	if (!td->haptic)
> +		return -ENOMEM;

One extra blank line wouldn't hurt here :)

> +	td->haptic->hdev = hdev;
>  	td->hdev = hdev;
>  	td->mtclass = *mtclass;
>  	td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN;
> @@ -1840,6 +1963,17 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
>  
>  	mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
>  
> +	if (td->is_haptic_touchpad) {
> +		if (mt_haptic_init(hdev, &td->haptic)) {
> +			dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
> +				 hdev->name);
> +			td->is_haptic_touchpad = false;
> +			kfree(td->haptic);
> +		}
> +	} else {
> +		kfree(td->haptic);
> +	}
> +
>  	return 0;
>  }
>  
> 
> -- 
> 2.50.1.565.gc32cd1483b-goog
> 

Cheers,
Benjamin

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v2 11/11] HID: multitouch: add haptic multitouch support
  2025-08-13  9:22   ` Benjamin Tissoires
@ 2025-08-13 15:52     ` Jonathan Denose
  0 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-13 15:52 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Jiri Kosina, Dmitry Torokhov, Jonathan Corbet, Henrik Rydberg,
	linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien

On Wed, Aug 13, 2025 at 4:22 AM Benjamin Tissoires <bentiss@kernel.org> wrote:
>
> On Aug 04 2025, Jonathan Denose wrote:
> > From: Angela Czubak <aczubak@google.com>
> >
> > Add new option (MULTITOUCH_HAPTIC) to mark whether hid-multitouch
> > should try and configure simple haptic device.
> > Once this option is configured, and the device is recognized to have simple
> > haptic capabilities, check input frames for pressure and handle it using
> > hid_haptic_* API.
>
> Why creating a new option? It seems it'll add unwanted work from
> distributions when we should have something that "just works" no?
>
> It makes sense to depend on FF, but adding a new option is probably
> useless IMO.
>
> >
> > Signed-off-by: Angela Czubak <aczubak@google.com>
> > Co-developed-by: Jonathan Denose <jdenose@google.com>
> > Signed-off-by: Jonathan Denose <jdenose@google.com>
> > ---
> >  drivers/hid/Kconfig          |  11 ++++
> >  drivers/hid/Makefile         |   2 +-
> >  drivers/hid/hid-haptic.h     |  52 +++++++++++++++++
> >  drivers/hid/hid-multitouch.c | 136 ++++++++++++++++++++++++++++++++++++++++++-
> >  4 files changed, 199 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> > index ad6bcc4248cc111705d7cfde2b1481b46353e2d7..b7452f11a4f914f92af582ed054d42ecbcd6cb9e 100644
> > --- a/drivers/hid/Kconfig
> > +++ b/drivers/hid/Kconfig
> > @@ -817,6 +817,17 @@ config HID_MULTITOUCH
> >         To compile this driver as a module, choose M here: the
> >         module will be called hid-multitouch.
> >
> > +config MULTITOUCH_HAPTIC
> > +     bool "Simple haptic multitouch support"
> > +     depends on HID_MULTITOUCH
> > +     select HID_HAPTIC
> > +     default n
> > +     help
> > +     Support for simple multitouch haptic devices.
> > +     Adds extra parsing and FF device for the hid multitouch driver.
> > +     It can be used for Elan 2703 haptic touchpad.
> > +     To enable, say Y.
> > +
> >  config HID_NINTENDO
> >       tristate "Nintendo Joy-Con, NSO, and Pro Controller support"
> >       depends on NEW_LEDS
> > diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> > index 361a7daedeb85454114def8afb5f58caeab58a00..be09b4f13b2058a0a1d7eab79f35def758120fc4 100644
> > --- a/drivers/hid/Makefile
> > +++ b/drivers/hid/Makefile
> > @@ -4,7 +4,7 @@
> >  #
> >  hid-y                        := hid-core.o hid-input.o hid-quirks.o
> >  hid-$(CONFIG_DEBUG_FS)               += hid-debug.o
> > -hid-$(CONFIG_HID_HAPTIC)     += hid-haptic.o
> > +hid-$(CONFIG_MULTITOUCH_HAPTIC)      += hid-haptic.o
> >
> >  obj-$(CONFIG_HID_BPF)                += bpf/
> >
> > diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h
> > index 0a34b0c6d706a985630962acc41f7a8eb73cd343..808cec0b4e51eba1f58b839f3e552493655b7899 100644
> > --- a/drivers/hid/hid-haptic.h
> > +++ b/drivers/hid/hid-haptic.h
> > @@ -58,6 +58,7 @@ struct hid_haptic_device {
> >       struct hid_haptic_effect stop_effect;
> >  };
> >
> > +#ifdef CONFIG_MULTITOUCH_HAPTIC
>
> There is something wrong with your ifdef usages:
> - here, you define the functions below conditionally to
>         CONFIG_MULTITOUCH_HAPTIC, which is fine
> - but in hid-multitouch, you also check for CONFIG_MULTITOUCH_HAPTIC
>         before calling the same set of functions.
>
> Either only define the haptic functions when CONFIG_MULTITOUCH_HAPTIC is
> set, and in multitouch check for that define, or define it conditionally
> and remove the checks in hid-multitouch (but probably add a comment).
> >  void hid_haptic_feature_mapping(struct hid_device *hdev,
> >                               struct hid_haptic_device *haptic,
> >                               struct hid_field *field, struct hid_usage
> > @@ -77,3 +78,54 @@ void hid_haptic_handle_press_release(struct hid_haptic_device *haptic);
> >  void hid_haptic_pressure_reset(struct hid_haptic_device *haptic);
> >  void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
> >                                 __s32 pressure);
> > +#else
> > +static inline
> > +void hid_haptic_feature_mapping(struct hid_device *hdev,
> > +                             struct hid_haptic_device *haptic,
> > +                             struct hid_field *field, struct hid_usage
> > +                             *usage)
> > +{}
> > +static inline
> > +bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
> > +                                 struct hid_input *hi, struct hid_field *field)
> > +{
> > +     return false;
> > +}
> > +static inline
> > +int hid_haptic_input_mapping(struct hid_device *hdev,
> > +                          struct hid_haptic_device *haptic,
> > +                          struct hid_input *hi,
> > +                          struct hid_field *field, struct hid_usage *usage,
> > +                          unsigned long **bit, int *max)
> > +{
> > +     return 0;
> > +}
> > +static inline
> > +int hid_haptic_input_configured(struct hid_device *hdev,
> > +                             struct hid_haptic_device *haptic,
> > +                             struct hid_input *hi)
> > +{
> > +     return 0;
> > +}
> > +static inline
> > +void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic)
> > +{}
> > +static inline
> > +int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr)
> > +{
> > +     return 0;
> > +}
> > +static inline
> > +void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) {}
> > +static inline
> > +bool hid_haptic_handle_input(struct hid_haptic_device *haptic)
> > +{
> > +     return false;
> > +}
> > +static inline
> > +void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) {}
> > +static inline
> > +void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
> > +                               __s32 pressure)
> > +{}
> > +#endif
> > diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
> > index b41001e02da7e02d492bd85743b359ed7ec16e7f..4ff9ac5022b13a0739dbc7ae5f6ebd84f0114a73 100644
> > --- a/drivers/hid/hid-multitouch.c
> > +++ b/drivers/hid/hid-multitouch.c
> > @@ -49,6 +49,8 @@ MODULE_LICENSE("GPL");
> >
> >  #include "hid-ids.h"
> >
> > +#include "hid-haptic.h"
> > +
> >  /* quirks to control the device */
> >  #define MT_QUIRK_NOT_SEEN_MEANS_UP   BIT(0)
> >  #define MT_QUIRK_SLOT_IS_CONTACTID   BIT(1)
> > @@ -167,11 +169,13 @@ struct mt_report_data {
> >  struct mt_device {
> >       struct mt_class mtclass;        /* our mt device class */
> >       struct timer_list release_timer;        /* to release sticky fingers */
> > +     struct hid_haptic_device *haptic;       /* haptic related configuration */
> >       struct hid_device *hdev;        /* hid_device we're attached to */
> >       unsigned long mt_io_flags;      /* mt flags (MT_IO_FLAGS_*) */
> >       __u8 inputmode_value;   /* InputMode HID feature value */
> >       __u8 maxcontacts;
> >       bool is_buttonpad;      /* is this device a button pad? */
> > +     bool is_haptic_touchpad;        /* is this device a haptic touchpad? */
> >       bool serial_maybe;      /* need to check for serial protocol */
> >
> >       struct list_head applications;
> > @@ -490,6 +494,95 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report)
> >       kfree(buf);
> >  }
> >
> > +#if defined(CONFIG_MULTITOUCH_HAPTIC)
> > +static int mt_haptic_init(struct hid_device *hdev,
> > +                             struct hid_haptic_device **haptic_ptr)
> > +{
> > +     return hid_haptic_init(hdev, haptic_ptr);
> > +}
> > +
> > +static void mt_haptic_feature_mapping(struct hid_device *hdev,
> > +                             struct hid_haptic_device *haptic,
> > +                             struct hid_field *field, struct hid_usage *usage)
> > +{
> > +     return hid_haptic_feature_mapping(hdev, haptic, field, usage);
> > +}
> > +
> > +static bool mt_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
> > +                                 struct hid_input *hi, struct hid_field *field)
> > +{
> > +     return hid_haptic_check_pressure_unit(haptic, hi, field);
> > +}
> > +
> > +static void mt_haptic_pressure_reset(struct hid_haptic_device *haptic)
> > +{
> > +     return hid_haptic_pressure_reset(haptic);
> > +}
> > +
> > +static void mt_haptic_pressure_increase(struct hid_haptic_device *haptic,
> > +                              __s32 pressure)
> > +{
> > +     return hid_haptic_pressure_increase(haptic, pressure);
> > +}
> > +
> > +static int mt_haptic_input_mapping(struct hid_device *hdev,
> > +                          struct hid_haptic_device *haptic,
> > +                          struct hid_input *hi,
> > +                          struct hid_field *field, struct hid_usage *usage,
> > +                          unsigned long **bit, int *max)
> > +{
> > +     return hid_haptic_input_mapping(hdev, haptic, hi, field, usage, bit, max);
> > +}
> > +
> > +static int mt_haptic_input_configured(struct hid_device *hdev,
> > +                             struct hid_haptic_device *haptic,
> > +                             struct hid_input *hi)
> > +{
> > +     return hid_haptic_input_configured(hdev, haptic, hi);
> > +}
> > +#else
> > +static int mt_haptic_init(struct hid_device *hdev,
> > +                             struct hid_haptic_device **haptic_ptr)
> > +{
> > +     return 0;
> > +}
> > +
> > +static void mt_haptic_feature_mapping(struct hid_device *hdev,
> > +                             struct hid_haptic_device *haptic,
> > +                             struct hid_field *field, struct hid_usage *usage)
> > +{}
> > +
> > +static bool mt_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
> > +                                 struct hid_input *hi, struct hid_field *field)
> > +{
> > +     return 0;
> > +}
> > +
> > +static void mt_haptic_pressure_reset(struct hid_haptic_device *haptic)
> > +{}
> > +
> > +static void mt_haptic_pressure_increase(struct hid_haptic_device *haptic,
> > +                              __s32 pressure)
> > +{}
> > +
> > +static int mt_haptic_input_mapping(struct hid_device *hdev,
> > +                          struct hid_haptic_device *haptic,
> > +                          struct hid_input *hi,
> > +                          struct hid_field *field, struct hid_usage *usage,
> > +                          unsigned long **bit, int *max)
> > +{
> > +     return 0;
> > +}
> > +
> > +static int mt_haptic_input_configured(struct hid_device *hdev,
> > +                             struct hid_haptic_device *haptic,
> > +                             struct hid_input *hi)
> > +{
> > +     return 0;
> > +}
> > +#endif
> > +
> > +
> >  static void mt_feature_mapping(struct hid_device *hdev,
> >               struct hid_field *field, struct hid_usage *usage)
> >  {
> > @@ -525,6 +618,8 @@ static void mt_feature_mapping(struct hid_device *hdev,
> >                       mt_get_feature(hdev, field->report);
> >               break;
> >       }
> > +
> > +     mt_haptic_feature_mapping(hdev, td->haptic, field, usage);
> >  }
> >
> >  static void set_abs(struct input_dev *input, unsigned int code,
> > @@ -856,6 +951,9 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
> >               case HID_DG_TIPPRESSURE:
> >                       set_abs(hi->input, ABS_MT_PRESSURE, field,
> >                               cls->sn_pressure);
> > +                     td->is_haptic_touchpad =
> > +                             mt_haptic_check_pressure_unit(td->haptic,
> > +                                                            hi, field);
> >                       MT_STORE_FIELD(p);
> >                       return 1;
> >               case HID_DG_SCANTIME:
> > @@ -980,6 +1078,8 @@ static void mt_sync_frame(struct mt_device *td, struct mt_application *app,
> >
> >       app->num_received = 0;
> >       app->left_button_state = 0;
> > +     if (td->is_haptic_touchpad)
> > +             mt_haptic_pressure_reset(td->haptic);
> >
> >       if (test_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags))
> >               set_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags);
> > @@ -1137,6 +1237,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
> >                       minor = minor >> 1;
> >               }
> >
> > +             if (td->is_haptic_touchpad)
> > +                     mt_haptic_pressure_increase(td->haptic, *slot->p);
> > +
> >               x = hdev->quirks & HID_QUIRK_X_INVERT ?
> >                       input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
> >                       *slot->x;
> > @@ -1324,6 +1427,9 @@ static int mt_touch_input_configured(struct hid_device *hdev,
> >       if (cls->is_indirect)
> >               app->mt_flags |= INPUT_MT_POINTER;
> >
> > +     if (td->is_haptic_touchpad)
> > +             app->mt_flags |= INPUT_MT_TOTAL_FORCE;
> > +
> >       if (app->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
> >               app->mt_flags |= INPUT_MT_DROP_UNUSED;
> >
> > @@ -1359,6 +1465,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
> >       struct mt_device *td = hid_get_drvdata(hdev);
> >       struct mt_application *application;
> >       struct mt_report_data *rdata;
> > +     int ret;
> >
> >       rdata = mt_find_report_data(td, field->report);
> >       if (!rdata) {
> > @@ -1421,6 +1528,11 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
> >       if (field->physical == HID_DG_STYLUS)
> >               hi->application = HID_DG_STYLUS;
> >
> > +     ret = mt_haptic_input_mapping(hdev, td->haptic, hi, field, usage, bit,
> > +                                    max);
> > +     if (ret != 0)
> > +             return ret;
> > +
> >       /* let hid-core decide for the others */
> >       return 0;
> >  }
> > @@ -1635,6 +1747,14 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
> >       struct hid_report *report;
> >       int ret;
> >
> > +     if (td->is_haptic_touchpad && (td->mtclass.name == MT_CLS_WIN_8 ||
> > +         td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT)) {
> > +             if (mt_haptic_input_configured(hdev, td->haptic, hi) == 0)
> > +                     td->is_haptic_touchpad = false;
> > +     } else {
> > +             td->is_haptic_touchpad = false;
> > +     }
> > +
> >       list_for_each_entry(report, &hi->reports, hidinput_list) {
> >               rdata = mt_find_report_data(td, report);
> >               if (!rdata) {
> > @@ -1764,7 +1884,6 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
> >       int ret, i;
> >       struct mt_device *td;
> >       const struct mt_class *mtclass = mt_classes; /* MT_CLS_DEFAULT */
> > -
>
> unrelated change (line removed).
>
> >       for (i = 0; mt_classes[i].name ; i++) {
> >               if (id->driver_data == mt_classes[i].name) {
> >                       mtclass = &(mt_classes[i]);
> > @@ -1777,6 +1896,10 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
> >               dev_err(&hdev->dev, "cannot allocate multitouch data\n");
> >               return -ENOMEM;
> >       }
> > +     td->haptic = kzalloc(sizeof(*(td->haptic)), GFP_KERNEL);
>
> Please make use of the devm api, you are leaking the allocated memory in
> the regular case (AFAICT).
>
> > +     if (!td->haptic)
> > +             return -ENOMEM;
>
> One extra blank line wouldn't hurt here :)
>
> > +     td->haptic->hdev = hdev;
> >       td->hdev = hdev;
> >       td->mtclass = *mtclass;
> >       td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN;
> > @@ -1840,6 +1963,17 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
> >
> >       mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
> >
> > +     if (td->is_haptic_touchpad) {
> > +             if (mt_haptic_init(hdev, &td->haptic)) {
> > +                     dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
> > +                              hdev->name);
> > +                     td->is_haptic_touchpad = false;
> > +                     kfree(td->haptic);
> > +             }
> > +     } else {
> > +             kfree(td->haptic);
> > +     }
> > +
> >       return 0;
> >  }
> >
> >
> > --
> > 2.50.1.565.gc32cd1483b-goog
> >
>
> Cheers,
> Benjamin
I'll make the changes and send out a new version.

Thanks for your review!
-- 
Jonathan

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 10/11] HID: haptic: add hid_haptic_switch_mode
  2025-08-18 14:28 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
@ 2025-08-18 14:28 ` Jonathan Denose
  0 siblings, 0 replies; 17+ messages in thread
From: Jonathan Denose @ 2025-08-18 14:28 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
	Henrik Rydberg
  Cc: linux-input, linux-kernel, linux-doc, Angela Czubak,
	Sean O'Brien, Jonathan Denose

From: Angela Czubak <aczubak@google.com>

Function hid_haptic_switch_mode() can be used to switch between
device-controlled mode and host-controlled mode. Uploading a
WAVEFORMPRESS or WAVEFORMRELEASE effect triggers host-controlled mode if
the device is in device-controlled mode.

Signed-off-by: Angela Czubak <aczubak@google.com>
Co-developed-by: Jonathan Denose <jdenose@google.com>
Signed-off-by: Jonathan Denose <jdenose@google.com>
---
 drivers/hid/hid-haptic.c | 66 +++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 59 insertions(+), 7 deletions(-)

diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c
index c02af820051c22d1c899db84496c5a44b868fe49..aa090684c1f23b61a1ac4e9e7e523b31a8166a21 100644
--- a/drivers/hid/hid-haptic.c
+++ b/drivers/hid/hid-haptic.c
@@ -5,6 +5,7 @@
  *  Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
  */
 
+#include <linux/input/mt.h>
 #include <linux/module.h>
 
 #include "hid-haptic.h"
@@ -197,12 +198,46 @@ static void fill_effect_buf(struct hid_haptic_device *haptic,
 	mutex_unlock(&haptic->manual_trigger_mutex);
 }
 
+static void switch_mode(struct hid_device *hdev, struct hid_haptic_device *haptic,
+			int mode)
+{
+	struct hid_report *rep = haptic->auto_trigger_report;
+	struct hid_field *field;
+	s32 value;
+	int i, j;
+
+	if (mode == HID_HAPTIC_MODE_HOST)
+		value = HID_HAPTIC_ORDINAL_WAVEFORMSTOP;
+	else
+		value = haptic->default_auto_trigger;
+
+	mutex_lock(&haptic->auto_trigger_mutex);
+	for (i = 0; i < rep->maxfield; i++) {
+		field = rep->field[i];
+		/* Ignore if report count is out of bounds. */
+		if (field->report_count < 1)
+			continue;
+
+		for (j = 0; j < field->maxusage; j++) {
+			if (field->usage[j].hid == HID_HP_AUTOTRIGGER)
+				field->value[j] = value;
+		}
+	}
+
+	/* send the report */
+	hid_hw_request(hdev, rep, HID_REQ_SET_REPORT);
+	mutex_unlock(&haptic->auto_trigger_mutex);
+	haptic->mode = mode;
+}
+
 static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect,
 				    struct ff_effect *old)
 {
+	struct hid_device *hdev = input_get_drvdata(dev);
 	struct ff_device *ff = dev->ff;
 	struct hid_haptic_device *haptic = ff->private;
 	int i, ordinal = 0;
+	bool switch_modes = false;
 
 	/* If vendor range, check vendor id and page */
 	if (effect->u.haptic.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) &&
@@ -225,6 +260,16 @@ static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *eff
 	fill_effect_buf(haptic, &effect->u.haptic, &haptic->effect[effect->id],
 			ordinal);
 
+	if (effect->u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE) ||
+			effect->u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE))
+		switch_modes = true;
+
+	/* If device is in autonomous mode, and the uploaded effect signals userspace
+	 * wants control of the device, change modes
+	 */
+	if (switch_modes && haptic->mode == HID_HAPTIC_MODE_DEVICE)
+		switch_mode(hdev, haptic, HID_HAPTIC_MODE_HOST);
+
 	return 0;
 }
 
@@ -290,6 +335,7 @@ static void effect_set_default(struct ff_effect *effect)
 static int hid_haptic_erase(struct input_dev *dev, int effect_id)
 {
 	struct hid_haptic_device *haptic = dev->ff->private;
+	struct hid_device *hdev = input_get_drvdata(dev);
 	struct ff_effect effect;
 	int ordinal;
 
@@ -297,20 +343,25 @@ static int hid_haptic_erase(struct input_dev *dev, int effect_id)
 
 	if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
 		ordinal = haptic->release_ordinal;
-		if (!ordinal)
+		if (!ordinal) {
 			ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
-		else
-			effect.u.haptic.hid_usage = HID_HP_WAVEFORMRELEASE &
-				HID_USAGE;
+			if (haptic->mode == HID_HAPTIC_MODE_HOST)
+				switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
+		} else
+			effect.u.haptic.hid_usage = HID_HP_WAVEFORMRELEASE & HID_USAGE;
+
 		fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
 				ordinal);
 	} else if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE)) {
 		ordinal = haptic->press_ordinal;
-		if (!ordinal)
+		if (!ordinal) {
 			ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
+			if (haptic->mode == HID_HAPTIC_MODE_HOST)
+				switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
+		}
 		else
-			effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS &
-				HID_USAGE;
+			effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS & HID_USAGE;
+
 		fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
 				ordinal);
 	}
@@ -392,6 +443,7 @@ int hid_haptic_init(struct hid_device *hdev,
 	haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMSTOP] =
 		HID_HP_WAVEFORMSTOP & HID_USAGE;
 
+	mutex_init(&haptic->auto_trigger_mutex);
 	for (r = 0; r < haptic->auto_trigger_report->maxfield; r++)
 		parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]);
 

-- 
2.51.0.rc1.163.g2494970778-goog


^ permalink raw reply related	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2025-08-18 14:29 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-04 14:11 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
2025-08-04 14:11 ` [PATCH v2 01/11] HID: add haptics page defines Jonathan Denose
2025-08-04 14:11 ` [PATCH v2 02/11] Input: add FF_HID effect type Jonathan Denose
2025-08-04 20:34   ` tomasz.pakula.oficjalny
2025-08-11 15:26     ` Jonathan Denose
2025-08-04 14:11 ` [PATCH v2 03/11] Input: add INPUT_PROP_HAPTIC_TOUCHPAD Jonathan Denose
2025-08-04 14:11 ` [PATCH v2 04/11] HID: haptic: introduce hid_haptic_device Jonathan Denose
2025-08-04 14:11 ` [PATCH v2 05/11] HID: input: allow mapping of haptic output Jonathan Denose
2025-08-04 14:11 ` [PATCH v2 06/11] HID: haptic: initialize haptic device Jonathan Denose
2025-08-04 14:11 ` [PATCH v2 07/11] HID: input: calculate resolution for pressure Jonathan Denose
2025-08-04 14:11 ` [PATCH v2 08/11] HID: haptic: add functions handling events Jonathan Denose
2025-08-04 14:11 ` [PATCH v2 09/11] Input: MT - add INPUT_MT_TOTAL_FORCE flags Jonathan Denose
2025-08-04 14:11 ` [PATCH v2 10/11] HID: haptic: add hid_haptic_switch_mode Jonathan Denose
2025-08-04 14:11 ` [PATCH v2 11/11] HID: multitouch: add haptic multitouch support Jonathan Denose
2025-08-13  9:22   ` Benjamin Tissoires
2025-08-13 15:52     ` Jonathan Denose
  -- strict thread matches above, loose matches on Subject: below --
2025-08-18 14:28 [PATCH v2 00/11] HID: Implement haptic touchpad support Jonathan Denose
2025-08-18 14:28 ` [PATCH v2 10/11] HID: haptic: add hid_haptic_switch_mode Jonathan Denose

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).