From nobody Sat Feb 7 17:20:53 2026 Received: from hognose1.porkbun.com (hognose1.porkbun.com [35.82.102.206]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 46D111B4138; Fri, 30 Jan 2026 06:29:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=35.82.102.206 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769754599; cv=none; b=B9yMjqBcEjdEkW5k3s3RqXX0sgEIWdHUiSayPCIoUkyOIPSJH/YzC0blQlmdRjlLfzeReEVbSeOHxO9vaW8WK2hyKw3CcaqBTwlrTv5LILDiRKOrQcAeP0E8Tb1Sdrx4tUYDrgc+qV6+geDvKGNEs2tTrek/EO5GQVdqB33JJ84= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769754599; c=relaxed/simple; bh=iplD68nO76W37y0UGxB7IbOVXQGfVOCgcft+XAfh4KY=; h=Date:From:To:Cc:Subject:Message-ID:MIME-Version:Content-Type: Content-Disposition; b=ipf60qAY8rZCvaFi6IG/vlYVWMfe5Kpy3aTLFI3DfDRJoffP/8MzRMHVw+cswlO7f0D7rV8gzgvjpRonqN6WQ12DddJPGmKIqdOEvHGfmX3Loucyejqym/EZfkyo5Cz3x56cr51ApJpSK35UiEDD2at3qh9PbGfgZDtgvhgAFtE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=altimeter.info; spf=pass smtp.mailfrom=altimeter.info; dkim=pass (1024-bit key) header.d=altimeter.info header.i=@altimeter.info header.b=rJxQK3fN; arc=none smtp.client-ip=35.82.102.206 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=altimeter.info Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=altimeter.info Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=altimeter.info header.i=@altimeter.info header.b="rJxQK3fN" Received: from altimeter-info (unknown [45.55.225.183]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) (Authenticated sender: linux-kernel@altimeter.info) by hognose1.porkbun.com (Postfix) with ESMTPSA id E74E64630C2; Fri, 30 Jan 2026 06:20:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=altimeter.info; s=default; t=1769754025; bh=AanEfuE4yilKoq6qwsprEzZJdiZE9Yi0U4ySJUWUiHU=; h=Date:From:To:Cc:Subject; b=rJxQK3fNoMJwsJe1AEhQjp2dYUHHhZchmmU4foNXi04jo5sAdaD8BCdwrn1IsTpH0 DUhMG2MAbkSMl3mqIlrcfiWcrU7StQ0KjyCmWpb2HeAf3TQB1iXcC0gxF1YVQJnxrM BSv5KujK6kr7/HAoWogkj7ByMnqhdDWtN8kgi5vo= Date: Fri, 30 Jan 2026 06:20:23 +0000 From: Ivan Gorinov To: Jiri Kosina Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH] HID: winwing: Enable rumble effects Message-ID: <20260130062023.GA24089@altimeter-info> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Disposition: inline Content-Transfer-Encoding: quoted-printable User-Agent: Mutt/1.5.24 (2015-08-30) 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 --- 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 #include #include +#include =20 #define MAX_REPORT 16 =20 @@ -35,10 +36,14 @@ static const struct winwing_led_info led_info[3] =3D { =20 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[]; }; =20 @@ -47,10 +52,10 @@ static int winwing_led_write(struct led_classdev *cdev, { struct winwing_led *led =3D (struct winwing_led *) cdev; struct winwing_drv_data *data =3D hid_get_drvdata(led->hdev); - __u8 *buf =3D data->report_buf; + __u8 *buf =3D data->report_lights; int ret; =20 - mutex_lock(&data->lock); + mutex_lock(&data->lights_lock); =20 buf[0] =3D 0x02; buf[1] =3D 0x60; @@ -69,7 +74,7 @@ static int winwing_led_write(struct led_classdev *cdev, =20 ret =3D hid_hw_output_report(led->hdev, buf, 14); =20 - mutex_unlock(&data->lock); + mutex_unlock(&data->lights_lock); =20 return ret; } @@ -87,9 +92,9 @@ static int winwing_init_led(struct hid_device *hdev, if (!data) return -EINVAL; =20 - data->report_buf =3D devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL); + data->report_lights =3D devm_kzalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL); =20 - if (!data->report_buf) + if (!data->report_lights) return -ENOMEM; =20 for (i =3D 0; i < 3; i +=3D 1) { @@ -117,7 +122,7 @@ static int winwing_init_led(struct hid_device *hdev, return ret; } =20 -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; } =20 - 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 =3D usage->hid & HID_USAGE; =20 - code =3D winwing_map_button(button, data->map_more_buttons); + code =3D winwing_map_button(button, data->has_grip15); =20 hid_map_usage(hi, usage, bit, max, EV_KEY, code); =20 return 1; } =20 +/* + * If x =E2=89=A4 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 =3D data->report_rumble; + + if (!buf) + return -EINVAL; + + if (!data->hdev) { + hid_err(data->hdev, "data->hdev =3D=3D NULL\n"); + return -EINVAL; + } + + if (!data->hdev->ll_driver) { + hid_err(data->hdev, "data->hdev->ll_driver =3D=3D NULL\n"); + return -EINVAL; + } + + m =3D convert_magnitude(data->rumble.strong_magnitude); + if (m !=3D data->rumble_left) { + int ret; + + buf[0] =3D 0x02; + buf[1] =3D 0x01; + buf[2] =3D 0xbf; + buf[3] =3D 0x00; + buf[4] =3D 0x00; + buf[5] =3D 0x03; + buf[6] =3D 0x49; + buf[7] =3D 0x00; + buf[8] =3D m; + buf[9] =3D 0x00; + buf[10] =3D 0; + buf[11] =3D 0; + buf[12] =3D 0; + buf[13] =3D 0; + + ret =3D 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 =3D m; + } + + m =3D convert_magnitude(data->rumble.weak_magnitude); + if (m !=3D data->rumble_right) { + int ret; + + buf[0] =3D 0x02; + buf[1] =3D 0x03; + buf[2] =3D 0xbf; + buf[3] =3D 0x00; + buf[4] =3D 0x00; + buf[5] =3D 0x03; + buf[6] =3D 0x49; + buf[7] =3D 0x00; + buf[8] =3D m; + buf[9] =3D 0x00; + buf[10] =3D 0; + buf[11] =3D 0; + buf[12] =3D 0; + buf[13] =3D 0; + + ret =3D 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 =3D m; + } + + return 0; +} + + +static void winwing_haptic_rumble_cb(struct work_struct *work) +{ + struct winwing_drv_data *data; + + data =3D 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 =3D (struct winwing_drv_data *) context; + + if (effect->type !=3D FF_RUMBLE) + return 0; + + if (!data) + return -EINVAL; + + data->rumble =3D effect->u.rumble; + + return schedule_work(&data->rumble_work); +} + +static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidi= nput) +{ + struct winwing_drv_data *data; + + data =3D (struct winwing_drv_data *) hid_get_drvdata(hdev); + data->report_rumble =3D devm_kzalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL); + data->rumble_left =3D -1; + data->rumble_right =3D -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; =20 - data->map_more_buttons =3D id->driver_data; - + data->hdev =3D hdev; + data->has_grip15 =3D id->driver_data; hid_set_drvdata(hdev, data); =20 + INIT_WORK(&data->rumble_work, winwing_haptic_rumble_cb); + ret =3D 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; } =20 +static void winwing_remove(struct hid_device *hdev) +{ + struct winwing_drv_data *data; + + data =3D (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; =20 + data =3D (struct winwing_drv_data *) hid_get_drvdata(hdev); + ret =3D winwing_init_led(hdev, hidinput->input); =20 if (ret) hid_err(hdev, "led init failed\n"); =20 + if (data->has_grip15) + winwing_init_ff(hdev, hidinput); + return ret; } =20 +/* Set driver_data to 1 for grips with rumble motor and more than 32 butto= ns */ static const struct hid_device_id winwing_devices[] =3D { { HID_USB_DEVICE(0x4098, 0xbd65), .driver_data =3D 1 }, /* TGRIP-15E */ { HID_USB_DEVICE(0x4098, 0xbd64), .driver_data =3D 1 }, /* TGRIP-15EX */ @@ -261,6 +425,7 @@ static struct hid_driver winwing_driver =3D { .input_configured =3D winwing_input_configured, .input_mapping =3D winwing_input_mapping, .probe =3D winwing_probe, + .remove =3D winwing_remove, }; module_hid_driver(winwing_driver); =20 --=20 2.43.0