[<prev] [next>] [day] [month] [year] [list]
Message-ID: <20260130062023.GA24089@altimeter-info>
Date: Fri, 30 Jan 2026 06:20:23 +0000
From: Ivan Gorinov <linux-kernel@...imeter.info>
To: Jiri Kosina <jikos@...nel.org>
Cc: linux-input@...r.kernel.org, linux-kernel@...r.kernel.org
Subject: [PATCH] HID: winwing: Enable rumble effects
Enable rumble motor control on TGRIP-15E and TGRIP-15EX throttle grips
by sending haptic feedback commands (EV_FF events) to the input device.
Signed-off-by: Ivan Gorinov <linux-kernel@...imeter.info>
---
drivers/hid/hid-winwing.c | 193 +++++++++++++++++++++++++++++++++++---
1 file changed, 179 insertions(+), 14 deletions(-)
diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c
index ab65dc12d1e0..031590ffd383 100644
--- a/drivers/hid/hid-winwing.c
+++ b/drivers/hid/hid-winwing.c
@@ -12,6 +12,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/workqueue.h>
#define MAX_REPORT 16
@@ -35,10 +36,14 @@ static const struct winwing_led_info led_info[3] = {
struct winwing_drv_data {
struct hid_device *hdev;
- __u8 *report_buf;
- struct mutex lock;
- int map_more_buttons;
- unsigned int num_leds;
+ struct mutex lights_lock;
+ __u8 *report_lights;
+ __u8 *report_rumble;
+ struct work_struct rumble_work;
+ struct ff_rumble_effect rumble;
+ int rumble_left;
+ int rumble_right;
+ int has_grip15;
struct winwing_led leds[];
};
@@ -47,10 +52,10 @@ static int winwing_led_write(struct led_classdev *cdev,
{
struct winwing_led *led = (struct winwing_led *) cdev;
struct winwing_drv_data *data = hid_get_drvdata(led->hdev);
- __u8 *buf = data->report_buf;
+ __u8 *buf = data->report_lights;
int ret;
- mutex_lock(&data->lock);
+ mutex_lock(&data->lights_lock);
buf[0] = 0x02;
buf[1] = 0x60;
@@ -69,7 +74,7 @@ static int winwing_led_write(struct led_classdev *cdev,
ret = hid_hw_output_report(led->hdev, buf, 14);
- mutex_unlock(&data->lock);
+ mutex_unlock(&data->lights_lock);
return ret;
}
@@ -87,9 +92,9 @@ static int winwing_init_led(struct hid_device *hdev,
if (!data)
return -EINVAL;
- data->report_buf = devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL);
+ data->report_lights = devm_kzalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL);
- if (!data->report_buf)
+ if (!data->report_lights)
return -ENOMEM;
for (i = 0; i < 3; i += 1) {
@@ -117,7 +122,7 @@ static int winwing_init_led(struct hid_device *hdev,
return ret;
}
-static int winwing_map_button(int button, int map_more_buttons)
+static int winwing_map_button(int button, int has_grip15)
{
if (button < 1)
return KEY_RESERVED;
@@ -141,7 +146,7 @@ static int winwing_map_button(int button, int map_more_buttons)
return (button - 65) + BTN_TRIGGER_HAPPY17;
}
- if (!map_more_buttons) {
+ if (!has_grip15) {
/*
* Not mapping numbers [33 .. 64] which
* are not assigned to any real buttons
@@ -194,13 +199,150 @@ static int winwing_input_mapping(struct hid_device *hdev,
/* Button numbers start with 1 */
button = usage->hid & HID_USAGE;
- code = winwing_map_button(button, data->map_more_buttons);
+ code = winwing_map_button(button, data->has_grip15);
hid_map_usage(hi, usage, bit, max, EV_KEY, code);
return 1;
}
+/*
+ * If x ≤ 0, return 0;
+ * if x is in [1 .. 65535], return a value in [1 .. 255]
+ */
+static inline int convert_magnitude(int x)
+{
+ if (x < 1)
+ return 0;
+
+ return ((x * 255) >> 16) + 1;
+}
+
+static int winwing_haptic_rumble(struct winwing_drv_data *data)
+{
+ __u8 *buf;
+ __u8 m;
+
+ if (!data)
+ return -EINVAL;
+
+ buf = data->report_rumble;
+
+ if (!buf)
+ return -EINVAL;
+
+ if (!data->hdev) {
+ hid_err(data->hdev, "data->hdev == NULL\n");
+ return -EINVAL;
+ }
+
+ if (!data->hdev->ll_driver) {
+ hid_err(data->hdev, "data->hdev->ll_driver == NULL\n");
+ return -EINVAL;
+ }
+
+ m = convert_magnitude(data->rumble.strong_magnitude);
+ if (m != data->rumble_left) {
+ int ret;
+
+ buf[0] = 0x02;
+ buf[1] = 0x01;
+ buf[2] = 0xbf;
+ buf[3] = 0x00;
+ buf[4] = 0x00;
+ buf[5] = 0x03;
+ buf[6] = 0x49;
+ buf[7] = 0x00;
+ buf[8] = m;
+ buf[9] = 0x00;
+ buf[10] = 0;
+ buf[11] = 0;
+ buf[12] = 0;
+ buf[13] = 0;
+
+ ret = hid_hw_output_report(data->hdev, buf, 14);
+ if (ret < 0) {
+ hid_err(data->hdev, "error %d (%*ph)\n", ret, 14, buf);
+ return ret;
+ }
+ data->rumble_left = m;
+ }
+
+ m = convert_magnitude(data->rumble.weak_magnitude);
+ if (m != data->rumble_right) {
+ int ret;
+
+ buf[0] = 0x02;
+ buf[1] = 0x03;
+ buf[2] = 0xbf;
+ buf[3] = 0x00;
+ buf[4] = 0x00;
+ buf[5] = 0x03;
+ buf[6] = 0x49;
+ buf[7] = 0x00;
+ buf[8] = m;
+ buf[9] = 0x00;
+ buf[10] = 0;
+ buf[11] = 0;
+ buf[12] = 0;
+ buf[13] = 0;
+
+ ret = hid_hw_output_report(data->hdev, buf, 14);
+ if (ret < 0) {
+ hid_err(data->hdev, "error %d (%*ph)\n", ret, 14, buf);
+ return ret;
+ }
+ data->rumble_right = m;
+ }
+
+ return 0;
+}
+
+
+static void winwing_haptic_rumble_cb(struct work_struct *work)
+{
+ struct winwing_drv_data *data;
+
+ data = container_of(work, struct winwing_drv_data, rumble_work);
+
+ if (data)
+ winwing_haptic_rumble(data);
+}
+
+static int winwing_play_effect(struct input_dev *dev, void *context,
+ struct ff_effect *effect)
+{
+ struct winwing_drv_data *data = (struct winwing_drv_data *) context;
+
+ if (effect->type != FF_RUMBLE)
+ return 0;
+
+ if (!data)
+ return -EINVAL;
+
+ data->rumble = effect->u.rumble;
+
+ return schedule_work(&data->rumble_work);
+}
+
+static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput)
+{
+ struct winwing_drv_data *data;
+
+ data = (struct winwing_drv_data *) hid_get_drvdata(hdev);
+ data->report_rumble = devm_kzalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL);
+ data->rumble_left = -1;
+ data->rumble_right = -1;
+
+ input_set_capability(hidinput->input, EV_FF, FF_RUMBLE);
+
+ if (!data)
+ return -EINVAL;
+
+ return input_ff_create_memless(hidinput->input, data,
+ winwing_play_effect);
+}
+
static int winwing_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
@@ -219,10 +361,12 @@ static int winwing_probe(struct hid_device *hdev,
if (!data)
return -ENOMEM;
- data->map_more_buttons = id->driver_data;
-
+ data->hdev = hdev;
+ data->has_grip15 = id->driver_data;
hid_set_drvdata(hdev, data);
+ INIT_WORK(&data->rumble_work, winwing_haptic_rumble_cb);
+
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
@@ -232,19 +376,39 @@ static int winwing_probe(struct hid_device *hdev,
return 0;
}
+static void winwing_remove(struct hid_device *hdev)
+{
+ struct winwing_drv_data *data;
+
+ data = (struct winwing_drv_data *) hid_get_drvdata(hdev);
+
+ if (data)
+ cancel_work_sync(&data->rumble_work);
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
static int winwing_input_configured(struct hid_device *hdev,
struct hid_input *hidinput)
{
+ struct winwing_drv_data *data;
int ret;
+ data = (struct winwing_drv_data *) hid_get_drvdata(hdev);
+
ret = winwing_init_led(hdev, hidinput->input);
if (ret)
hid_err(hdev, "led init failed\n");
+ if (data->has_grip15)
+ winwing_init_ff(hdev, hidinput);
+
return ret;
}
+/* Set driver_data to 1 for grips with rumble motor and more than 32 buttons */
static const struct hid_device_id winwing_devices[] = {
{ HID_USB_DEVICE(0x4098, 0xbd65), .driver_data = 1 }, /* TGRIP-15E */
{ HID_USB_DEVICE(0x4098, 0xbd64), .driver_data = 1 }, /* TGRIP-15EX */
@@ -261,6 +425,7 @@ static struct hid_driver winwing_driver = {
.input_configured = winwing_input_configured,
.input_mapping = winwing_input_mapping,
.probe = winwing_probe,
+ .remove = winwing_remove,
};
module_hid_driver(winwing_driver);
--
2.43.0
Powered by blists - more mailing lists