270 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Force feedback support for various HID compliant devices by ThrustMaster:4 * ThrustMaster FireStorm Dual Power 25 * and possibly others whose device ids haven't been added.6 *7 * Modified to support ThrustMaster devices by Zinx Verituse8 * on 2003-01-25 from the Logitech force feedback driver,9 * which is by Johann Deneux.10 *11 * Copyright (c) 2003 Zinx Verituse <zinx@epicsol.org>12 * Copyright (c) 2002 Johann Deneux13 */14 15/*16 */17 18#include <linux/hid.h>19#include <linux/input.h>20#include <linux/slab.h>21#include <linux/module.h>22 23#include "hid-ids.h"24 25#define THRUSTMASTER_DEVICE_ID_2_IN_1_DT 0xb32026 27static const signed short ff_rumble[] = {28 FF_RUMBLE,29 -130};31 32static const signed short ff_joystick[] = {33 FF_CONSTANT,34 -135};36 37#ifdef CONFIG_THRUSTMASTER_FF38 39/* Usages for thrustmaster devices I know about */40#define THRUSTMASTER_USAGE_FF (HID_UP_GENDESK | 0xbb)41 42struct tmff_device {43 struct hid_report *report;44 struct hid_field *ff_field;45};46 47/* Changes values from 0 to 0xffff into values from minimum to maximum */48static inline int tmff_scale_u16(unsigned int in, int minimum, int maximum)49{50 int ret;51 52 ret = (in * (maximum - minimum) / 0xffff) + minimum;53 if (ret < minimum)54 return minimum;55 if (ret > maximum)56 return maximum;57 return ret;58}59 60/* Changes values from -0x80 to 0x7f into values from minimum to maximum */61static inline int tmff_scale_s8(int in, int minimum, int maximum)62{63 int ret;64 65 ret = (((in + 0x80) * (maximum - minimum)) / 0xff) + minimum;66 if (ret < minimum)67 return minimum;68 if (ret > maximum)69 return maximum;70 return ret;71}72 73static int tmff_play(struct input_dev *dev, void *data,74 struct ff_effect *effect)75{76 struct hid_device *hid = input_get_drvdata(dev);77 struct tmff_device *tmff = data;78 struct hid_field *ff_field = tmff->ff_field;79 int x, y;80 int left, right; /* Rumbling */81 82 switch (effect->type) {83 case FF_CONSTANT:84 x = tmff_scale_s8(effect->u.ramp.start_level,85 ff_field->logical_minimum,86 ff_field->logical_maximum);87 y = tmff_scale_s8(effect->u.ramp.end_level,88 ff_field->logical_minimum,89 ff_field->logical_maximum);90 91 dbg_hid("(x, y)=(%04x, %04x)\n", x, y);92 ff_field->value[0] = x;93 ff_field->value[1] = y;94 hid_hw_request(hid, tmff->report, HID_REQ_SET_REPORT);95 break;96 97 case FF_RUMBLE:98 left = tmff_scale_u16(effect->u.rumble.weak_magnitude,99 ff_field->logical_minimum,100 ff_field->logical_maximum);101 right = tmff_scale_u16(effect->u.rumble.strong_magnitude,102 ff_field->logical_minimum,103 ff_field->logical_maximum);104 105 /* 2-in-1 strong motor is left */106 if (hid->product == THRUSTMASTER_DEVICE_ID_2_IN_1_DT)107 swap(left, right);108 109 dbg_hid("(left,right)=(%08x, %08x)\n", left, right);110 ff_field->value[0] = left;111 ff_field->value[1] = right;112 hid_hw_request(hid, tmff->report, HID_REQ_SET_REPORT);113 break;114 }115 return 0;116}117 118static int tmff_init(struct hid_device *hid, const signed short *ff_bits)119{120 struct tmff_device *tmff;121 struct hid_report *report;122 struct list_head *report_list;123 struct hid_input *hidinput;124 struct input_dev *input_dev;125 int error;126 int i;127 128 if (list_empty(&hid->inputs)) {129 hid_err(hid, "no inputs found\n");130 return -ENODEV;131 }132 hidinput = list_entry(hid->inputs.next, struct hid_input, list);133 input_dev = hidinput->input;134 135 tmff = kzalloc(sizeof(struct tmff_device), GFP_KERNEL);136 if (!tmff)137 return -ENOMEM;138 139 /* Find the report to use */140 report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;141 list_for_each_entry(report, report_list, list) {142 int fieldnum;143 144 for (fieldnum = 0; fieldnum < report->maxfield; ++fieldnum) {145 struct hid_field *field = report->field[fieldnum];146 147 if (field->maxusage <= 0)148 continue;149 150 switch (field->usage[0].hid) {151 case THRUSTMASTER_USAGE_FF:152 if (field->report_count < 2) {153 hid_warn(hid, "ignoring FF field with report_count < 2\n");154 continue;155 }156 157 if (field->logical_maximum ==158 field->logical_minimum) {159 hid_warn(hid, "ignoring FF field with logical_maximum == logical_minimum\n");160 continue;161 }162 163 if (tmff->report && tmff->report != report) {164 hid_warn(hid, "ignoring FF field in other report\n");165 continue;166 }167 168 if (tmff->ff_field && tmff->ff_field != field) {169 hid_warn(hid, "ignoring duplicate FF field\n");170 continue;171 }172 173 tmff->report = report;174 tmff->ff_field = field;175 176 for (i = 0; ff_bits[i] >= 0; i++)177 set_bit(ff_bits[i], input_dev->ffbit);178 179 break;180 181 default:182 hid_warn(hid, "ignoring unknown output usage %08x\n",183 field->usage[0].hid);184 continue;185 }186 }187 }188 189 if (!tmff->report) {190 hid_err(hid, "can't find FF field in output reports\n");191 error = -ENODEV;192 goto fail;193 }194 195 error = input_ff_create_memless(input_dev, tmff, tmff_play);196 if (error)197 goto fail;198 199 hid_info(hid, "force feedback for ThrustMaster devices by Zinx Verituse <zinx@epicsol.org>\n");200 return 0;201 202fail:203 kfree(tmff);204 return error;205}206#else207static inline int tmff_init(struct hid_device *hid, const signed short *ff_bits)208{209 return 0;210}211#endif212 213static int tm_probe(struct hid_device *hdev, const struct hid_device_id *id)214{215 int ret;216 217 ret = hid_parse(hdev);218 if (ret) {219 hid_err(hdev, "parse failed\n");220 goto err;221 }222 223 ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);224 if (ret) {225 hid_err(hdev, "hw start failed\n");226 goto err;227 }228 229 tmff_init(hdev, (void *)id->driver_data);230 231 return 0;232err:233 return ret;234}235 236static const struct hid_device_id tm_devices[] = {237 { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300),238 .driver_data = (unsigned long)ff_rumble },239 { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304), /* FireStorm Dual Power 2 (and 3) */240 .driver_data = (unsigned long)ff_rumble },241 { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, THRUSTMASTER_DEVICE_ID_2_IN_1_DT), /* Dual Trigger 2-in-1 */242 .driver_data = (unsigned long)ff_rumble },243 { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb323), /* Dual Trigger 3-in-1 (PC Mode) */244 .driver_data = (unsigned long)ff_rumble },245 { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb324), /* Dual Trigger 3-in-1 (PS3 Mode) */246 .driver_data = (unsigned long)ff_rumble },247 { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb605), /* NASCAR PRO FF2 Wheel */248 .driver_data = (unsigned long)ff_joystick },249 { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb651), /* FGT Rumble Force Wheel */250 .driver_data = (unsigned long)ff_rumble },251 { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb653), /* RGT Force Feedback CLUTCH Raging Wheel */252 .driver_data = (unsigned long)ff_joystick },253 { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb654), /* FGT Force Feedback Wheel */254 .driver_data = (unsigned long)ff_joystick },255 { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb65a), /* F430 Force Feedback Wheel */256 .driver_data = (unsigned long)ff_joystick },257 { }258};259MODULE_DEVICE_TABLE(hid, tm_devices);260 261static struct hid_driver tm_driver = {262 .name = "thrustmaster",263 .id_table = tm_devices,264 .probe = tm_probe,265};266module_hid_driver(tm_driver);267 268MODULE_DESCRIPTION("Force feedback support for various HID compliant devices by ThrustMaster");269MODULE_LICENSE("GPL");270