Add Fanatec wheel driver (#24)

This commit is contained in:
Michael Huang
2022-04-17 01:28:56 -07:00
committed by GitHub
parent c708f22224
commit e82a592804
6 changed files with 1671 additions and 0 deletions

View File

@@ -2608,6 +2608,7 @@ CONFIG_HID_ELECOM=y
# CONFIG_HID_ELO is not set
CONFIG_HID_EZKEY=y
# CONFIG_HID_FT260 is not set
CONFIG_HID_FTEC=y
CONFIG_HID_GEMBIRD=y
# CONFIG_HID_GFRM is not set
# CONFIG_HID_GLORIOUS is not set

View File

@@ -363,6 +363,12 @@ config HID_FT260
To compile this driver as a module, choose M here: the module
will be called hid-ft260.
config HID_FTEC
tristate "Fanatec Wheels"
depends on USB_HID
help
Support Fanatec racing wheels.
config HID_GEMBIRD
tristate "Gembird Joypad"
depends on HID

View File

@@ -47,6 +47,7 @@ obj-$(CONFIG_HID_ELECOM) += hid-elecom.o
obj-$(CONFIG_HID_ELO) += hid-elo.o
obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o
obj-$(CONFIG_HID_FT260) += hid-ft260.o
obj-$(CONFIG_HID_FTEC) += hid-ftec.o hid-ftecff.o
obj-$(CONFIG_HID_GEMBIRD) += hid-gembird.o
obj-$(CONFIG_HID_GFRM) += hid-gfrm.o
obj-$(CONFIG_HID_GLORIOUS) += hid-glorious.o

313
drivers/hid/hid-ftec.c Normal file
View File

@@ -0,0 +1,313 @@
#include <linux/device.h>
#include <linux/usb.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/input.h>
#include "hid-ftec.h"
#define FTEC_FF 0x001
#define FTEC_PEDALS 0x002
#define FTEC_LEDS 0x004
// adjustabel initial value for break load cell
int init_load = 4;
module_param(init_load, int, 0);
int ftecff_init(struct hid_device *hdev);
void ftecff_remove(struct hid_device *hdev);
static u8 ftec_get_load(struct hid_device *hid)
{
struct list_head *report_list = &hid->report_enum[HID_INPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
struct ftec_drv_data *drv_data;
unsigned long flags;
s32 *value;
dbg_hid(" ... get_load; %i\n", hid_report_len(report));
drv_data = hid_get_drvdata(hid);
if (!drv_data) {
hid_err(hid, "Private driver data not found!\n");
return -EINVAL;
}
value = drv_data->report->field[0]->value;
spin_lock_irqsave(&drv_data->report_lock, flags);
value[0] = 0xf8;
value[1] = 0x09;
value[2] = 0x01;
value[3] = 0x06;
value[4] = 0x00;
value[5] = 0x00;
value[6] = 0x00;
hid_hw_request(hid, drv_data->report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&drv_data->report_lock, flags);
hid_hw_request(hid, report, HID_REQ_GET_REPORT);
hid_hw_wait(hid);
return report->field[0]->value[10];
}
static void ftec_set_load(struct hid_device *hid, u8 val)
{
struct ftec_drv_data *drv_data;
unsigned long flags;
s32 *value;
dbg_hid(" ... set_load %02X\n", val);
drv_data = hid_get_drvdata(hid);
if (!drv_data) {
hid_err(hid, "Private driver data not found!\n");
return;
}
value = drv_data->report->field[0]->value;
spin_lock_irqsave(&drv_data->report_lock, flags);
value[0] = 0xf8;
value[1] = 0x09;
value[2] = 0x01;
value[3] = 0x16;
value[4] = val+1; // actual value has an offset of 1
value[5] = 0x00;
value[6] = 0x00;
hid_hw_request(hid, drv_data->report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&drv_data->report_lock, flags);
}
static void ftec_set_rumble(struct hid_device *hid, u32 val)
{
struct ftec_drv_data *drv_data;
unsigned long flags;
s32 *value;
dbg_hid(" ... set_rumble %02X\n", val);
drv_data = hid_get_drvdata(hid);
if (!drv_data) {
hid_err(hid, "Private driver data not found!\n");
return;
}
value = drv_data->report->field[0]->value;
spin_lock_irqsave(&drv_data->report_lock, flags);
value[0] = 0xf8;
value[1] = 0x09;
value[2] = 0x01;
value[3] = 0x04;
value[4] = (val>>16)&0xff;
value[5] = (val>>8)&0xff;
value[6] = (val)&0xff;
hid_hw_request(hid, drv_data->report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&drv_data->report_lock, flags);
}
static ssize_t ftec_load_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct hid_device *hid = to_hid_device(dev);
struct ftec_drv_data *drv_data;
size_t count;
drv_data = hid_get_drvdata(hid);
if (!drv_data) {
hid_err(hid, "Private driver data not found!\n");
return 0;
}
count = scnprintf(buf, PAGE_SIZE, "%u\n", ftec_get_load(hid));
return count;
}
static ssize_t ftec_load_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct hid_device *hid = to_hid_device(dev);
u8 load = simple_strtoul(buf, NULL, 10);
ftec_set_load(hid, load);
return count;
}
static DEVICE_ATTR(load, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, ftec_load_show, ftec_load_store);
static ssize_t ftec_rumble_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct hid_device *hid = to_hid_device(dev);
u32 rumble = simple_strtoul(buf, NULL, 10);
ftec_set_rumble(hid, rumble);
return count;
}
static DEVICE_ATTR(rumble, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, NULL, ftec_rumble_store);
static int ftec_init(struct hid_device *hdev) {
struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
struct ftec_drv_data *drv_data;
dbg_hid(" ... %i %i %i %i\n%i %i %i %i\n\n",
report->id, report->type, // report->application,
report->maxfield, report->size,
report->field[0]->logical_minimum,report->field[0]->logical_maximum,
report->field[0]->physical_minimum,report->field[0]->physical_maximum
);
/* Check that the report looks ok */
if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 7))
return -1;
drv_data = hid_get_drvdata(hdev);
if (!drv_data) {
hid_err(hdev, "Cannot add device, private driver data not allocated\n");
return -1;
}
drv_data->report = report;
drv_data->hid = hdev;
spin_lock_init(&drv_data->report_lock);
return 0;
}
static int ftec_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
__u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber;
struct ftec_drv_data *drv_data;
unsigned int connect_mask = HID_CONNECT_DEFAULT;
int ret;
dbg_hid("%s: ifnum %d\n", __func__, iface_num);
drv_data = kzalloc(sizeof(struct ftec_drv_data), GFP_KERNEL);
if (!drv_data) {
hid_err(hdev, "Insufficient memory, cannot allocate driver data\n");
return -ENOMEM;
}
drv_data->quirks = id->driver_data;
drv_data->min_range = 90;
drv_data->max_range = 1080;
if (hdev->product == CLUBSPORT_V2_WHEELBASE_DEVICE_ID ||
hdev->product == CLUBSPORT_V25_WHEELBASE_DEVICE_ID ||
hdev->product == CSR_ELITE_WHEELBASE_DEVICE_ID) {
drv_data->max_range = 900;
}
hid_set_drvdata(hdev, (void *)drv_data);
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err_free;
}
if (drv_data->quirks & FTEC_FF) {
connect_mask &= ~HID_CONNECT_FF;
}
ret = hid_hw_start(hdev, connect_mask);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto err_free;
}
ret = ftec_init(hdev);
if (ret) {
hid_err(hdev, "hw init failed\n");
goto err_stop;
}
if (drv_data->quirks & FTEC_FF) {
ret = ftecff_init(hdev);
if (ret) {
hid_err(hdev, "ff init failed\n");
goto err_stop;
}
}
if (drv_data->quirks & FTEC_PEDALS) {
struct hid_input *hidinput = list_entry(hdev->inputs.next, struct hid_input, list);
struct input_dev *inputdev = hidinput->input;
// if these bits are not set, the pedals are not recognized in newer proton/wine verisons
set_bit(EV_KEY, inputdev->evbit);
set_bit(BTN_WHEEL, inputdev->keybit);
if (init_load >= 0 && init_load < 10) {
ftec_set_load(hdev, init_load);
}
ret = device_create_file(&hdev->dev, &dev_attr_load);
if (ret)
hid_warn(hdev, "Unable to create sysfs interface for \"load\", errno %d\n", ret);
if (hdev->product == CLUBSPORT_PEDALS_V3_DEVICE_ID) {
ret = device_create_file(&hdev->dev, &dev_attr_rumble);
if (ret)
hid_warn(hdev, "Unable to create sysfs interface for \"rumble\", errno %d\n", ret);
}
}
return 0;
err_stop:
hid_hw_stop(hdev);
err_free:
kfree(drv_data);
return ret;
}
static void ftec_remove(struct hid_device *hdev)
{
struct ftec_drv_data *drv_data = hid_get_drvdata(hdev);
if (drv_data->quirks & FTEC_PEDALS) {
device_remove_file(&hdev->dev, &dev_attr_load);
if (hdev->product == CLUBSPORT_PEDALS_V3_DEVICE_ID) {
device_remove_file(&hdev->dev, &dev_attr_rumble);
}
}
if (drv_data->quirks & FTEC_FF) {
ftecff_remove(hdev);
}
hid_hw_stop(hdev);
kfree(drv_data);
}
static const struct hid_device_id devices[] = {
{ HID_USB_DEVICE(FANATEC_VENDOR_ID, CLUBSPORT_V2_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF | FTEC_LEDS},
{ HID_USB_DEVICE(FANATEC_VENDOR_ID, CLUBSPORT_V25_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF | FTEC_LEDS},
{ HID_USB_DEVICE(FANATEC_VENDOR_ID, CLUBSPORT_PEDALS_V3_DEVICE_ID), .driver_data = FTEC_PEDALS },
{ HID_USB_DEVICE(FANATEC_VENDOR_ID, CSL_ELITE_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF | FTEC_LEDS},
{ HID_USB_DEVICE(FANATEC_VENDOR_ID, CSL_ELITE_PS4_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF | FTEC_LEDS},
{ HID_USB_DEVICE(FANATEC_VENDOR_ID, CSL_ELITE_PEDALS_DEVICE_ID), .driver_data = FTEC_PEDALS },
{ HID_USB_DEVICE(FANATEC_VENDOR_ID, PODIUM_WHEELBASE_DD1_DEVICE_ID), .driver_data = FTEC_FF | FTEC_LEDS },
{ HID_USB_DEVICE(FANATEC_VENDOR_ID, PODIUM_WHEELBASE_DD2_DEVICE_ID), .driver_data = FTEC_FF | FTEC_LEDS },
{ HID_USB_DEVICE(FANATEC_VENDOR_ID, CSL_DD_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF | FTEC_LEDS },
{ HID_USB_DEVICE(FANATEC_VENDOR_ID, CSR_ELITE_WHEELBASE_DEVICE_ID), .driver_data = FTEC_FF | FTEC_LEDS },
{ }
};
MODULE_DEVICE_TABLE(hid, devices);
static struct hid_driver ftec_csl_elite = {
.name = "ftec_csl_elite",
.id_table = devices,
.probe = ftec_probe,
.remove = ftec_remove,
};
module_hid_driver(ftec_csl_elite)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("gotzl");
MODULE_DESCRIPTION("A driver for the Fanatec CSL Elite");

82
drivers/hid/hid-ftec.h Normal file
View File

@@ -0,0 +1,82 @@
#ifndef __HID_FTEC_H
#define __HID_FTEC_H
#define FANATEC_VENDOR_ID 0x0eb7
#define CLUBSPORT_V2_WHEELBASE_DEVICE_ID 0x0001
#define CLUBSPORT_V25_WHEELBASE_DEVICE_ID 0x0004
#define CLUBSPORT_PEDALS_V3_DEVICE_ID 0x183b
#define PODIUM_WHEELBASE_DD1_DEVICE_ID 0x0006
#define PODIUM_WHEELBASE_DD2_DEVICE_ID 0x0007
#define CSL_ELITE_WHEELBASE_DEVICE_ID 0x0E03
#define CSL_ELITE_PS4_WHEELBASE_DEVICE_ID 0x0005
#define CSL_ELITE_PEDALS_DEVICE_ID 0x6204
#define CSL_DD_WHEELBASE_DEVICE_ID 0x0020
#define CSR_ELITE_WHEELBASE_DEVICE_ID 0x0011
#define CSL_ELITE_STEERING_WHEEL_WRC_ID 0x0112
#define CLUBSPORT_STEERING_WHEEL_F1_IS_ID 0x1102
#define CLUBSPORT_STEERING_WHEEL_FORMULA_V2_ID 0x280a
#define PODIUM_STEERING_WHEEL_PORSCHE_911_GT3_R_ID 0x050c
#define LEDS 9
#define NUM_TUNING_SLOTS 5
#define FTECFF_MAX_EFFECTS 16
struct ftecff_effect_state {
struct ff_effect effect;
struct ff_envelope *envelope;
unsigned long start_at;
unsigned long play_at;
unsigned long stop_at;
unsigned long flags;
unsigned long time_playing;
unsigned long updated_at;
unsigned int phase;
unsigned int phase_adj;
unsigned int count;
unsigned int cmd;
unsigned int cmd_start_time;
unsigned int cmd_start_count;
int direction_gain;
int slope;
};
struct ftecff_effect_parameters {
int level;
int d1;
int d2;
int k1;
int k2;
unsigned int clip;
};
struct ftecff_slot {
int id;
struct ftecff_effect_parameters parameters;
u8 current_cmd[7];
int is_updated;
int effect_type;
};
struct ftec_drv_data {
unsigned long quirks;
spinlock_t report_lock; /* Protect output HID report */
spinlock_t timer_lock;
struct hrtimer hrtimer;
struct hid_device *hid;
struct hid_report *report;
struct ftecff_slot slots[4];
struct ftecff_effect_state states[FTECFF_MAX_EFFECTS];
int effects_used;
u16 range;
u16 max_range;
u16 min_range;
#ifdef CONFIG_LEDS_CLASS
u16 led_state;
struct led_classdev *led[LEDS];
#endif
};
#endif

1268
drivers/hid/hid-ftecff.c Normal file

File diff suppressed because it is too large Load Diff