Commit 765b8aa0 authored by Jiri Kosina's avatar Jiri Kosina
Browse files

Merge branch 'for-6.15/pidff' into for-linus

From: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>

This patch series is focused on improving the compatibility and usability of the
hid-pidff force feedback driver. Last patch introduces a new, universal driver
for PID devices that need some special handling like report fixups, remapping the
button range, managing new pidff quirks and setting desirable fuzz/flat values.

This work has been done in the span of the past months with the help of the great
Linux simracing community, with a little input from sim flight fans from FFBeast.

No changes interfere with compliant and currently working PID devices.
"Generic" codepath was tested as well with Moza and Simxperience AccuForce v2.

I'm not married to the name. It's what we used previously, but if "universal" is
confusing (pidff is already the generic driver), we can come up with something
better like "hid-quirky-pidff" :)

With v8 and  tiny finx in v9, all the outstanding issues were resolved,
additional pidff issues were fixed and hid-pidff defines moved to a dedicated
header file. This patch series could be considered done bar any comments and
requests from input maintainers.

I could save more then a dozen lines of code by changing simple if statements
to only occupy on line instead of two in there's a need for that.
parents 867bc163 e2fa0bdf
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -10316,6 +10316,14 @@ F: drivers/hid/hid-sensor-*
F:	drivers/iio/*/hid-*
F:	include/linux/hid-sensor-*
HID UNIVERSAL PIDFF DRIVER
M:	Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>
M:	Oleg Makarenko <oleg@makarenk.ooo>
L:	linux-input@vger.kernel.org
S:	Maintained
B:	https://github.com/JacKeTUs/universal-pidff/issues
F:	drivers/hid/hid-universal-pidff.c
HID VRC-2 CAR CONTROLLER DRIVER
M:	Marcus Folkesson <marcus.folkesson@gmail.com>
L:	linux-input@vger.kernel.org
+14 −0
Original line number Diff line number Diff line
@@ -1246,6 +1246,20 @@ config HID_U2FZERO
	  allow setting the brightness to anything but 1, which will
	  trigger a single blink and immediately reset back to 0.

config HID_UNIVERSAL_PIDFF
	tristate "universal-pidff: extended USB PID driver compatibility and usage"
	depends on USB_HID
	depends on HID_PID
	help
	  Extended PID support for selected devices.

	  Contains report fixups, extended usable button range and
	  pidff quirk management to extend compatibility with slightly
	  non-compliant USB PID devices and better fuzz/flat values for
	  high precision direct drive devices.

	  Supports Moza Racing, Cammus, VRS, FFBeast and more.

config HID_WACOM
	tristate "Wacom Intuos/Graphire tablet support (USB)"
	depends on USB_HID
+1 −0
Original line number Diff line number Diff line
@@ -142,6 +142,7 @@ hid-uclogic-objs := hid-uclogic-core.o \
				   hid-uclogic-params.o
obj-$(CONFIG_HID_UCLOGIC)	+= hid-uclogic.o
obj-$(CONFIG_HID_UDRAW_PS3)	+= hid-udraw-ps3.o
obj-$(CONFIG_HID_UNIVERSAL_PIDFF)	+= hid-universal-pidff.o
obj-$(CONFIG_HID_LED)		+= hid-led.o
obj-$(CONFIG_HID_XIAOMI)	+= hid-xiaomi.o
obj-$(CONFIG_HID_XINMO)		+= hid-xinmo.o
+37 −0
Original line number Diff line number Diff line
@@ -190,6 +190,12 @@
#define USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT 0x8102
#define USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY 0x8302

#define USB_VENDOR_ID_ASETEK			0x2433
#define USB_DEVICE_ID_ASETEK_INVICTA		0xf300
#define USB_DEVICE_ID_ASETEK_FORTE		0xf301
#define USB_DEVICE_ID_ASETEK_LA_PRIMA		0xf303
#define USB_DEVICE_ID_ASETEK_TONY_KANAAN	0xf306

#define USB_VENDOR_ID_ASUS		0x0486
#define USB_DEVICE_ID_ASUS_T91MT	0x0185
#define USB_DEVICE_ID_ASUSTEK_MULTITOUCH_YFO	0x0186
@@ -262,6 +268,10 @@
#define USB_DEVICE_ID_BTC_EMPREX_REMOTE	0x5578
#define USB_DEVICE_ID_BTC_EMPREX_REMOTE_2	0x5577

#define USB_VENDOR_ID_CAMMUS		0x3416
#define USB_DEVICE_ID_CAMMUS_C5		0x0301
#define USB_DEVICE_ID_CAMMUS_C12	0x0302

#define USB_VENDOR_ID_CANDO		0x2087
#define USB_DEVICE_ID_CANDO_PIXCIR_MULTI_TOUCH 0x0703
#define USB_DEVICE_ID_CANDO_MULTI_TOUCH	0x0a01
@@ -453,6 +463,11 @@
#define USB_VENDOR_ID_EVISION           0x320f
#define USB_DEVICE_ID_EVISION_ICL01     0x5041

#define USB_VENDOR_ID_FFBEAST		0x045b
#define USB_DEVICE_ID_FFBEAST_JOYSTICK	0x58f9
#define USB_DEVICE_ID_FFBEAST_RUDDER	0x5968
#define USB_DEVICE_ID_FFBEAST_WHEEL	0x59d7

#define USB_VENDOR_ID_FLATFROG		0x25b5
#define USB_DEVICE_ID_MULTITOUCH_3200	0x0002

@@ -817,6 +832,13 @@
#define I2C_DEVICE_ID_LG_8001		0x8001
#define I2C_DEVICE_ID_LG_7010		0x7010

#define USB_VENDOR_ID_LITE_STAR		0x11ff
#define USB_DEVICE_ID_PXN_V10		0x3245
#define USB_DEVICE_ID_PXN_V12		0x1212
#define USB_DEVICE_ID_PXN_V12_LITE	0x1112
#define USB_DEVICE_ID_PXN_V12_LITE_2	0x1211
#define USB_DEVICE_LITE_STAR_GT987_FF	0x2141

#define USB_VENDOR_ID_LOGITECH		0x046d
#define USB_DEVICE_ID_LOGITECH_Z_10_SPK	0x0a07
#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e
@@ -964,6 +986,18 @@
#define USB_VENDOR_ID_MONTEREY		0x0566
#define USB_DEVICE_ID_GENIUS_KB29E	0x3004

#define USB_VENDOR_ID_MOZA		0x346e
#define USB_DEVICE_ID_MOZA_R3		0x0005
#define USB_DEVICE_ID_MOZA_R3_2		0x0015
#define USB_DEVICE_ID_MOZA_R5		0x0004
#define USB_DEVICE_ID_MOZA_R5_2		0x0014
#define USB_DEVICE_ID_MOZA_R9		0x0002
#define USB_DEVICE_ID_MOZA_R9_2		0x0012
#define USB_DEVICE_ID_MOZA_R12		0x0006
#define USB_DEVICE_ID_MOZA_R12_2	0x0016
#define USB_DEVICE_ID_MOZA_R16_R21	0x0000
#define USB_DEVICE_ID_MOZA_R16_R21_2	0x0010

#define USB_VENDOR_ID_MSI		0x1770
#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00

@@ -1377,6 +1411,9 @@
#define USB_DEVICE_ID_VELLEMAN_K8061_FIRST	0x8061
#define USB_DEVICE_ID_VELLEMAN_K8061_LAST	0x8068

#define USB_VENDOR_ID_VRS	0x0483
#define USB_DEVICE_ID_VRS_DFP	0xa355

#define USB_VENDOR_ID_VTL		0x0306
#define USB_DEVICE_ID_VTL_MULTITOUCH_FF3F	0xff3f

+202 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * HID UNIVERSAL PIDFF
 * hid-pidff wrapper for PID-enabled devices
 * Handles device reports, quirks and extends usable button range
 *
 * Copyright (c) 2024, 2025 Oleg Makarenko
 * Copyright (c) 2024, 2025 Tomasz Pakuła
 */

