]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
HID: haptic: add hid_haptic_switch_mode
authorAngela Czubak <aczubak@google.com>
Mon, 18 Aug 2025 23:08:51 +0000 (23:08 +0000)
committerBenjamin Tissoires <bentiss@kernel.org>
Mon, 15 Sep 2025 12:32:55 +0000 (14:32 +0200)
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>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
drivers/hid/hid-haptic.c

index c02af820051c22d1c899db84496c5a44b868fe49..aa090684c1f23b61a1ac4e9e7e523b31a8166a21 100644 (file)
@@ -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]);