// SPDX-License-Identifier: GPL-2.0-or-later /* * HID PIDFF wrapper * First of all targeting steering wheels and wheelbases * * Copyright (c) 2024 Makarenko Oleg */ #include #include #include #include #include "hid-ids.h" #include "hid-pidff.h" #define BTN_RANGE (BTN_9 - BTN_0 + 1) #define JOY_RANGE (BTN_DEAD - BTN_JOYSTICK + 1) static const struct hid_device_id pidff_wheel_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3), .driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5), .driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9), .driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12), .driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21), .driver_data = PIDFF_QUIRK_FIX_WHEEL_DIRECTION }, { HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C5), .driver_data = PIDFF_QUIRK_NO_DELAY_EFFECT }, { HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C12), .driver_data = PIDFF_QUIRK_NO_DELAY_EFFECT }, { } }; MODULE_DEVICE_TABLE(hid, pidff_wheel_devices); static u8 *moza_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { // Fix data type on PID Device Control if (rdesc[1002] == 0x91 && rdesc[1003] == 0x02) { rdesc[1003] = 0x00; // Fix header, it needs to be Array. } return rdesc; } static u8 *universal_pidff_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { if (hdev->vendor == USB_VENDOR_ID_MOZA) { return moza_report_fixup(hdev, rdesc, rsize); } return rdesc; } 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) { // Let the default behavior handle mapping if usage is not a button if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON) return 0; int code = ((usage->hid - 1) & HID_USAGE); int new_code = code + BTN_0; if (new_code > BTN_9) new_code = code + BTN_JOYSTICK - BTN_RANGE; if (new_code > BTN_DEAD) new_code = code + KEY_MACRO1 - BTN_RANGE - JOY_RANGE; // Map overflowing buttons to KEY_RESERVED for the upcomng new input usages if (new_code > KEY_MAX) new_code = KEY_RESERVED; hid_map_usage(hi, usage, bit, max, EV_KEY, new_code); hid_dbg(hdev, "Button %d: usage %d", code, new_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 ret; ret = hid_parse(hdev); if (ret) { hid_err(hdev, "parse failed\n"); goto err; } ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); if (ret) { hid_err(hdev, "hw start failed\n"); goto err; } ret = hid_pidff_init_quirks(hdev, id); if (ret) { hid_warn(hdev, "Force feedback is not supported\n"); goto err; } return 0; err: return ret; } static int universal_pidff_input_configured(struct hid_device *hdev, struct hid_input *hidinput) { // Remove fuzz and deadzone struct input_dev *input = hidinput->input; input_set_abs_params(input, ABS_X, input->absinfo[ABS_X].minimum, input->absinfo[ABS_X].maximum, 0, 0); return 0; } static struct hid_driver universal_pidff = { .name = "hid-universal-pidff", .id_table = pidff_wheel_devices, .input_mapping = universal_pidff_input_mapping, .probe = universal_pidff_probe, .input_configured = universal_pidff_input_configured, .report_fixup = universal_pidff_report_fixup }; module_hid_driver(universal_pidff); MODULE_AUTHOR("Oleg Makarenko "); MODULE_DESCRIPTION("Universal HID PIDFF Driver"); MODULE_LICENSE("GPL");