1c1fde337STomasz Pakuła // SPDX-License-Identifier: GPL-2.0-or-later
2c1fde337STomasz Pakuła /*
3c1fde337STomasz Pakuła * HID UNIVERSAL PIDFF
4c1fde337STomasz Pakuła * hid-pidff wrapper for PID-enabled devices
5c1fde337STomasz Pakuła * Handles device reports, quirks and extends usable button range
6c1fde337STomasz Pakuła *
7c1fde337STomasz Pakuła * Copyright (c) 2024, 2025 Makarenko Oleg
8c1fde337STomasz Pakuła * Copyright (c) 2024, 2025 Tomasz Pakuła
9c1fde337STomasz Pakuła */
10c1fde337STomasz Pakuła
11c1fde337STomasz Pakuła #include <linux/device.h>
12c1fde337STomasz Pakuła #include <linux/hid.h>
13c1fde337STomasz Pakuła #include <linux/module.h>
14c1fde337STomasz Pakuła #include <linux/input-event-codes.h>
15c1fde337STomasz Pakuła #include "hid-ids.h"
16c1fde337STomasz Pakuła
17c1fde337STomasz Pakuła #define JOY_RANGE (BTN_DEAD - BTN_JOYSTICK + 1)
18c1fde337STomasz Pakuła
19c1fde337STomasz Pakuła /*
20c1fde337STomasz Pakuła * Map buttons manually to extend the default joystick button limit
21c1fde337STomasz Pakuła */
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)22c1fde337STomasz Pakuła static int universal_pidff_input_mapping(struct hid_device *hdev,
23c1fde337STomasz Pakuła struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
24c1fde337STomasz Pakuła unsigned long **bit, int *max)
25c1fde337STomasz Pakuła {
26c1fde337STomasz Pakuła if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
27c1fde337STomasz Pakuła return 0;
28c1fde337STomasz Pakuła
29c1fde337STomasz Pakuła if (field->application != HID_GD_JOYSTICK)
30c1fde337STomasz Pakuła return 0;
31c1fde337STomasz Pakuła
32c1fde337STomasz Pakuła int button = ((usage->hid - 1) & HID_USAGE);
33c1fde337STomasz Pakuła int code = button + BTN_JOYSTICK;
34c1fde337STomasz Pakuła
35c1fde337STomasz Pakuła /* Detect the end of JOYSTICK buttons range */
36c1fde337STomasz Pakuła if (code > BTN_DEAD)
37c1fde337STomasz Pakuła code = button + KEY_NEXT_FAVORITE - JOY_RANGE;
38c1fde337STomasz Pakuła
39c1fde337STomasz Pakuła /*
40c1fde337STomasz Pakuła * Map overflowing buttons to KEY_RESERVED to not ignore
41c1fde337STomasz Pakuła * them and let them still trigger MSC_SCAN
42c1fde337STomasz Pakuła */
43c1fde337STomasz Pakuła if (code > KEY_MAX)
44c1fde337STomasz Pakuła code = KEY_RESERVED;
45c1fde337STomasz Pakuła
46c1fde337STomasz Pakuła hid_map_usage(hi, usage, bit, max, EV_KEY, code);
47c1fde337STomasz Pakuła hid_dbg(hdev, "Button %d: usage %d", button, code);
48c1fde337STomasz Pakuła return 1;
49c1fde337STomasz Pakuła }
50c1fde337STomasz Pakuła
51c1fde337STomasz Pakuła /*
52c1fde337STomasz Pakuła * Check if the device is PID and initialize it
53c1fde337STomasz Pakuła * Add quirks after initialisation
54c1fde337STomasz Pakuła */
universal_pidff_probe(struct hid_device * hdev,const struct hid_device_id * id)55c1fde337STomasz Pakuła static int universal_pidff_probe(struct hid_device *hdev,
56c1fde337STomasz Pakuła const struct hid_device_id *id)
57c1fde337STomasz Pakuła {
58c1fde337STomasz Pakuła int i, error;
59c1fde337STomasz Pakuła error = hid_parse(hdev);
60c1fde337STomasz Pakuła if (error) {
61c1fde337STomasz Pakuła hid_err(hdev, "HID parse failed\n");
62c1fde337STomasz Pakuła goto err;
63c1fde337STomasz Pakuła }
64c1fde337STomasz Pakuła
65c1fde337STomasz Pakuła error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
66c1fde337STomasz Pakuła if (error) {
67c1fde337STomasz Pakuła hid_err(hdev, "HID hw start failed\n");
68c1fde337STomasz Pakuła goto err;
69c1fde337STomasz Pakuła }
70c1fde337STomasz Pakuła
71c1fde337STomasz Pakuła /* Check if device contains PID usage page */
72c1fde337STomasz Pakuła error = 1;
73c1fde337STomasz Pakuła for (i = 0; i < hdev->collection_size; i++)
74c1fde337STomasz Pakuła if ((hdev->collection[i].usage & HID_USAGE_PAGE) == HID_UP_PID) {
75c1fde337STomasz Pakuła error = 0;
76c1fde337STomasz Pakuła hid_dbg(hdev, "PID usage page found\n");
77c1fde337STomasz Pakuła break;
78c1fde337STomasz Pakuła }
79c1fde337STomasz Pakuła
80c1fde337STomasz Pakuła /*
81c1fde337STomasz Pakuła * Do not fail as this might be the second "device"
82c1fde337STomasz Pakuła * just for additional buttons/axes. Exit cleanly if force
83c1fde337STomasz Pakuła * feedback usage page wasn't found (included devices were
84c1fde337STomasz Pakuła * tested and confirmed to be USB PID after all).
85c1fde337STomasz Pakuła */
86c1fde337STomasz Pakuła if (error) {
87c1fde337STomasz Pakuła hid_dbg(hdev, "PID usage page not found in the descriptor\n");
88c1fde337STomasz Pakuła return 0;
89c1fde337STomasz Pakuła }
90c1fde337STomasz Pakuła
91c1fde337STomasz Pakuła /* Check if HID_PID support is enabled */
92c1fde337STomasz Pakuła int (*init_function)(struct hid_device *, __u32);
93c1fde337STomasz Pakuła init_function = hid_pidff_init_with_quirks;
94c1fde337STomasz Pakuła
95c1fde337STomasz Pakuła if (!init_function) {
96c1fde337STomasz Pakuła hid_warn(hdev, "HID_PID support not enabled!\n");
97c1fde337STomasz Pakuła return 0;
98c1fde337STomasz Pakuła }
99c1fde337STomasz Pakuła
100c1fde337STomasz Pakuła error = init_function(hdev, id->driver_data);
101c1fde337STomasz Pakuła if (error) {
102c1fde337STomasz Pakuła hid_warn(hdev, "Error initialising force feedback\n");
103c1fde337STomasz Pakuła goto err;
104c1fde337STomasz Pakuła }
105c1fde337STomasz Pakuła
106c1fde337STomasz Pakuła hid_info(hdev, "Universal pidff driver loaded sucesfully!");
107c1fde337STomasz Pakuła
108c1fde337STomasz Pakuła return 0;
109c1fde337STomasz Pakuła err:
110c1fde337STomasz Pakuła return error;
111c1fde337STomasz Pakuła }
112c1fde337STomasz Pakuła
universal_pidff_input_configured(struct hid_device * hdev,struct hid_input * hidinput)113c1fde337STomasz Pakuła static int universal_pidff_input_configured(struct hid_device *hdev,
114c1fde337STomasz Pakuła struct hid_input *hidinput)
115c1fde337STomasz Pakuła {
116c1fde337STomasz Pakuła int axis;
117c1fde337STomasz Pakuła struct input_dev *input = hidinput->input;
118c1fde337STomasz Pakuła
119c1fde337STomasz Pakuła if (!input->absinfo)
120c1fde337STomasz Pakuła return 0;
121c1fde337STomasz Pakuła
122c1fde337STomasz Pakuła /* Decrease fuzz and deadzone on available axes */
123c1fde337STomasz Pakuła for (axis = ABS_X; axis <= ABS_BRAKE; axis++) {
124c1fde337STomasz Pakuła if (!test_bit(axis, input->absbit))
125c1fde337STomasz Pakuła continue;
126c1fde337STomasz Pakuła
127c1fde337STomasz Pakuła input_set_abs_params(input, axis,
128c1fde337STomasz Pakuła input->absinfo[axis].minimum,
129c1fde337STomasz Pakuła input->absinfo[axis].maximum,
130c1fde337STomasz Pakuła axis == ABS_X ? 0 : 8, 0);
131c1fde337STomasz Pakuła }
132c1fde337STomasz Pakuła
133c1fde337STomasz Pakuła /* Remove fuzz and deadzone from the second joystick axis */
134c1fde337STomasz Pakuła if (hdev->vendor == USB_VENDOR_ID_FFBEAST &&
135c1fde337STomasz Pakuła hdev->product == USB_DEVICE_ID_FFBEAST_JOYSTICK)
136c1fde337STomasz Pakuła input_set_abs_params(input, ABS_Y,
137c1fde337STomasz Pakuła input->absinfo[ABS_Y].minimum,
138c1fde337STomasz Pakuła input->absinfo[ABS_Y].maximum, 0, 0);
139c1fde337STomasz Pakuła
140c1fde337STomasz Pakuła return 0;
141c1fde337STomasz Pakuła }
142c1fde337STomasz Pakuła
143c1fde337STomasz Pakuła static const struct hid_device_id universal_pidff_devices[] = {
144c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3),
145c1fde337STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
146c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3_2),
147c1fde337STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
148c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5),
149c1fde337STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
150c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5_2),
151c1fde337STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
152c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9),
153c1fde337STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
154c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9_2),
155c1fde337STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
156c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12),
157c1fde337STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
158c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12_2),
159c1fde337STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
160c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21),
161c1fde337STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
162c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21_2),
163c1fde337STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
164c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C5) },
165c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C12) },
166c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_VRS, USB_DEVICE_ID_VRS_DFP),
167c1fde337STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_PERMISSIVE_CONTROL },
168c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_JOYSTICK), },
169c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_RUDDER), },
170c1fde337STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_WHEEL) },
171*4d5bcca2STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V10),
172*4d5bcca2STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
173*4d5bcca2STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12),
174*4d5bcca2STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
175*4d5bcca2STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE),
176*4d5bcca2STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
177*4d5bcca2STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE_2),
178*4d5bcca2STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
179*4d5bcca2STomasz Pakuła { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_LITE_STAR_GT987_FF),
180*4d5bcca2STomasz Pakuła .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
181c1fde337STomasz Pakuła { }
182c1fde337STomasz Pakuła };
183c1fde337STomasz Pakuła MODULE_DEVICE_TABLE(hid, universal_pidff_devices);
184c1fde337STomasz Pakuła
185c1fde337STomasz Pakuła static struct hid_driver universal_pidff = {
186c1fde337STomasz Pakuła .name = "hid-universal-pidff",
187c1fde337STomasz Pakuła .id_table = universal_pidff_devices,
188c1fde337STomasz Pakuła .input_mapping = universal_pidff_input_mapping,
189c1fde337STomasz Pakuła .probe = universal_pidff_probe,
190c1fde337STomasz Pakuła .input_configured = universal_pidff_input_configured
191c1fde337STomasz Pakuła };
192c1fde337STomasz Pakuła module_hid_driver(universal_pidff);
193c1fde337STomasz Pakuła
194c1fde337STomasz Pakuła MODULE_DESCRIPTION("Universal driver for USB PID Force Feedback devices");
195c1fde337STomasz Pakuła MODULE_LICENSE("GPL");
196c1fde337STomasz Pakuła MODULE_AUTHOR("Makarenko Oleg <oleg@makarenk.ooo>");
197c1fde337STomasz Pakuła MODULE_AUTHOR("Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>");
198