#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/input-event-codes.h>
#include "hid-ids.h"
#include "usbhid/hid-pidff.h"

#define JOY_RANGE (BTN_DEAD - BTN_JOYSTICK + 1)

/*
 * Map buttons manually to extend the default joystick button limit
 */
static int universal_pidff_input_mapping(struct hid_device *hdev,
	struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
	unsigned long **bit, int *max)
{
	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
		return 0;

	if (field->application != HID_GD_JOYSTICK)
		return 0;

	int button = ((usage->hid - 1) & HID_USAGE);
	int code = button + BTN_JOYSTICK;

	/* Detect the end of JOYSTICK buttons range */
	if (code > BTN_DEAD)
		code = button + KEY_NEXT_FAVORITE - JOY_RANGE;

	/*
	 * Map overflowing buttons to KEY_RESERVED to not ignore
	 * them and let them still trigger MSC_SCAN
	 */
	if (code > KEY_MAX)
		code = KEY_RESERVED;

	hid_map_usage(hi, usage, bit, max, EV_KEY, code);
	hid_dbg(hdev, "Button %d: usage %d", button, code);
	return 1;
}

/*
 * Check if the device is PID and initialize it
 * Add quirks after initialisation
 */
static int universal_pidff_probe(struct hid_device *hdev,
				 const struct hid_device_id *id)
{
	int i, error;
	error = hid_parse(hdev);
	if (error) {
		hid_err(hdev, "HID parse failed\n");
		goto err;
	}

	error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
	if (error) {
		hid_err(hdev, "HID hw start failed\n");
		goto err;
	}

	/* Check if device contains PID usage page */
	error = 1;
	for (i = 0; i < hdev->collection_size; i++)
		if ((hdev->collection[i].usage & HID_USAGE_PAGE) == HID_UP_PID) {
			error = 0;
			hid_dbg(hdev, "PID usage page found\n");
			break;
		}

	/*
	 * Do not fail as this might be the second "device"
	 * just for additional buttons/axes. Exit cleanly if force
	 * feedback usage page wasn't found (included devices were
	 * tested and confirmed to be USB PID after all).
	 */
	if (error) {
		hid_dbg(hdev, "PID usage page not found in the descriptor\n");
		return 0;
	}

	/* Check if HID_PID support is enabled */
	int (*init_function)(struct hid_device *, u32);
	init_function = hid_pidff_init_with_quirks;

	if (!init_function) {
		hid_warn(hdev, "HID_PID support not enabled!\n");
		return 0;
	}

	error = init_function(hdev, id->driver_data);
	if (error) {
		hid_warn(hdev, "Error initialising force feedback\n");
		goto err;
	}

	hid_info(hdev, "Universal pidff driver loaded successfully!");

	return 0;
err:
	return error;
}

