projects/linux·blob master · refs

251 lines · 6.8 KiB

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