| From 059325f54306b96ac82dea42a614d32095c799ce Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Sat, 1 Feb 2025 12:38:54 +0100 |
| Subject: HID: Add hid-universal-pidff driver and supported device ids |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| From: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com> |
| |
| [ Upstream commit f06bf8d94fffbb544b1cb5402c92e0a075f0d420 ] |
| |
| Extend pidff compatibility, usable button range, manage pidff quirks and |
| set improved fuzz/flat default for high precision devices. Possibility |
| of fixing device descriptors in the future if such needs arises. |
| |
| As many of PID devices are quite similar and not dependent on |
| custom drivers, this one can handle all of PID devices which |
| need special care. |
| |
| Numerous sim racing/sim flight bases report a lot of buttons |
| in excess of 100. Moza Racing exposes 128 of them and thus |
| the need to extend the available range. |
| |
| All the included devices were tested and confirmed working |
| with the help of the sim racing community. |
| |
| Changes in v6: |
| - Support "split" devices with a separate "input device" for buttons |
| - Fixed comment styling |
| |
| Co-developed-by: Makarenko Oleg <oleg@makarenk.ooo> |
| Signed-off-by: Makarenko Oleg <oleg@makarenk.ooo> |
| Signed-off-by: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com> |
| Reviewed-by: Michał Kopeć <michal@nozomi.space> |
| Reviewed-by: Paul Dino Jones <paul@spacefreak18.xyz> |
| Tested-by: Cristóferson Bueno <cbueno81@gmail.com> |
| Tested-by: Pablo Cisneros <patchkez@protonmail.com> |
| Signed-off-by: Jiri Kosina <jkosina@suse.com> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/hid/Kconfig | 14 +++ |
| drivers/hid/Makefile | 1 + |
| drivers/hid/hid-ids.h | 31 +++++ |
| drivers/hid/hid-universal-pidff.c | 192 ++++++++++++++++++++++++++++++ |
| 4 files changed, 238 insertions(+) |
| create mode 100644 drivers/hid/hid-universal-pidff.c |
| |
| diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig |
| index 4500d7653b05e..95a4ede270991 100644 |
| --- a/drivers/hid/Kconfig |
| +++ b/drivers/hid/Kconfig |
| @@ -1205,6 +1205,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 |
| diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile |
| index f2900ee2ef858..27ee02bf6f26d 100644 |
| --- a/drivers/hid/Makefile |
| +++ b/drivers/hid/Makefile |
| @@ -139,6 +139,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 |
| diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h |
| index c6ae7c4268b84..08be87b3cc990 100644 |
| --- a/drivers/hid/hid-ids.h |
| +++ b/drivers/hid/hid-ids.h |
| @@ -262,6 +262,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 +457,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 |
| |
| @@ -813,6 +822,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 |
| @@ -960,6 +976,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 |
| |
| @@ -1371,6 +1399,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 |
| |
| diff --git a/drivers/hid/hid-universal-pidff.c b/drivers/hid/hid-universal-pidff.c |
| new file mode 100644 |
| index 0000000000000..55aad2e4ac1b8 |
| --- /dev/null |
| +++ b/drivers/hid/hid-universal-pidff.c |
| @@ -0,0 +1,192 @@ |
| +// 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 Makarenko Oleg |
| + * 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" |
| + |
| +#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 sucesfully!"); |
| + |
| + 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) }, |
| + { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12) }, |
| + { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE) }, |
| + { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE_2) }, |
| + { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_LITE_STAR_GT987_FF) }, |
| + { } |
| +}; |
| +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("Makarenko Oleg <oleg@makarenk.ooo>"); |
| +MODULE_AUTHOR("Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>"); |
| -- |
| 2.39.5 |
| |