From 480ffd106c34440b08f3dd02d82641f2dae4fdd0 Mon Sep 17 00:00:00 2001 From: eispala Date: Thu, 14 May 2026 12:49:40 +0200 Subject: [PATCH] hid-tmff2: add combine_pedals and center_clutch support - Add combine_pedals module param to merge gas/brake into a single Z axis - Add center_clutch module param to fix clutch at center (512) when combine_pedals is active (Needed for some games like NFS: MW 2005) - Expose both as sysfs attributes for runtime control - Add t300rs_raw_event to rewrite pedal data on the fly - Add t300rs_rdesc_nrm_combined HID descriptor for combined pedal mode --- src/hid-tmff2.c | 127 ++++++++++++++++++++++++++++++++++++ src/hid-tmff2.h | 14 ++++ src/tmt300rs/hid-tmt300rs.c | 120 +++++++++++++++++++++++++++++++++- 3 files changed, 259 insertions(+), 2 deletions(-) diff --git a/src/hid-tmff2.c b/src/hid-tmff2.c index 162a30a..f9c2008 100644 --- a/src/hid-tmff2.c +++ b/src/hid-tmff2.c @@ -48,6 +48,19 @@ module_param(gain, int, 0); MODULE_PARM_DESC(gain, "Level of gain (0-65535)"); + +//Combine Pedals / Center Clutch patch +bool combine_pedals = false; +bool center_clutch = false; + +module_param(combine_pedals, bool, 0); +MODULE_PARM_DESC(combine_pedals, "Combine gas and brake into a single Z axis (default: false)"); + +module_param(center_clutch, bool, 0); +MODULE_PARM_DESC(center_clutch, "Center clutch axis when combine_pedals is active (default: false)"); + +//Combine Pedals / Center Clutch patch + static spinlock_t lock; static struct tmff2_device_entry *tmff2_from_hdev(struct hid_device *hdev) @@ -255,6 +268,78 @@ static ssize_t gain_show(struct device *dev, } static DEVICE_ATTR_RW(gain); +//Combine Pedals patch + +static ssize_t combine_pedals_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + unsigned int value; + int ret; + + ret = kstrtouint(buf, 0, &value); + if (ret) { + dev_err(dev, "kstrtouint failed at combine_pedals_store: %i", ret); + return ret; + } + + combine_pedals = (bool)value; + + if (tmff2) + tmff2->combine_pedals = (bool)value; + + return count; +} + +static ssize_t combine_pedals_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + + if (!tmff2) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%u\n", tmff2->combine_pedals); +} + +static DEVICE_ATTR_RW(combine_pedals); + +//Center Clutch Settings +static ssize_t center_clutch_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + unsigned int value; + int ret; + + ret = kstrtouint(buf, 0, &value); + if (ret) { + dev_err(dev, "kstrtouint failed at center_clutch_store: %i", ret); + return ret; + } + + center_clutch = (bool)value; + + if (tmff2) + tmff2->center_clutch = (bool)value; + + return count; +} + +static ssize_t center_clutch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(to_hid_device(dev)); + + if (!tmff2) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%u\n", tmff2->center_clutch); +} +static DEVICE_ATTR_RW(center_clutch); + +//Combine Pedals / Center Clutch patch + static void tmff2_set_gain(struct input_dev *dev, uint16_t value) { struct tmff2_device_entry *tmff2 = tmff2_from_input(dev); @@ -569,8 +654,28 @@ static int tmff2_create_files(struct tmff2_device_entry *tmff2) } } + //Combine Pedals / Center Clutch patch + if (tmff2->params & PARAM_COMBINE_PEDALS) { + if ((ret = device_create_file(dev, &dev_attr_combine_pedals))) { + hid_warn(tmff2->hdev, "unable to create sysfs for combine_pedals\n"); + goto combine_err; + } + } + + //Combine Pedals / Center Clutch patch + if (tmff2->params & PARAM_CENTER_CLUTCH) { + if ((ret = device_create_file(dev, &dev_attr_center_clutch))) { + hid_warn(tmff2->hdev, "unable to create sysfs for center_clutch\n"); + goto center_clutch_err; + } + } + return 0; + center_clutch_err: + device_remove_file(dev, &dev_attr_combine_pedals); //Combine Pedals / Center Clutch patch + combine_err: + device_remove_file(dev, &dev_attr_friction_level); //Combine Pedals / Center Clutch patch friction_err: device_remove_file(dev, &dev_attr_damper_level); damper_err: @@ -716,6 +821,10 @@ static int tmff2_probe(struct hid_device *hdev, const struct hid_device_id *id) goto wheel_err; } + //Combine Pedals / Center Clutch patch + tmff2->combine_pedals = combine_pedals; + tmff2->center_clutch = center_clutch; + if ((ret = hid_parse(tmff2->hdev))) { hid_err(hdev, "parse failed\n"); goto hid_err; @@ -764,6 +873,17 @@ static const __u8 *tmff2_report_fixup(struct hid_device *hdev, __u8 *rdesc, return rdesc; } +static int tmff2_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct tmff2_device_entry *tmff2 = tmff2_from_hdev(hdev); + + if (!tmff2 || !tmff2->raw_event) + return 0; + + return tmff2->raw_event(hdev, report, data, size); +} + static void tmff2_remove(struct hid_device *hdev) { struct tmff2_device_entry *tmff2 = tmff2_from_hdev(hdev); @@ -794,6 +914,12 @@ static void tmff2_remove(struct hid_device *hdev) if (tmff2->params & PARAM_GAIN) device_remove_file(dev, &dev_attr_gain); + if (tmff2->params & PARAM_COMBINE_PEDALS) + device_remove_file(dev, &dev_attr_combine_pedals); + + if (tmff2->params & PARAM_CENTER_CLUTCH) + device_remove_file(dev, &dev_attr_center_clutch); + hid_hw_stop(hdev); tmff2->wheel_destroy(tmff2->data); @@ -825,6 +951,7 @@ static struct hid_driver tmff2_driver = { .probe = tmff2_probe, .remove = tmff2_remove, .report_fixup = tmff2_report_fixup, + .raw_event = tmff2_raw_event, //Combine Pedals patch }; module_hid_driver(tmff2_driver); diff --git a/src/hid-tmff2.h b/src/hid-tmff2.h index 6ad1d82..058f40c 100644 --- a/src/hid-tmff2.h +++ b/src/hid-tmff2.h @@ -14,6 +14,10 @@ extern int range; extern int gain; extern int alt_mode; +//Combine Pedals / Center Clutch patch +extern bool combine_pedals; +extern bool center_clutch; + #define USB_VENDOR_ID_THRUSTMASTER 0x044f /* the wheel seems to only be capable of processing a certain number of @@ -37,6 +41,10 @@ extern int alt_mode; #define PARAM_ALT_MODE (1 << 4) #define PARAM_GAIN (1 << 5) +//Combine Pedals / Center Clutch patch +#define PARAM_COMBINE_PEDALS (1 << 6) //Combine Pedals +#define PARAM_CENTER_CLUTCH (1 << 7) //Clutch always 512 (Centered) + #undef fixp_sin16 #define fixp_sin16(v) (((v % 360) > 180) ?\ -(fixp_sin32((v % 360) - 180) >> 16)\ @@ -93,9 +101,15 @@ struct tmff2_device_entry { ssize_t (*alt_mode_store)(void *data, const char *buf, size_t count); int (*set_autocenter)(void *data, uint16_t autocenter); __u8 *(*wheel_fixup)(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize); + int (*raw_event)(struct hid_device *hdev, struct hid_report *report, u8 *data, int size); /* void pointers are dangerous, I know, but in this case likely the * best option... */ + + //Combine Pedals / Center Clutch patch + bool combine_pedals; + bool center_clutch; + }; /* external */ diff --git a/src/tmt300rs/hid-tmt300rs.c b/src/tmt300rs/hid-tmt300rs.c index 00eecee..c074cdd 100644 --- a/src/tmt300rs/hid-tmt300rs.c +++ b/src/tmt300rs/hid-tmt300rs.c @@ -2,6 +2,7 @@ #include #include #include "../hid-tmff2.h" +#include #define T300RS_MAX_EFFECTS 16 #define T300RS_NORM_BUFFER_LENGTH 63 @@ -17,6 +18,8 @@ static const unsigned long t300rs_params = | PARAM_GAIN | PARAM_RANGE | PARAM_ALT_MODE + | PARAM_COMBINE_PEDALS + | PARAM_CENTER_CLUTCH ; static const signed short t300rs_effects[] = { @@ -84,6 +87,73 @@ struct t300rs_data { void *device_props; }; +static u8 t300rs_rdesc_nrm_combined[] = { + 0x05, 0x01, + 0x09, 0x04, + 0xa1, 0x01, + 0x09, 0x01, + 0xa1, 0x00, + 0x85, 0x07, + 0x09, 0x30, /* X (wheel) */ + 0x15, 0x00, + 0x27, 0xff, 0xff, 0x00, 0x00, + 0x35, 0x00, + 0x47, 0xff, 0xff, 0x00, 0x00, + 0x75, 0x10, + 0x95, 0x01, + 0x81, 0x02, + 0x81, 0x03, /* brake slot → constant (hidden) */ + 0x09, 0x32, /* Z (combined gas/brake) */ + 0x26, 0xff, 0x03, + 0x46, 0xff, 0x03, + 0x75, 0x10, + 0x95, 0x01, + 0x81, 0x02, + 0x09, 0x31, /* Y (clutch) — unchanged */ + 0x81, 0x02, + 0x81, 0x03, + /* rest identical to t300rs_rdesc_nrm_fixed from buttons onward */ + 0x05, 0x09, + 0x19, 0x01, + 0x29, 0x0d, + 0x25, 0x01, + 0x45, 0x01, + 0x75, 0x01, + 0x95, 0x0d, + 0x81, 0x02, + 0x75, 0x0b, + 0x95, 0x01, + 0x81, 0x03, + 0x05, 0x01, + 0x09, 0x39, + 0x25, 0x07, + 0x46, 0x3b, 0x01, + 0x55, 0x00, + 0x65, 0x14, + 0x75, 0x04, + 0x81, 0x42, + 0x65, 0x00, + 0x81, 0x03, + 0x85, 0x60, + 0x06, 0x00, 0xff, + 0x09, 0x60, + 0x75, 0x08, + 0x95, 0x3f, + 0x26, 0xff, 0x7f, + 0x15, 0x00, + 0x46, 0xff, 0x7f, + 0x36, 0x00, 0x80, + 0x91, 0x02, + 0x85, 0x02, + 0x09, 0x02, + 0x81, 0x02, + 0x09, 0x14, + 0x85, 0x14, + 0x81, 0x02, + 0xc0, + 0xc0, +}; + static u8 t300rs_rdesc_nrm_fixed[] = { 0x05, 0x01, /* Usage page (Generic Desktop) */ 0x09, 0x04, /* Usage (Joystick) */ @@ -1512,11 +1582,20 @@ static int t300rs_wheel_destroy(void *data) static __u8 *t300rs_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { + struct tmff2_device_entry *tmff2 = hid_get_drvdata(hdev); + switch (hdev->product) { case TMT300RS_PS3_NORM_ID: /* normal PS3 mode */ - rdesc = t300rs_rdesc_nrm_fixed; - *rsize = sizeof(t300rs_rdesc_nrm_fixed); + + //Combine Pedals patch + if (tmff2 && tmff2->combine_pedals) { + rdesc = t300rs_rdesc_nrm_combined; + *rsize = sizeof(t300rs_rdesc_nrm_combined); + } else { + rdesc = t300rs_rdesc_nrm_fixed; + *rsize = sizeof(t300rs_rdesc_nrm_fixed); + } break; case TMT300RS_PS4_NORM_ID: @@ -1535,6 +1614,41 @@ static __u8 *t300rs_wheel_fixup(struct hid_device *hdev, __u8 *rdesc, return rdesc; } +//Combine Pedals / Center Clutch patch +//This method is the one that actually modifies the raw data coming from the wheel +static int t300rs_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct tmff2_device_entry *tmff2 = hid_get_drvdata(hdev); + struct t300rs_device_entry *t300rs; + s32 combined; + u16 gas, brake; + + if (!tmff2 || !tmff2->combine_pedals) + return 0; + + if (hdev->product != TMT300RS_PS3_NORM_ID) + return 0; + + if (data[0] != 0x07 || size < 9) + return 0; + + brake = get_unaligned_le16(&data[3]); + gas = get_unaligned_le16(&data[5]); + + combined = ((s32)gas - (s32)brake + 1023) / 2; + combined = clamp(combined, 0, 1023); + + put_unaligned_le16(0, &data[3]); + put_unaligned_le16((u16)combined, &data[5]); + + t300rs = tmff2->data; + if (t300rs && tmff2->center_clutch) + put_unaligned_le16(512, &data[7]); + + return 0; +} + int t300rs_populate_api(struct tmff2_device_entry *tmff2) { /* set callbacks */ @@ -1556,5 +1670,7 @@ int t300rs_populate_api(struct tmff2_device_entry *tmff2) tmff2->set_autocenter = t300rs_set_autocenter; tmff2->wheel_fixup = t300rs_wheel_fixup; + tmff2->raw_event = t300rs_raw_event; //Combine Pedals patch + return 0; }