static int universal_pidff_input_configured(struct hid_device *hdev,
					    struct hid_input *hidinput)
{
	int axis;
	struct input_dev *input = hidinput->input;

	if (!input->absinfo)
		return 0;

	/* Decrease fuzz and deadzone on available axes */
	for (axis = ABS_X; axis <= ABS_BRAKE; axis++) {
		if (!test_bit(axis, input->absbit))
			continue;

		input_set_abs_params(input, axis,
			input->absinfo[axis].minimum,
			input->absinfo[axis].maximum,
			axis == ABS_X ? 0 : 8, 0);
	}

	/* Remove fuzz and deadzone from the second joystick axis */
	if (hdev->vendor == USB_VENDOR_ID_FFBEAST &&
	    hdev->product == USB_DEVICE_ID_FFBEAST_JOYSTICK)
		input_set_abs_params(input, ABS_Y,
			input->absinfo[ABS_Y].minimum,
			input->absinfo[ABS_Y].maximum, 0, 0);

	return 0;
}

static const struct hid_device_id universal_pidff_devices[] = {
	{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3),
		.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
	{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3_2),
		.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
	{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5),
		.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
	{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5_2),
		.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
	{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9),
		.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
	{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9_2),
		.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
	{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12),
		.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
	{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12_2),
		.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
	{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21),
		.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
	{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21_2),
		.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
	{ HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C5) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C12) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_VRS, USB_DEVICE_ID_VRS_DFP),
		.driver_data = HID_PIDFF_QUIRK_PERMISSIVE_CONTROL },
	{ HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_JOYSTICK), },
	{ HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_RUDDER), },
	{ HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_WHEEL) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V10),
		.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
	{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12),
		.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
	{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE),
		.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
	{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE_2),
		.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
	{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_LITE_STAR_GT987_FF),
		.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
	{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_INVICTA) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_FORTE) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_LA_PRIMA) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_TONY_KANAAN) },
	{ }
};
MODULE_DEVICE_TABLE(hid, universal_pidff_devices);

static struct hid_driver universal_pidff = {
	.name = "hid-universal-pidff",
	.id_table = universal_pidff_devices,
	.input_mapping = universal_pidff_input_mapping,
	.probe = universal_pidff_probe,
	.input_configured = universal_pidff_input_configured
};
module_hid_driver(universal_pidff);

MODULE_DESCRIPTION("Universal driver for USB PID Force Feedback devices");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Oleg Makarenko <oleg@makarenk.ooo>");
MODULE_AUTHOR("Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>");
Loading