* Re: [PATCH 2/3] mfd: rohm-bd71828: Use software nodes for gpio-keys
From: Matti Vaittinen @ 2025-08-18 6:54 UTC (permalink / raw)
To: Dmitry Torokhov, Lee Jones
Cc: Andy Shevchenko, Arnd Bergmann, Bartosz Golaszewski,
Linus Walleij, linux-input, linux-kernel
In-Reply-To: <20250817224731.1911207-2-dmitry.torokhov@gmail.com>
On 18/08/2025 01:47, Dmitry Torokhov wrote:
> Refactor the rohm-bd71828 MFD driver to use software nodes for
> instantiating the gpio-keys child device, replacing the old
> platform_data mechanism.
Thanks for doing this Dmitry! I believe I didn't understand how
providing the IRQs via swnode works... :)
If I visit the ROHM office this week, then I will try to test this using
the PMIC HW. (Next week I'll be in ELCE, and after it I have probably
already forgotten this...)
> The power key's properties are now defined using software nodes and
> property entries. The IRQ is passed as a resource attached to the
> platform device.
>
> This will allow dropping support for using platform data for configuring
> gpio-keys in the future.
>
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> ---
> drivers/mfd/rohm-bd71828.c | 81 +++++++++++++++++++++++++++-----------
> 1 file changed, 58 insertions(+), 23 deletions(-)
>
> diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c
> index a14b7aa69c3c..c29dde9996b7 100644
> --- a/drivers/mfd/rohm-bd71828.c
> +++ b/drivers/mfd/rohm-bd71828.c
> @@ -4,7 +4,6 @@
// ...snip
>
> +static int bd71828_reg_cnt;
> +
> +static int bd71828_i2c_register_swnodes(void)
> +{
> + int error;
> +
> + if (bd71828_reg_cnt == 0) {
Isn't this check racy...
> + error = software_node_register_node_group(bd71828_swnodes);
> + if (error)
> + return error;
> + }
> +
> + bd71828_reg_cnt++;
... with this...
> + return 0;
> +}
> +
> +static void bd71828_i2c_unregister_swnodes(void *dummy)
> +{
> + if (bd71828_reg_cnt != 0) {
...this...
> + software_node_unregister_node_group(bd71828_swnodes);
> + bd71828_reg_cnt--;
...and this? Perhaps add a mutex or use atomics?
Also, shouldn't the software_node_unregister_node_group() be only called
for the last instance to exit (Eg, "if (bd71828_reg_cnt == 0)" instead
of the "if (bd71828_reg_cnt != 0) {")?
> + }
> +}
> +
Other than that - I like this idea :)
Thanks!
Yours,
-- Matti
^ permalink raw reply
* Re: [PATCH 2/3] mfd: rohm-bd71828: Use software nodes for gpio-keys
From: Matti Vaittinen @ 2025-08-18 6:56 UTC (permalink / raw)
To: Dmitry Torokhov, Lee Jones
Cc: Andy Shevchenko, Arnd Bergmann, Bartosz Golaszewski,
Linus Walleij, linux-input, linux-kernel
In-Reply-To: <0adb5e0a-ea37-4bd5-87ff-654b770261f2@gmail.com>
On 18/08/2025 09:54, Matti Vaittinen wrote:
> On 18/08/2025 01:47, Dmitry Torokhov wrote:
>> Refactor the rohm-bd71828 MFD driver to use software nodes for
>> instantiating the gpio-keys child device, replacing the old
>> platform_data mechanism.
>
> Thanks for doing this Dmitry! I believe I didn't understand how
> providing the IRQs via swnode works... :)
>
> If I visit the ROHM office this week, then I will try to test this using
> the PMIC HW. (Next week I'll be in ELCE, and after it I have probably
> already forgotten this...)
>
>> The power key's properties are now defined using software nodes and
>> property entries. The IRQ is passed as a resource attached to the
>> platform device.
>>
>> This will allow dropping support for using platform data for configuring
>> gpio-keys in the future.
>>
>> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
>> ---
>> drivers/mfd/rohm-bd71828.c | 81 +++++++++++++++++++++++++++-----------
>> 1 file changed, 58 insertions(+), 23 deletions(-)
>>
>> diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c
>> index a14b7aa69c3c..c29dde9996b7 100644
>> --- a/drivers/mfd/rohm-bd71828.c
>> +++ b/drivers/mfd/rohm-bd71828.c
>> @@ -4,7 +4,6 @@
>
> // ...snip
>
>> +static int bd71828_reg_cnt;
>> +
>> +static int bd71828_i2c_register_swnodes(void)
>> +{
>> + int error;
>> +
>> + if (bd71828_reg_cnt == 0) {
>
> Isn't this check racy...
>
>> + error = software_node_register_node_group(bd71828_swnodes);
>> + if (error)
>> + return error;
>> + }
>> +
>> + bd71828_reg_cnt++;
>
> ... with this...
>
>> + return 0;
>> +}
>> +
>> +static void bd71828_i2c_unregister_swnodes(void *dummy)
>> +{
>> + if (bd71828_reg_cnt != 0) {
>
> ...this...
>
>> + software_node_unregister_node_group(bd71828_swnodes);
>> + bd71828_reg_cnt--;
>
> ...and this? Perhaps add a mutex or use atomics?
>
> Also, shouldn't the software_node_unregister_node_group() be only called
> for the last instance to exit (Eg, "if (bd71828_reg_cnt == 0)" instead
> of the "if (bd71828_reg_cnt != 0) {")?
Oh. Probably "if (bd71828_reg_cnt == 1)".
>
>> + }
>> +}
>> +
>
> Other than that - I like this idea :)
>
> Thanks!
>
> Yours,
> -- Matti
^ permalink raw reply
* Re: [PATCH 3/3] mfd: rohm-bd718x7: Use software nodes for gpio-keys
From: Matti Vaittinen @ 2025-08-18 6:57 UTC (permalink / raw)
To: Dmitry Torokhov, Lee Jones
Cc: Andy Shevchenko, Arnd Bergmann, Bartosz Golaszewski,
Linus Walleij, linux-input, linux-kernel
In-Reply-To: <20250817224731.1911207-3-dmitry.torokhov@gmail.com>
On 18/08/2025 01:47, Dmitry Torokhov wrote:
> Refactor the rohm-bd7182x7 MFD driver to use software nodes for
> instantiating the gpio-keys child device, replacing the old
> platform_data mechanism.
>
> The power key's properties are now defined using software nodes and
> property entries. The IRQ is passed as a resource attached to the
> platform device.
>
> This will allow dropping support for using platform data for configuring
> gpio-keys in the future.
> ---
> drivers/mfd/rohm-bd718x7.c | 76 ++++++++++++++++++++++++++++++--------
> 1 file changed, 60 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/mfd/rohm-bd718x7.c b/drivers/mfd/rohm-bd718x7.c
> index 25e494a93d48..20150656ac9c 100644
> --- a/drivers/mfd/rohm-bd718x7.c
> +++ b/drivers/mfd/rohm-bd718x7.c
> @@ -7,7 +7,6 @@
> // Datasheet for BD71837MWV available from
> // https://www.rohm.com/datasheet/BD71837MWV/bd71837mwv-e
...
>
> +static int bd718xx_reg_cnt;
> +
> +static int bd718xx_i2c_register_swnodes(void)
> +{
> + int error;
> +
> + if (bd718xx_reg_cnt == 0) {
> + error = software_node_register_node_group(bd718xx_swnodes);
> + if (error)
> + return error;
> + }
> +
> + bd718xx_reg_cnt++;
> + return 0;
> +}
> +
> +static void bd718xx_i2c_unregister_swnodes(void *dummy)
> +{
> + if (bd718xx_reg_cnt != 0) {
> + software_node_unregister_node_group(bd718xx_swnodes);
> + bd718xx_reg_cnt--;
> + }
> +}
Same questions here as with the patch 2/3.
Yours,
-- Matti
^ permalink raw reply
* Re: [PATCH v13 04/10] pwm: max7360: Add MAX7360 PWM support
From: Uwe Kleine-König @ 2025-08-18 9:05 UTC (permalink / raw)
To: Mathieu Dubois-Briand
Cc: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Michael Walle, Mark Brown, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, devicetree, linux-kernel,
linux-gpio, linux-input, linux-pwm, andriy.shevchenko,
Grégory Clement, Thomas Petazzoni, Andy Shevchenko
In-Reply-To: <20250811-mdb-max7360-support-v13-4-e79fcabff386@bootlin.com>
[-- Attachment #1: Type: text/plain, Size: 5389 bytes --]
Hello,
On Mon, Aug 11, 2025 at 12:46:22PM +0200, Mathieu Dubois-Briand wrote:
> From: Kamel Bouhara <kamel.bouhara@bootlin.com>
>
> Add driver for Maxim Integrated MAX7360 PWM controller, supporting up to
> 8 independent PWM outputs.
>
> Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
> Co-developed-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> ---
> drivers/pwm/Kconfig | 10 +++
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-max7360.c | 209 ++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 220 insertions(+)
>
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index f00ce973dddf..f2b1ce47de7f 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -432,6 +432,16 @@ config PWM_LPSS_PLATFORM
> To compile this driver as a module, choose M here: the module
> will be called pwm-lpss-platform.
>
> +config PWM_MAX7360
> + tristate "MAX7360 PWMs"
> + depends on MFD_MAX7360
> + help
> + PWM driver for Maxim Integrated MAX7360 multifunction device, with
> + support for up to 8 PWM outputs.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called pwm-max7360.
> +
> config PWM_MC33XS2410
> tristate "MC33XS2410 PWM support"
> depends on OF
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index ff4f47e5fb7a..dfa8b4966ee1 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -38,6 +38,7 @@ obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
> obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o
> obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o
> obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o
> +obj-$(CONFIG_PWM_MAX7360) += pwm-max7360.o
> obj-$(CONFIG_PWM_MC33XS2410) += pwm-mc33xs2410.o
> obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o
> obj-$(CONFIG_PWM_MESON) += pwm-meson.o
> diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c
> new file mode 100644
> index 000000000000..5a0c10d2320e
> --- /dev/null
> +++ b/drivers/pwm/pwm-max7360.c
> @@ -0,0 +1,209 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright 2025 Bootlin
> + *
> + * Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
> + * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
> + *
> + * PWM functionality of the MAX7360 multi-function device.
> + * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX7360.pdf
> + *
> + * Limitations:
> + * - Only supports normal polarity.
> + * - The period is fixed to 2 ms.
> + * - Only the duty cycle can be changed, new values are applied at the beginning
> + * of the next cycle.
> + * - When disabled, the output is put in Hi-Z immediately.
> + */
> +#include <linux/bits.h>
> +#include <linux/dev_printk.h>
> +#include <linux/err.h>
> +#include <linux/math64.h>
> +#include <linux/mfd/max7360.h>
> +#include <linux/minmax.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/regmap.h>
> +#include <linux/time.h>
> +#include <linux/types.h>
> +
> +#define MAX7360_NUM_PWMS 8
> +#define MAX7360_PWM_MAX 255
> +#define MAX7360_PWM_STEPS 256
> +#define MAX7360_PWM_PERIOD_NS (2 * NSEC_PER_MSEC)
> +
> +struct max7360_pwm_waveform {
> + u8 duty_steps;
> + bool enabled;
> +};
> +
> +static int max7360_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> + struct regmap *regmap = pwmchip_get_drvdata(chip);
> +
> + /*
> + * Make sure we use the individual PWM configuration register and not
> + * the global one.
> + * We never need to use the global one, so there is no need to revert
> + * that in the .free() callback.
> + */
> + return regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm),
> + MAX7360_PORT_CFG_COMMON_PWM, 0);
> +}
> +
> +static int max7360_pwm_round_waveform_tohw(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + const struct pwm_waveform *wf,
> + void *_wfhw)
> +{
> + struct max7360_pwm_waveform *wfhw = _wfhw;
> + u64 duty_steps;
> +
> + /*
> + * Ignore user provided values for period_length_ns and duty_offset_ns:
> + * we only support fixed period of MAX7360_PWM_PERIOD_NS and offset of 0.
> + * Values from 0 to 254 as duty_steps will provide duty cycles of 0/256
> + * to 254/256, while value 255 will provide a duty cycle of 100%.
> + */
> + if (wf->duty_length_ns >= MAX7360_PWM_PERIOD_NS) {
> + duty_steps = MAX7360_PWM_MAX;
> + } else {
> + duty_steps = (u32)wf->duty_length_ns * MAX7360_PWM_STEPS / MAX7360_PWM_PERIOD_NS;
> + if (duty_steps == MAX7360_PWM_MAX)
> + duty_steps = MAX7360_PWM_MAX - 1;
> + }
> +
> + wfhw->duty_steps = min(MAX7360_PWM_MAX, duty_steps);
> + wfhw->enabled = !!wf->period_length_ns;
> +
> + if (wf->period_length_ns < MAX7360_PWM_PERIOD_NS)
I know this code was suggested as is by me, but I think we need:
if (wf->period_length_ns && wf->period_length_ns < MAX7360_PWM_PERIOD_NS)
here to prevent to trigger a PWM_DEBUG warning. Sorry to spot this only
now.
> + return 1;
> + else
> + return 0;
> +}
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH v2 2/2] HID: Make elan touch controllers power on after panel is enabled
From: Pin-yen Lin @ 2025-08-18 11:49 UTC (permalink / raw)
To: Doug Anderson
Cc: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Jiri Kosina,
Benjamin Tissoires, dri-devel, linux-kernel, linux-input,
Chen-Yu Tsai
In-Reply-To: <CAD=FV=XmMwocA_oZVi2OS2Z4+5LBT4BXZE2C1mx1U-Tcs+5SOg@mail.gmail.com>
Hi Doug,
On Fri, Aug 15, 2025 at 8:27 AM Doug Anderson <dianders@chromium.org> wrote:
>
> Hi,
>
> On Wed, Aug 13, 2025 at 5:51 AM Pin-yen Lin <treapking@chromium.org> wrote:
> >
> > Introduce a new HID quirk to indicate that this device has to be enabled
> > after the panel's backlight is enabled, and update the driver data for
> > the elan devices to enable this quirk. This cannot be a I2C HID quirk
> > because the kernel needs to acknowledge this before powering up the
> > device and read the VID/PID. When this quirk is enabled, register
> > .panel_enabled()/.panel_disabling() instead for the panel follower.
> >
> > Also rename the *panel_prepare* functions into *panel_follower* because
> > they could be called in other situations now.
> >
> > Signed-off-by: Pin-yen Lin <treapking@chromium.org>
> >
> > ---
> >
> > Changes in v2:
> > - Rename *panel_prepare* functions to *panel_follower*
> > - Replace after_panel_enabled flag with enabled/disabling callbacks
> >
> > drivers/hid/i2c-hid/i2c-hid-core.c | 46 ++++++++++++++++-----------
> > drivers/hid/i2c-hid/i2c-hid-of-elan.c | 11 ++++++-
> > include/linux/hid.h | 2 ++
> > 3 files changed, 40 insertions(+), 19 deletions(-)
>
> This seems reasonable to me.
>
> Reviewed-by: Douglas Anderson <dianders@chromium.org>
>
> Given that this affects devices that we already had support for
> (you're changing the behavior of two touchscreens), should it have a
> Fixes tag?
I'll add Fixes tags in the next version.
>
> We'll also need to figure out a process for landing the two patches. I
> can easily land the first one in drm-misc-next, but then it'll be a
> while before the i2c-hid one can land. Is it OK to wait?
I'm okay with waiting for the next release for i2c-hid patch to land.
Or should will ask the HID maintainers to ack this patch?
>
>
> -Doug
Regards,
Pin-yen
^ permalink raw reply
* [PATCH v3 1/2] drm/panel: Allow powering on panel follower after panel is enabled
From: Pin-yen Lin @ 2025-08-18 11:49 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Jiri Kosina,
Benjamin Tissoires
Cc: linux-kernel, Douglas Anderson, linux-input, Chen-Yu Tsai,
dri-devel, Pin-yen Lin
Some touch controllers have to be powered on after the panel's backlight
is enabled. To support these controllers, introduce .panel_enabled() and
.panel_disabling() to panel_follower_funcs and use them to power on the
device after the panel and its backlight are enabled.
Signed-off-by: Pin-yen Lin <treapking@chromium.org>
---
Changes in v3:
- Update kernel-docs of drm_panel_add_follower() and drm_panel_remove_follower()
- Fix the order of calling .panel_disabling() and .panel_unpreparing()
- Add a blank line before the goto label
Changes in v2:
- Replace after_panel_enabled flag with enabled/disabling callbacks
drivers/gpu/drm/drm_panel.c | 73 +++++++++++++++++++++++++++++++------
include/drm/drm_panel.h | 14 +++++++
2 files changed, 76 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index c8bb28dccdc1b..d1e6598ea3bc0 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -134,6 +134,9 @@ void drm_panel_prepare(struct drm_panel *panel)
panel->prepared = true;
list_for_each_entry(follower, &panel->followers, list) {
+ if (!follower->funcs->panel_prepared)
+ continue;
+
ret = follower->funcs->panel_prepared(follower);
if (ret < 0)
dev_info(panel->dev, "%ps failed: %d\n",
@@ -179,6 +182,9 @@ void drm_panel_unprepare(struct drm_panel *panel)
mutex_lock(&panel->follower_lock);
list_for_each_entry(follower, &panel->followers, list) {
+ if (!follower->funcs->panel_unpreparing)
+ continue;
+
ret = follower->funcs->panel_unpreparing(follower);
if (ret < 0)
dev_info(panel->dev, "%ps failed: %d\n",
@@ -209,6 +215,7 @@ EXPORT_SYMBOL(drm_panel_unprepare);
*/
void drm_panel_enable(struct drm_panel *panel)
{
+ struct drm_panel_follower *follower;
int ret;
if (!panel)
@@ -219,10 +226,12 @@ void drm_panel_enable(struct drm_panel *panel)
return;
}
+ mutex_lock(&panel->follower_lock);
+
if (panel->funcs && panel->funcs->enable) {
ret = panel->funcs->enable(panel);
if (ret < 0)
- return;
+ goto exit;
}
panel->enabled = true;
@@ -230,6 +239,19 @@ void drm_panel_enable(struct drm_panel *panel)
if (ret < 0)
DRM_DEV_INFO(panel->dev, "failed to enable backlight: %d\n",
ret);
+
+ list_for_each_entry(follower, &panel->followers, list) {
+ if (!follower->funcs->panel_enabled)
+ continue;
+
+ ret = follower->funcs->panel_enabled(follower);
+ if (ret < 0)
+ dev_info(panel->dev, "%ps failed: %d\n",
+ follower->funcs->panel_enabled, ret);
+ }
+
+exit:
+ mutex_unlock(&panel->follower_lock);
}
EXPORT_SYMBOL(drm_panel_enable);
@@ -243,6 +265,7 @@ EXPORT_SYMBOL(drm_panel_enable);
*/
void drm_panel_disable(struct drm_panel *panel)
{
+ struct drm_panel_follower *follower;
int ret;
if (!panel)
@@ -262,6 +285,18 @@ void drm_panel_disable(struct drm_panel *panel)
return;
}
+ mutex_lock(&panel->follower_lock);
+
+ list_for_each_entry(follower, &panel->followers, list) {
+ if (!follower->funcs->panel_disabling)
+ continue;
+
+ ret = follower->funcs->panel_disabling(follower);
+ if (ret < 0)
+ dev_info(panel->dev, "%ps failed: %d\n",
+ follower->funcs->panel_disabling, ret);
+ }
+
ret = backlight_disable(panel->backlight);
if (ret < 0)
DRM_DEV_INFO(panel->dev, "failed to disable backlight: %d\n",
@@ -270,9 +305,12 @@ void drm_panel_disable(struct drm_panel *panel)
if (panel->funcs && panel->funcs->disable) {
ret = panel->funcs->disable(panel);
if (ret < 0)
- return;
+ goto exit;
}
panel->enabled = false;
+
+exit:
+ mutex_unlock(&panel->follower_lock);
}
EXPORT_SYMBOL(drm_panel_disable);
@@ -539,13 +577,13 @@ EXPORT_SYMBOL(drm_is_panel_follower);
* @follower_dev: The 'struct device' for the follower.
* @follower: The panel follower descriptor for the follower.
*
- * A panel follower is called right after preparing the panel and right before
- * unpreparing the panel. It's primary intention is to power on an associated
- * touchscreen, though it could be used for any similar devices. Multiple
- * devices are allowed the follow the same panel.
+ * A panel follower is called right after preparing/enabling the panel and right
+ * before unpreparing/disabling the panel. It's primary intention is to power on
+ * an associated touchscreen, though it could be used for any similar devices.
+ * Multiple devices are allowed the follow the same panel.
*
- * If a follower is added to a panel that's already been turned on, the
- * follower's prepare callback is called right away.
+ * If a follower is added to a panel that's already been prepared/enabled, the
+ * follower's prepared/enabled callback is called right away.
*
* The "panel" property of the follower points to the panel to be followed.
*
@@ -569,12 +607,18 @@ int drm_panel_add_follower(struct device *follower_dev,
mutex_lock(&panel->follower_lock);
list_add_tail(&follower->list, &panel->followers);
- if (panel->prepared) {
+ if (panel->prepared && follower->funcs->panel_prepared) {
ret = follower->funcs->panel_prepared(follower);
if (ret < 0)
dev_info(panel->dev, "%ps failed: %d\n",
follower->funcs->panel_prepared, ret);
}
+ if (panel->enabled && follower->funcs->panel_enabled) {
+ ret = follower->funcs->panel_enabled(follower);
+ if (ret < 0)
+ dev_info(panel->dev, "%ps failed: %d\n",
+ follower->funcs->panel_enabled, ret);
+ }
mutex_unlock(&panel->follower_lock);
@@ -587,7 +631,8 @@ EXPORT_SYMBOL(drm_panel_add_follower);
* @follower: The panel follower descriptor for the follower.
*
* Undo drm_panel_add_follower(). This includes calling the follower's
- * unprepare function if we're removed from a panel that's currently prepared.
+ * unpreparing/disabling function if we're removed from a panel that's currently
+ * prepared/enabled.
*
* Return: 0 or an error code.
*/
@@ -598,7 +643,13 @@ void drm_panel_remove_follower(struct drm_panel_follower *follower)
mutex_lock(&panel->follower_lock);
- if (panel->prepared) {
+ if (panel->enabled && follower->funcs->panel_disabling) {
+ ret = follower->funcs->panel_disabling(follower);
+ if (ret < 0)
+ dev_info(panel->dev, "%ps failed: %d\n",
+ follower->funcs->panel_disabling, ret);
+ }
+ if (panel->prepared && follower->funcs->panel_unpreparing) {
ret = follower->funcs->panel_unpreparing(follower);
if (ret < 0)
dev_info(panel->dev, "%ps failed: %d\n",
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index 843fb756a2950..2407bfa60236f 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -160,6 +160,20 @@ struct drm_panel_follower_funcs {
* Called before the panel is powered off.
*/
int (*panel_unpreparing)(struct drm_panel_follower *follower);
+
+ /**
+ * @panel_enabled:
+ *
+ * Called after the panel and the backlight have been enabled.
+ */
+ int (*panel_enabled)(struct drm_panel_follower *follower);
+
+ /**
+ * @panel_disabling:
+ *
+ * Called before the panel and the backlight are disabled.
+ */
+ int (*panel_disabling)(struct drm_panel_follower *follower);
};
struct drm_panel_follower {
--
2.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* [PATCH v3 2/2] HID: Make elan touch controllers power on after panel is enabled
From: Pin-yen Lin @ 2025-08-18 11:49 UTC (permalink / raw)
To: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Jiri Kosina,
Benjamin Tissoires
Cc: linux-kernel, Douglas Anderson, linux-input, Chen-Yu Tsai,
dri-devel, Pin-yen Lin
In-Reply-To: <20250818115015.2909525-1-treapking@chromium.org>
Introduce a new HID quirk to indicate that this device has to be enabled
after the panel's backlight is enabled, and update the driver data for
the elan devices to enable this quirk. This cannot be a I2C HID quirk
because the kernel needs to acknowledge this before powering up the
device and read the VID/PID. When this quirk is enabled, register
.panel_enabled()/.panel_disabling() instead for the panel follower.
Also rename the *panel_prepare* functions into *panel_follower* because
they could be called in other situations now.
Fixes: bd3cba00dcc63 ("HID: i2c-hid: elan: Add support for Elan eKTH6915 i2c-hid touchscreens")
Fixes: d06651bebf99e ("HID: i2c-hid: elan: Add elan-ekth6a12nay timing")
Reviewed-by: Douglas Anderson <dianders@chromium.org>
Signed-off-by: Pin-yen Lin <treapking@chromium.org>
---
Changes in v3:
- Collect review tag
- Add fixes tags
Changes in v2:
- Rename *panel_prepare* functions to *panel_follower*
- Replace after_panel_enabled flag with enabled/disabling callbacks
drivers/hid/i2c-hid/i2c-hid-core.c | 46 ++++++++++++++++-----------
drivers/hid/i2c-hid/i2c-hid-of-elan.c | 11 ++++++-
include/linux/hid.h | 2 ++
3 files changed, 40 insertions(+), 19 deletions(-)
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index d3912e3f2f13a..99ce6386176c6 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -112,9 +112,9 @@ struct i2c_hid {
struct i2chid_ops *ops;
struct drm_panel_follower panel_follower;
- struct work_struct panel_follower_prepare_work;
+ struct work_struct panel_follower_work;
bool is_panel_follower;
- bool prepare_work_finished;
+ bool panel_follower_work_finished;
};
static const struct i2c_hid_quirks {
@@ -1110,10 +1110,10 @@ static int i2c_hid_core_probe_panel_follower(struct i2c_hid *ihid)
return ret;
}
-static void ihid_core_panel_prepare_work(struct work_struct *work)
+static void ihid_core_panel_follower_work(struct work_struct *work)
{
struct i2c_hid *ihid = container_of(work, struct i2c_hid,
- panel_follower_prepare_work);
+ panel_follower_work);
struct hid_device *hid = ihid->hid;
int ret;
@@ -1130,7 +1130,7 @@ static void ihid_core_panel_prepare_work(struct work_struct *work)
if (ret)
dev_warn(&ihid->client->dev, "Power on failed: %d\n", ret);
else
- WRITE_ONCE(ihid->prepare_work_finished, true);
+ WRITE_ONCE(ihid->panel_follower_work_finished, true);
/*
* The work APIs provide a number of memory ordering guarantees
@@ -1139,12 +1139,12 @@ static void ihid_core_panel_prepare_work(struct work_struct *work)
* guarantee that a write that happened in the work is visible after
* cancel_work_sync(). We'll add a write memory barrier here to match
* with i2c_hid_core_panel_unpreparing() to ensure that our write to
- * prepare_work_finished is visible there.
+ * panel_follower_work_finished is visible there.
*/
smp_wmb();
}
-static int i2c_hid_core_panel_prepared(struct drm_panel_follower *follower)
+static int i2c_hid_core_panel_follower_resume(struct drm_panel_follower *follower)
{
struct i2c_hid *ihid = container_of(follower, struct i2c_hid, panel_follower);
@@ -1152,29 +1152,36 @@ static int i2c_hid_core_panel_prepared(struct drm_panel_follower *follower)
* Powering on a touchscreen can be a slow process. Queue the work to
* the system workqueue so we don't block the panel's power up.
*/
- WRITE_ONCE(ihid->prepare_work_finished, false);
- schedule_work(&ihid->panel_follower_prepare_work);
+ WRITE_ONCE(ihid->panel_follower_work_finished, false);
+ schedule_work(&ihid->panel_follower_work);
return 0;
}
-static int i2c_hid_core_panel_unpreparing(struct drm_panel_follower *follower)
+static int i2c_hid_core_panel_follower_suspend(struct drm_panel_follower *follower)
{
struct i2c_hid *ihid = container_of(follower, struct i2c_hid, panel_follower);
- cancel_work_sync(&ihid->panel_follower_prepare_work);
+ cancel_work_sync(&ihid->panel_follower_work);
- /* Match with ihid_core_panel_prepare_work() */
+ /* Match with ihid_core_panel_follower_work() */
smp_rmb();
- if (!READ_ONCE(ihid->prepare_work_finished))
+ if (!READ_ONCE(ihid->panel_follower_work_finished))
return 0;
return i2c_hid_core_suspend(ihid, true);
}
-static const struct drm_panel_follower_funcs i2c_hid_core_panel_follower_funcs = {
- .panel_prepared = i2c_hid_core_panel_prepared,
- .panel_unpreparing = i2c_hid_core_panel_unpreparing,
+static const struct drm_panel_follower_funcs
+ i2c_hid_core_panel_follower_prepare_funcs = {
+ .panel_prepared = i2c_hid_core_panel_follower_resume,
+ .panel_unpreparing = i2c_hid_core_panel_follower_suspend,
+};
+
+static const struct drm_panel_follower_funcs
+ i2c_hid_core_panel_follower_enable_funcs = {
+ .panel_enabled = i2c_hid_core_panel_follower_resume,
+ .panel_disabling = i2c_hid_core_panel_follower_suspend,
};
static int i2c_hid_core_register_panel_follower(struct i2c_hid *ihid)
@@ -1182,7 +1189,10 @@ static int i2c_hid_core_register_panel_follower(struct i2c_hid *ihid)
struct device *dev = &ihid->client->dev;
int ret;
- ihid->panel_follower.funcs = &i2c_hid_core_panel_follower_funcs;
+ if (ihid->hid->initial_quirks | HID_QUIRK_POWER_ON_AFTER_BACKLIGHT)
+ ihid->panel_follower.funcs = &i2c_hid_core_panel_follower_enable_funcs;
+ else
+ ihid->panel_follower.funcs = &i2c_hid_core_panel_follower_prepare_funcs;
/*
* If we're not in control of our own power up/power down then we can't
@@ -1237,7 +1247,7 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
init_waitqueue_head(&ihid->wait);
mutex_init(&ihid->cmd_lock);
mutex_init(&ihid->reset_lock);
- INIT_WORK(&ihid->panel_follower_prepare_work, ihid_core_panel_prepare_work);
+ INIT_WORK(&ihid->panel_follower_work, ihid_core_panel_follower_work);
/* we need to allocate the command buffer without knowing the maximum
* size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the
diff --git a/drivers/hid/i2c-hid/i2c-hid-of-elan.c b/drivers/hid/i2c-hid/i2c-hid-of-elan.c
index 3fcff6daa0d3a..0215f217f6d86 100644
--- a/drivers/hid/i2c-hid/i2c-hid-of-elan.c
+++ b/drivers/hid/i2c-hid/i2c-hid-of-elan.c
@@ -8,6 +8,7 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
+#include <linux/hid.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -23,6 +24,7 @@ struct elan_i2c_hid_chip_data {
unsigned int post_power_delay_ms;
u16 hid_descriptor_address;
const char *main_supply_name;
+ bool power_after_backlight;
};
struct i2c_hid_of_elan {
@@ -97,6 +99,7 @@ static int i2c_hid_of_elan_probe(struct i2c_client *client)
{
struct i2c_hid_of_elan *ihid_elan;
int ret;
+ u32 quirks = 0;
ihid_elan = devm_kzalloc(&client->dev, sizeof(*ihid_elan), GFP_KERNEL);
if (!ihid_elan)
@@ -131,8 +134,12 @@ static int i2c_hid_of_elan_probe(struct i2c_client *client)
}
}
+ if (ihid_elan->chip_data->power_after_backlight)
+ quirks = HID_QUIRK_POWER_ON_AFTER_BACKLIGHT;
+
ret = i2c_hid_core_probe(client, &ihid_elan->ops,
- ihid_elan->chip_data->hid_descriptor_address, 0);
+ ihid_elan->chip_data->hid_descriptor_address,
+ quirks);
if (ret)
goto err_deassert_reset;
@@ -150,6 +157,7 @@ static const struct elan_i2c_hid_chip_data elan_ekth6915_chip_data = {
.post_gpio_reset_on_delay_ms = 300,
.hid_descriptor_address = 0x0001,
.main_supply_name = "vcc33",
+ .power_after_backlight = true,
};
static const struct elan_i2c_hid_chip_data elan_ekth6a12nay_chip_data = {
@@ -157,6 +165,7 @@ static const struct elan_i2c_hid_chip_data elan_ekth6a12nay_chip_data = {
.post_gpio_reset_on_delay_ms = 300,
.hid_descriptor_address = 0x0001,
.main_supply_name = "vcc33",
+ .power_after_backlight = true,
};
static const struct elan_i2c_hid_chip_data ilitek_ili9882t_chip_data = {
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 2cc4f1e4ea963..c32425b5d0119 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -364,6 +364,7 @@ struct hid_item {
* | @HID_QUIRK_HAVE_SPECIAL_DRIVER:
* | @HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE:
* | @HID_QUIRK_IGNORE_SPECIAL_DRIVER
+ * | @HID_QUIRK_POWER_ON_AFTER_BACKLIGHT
* | @HID_QUIRK_FULLSPEED_INTERVAL:
* | @HID_QUIRK_NO_INIT_REPORTS:
* | @HID_QUIRK_NO_IGNORE:
@@ -391,6 +392,7 @@ struct hid_item {
#define HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE BIT(20)
#define HID_QUIRK_NOINVERT BIT(21)
#define HID_QUIRK_IGNORE_SPECIAL_DRIVER BIT(22)
+#define HID_QUIRK_POWER_ON_AFTER_BACKLIGHT BIT(23)
#define HID_QUIRK_FULLSPEED_INTERVAL BIT(28)
#define HID_QUIRK_NO_INIT_REPORTS BIT(29)
#define HID_QUIRK_NO_IGNORE BIT(30)
--
2.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* [PATCH v2 00/11] HID: Implement haptic touchpad support
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
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_haptic_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_haptic_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:
- Rename FF_HID and ff_hid_effect to FF_HAPTIC and ff_haptic_effect
- Add more detail to CONFIG_HID_HAPTIC config option description
- Remove CONFIG_MULTITOUCH_HAPTIC config option
- Utilize devm api in hid-multitouch haptic functions
- Link to v1: https://lore.kernel.org/all/20250714-support-forcepads-v1-0-71c7c05748c9@google.com
---
Angela Czubak (11):
HID: add haptics page defines
Input: add FF_HAPTIC 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 | 11 +
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 | 47 +++
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, 862 insertions(+), 7 deletions(-)
---
base-commit: 86731a2a651e58953fc949573895f2fa6d456841
change-id: 20250625-support-forcepads-0b4f74fd3d0a
Best regards,
--
Jonathan Denose <jdenose@google.com>
^ permalink raw reply
* [PATCH v2 01/11] HID: add haptics page defines
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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
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.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* [PATCH v2 02/11] Input: add FF_HAPTIC effect type
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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
From: Angela Czubak <aczubak@google.com>
FF_HAPTIC 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..a440fafaa018947f4d9ca0a15af2b4c6bd0ae6a0 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_haptic_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_haptic_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_haptic_effect haptic;
} u;
};
@@ -471,6 +490,7 @@ struct ff_effect {
* Force feedback effect types
*/
+#define FF_HAPTIC 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_HAPTIC
#define FF_EFFECT_MAX FF_RAMP
/*
--
2.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* [PATCH v2 03/11] Input: add INPUT_PROP_HAPTIC_TOUCHPAD
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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
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.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* [PATCH v2 04/11] HID: haptic: introduce hid_haptic_device
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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
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 | 11 +++++
drivers/hid/Makefile | 1 +
drivers/hid/hid-haptic.c | 72 ++++++++++++++++++++++++++++++++
drivers/hid/hid-haptic.h | 105 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 189 insertions(+)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 43859fc757470caf6ad43bd5f72f119e9c36aea7..cbbe82a0a2ba257e45f77ca014fb5f08b71fc62f 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -92,6 +92,17 @@ 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.
+ Adds extra parsing and FF device for the hid multitouch driver.
+ It can be used for Elan 2703 haptic touchpad.
+
+ 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..b729f8245aa60c3d06b79b11846dccf6fcc0c08b
--- /dev/null
+++ b/drivers/hid/hid-haptic.h
@@ -0,0 +1,105 @@
+/* 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;
+};
+
+#ifdef CONFIG_HID_HAPTIC
+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);
+#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;
+}
+#endif
--
2.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* [PATCH v2 05/11] HID: input: allow mapping of haptic output
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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
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.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* [PATCH v2 06/11] HID: haptic: initialize haptic device
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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
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 | 5 +
2 files changed, 443 insertions(+)
diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c
index d659a430c1a6b06ded31d49efe4bded909671cb6..ef09b4039f33f15d7220e69fbed10bd8b0362bb4 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_haptic_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.haptic.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) &&
+ effect->u.haptic.hid_usage <= (HID_HP_VENDORWAVEFORMMAX & HID_USAGE) &&
+ (effect->u.haptic.vendor_id != haptic->vendor_id ||
+ effect->u.haptic.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.haptic.hid_usage) {
+ ordinal = i;
+ break;
+ }
+ }
+ if (ordinal < 1)
+ return -EINVAL;
+
+ /* Fill the buffer for the effect id */
+ fill_effect_buf(haptic, &effect->u.haptic, &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_HAPTIC;
+ effect->id = -1;
+ effect->u.haptic.hid_usage = HID_HP_WAVEFORMNONE & HID_USAGE;
+ effect->u.haptic.intensity = 100;
+ effect->u.haptic.retrigger_period = 0;
+ effect->u.haptic.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.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
+ ordinal = haptic->release_ordinal;
+ if (!ordinal)
+ ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
+ 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)
+ ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
+ else
+ effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS &
+ HID_USAGE;
+ fill_effect_buf(haptic, &effect.u.haptic, &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_haptic_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_HAPTIC);
+
+ 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 b729f8245aa60c3d06b79b11846dccf6fcc0c08b..402ac66954e315eb5444df277ab2b9a4ec415dd6 100644
--- a/drivers/hid/hid-haptic.h
+++ b/drivers/hid/hid-haptic.h
@@ -73,6 +73,7 @@ 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);
#else
static inline
void hid_haptic_feature_mapping(struct hid_device *hdev,
@@ -102,4 +103,8 @@ int hid_haptic_input_configured(struct hid_device *hdev,
{
return 0;
}
+int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr)
+{
+ return 0;
+}
#endif
--
2.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* [PATCH v2 07/11] HID: input: calculate resolution for pressure
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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
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.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* [PATCH v2 08/11] HID: haptic: add functions handling events
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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
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 | 15 +++++++++++++++
2 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c
index ef09b4039f33f15d7220e69fbed10bd8b0362bb4..c02af820051c22d1c899db84496c5a44b868fe49 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 402ac66954e315eb5444df277ab2b9a4ec415dd6..73601f429de16bae3b1d877445f7eebf41a69d94 100644
--- a/drivers/hid/hid-haptic.h
+++ b/drivers/hid/hid-haptic.h
@@ -74,6 +74,10 @@ 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);
#else
static inline
void hid_haptic_feature_mapping(struct hid_device *hdev,
@@ -107,4 +111,15 @@ int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_p
{
return 0;
}
+static inline
+void hid_haptic_handle_press_release(struct hid_haptic_device *haptic)
+{}
+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
+
--
2.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* [PATCH v2 09/11] Input: MT - add INPUT_MT_TOTAL_FORCE flags
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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
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.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* [PATCH v2 10/11] HID: haptic: add hid_haptic_switch_mode
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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
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
* [PATCH v2 11/11] HID: multitouch: add haptic multitouch support
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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
From: Angela Czubak <aczubak@google.com>
If CONFIG_HID_HAPTIC is configured, and the device is recognized to have
simple haptic capabilities, try initializing the haptic device, 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/hid-haptic.h | 16 ++++++++++-----
drivers/hid/hid-multitouch.c | 47 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 58 insertions(+), 5 deletions(-)
diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h
index 73601f429de16bae3b1d877445f7eebf41a69d94..6fbe07a8df42c2dc77681b013a6f948357253bf8 100644
--- a/drivers/hid/hid-haptic.h
+++ b/drivers/hid/hid-haptic.h
@@ -107,19 +107,25 @@ int hid_haptic_input_configured(struct hid_device *hdev,
{
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)
-{}
+void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) {}
static inline
-void hid_haptic_pressure_reset(struct hid_haptic_device *haptic)
-{}
+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..bbbd5c49dbd480ecad2a0c074b2f973f099dc054 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;
@@ -525,6 +529,8 @@ static void mt_feature_mapping(struct hid_device *hdev,
mt_get_feature(hdev, field->report);
break;
}
+
+ hid_haptic_feature_mapping(hdev, td->haptic, field, usage);
}
static void set_abs(struct input_dev *input, unsigned int code,
@@ -856,6 +862,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 =
+ hid_haptic_check_pressure_unit(td->haptic,
+ hi, field);
MT_STORE_FIELD(p);
return 1;
case HID_DG_SCANTIME:
@@ -980,6 +989,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)
+ hid_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 +1148,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
minor = minor >> 1;
}
+ if (td->is_haptic_touchpad)
+ hid_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 +1338,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 +1376,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 +1439,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 = hid_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 +1658,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 (hid_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) {
@@ -1777,6 +1808,11 @@ 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 = devm_kzalloc(&td->hdev->dev, 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 +1876,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 (hid_haptic_init(hdev, &td->haptic)) {
+ dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
+ hdev->name);
+ td->is_haptic_touchpad = false;
+ devm_kfree(&td->hdev->dev, td->haptic);
+ }
+ } else {
+ devm_kfree(&td->hdev->dev, td->haptic);
+ }
+
return 0;
}
--
2.51.0.rc1.163.g2494970778-goog
^ permalink raw reply related
* Re: [PATCH v2 00/11] HID: Implement haptic touchpad support
From: Jonathan Denose @ 2025-08-18 14:34 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
In-Reply-To: <20250818-support-forcepads-v2-0-ca2546e319d5@google.com>
On Mon, Aug 18, 2025 at 9:28 AM Jonathan Denose <jdenose@google.com> wrote:
>
> 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_haptic_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_haptic_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:
> - Rename FF_HID and ff_hid_effect to FF_HAPTIC and ff_haptic_effect
> - Add more detail to CONFIG_HID_HAPTIC config option description
> - Remove CONFIG_MULTITOUCH_HAPTIC config option
> - Utilize devm api in hid-multitouch haptic functions
> - Link to v1: https://lore.kernel.org/all/20250714-support-forcepads-v1-0-71c7c05748c9@google.com
>
> ---
> Angela Czubak (11):
> HID: add haptics page defines
> Input: add FF_HAPTIC 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 | 11 +
> 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 | 47 +++
> 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, 862 insertions(+), 7 deletions(-)
> ---
> base-commit: 86731a2a651e58953fc949573895f2fa6d456841
> change-id: 20250625-support-forcepads-0b4f74fd3d0a
>
> Best regards,
> --
> Jonathan Denose <jdenose@google.com>
>
My apologies, I'm using b4 to manage these patches and submission and
I'm not sure why I couldn't get it to increment this version like it
did the last time.
Link to the previous version of this patchset is here:
https://lore.kernel.org/all/20250804-support-forcepads-v2-0-138ca980261d@google.com/
--
Jonathan
^ permalink raw reply
* [PATCH] HID: axff: add cleanup allocated struct axff_device heap
From: Jeongjun Park @ 2025-08-18 15:43 UTC (permalink / raw)
To: jikos, bentiss; +Cc: dtor, x0r, linux-input, linux-kernel, Jeongjun Park
Currently, acrux hid driver allocates heap memory equal to
sizeof(struct axff_device) to support force feedback, but there's no code
to free this memory except when initialization fails. This causes the
allocated memory to not be freed even if the driver is detached.
Therefore, to properly clean up and safely manage the allocated heap,
must be modified to use devm_kzalloc().
Fixes: c0dbcc33c652 ("HID: add ACRUX game controller force feedback support")
Signed-off-by: Jeongjun Park <aha310510@gmail.com>
---
drivers/hid/hid-axff.c | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/drivers/hid/hid-axff.c b/drivers/hid/hid-axff.c
index fbe4e16ab029..b8202737f4c8 100644
--- a/drivers/hid/hid-axff.c
+++ b/drivers/hid/hid-axff.c
@@ -96,7 +96,7 @@ static int axff_init(struct hid_device *hid)
return -ENODEV;
}
- axff = kzalloc(sizeof(struct axff_device), GFP_KERNEL);
+ axff = devm_kzalloc(&hid->dev, sizeof(struct axff_device), GFP_KERNEL);
if (!axff)
return -ENOMEM;
@@ -104,7 +104,7 @@ static int axff_init(struct hid_device *hid)
error = input_ff_create_memless(dev, axff, axff_play);
if (error)
- goto err_free_mem;
+ return error;
axff->report = report;
hid_hw_request(hid, axff->report, HID_REQ_SET_REPORT);
@@ -112,10 +112,6 @@ static int axff_init(struct hid_device *hid)
hid_info(hid, "Force Feedback for ACRUX game controllers by Sergei Kolzun <x0r@dv-life.ru>\n");
return 0;
-
-err_free_mem:
- kfree(axff);
- return error;
}
#else
static inline int axff_init(struct hid_device *hid)
--
^ permalink raw reply related
* Re: [PATCH v2 2/2] HID: Make elan touch controllers power on after panel is enabled
From: Doug Anderson @ 2025-08-18 15:45 UTC (permalink / raw)
To: Pin-yen Lin
Cc: Neil Armstrong, Jessica Zhang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Jiri Kosina,
Benjamin Tissoires, dri-devel, linux-kernel, linux-input,
Chen-Yu Tsai
In-Reply-To: <CAEXTbpcTJVCqR-NAfddd2H9oLUitcUi3XnX1GQf7hA06-Vx9UQ@mail.gmail.com>
Hi,
On Mon, Aug 18, 2025 at 4:49 AM Pin-yen Lin <treapking@chromium.org> wrote:
>
> Hi Doug,
>
> On Fri, Aug 15, 2025 at 8:27 AM Doug Anderson <dianders@chromium.org> wrote:
> >
> > Hi,
> >
> > On Wed, Aug 13, 2025 at 5:51 AM Pin-yen Lin <treapking@chromium.org> wrote:
> > >
> > > Introduce a new HID quirk to indicate that this device has to be enabled
> > > after the panel's backlight is enabled, and update the driver data for
> > > the elan devices to enable this quirk. This cannot be a I2C HID quirk
> > > because the kernel needs to acknowledge this before powering up the
> > > device and read the VID/PID. When this quirk is enabled, register
> > > .panel_enabled()/.panel_disabling() instead for the panel follower.
> > >
> > > Also rename the *panel_prepare* functions into *panel_follower* because
> > > they could be called in other situations now.
> > >
> > > Signed-off-by: Pin-yen Lin <treapking@chromium.org>
> > >
> > > ---
> > >
> > > Changes in v2:
> > > - Rename *panel_prepare* functions to *panel_follower*
> > > - Replace after_panel_enabled flag with enabled/disabling callbacks
> > >
> > > drivers/hid/i2c-hid/i2c-hid-core.c | 46 ++++++++++++++++-----------
> > > drivers/hid/i2c-hid/i2c-hid-of-elan.c | 11 ++++++-
> > > include/linux/hid.h | 2 ++
> > > 3 files changed, 40 insertions(+), 19 deletions(-)
> >
> > This seems reasonable to me.
> >
> > Reviewed-by: Douglas Anderson <dianders@chromium.org>
> >
> > Given that this affects devices that we already had support for
> > (you're changing the behavior of two touchscreens), should it have a
> > Fixes tag?
>
> I'll add Fixes tags in the next version.
> >
> > We'll also need to figure out a process for landing the two patches. I
> > can easily land the first one in drm-misc-next, but then it'll be a
> > while before the i2c-hid one can land. Is it OK to wait?
>
> I'm okay with waiting for the next release for i2c-hid patch to land.
> Or should will ask the HID maintainers to ack this patch?
I'll also respond on the v3, but probably the easiest is just:
* I won't wait for a response for the HID maintainers before landing
patch #1 into drm-misc.
* If HID maintainers don't want to Ack the patch for landing through
drm-misc, we'll just pause landing of patch #2 until things percolate
to mainline.
-Doug
^ permalink raw reply
* Re: [PATCH v2 03/11] Input: add INPUT_PROP_HAPTIC_TOUCHPAD
From: Randy Dunlap @ 2025-08-18 16:31 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
In-Reply-To: <20250818-support-forcepads-v2-3-ca2546e319d5@google.com>
On 8/18/25 7:28 AM, Jonathan Denose wrote:
> 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)
reports
> +- follows the MT protocol type B
> +
> +Summing up, such devices follow the MS spec for input devices in
--
~Randy
^ permalink raw reply
* Re: [PATCH v2 04/11] HID: haptic: introduce hid_haptic_device
From: Randy Dunlap @ 2025-08-18 16:36 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
In-Reply-To: <20250818-support-forcepads-v2-4-ca2546e319d5@google.com>
Hi,
On 8/18/25 7:28 AM, Jonathan Denose wrote:
> 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 | 11 +++++
> drivers/hid/Makefile | 1 +
> drivers/hid/hid-haptic.c | 72 ++++++++++++++++++++++++++++++++
> drivers/hid/hid-haptic.h | 105 +++++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 189 insertions(+)
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 43859fc757470caf6ad43bd5f72f119e9c36aea7..cbbe82a0a2ba257e45f77ca014fb5f08b71fc62f 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -92,6 +92,17 @@ config HID_GENERIC
>
> If unsure, say Y.
>
> +config HID_HAPTIC
> + bool "Haptic touchpad support"
Why bool instead of tristate?
> + default n
> + help
> + Support for touchpads with force sensors and haptic actuators instead of a
> + traditional button.
> + Adds extra parsing and FF device for the hid multitouch driver.
> + It can be used for Elan 2703 haptic touchpad.
> +
> + If unsure, say N.
> +
I do wish that hid/Kconfig indentation didn't vary so much from coding-style.rst.
> menu "Special HID drivers"
>
> config HID_A4TECH
> diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..b729f8245aa60c3d06b79b11846dccf6fcc0c08b
> --- /dev/null
> +++ b/drivers/hid/hid-haptic.h
> @@ -0,0 +1,105 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * HID Haptic support for Linux
> + *
> + * Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
> + */
> +
> +/*
> + */
eh?
> +
> +
> +#include <linux/hid.h>
?
--
~Randy
^ permalink raw reply
* Re: [PATCH 2/3] mfd: rohm-bd71828: Use software nodes for gpio-keys
From: Dmitry Torokhov @ 2025-08-18 17:11 UTC (permalink / raw)
To: Matti Vaittinen
Cc: Lee Jones, Andy Shevchenko, Arnd Bergmann, Bartosz Golaszewski,
Linus Walleij, linux-input, linux-kernel
In-Reply-To: <a3fb7466-6774-4ae6-9e67-d35247ffa765@gmail.com>
On Mon, Aug 18, 2025 at 09:56:07AM +0300, Matti Vaittinen wrote:
> On 18/08/2025 09:54, Matti Vaittinen wrote:
> > On 18/08/2025 01:47, Dmitry Torokhov wrote:
> > > Refactor the rohm-bd71828 MFD driver to use software nodes for
> > > instantiating the gpio-keys child device, replacing the old
> > > platform_data mechanism.
> >
> > Thanks for doing this Dmitry! I believe I didn't understand how
> > providing the IRQs via swnode works... :)
> >
> > If I visit the ROHM office this week, then I will try to test this using
> > the PMIC HW. (Next week I'll be in ELCE, and after it I have probably
> > already forgotten this...)
> >
> > > The power key's properties are now defined using software nodes and
> > > property entries. The IRQ is passed as a resource attached to the
> > > platform device.
> > >
> > > This will allow dropping support for using platform data for configuring
> > > gpio-keys in the future.
> > >
> > > Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> > > ---
> > > drivers/mfd/rohm-bd71828.c | 81 +++++++++++++++++++++++++++-----------
> > > 1 file changed, 58 insertions(+), 23 deletions(-)
> > >
> > > diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c
> > > index a14b7aa69c3c..c29dde9996b7 100644
> > > --- a/drivers/mfd/rohm-bd71828.c
> > > +++ b/drivers/mfd/rohm-bd71828.c
> > > @@ -4,7 +4,6 @@
> >
> > // ...snip
> >
> > > +static int bd71828_reg_cnt;
> > > +
> > > +static int bd71828_i2c_register_swnodes(void)
> > > +{
> > > + int error;
> > > +
> > > + if (bd71828_reg_cnt == 0) {
> >
> > Isn't this check racy...
> >
> > > + error = software_node_register_node_group(bd71828_swnodes);
> > > + if (error)
> > > + return error;
> > > + }
> > > +
> > > + bd71828_reg_cnt++;
> >
> > ... with this...
> >
> > > + return 0;
> > > +}
> > > +
> > > +static void bd71828_i2c_unregister_swnodes(void *dummy)
> > > +{
> > > + if (bd71828_reg_cnt != 0) {
> >
> > ...this...
> >
> > > + software_node_unregister_node_group(bd71828_swnodes);
> > > + bd71828_reg_cnt--;
> >
> > ...and this? Perhaps add a mutex or use atomics?
> >
> > Also, shouldn't the software_node_unregister_node_group() be only called
> > for the last instance to exit (Eg, "if (bd71828_reg_cnt == 0)" instead
> > of the "if (bd71828_reg_cnt != 0) {")?
>
> Oh. Probably "if (bd71828_reg_cnt == 1)".
You are right, I am not sure what I was thinking when I wrote this.
I actually doubt that sharing of nodes between devices would work well.
But I believe these devices are singletons, it should not be possible to
have several of them in a single system, right? So maybe the best way is
to simply instantiate them in probe and bail out if they are already
registered.
Thanks.
--
Dmitry
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox