Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-or-later
2 : : /*
3 : : * Force feedback support for Logitech Gaming Wheels
4 : : *
5 : : * Including G27, G25, DFP, DFGT, FFEX, Momo, Momo2 &
6 : : * Speed Force Wireless (WiiWheel)
7 : : *
8 : : * Copyright (c) 2010 Simon Wood <simon@mungewell.org>
9 : : */
10 : :
11 : : /*
12 : : */
13 : :
14 : :
15 : : #include <linux/input.h>
16 : : #include <linux/usb.h>
17 : : #include <linux/hid.h>
18 : :
19 : : #include "usbhid/usbhid.h"
20 : : #include "hid-lg.h"
21 : : #include "hid-lg4ff.h"
22 : : #include "hid-ids.h"
23 : :
24 : : #define LG4FF_MMODE_IS_MULTIMODE 0
25 : : #define LG4FF_MMODE_SWITCHED 1
26 : : #define LG4FF_MMODE_NOT_MULTIMODE 2
27 : :
28 : : #define LG4FF_MODE_NATIVE_IDX 0
29 : : #define LG4FF_MODE_DFEX_IDX 1
30 : : #define LG4FF_MODE_DFP_IDX 2
31 : : #define LG4FF_MODE_G25_IDX 3
32 : : #define LG4FF_MODE_DFGT_IDX 4
33 : : #define LG4FF_MODE_G27_IDX 5
34 : : #define LG4FF_MODE_G29_IDX 6
35 : : #define LG4FF_MODE_MAX_IDX 7
36 : :
37 : : #define LG4FF_MODE_NATIVE BIT(LG4FF_MODE_NATIVE_IDX)
38 : : #define LG4FF_MODE_DFEX BIT(LG4FF_MODE_DFEX_IDX)
39 : : #define LG4FF_MODE_DFP BIT(LG4FF_MODE_DFP_IDX)
40 : : #define LG4FF_MODE_G25 BIT(LG4FF_MODE_G25_IDX)
41 : : #define LG4FF_MODE_DFGT BIT(LG4FF_MODE_DFGT_IDX)
42 : : #define LG4FF_MODE_G27 BIT(LG4FF_MODE_G27_IDX)
43 : : #define LG4FF_MODE_G29 BIT(LG4FF_MODE_G29_IDX)
44 : :
45 : : #define LG4FF_DFEX_TAG "DF-EX"
46 : : #define LG4FF_DFEX_NAME "Driving Force / Formula EX"
47 : : #define LG4FF_DFP_TAG "DFP"
48 : : #define LG4FF_DFP_NAME "Driving Force Pro"
49 : : #define LG4FF_G25_TAG "G25"
50 : : #define LG4FF_G25_NAME "G25 Racing Wheel"
51 : : #define LG4FF_G27_TAG "G27"
52 : : #define LG4FF_G27_NAME "G27 Racing Wheel"
53 : : #define LG4FF_G29_TAG "G29"
54 : : #define LG4FF_G29_NAME "G29 Racing Wheel"
55 : : #define LG4FF_DFGT_TAG "DFGT"
56 : : #define LG4FF_DFGT_NAME "Driving Force GT"
57 : :
58 : : #define LG4FF_FFEX_REV_MAJ 0x21
59 : : #define LG4FF_FFEX_REV_MIN 0x00
60 : :
61 : : static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
62 : : static void lg4ff_set_range_g25(struct hid_device *hid, u16 range);
63 : :
64 : : struct lg4ff_wheel_data {
65 : : const u32 product_id;
66 : : u16 combine;
67 : : u16 range;
68 : : const u16 min_range;
69 : : const u16 max_range;
70 : : #ifdef CONFIG_LEDS_CLASS
71 : : u8 led_state;
72 : : struct led_classdev *led[5];
73 : : #endif
74 : : const u32 alternate_modes;
75 : : const char * const real_tag;
76 : : const char * const real_name;
77 : : const u16 real_product_id;
78 : :
79 : : void (*set_range)(struct hid_device *hid, u16 range);
80 : : };
81 : :
82 : : struct lg4ff_device_entry {
83 : : spinlock_t report_lock; /* Protect output HID report */
84 : : struct hid_report *report;
85 : : struct lg4ff_wheel_data wdata;
86 : : };
87 : :
88 : : static const signed short lg4ff_wheel_effects[] = {
89 : : FF_CONSTANT,
90 : : FF_AUTOCENTER,
91 : : -1
92 : : };
93 : :
94 : : static const signed short no_wheel_effects[] = {
95 : : -1
96 : : };
97 : :
98 : : struct lg4ff_wheel {
99 : : const u32 product_id;
100 : : const signed short *ff_effects;
101 : : const u16 min_range;
102 : : const u16 max_range;
103 : : void (*set_range)(struct hid_device *hid, u16 range);
104 : : };
105 : :
106 : : struct lg4ff_compat_mode_switch {
107 : : const u8 cmd_count; /* Number of commands to send */
108 : : const u8 cmd[];
109 : : };
110 : :
111 : : struct lg4ff_wheel_ident_info {
112 : : const u32 modes;
113 : : const u16 mask;
114 : : const u16 result;
115 : : const u16 real_product_id;
116 : : };
117 : :
118 : : struct lg4ff_multimode_wheel {
119 : : const u16 product_id;
120 : : const u32 alternate_modes;
121 : : const char *real_tag;
122 : : const char *real_name;
123 : : };
124 : :
125 : : struct lg4ff_alternate_mode {
126 : : const u16 product_id;
127 : : const char *tag;
128 : : const char *name;
129 : : };
130 : :
131 : : static const struct lg4ff_wheel lg4ff_devices[] = {
132 : : {USB_DEVICE_ID_LOGITECH_WINGMAN_FG, no_wheel_effects, 40, 180, NULL},
133 : : {USB_DEVICE_ID_LOGITECH_WINGMAN_FFG, lg4ff_wheel_effects, 40, 180, NULL},
134 : : {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
135 : : {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
136 : : {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_dfp},
137 : : {USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
138 : : {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
139 : : {USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
140 : : {USB_DEVICE_ID_LOGITECH_G29_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
141 : : {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270, NULL},
142 : : {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}
143 : : };
144 : :
145 : : static const struct lg4ff_multimode_wheel lg4ff_multimode_wheels[] = {
146 : : {USB_DEVICE_ID_LOGITECH_DFP_WHEEL,
147 : : LG4FF_MODE_NATIVE | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
148 : : LG4FF_DFP_TAG, LG4FF_DFP_NAME},
149 : : {USB_DEVICE_ID_LOGITECH_G25_WHEEL,
150 : : LG4FF_MODE_NATIVE | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
151 : : LG4FF_G25_TAG, LG4FF_G25_NAME},
152 : : {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,
153 : : LG4FF_MODE_NATIVE | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
154 : : LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
155 : : {USB_DEVICE_ID_LOGITECH_G27_WHEEL,
156 : : LG4FF_MODE_NATIVE | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
157 : : LG4FF_G27_TAG, LG4FF_G27_NAME},
158 : : {USB_DEVICE_ID_LOGITECH_G29_WHEEL,
159 : : LG4FF_MODE_NATIVE | LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
160 : : LG4FF_G29_TAG, LG4FF_G29_NAME},
161 : : };
162 : :
163 : : static const struct lg4ff_alternate_mode lg4ff_alternate_modes[] = {
164 : : [LG4FF_MODE_NATIVE_IDX] = {0, "native", ""},
165 : : [LG4FF_MODE_DFEX_IDX] = {USB_DEVICE_ID_LOGITECH_WHEEL, LG4FF_DFEX_TAG, LG4FF_DFEX_NAME},
166 : : [LG4FF_MODE_DFP_IDX] = {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, LG4FF_DFP_TAG, LG4FF_DFP_NAME},
167 : : [LG4FF_MODE_G25_IDX] = {USB_DEVICE_ID_LOGITECH_G25_WHEEL, LG4FF_G25_TAG, LG4FF_G25_NAME},
168 : : [LG4FF_MODE_DFGT_IDX] = {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
169 : : [LG4FF_MODE_G27_IDX] = {USB_DEVICE_ID_LOGITECH_G27_WHEEL, LG4FF_G27_TAG, LG4FF_G27_NAME},
170 : : [LG4FF_MODE_G29_IDX] = {USB_DEVICE_ID_LOGITECH_G29_WHEEL, LG4FF_G29_TAG, LG4FF_G29_NAME},
171 : : };
172 : :
173 : : /* Multimode wheel identificators */
174 : : static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = {
175 : : LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
176 : : 0xf000,
177 : : 0x1000,
178 : : USB_DEVICE_ID_LOGITECH_DFP_WHEEL
179 : : };
180 : :
181 : : static const struct lg4ff_wheel_ident_info lg4ff_g25_ident_info = {
182 : : LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
183 : : 0xff00,
184 : : 0x1200,
185 : : USB_DEVICE_ID_LOGITECH_G25_WHEEL
186 : : };
187 : :
188 : : static const struct lg4ff_wheel_ident_info lg4ff_g27_ident_info = {
189 : : LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
190 : : 0xfff0,
191 : : 0x1230,
192 : : USB_DEVICE_ID_LOGITECH_G27_WHEEL
193 : : };
194 : :
195 : : static const struct lg4ff_wheel_ident_info lg4ff_dfgt_ident_info = {
196 : : LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
197 : : 0xff00,
198 : : 0x1300,
199 : : USB_DEVICE_ID_LOGITECH_DFGT_WHEEL
200 : : };
201 : :
202 : : static const struct lg4ff_wheel_ident_info lg4ff_g29_ident_info = {
203 : : LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
204 : : 0xfff8,
205 : : 0x1350,
206 : : USB_DEVICE_ID_LOGITECH_G29_WHEEL
207 : : };
208 : :
209 : : static const struct lg4ff_wheel_ident_info lg4ff_g29_ident_info2 = {
210 : : LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
211 : : 0xff00,
212 : : 0x8900,
213 : : USB_DEVICE_ID_LOGITECH_G29_WHEEL
214 : : };
215 : :
216 : : /* Multimode wheel identification checklists */
217 : : static const struct lg4ff_wheel_ident_info *lg4ff_main_checklist[] = {
218 : : &lg4ff_g29_ident_info,
219 : : &lg4ff_g29_ident_info2,
220 : : &lg4ff_dfgt_ident_info,
221 : : &lg4ff_g27_ident_info,
222 : : &lg4ff_g25_ident_info,
223 : : &lg4ff_dfp_ident_info
224 : : };
225 : :
226 : : /* Compatibility mode switching commands */
227 : : /* EXT_CMD9 - Understood by G27 and DFGT */
228 : : static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfex = {
229 : : 2,
230 : : {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
231 : : 0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DF-EX with detach */
232 : : };
233 : :
234 : : static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfp = {
235 : : 2,
236 : : {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
237 : : 0xf8, 0x09, 0x01, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFP with detach */
238 : : };
239 : :
240 : : static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g25 = {
241 : : 2,
242 : : {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
243 : : 0xf8, 0x09, 0x02, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G25 with detach */
244 : : };
245 : :
246 : : static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfgt = {
247 : : 2,
248 : : {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
249 : : 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFGT with detach */
250 : : };
251 : :
252 : : static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g27 = {
253 : : 2,
254 : : {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
255 : : 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G27 with detach */
256 : : };
257 : :
258 : : static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g29 = {
259 : : 2,
260 : : {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
261 : : 0xf8, 0x09, 0x05, 0x01, 0x01, 0x00, 0x00} /* Switch mode to G29 with detach */
262 : : };
263 : :
264 : : /* EXT_CMD1 - Understood by DFP, G25, G27 and DFGT */
265 : : static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext01_dfp = {
266 : : 1,
267 : : {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
268 : : };
269 : :
270 : : /* EXT_CMD16 - Understood by G25 and G27 */
271 : : static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext16_g25 = {
272 : : 1,
273 : : {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
274 : : };
275 : :
276 : : /* Recalculates X axis value accordingly to currently selected range */
277 : 0 : static s32 lg4ff_adjust_dfp_x_axis(s32 value, u16 range)
278 : : {
279 : 0 : u16 max_range;
280 : 0 : s32 new_value;
281 : :
282 : 0 : if (range == 900)
283 : : return value;
284 [ # # ]: 0 : else if (range == 200)
285 : : return value;
286 [ # # ]: 0 : else if (range < 200)
287 : : max_range = 200;
288 : : else
289 : 0 : max_range = 900;
290 : :
291 : 0 : new_value = 8192 + mult_frac(value - 8192, max_range, range);
292 : 0 : if (new_value < 0)
293 : : return 0;
294 : : else if (new_value > 16383)
295 : : return 16383;
296 : : else
297 : : return new_value;
298 : : }
299 : :
300 : 0 : int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
301 : : struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data)
302 : : {
303 : 0 : struct lg4ff_device_entry *entry = drv_data->device_props;
304 : 0 : s32 new_value = 0;
305 : :
306 [ # # ]: 0 : if (!entry) {
307 : 0 : hid_err(hid, "Device properties not found");
308 : 0 : return 0;
309 : : }
310 : :
311 [ # # ]: 0 : switch (entry->wdata.product_id) {
312 : 0 : case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
313 [ # # ]: 0 : switch (usage->code) {
314 : 0 : case ABS_X:
315 [ # # ]: 0 : new_value = lg4ff_adjust_dfp_x_axis(value, entry->wdata.range);
316 : 0 : input_event(field->hidinput->input, usage->type, usage->code, new_value);
317 : 0 : return 1;
318 : : default:
319 : : return 0;
320 : : }
321 : : default:
322 : : return 0;
323 : : }
324 : : }
325 : :
326 : 0 : int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report,
327 : : u8 *rd, int size, struct lg_drv_data *drv_data)
328 : : {
329 : 0 : int offset;
330 : 0 : struct lg4ff_device_entry *entry = drv_data->device_props;
331 : :
332 [ # # ]: 0 : if (!entry)
333 : : return 0;
334 : :
335 : : /* adjust HID report present combined pedals data */
336 [ # # ]: 0 : if (entry->wdata.combine) {
337 [ # # # # : 0 : switch (entry->wdata.product_id) {
# # # ]
338 : 0 : case USB_DEVICE_ID_LOGITECH_WHEEL:
339 : 0 : rd[5] = rd[3];
340 : 0 : rd[6] = 0x7F;
341 : 0 : return 1;
342 : 0 : case USB_DEVICE_ID_LOGITECH_WINGMAN_FG:
343 : : case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG:
344 : : case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
345 : : case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
346 : 0 : rd[4] = rd[3];
347 : 0 : rd[5] = 0x7F;
348 : 0 : return 1;
349 : 0 : case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
350 : 0 : rd[5] = rd[4];
351 : 0 : rd[6] = 0x7F;
352 : 0 : return 1;
353 : : case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
354 : : case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
355 : : offset = 5;
356 : : break;
357 : 0 : case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
358 : : case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
359 : 0 : offset = 6;
360 : 0 : break;
361 : 0 : case USB_DEVICE_ID_LOGITECH_WII_WHEEL:
362 : 0 : offset = 3;
363 : 0 : break;
364 : : default:
365 : : return 0;
366 : : }
367 : :
368 : : /* Compute a combined axis when wheel does not supply it */
369 : 0 : rd[offset] = (0xFF + rd[offset] - rd[offset+1]) >> 1;
370 : 0 : rd[offset+1] = 0x7F;
371 : 0 : return 1;
372 : : }
373 : :
374 : : return 0;
375 : : }
376 : :
377 : 0 : static void lg4ff_init_wheel_data(struct lg4ff_wheel_data * const wdata, const struct lg4ff_wheel *wheel,
378 : : const struct lg4ff_multimode_wheel *mmode_wheel,
379 : : const u16 real_product_id)
380 : : {
381 : 0 : u32 alternate_modes = 0;
382 : 0 : const char *real_tag = NULL;
383 : 0 : const char *real_name = NULL;
384 : :
385 [ # # ]: 0 : if (mmode_wheel) {
386 : 0 : alternate_modes = mmode_wheel->alternate_modes;
387 : 0 : real_tag = mmode_wheel->real_tag;
388 : 0 : real_name = mmode_wheel->real_name;
389 : : }
390 : :
391 : : {
392 : 0 : struct lg4ff_wheel_data t_wdata = { .product_id = wheel->product_id,
393 : : .real_product_id = real_product_id,
394 : : .combine = 0,
395 : 0 : .min_range = wheel->min_range,
396 : 0 : .max_range = wheel->max_range,
397 : 0 : .set_range = wheel->set_range,
398 : : .alternate_modes = alternate_modes,
399 : : .real_tag = real_tag,
400 : : .real_name = real_name };
401 : :
402 : 0 : memcpy(wdata, &t_wdata, sizeof(t_wdata));
403 : : }
404 : 0 : }
405 : :
406 : 0 : static int lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
407 : : {
408 [ # # ]: 0 : struct hid_device *hid = input_get_drvdata(dev);
409 : 0 : struct lg4ff_device_entry *entry;
410 : 0 : struct lg_drv_data *drv_data;
411 : 0 : unsigned long flags;
412 : 0 : s32 *value;
413 : 0 : int x;
414 : :
415 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
416 [ # # ]: 0 : if (!drv_data) {
417 : 0 : hid_err(hid, "Private driver data not found!\n");
418 : 0 : return -EINVAL;
419 : : }
420 : :
421 : 0 : entry = drv_data->device_props;
422 [ # # ]: 0 : if (!entry) {
423 : 0 : hid_err(hid, "Device properties not found!\n");
424 : 0 : return -EINVAL;
425 : : }
426 : 0 : value = entry->report->field[0]->value;
427 : :
428 : : #define CLAMP(x) do { if (x < 0) x = 0; else if (x > 0xff) x = 0xff; } while (0)
429 : :
430 [ # # ]: 0 : switch (effect->type) {
431 : 0 : case FF_CONSTANT:
432 : 0 : x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */
433 : 0 : CLAMP(x);
434 : :
435 : 0 : spin_lock_irqsave(&entry->report_lock, flags);
436 [ # # ]: 0 : if (x == 0x80) {
437 : : /* De-activate force in slot-1*/
438 : 0 : value[0] = 0x13;
439 : 0 : value[1] = 0x00;
440 : 0 : value[2] = 0x00;
441 : 0 : value[3] = 0x00;
442 : 0 : value[4] = 0x00;
443 : 0 : value[5] = 0x00;
444 : 0 : value[6] = 0x00;
445 : :
446 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
447 : 0 : spin_unlock_irqrestore(&entry->report_lock, flags);
448 : 0 : return 0;
449 : : }
450 : :
451 : 0 : value[0] = 0x11; /* Slot 1 */
452 : 0 : value[1] = 0x08;
453 : 0 : value[2] = x;
454 : 0 : value[3] = 0x80;
455 : 0 : value[4] = 0x00;
456 : 0 : value[5] = 0x00;
457 : 0 : value[6] = 0x00;
458 : :
459 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
460 : 0 : spin_unlock_irqrestore(&entry->report_lock, flags);
461 : : break;
462 : : }
463 : : return 0;
464 : : }
465 : :
466 : : /* Sends default autocentering command compatible with
467 : : * all wheels except Formula Force EX */
468 : 0 : static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude)
469 : : {
470 [ # # ]: 0 : struct hid_device *hid = input_get_drvdata(dev);
471 : 0 : s32 *value;
472 : 0 : u32 expand_a, expand_b;
473 : 0 : struct lg4ff_device_entry *entry;
474 : 0 : struct lg_drv_data *drv_data;
475 : 0 : unsigned long flags;
476 : :
477 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
478 [ # # ]: 0 : if (!drv_data) {
479 : 0 : hid_err(hid, "Private driver data not found!\n");
480 : 0 : return;
481 : : }
482 : :
483 : 0 : entry = drv_data->device_props;
484 [ # # ]: 0 : if (!entry) {
485 : 0 : hid_err(hid, "Device properties not found!\n");
486 : 0 : return;
487 : : }
488 : 0 : value = entry->report->field[0]->value;
489 : :
490 : : /* De-activate Auto-Center */
491 : 0 : spin_lock_irqsave(&entry->report_lock, flags);
492 [ # # ]: 0 : if (magnitude == 0) {
493 : 0 : value[0] = 0xf5;
494 : 0 : value[1] = 0x00;
495 : 0 : value[2] = 0x00;
496 : 0 : value[3] = 0x00;
497 : 0 : value[4] = 0x00;
498 : 0 : value[5] = 0x00;
499 : 0 : value[6] = 0x00;
500 : :
501 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
502 : 0 : spin_unlock_irqrestore(&entry->report_lock, flags);
503 : 0 : return;
504 : : }
505 : :
506 [ # # ]: 0 : if (magnitude <= 0xaaaa) {
507 : 0 : expand_a = 0x0c * magnitude;
508 : 0 : expand_b = 0x80 * magnitude;
509 : : } else {
510 : 0 : expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa);
511 : 0 : expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa);
512 : : }
513 : :
514 : : /* Adjust for non-MOMO wheels */
515 [ # # ]: 0 : switch (entry->wdata.product_id) {
516 : : case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
517 : : case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
518 : : break;
519 : 0 : default:
520 : 0 : expand_a = expand_a >> 1;
521 : 0 : break;
522 : : }
523 : :
524 : 0 : value[0] = 0xfe;
525 : 0 : value[1] = 0x0d;
526 : 0 : value[2] = expand_a / 0xaaaa;
527 : 0 : value[3] = expand_a / 0xaaaa;
528 : 0 : value[4] = expand_b / 0xaaaa;
529 : 0 : value[5] = 0x00;
530 : 0 : value[6] = 0x00;
531 : :
532 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
533 : :
534 : : /* Activate Auto-Center */
535 : 0 : value[0] = 0x14;
536 : 0 : value[1] = 0x00;
537 : 0 : value[2] = 0x00;
538 : 0 : value[3] = 0x00;
539 : 0 : value[4] = 0x00;
540 : 0 : value[5] = 0x00;
541 : 0 : value[6] = 0x00;
542 : :
543 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
544 : 0 : spin_unlock_irqrestore(&entry->report_lock, flags);
545 : : }
546 : :
547 : : /* Sends autocentering command compatible with Formula Force EX */
548 : 0 : static void lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude)
549 : : {
550 [ # # ]: 0 : struct hid_device *hid = input_get_drvdata(dev);
551 : 0 : struct lg4ff_device_entry *entry;
552 : 0 : struct lg_drv_data *drv_data;
553 : 0 : unsigned long flags;
554 : 0 : s32 *value;
555 : 0 : magnitude = magnitude * 90 / 65535;
556 : :
557 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
558 [ # # ]: 0 : if (!drv_data) {
559 : 0 : hid_err(hid, "Private driver data not found!\n");
560 : 0 : return;
561 : : }
562 : :
563 : 0 : entry = drv_data->device_props;
564 [ # # ]: 0 : if (!entry) {
565 : 0 : hid_err(hid, "Device properties not found!\n");
566 : 0 : return;
567 : : }
568 : 0 : value = entry->report->field[0]->value;
569 : :
570 : 0 : spin_lock_irqsave(&entry->report_lock, flags);
571 : 0 : value[0] = 0xfe;
572 : 0 : value[1] = 0x03;
573 : 0 : value[2] = magnitude >> 14;
574 : 0 : value[3] = magnitude >> 14;
575 : 0 : value[4] = magnitude;
576 : 0 : value[5] = 0x00;
577 : 0 : value[6] = 0x00;
578 : :
579 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
580 : 0 : spin_unlock_irqrestore(&entry->report_lock, flags);
581 : : }
582 : :
583 : : /* Sends command to set range compatible with G25/G27/Driving Force GT */
584 : 0 : static void lg4ff_set_range_g25(struct hid_device *hid, u16 range)
585 : : {
586 : 0 : struct lg4ff_device_entry *entry;
587 : 0 : struct lg_drv_data *drv_data;
588 : 0 : unsigned long flags;
589 : 0 : s32 *value;
590 : :
591 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
592 [ # # ]: 0 : if (!drv_data) {
593 : 0 : hid_err(hid, "Private driver data not found!\n");
594 : 0 : return;
595 : : }
596 : :
597 : 0 : entry = drv_data->device_props;
598 [ # # ]: 0 : if (!entry) {
599 : 0 : hid_err(hid, "Device properties not found!\n");
600 : 0 : return;
601 : : }
602 : 0 : value = entry->report->field[0]->value;
603 [ # # ]: 0 : dbg_hid("G25/G27/DFGT: setting range to %u\n", range);
604 : :
605 : 0 : spin_lock_irqsave(&entry->report_lock, flags);
606 : 0 : value[0] = 0xf8;
607 : 0 : value[1] = 0x81;
608 : 0 : value[2] = range & 0x00ff;
609 : 0 : value[3] = (range & 0xff00) >> 8;
610 : 0 : value[4] = 0x00;
611 : 0 : value[5] = 0x00;
612 : 0 : value[6] = 0x00;
613 : :
614 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
615 : 0 : spin_unlock_irqrestore(&entry->report_lock, flags);
616 : : }
617 : :
618 : : /* Sends commands to set range compatible with Driving Force Pro wheel */
619 : 0 : static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range)
620 : : {
621 : 0 : struct lg4ff_device_entry *entry;
622 : 0 : struct lg_drv_data *drv_data;
623 : 0 : unsigned long flags;
624 : 0 : int start_left, start_right, full_range;
625 : 0 : s32 *value;
626 : :
627 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
628 [ # # ]: 0 : if (!drv_data) {
629 : 0 : hid_err(hid, "Private driver data not found!\n");
630 : 0 : return;
631 : : }
632 : :
633 : 0 : entry = drv_data->device_props;
634 [ # # ]: 0 : if (!entry) {
635 : 0 : hid_err(hid, "Device properties not found!\n");
636 : 0 : return;
637 : : }
638 : 0 : value = entry->report->field[0]->value;
639 [ # # ]: 0 : dbg_hid("Driving Force Pro: setting range to %u\n", range);
640 : :
641 : : /* Prepare "coarse" limit command */
642 : 0 : spin_lock_irqsave(&entry->report_lock, flags);
643 : 0 : value[0] = 0xf8;
644 : 0 : value[1] = 0x00; /* Set later */
645 : 0 : value[2] = 0x00;
646 : 0 : value[3] = 0x00;
647 : 0 : value[4] = 0x00;
648 : 0 : value[5] = 0x00;
649 : 0 : value[6] = 0x00;
650 : :
651 [ # # ]: 0 : if (range > 200) {
652 : 0 : value[1] = 0x03;
653 : 0 : full_range = 900;
654 : : } else {
655 : 0 : value[1] = 0x02;
656 : 0 : full_range = 200;
657 : : }
658 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
659 : :
660 : : /* Prepare "fine" limit command */
661 : 0 : value[0] = 0x81;
662 : 0 : value[1] = 0x0b;
663 : 0 : value[2] = 0x00;
664 : 0 : value[3] = 0x00;
665 : 0 : value[4] = 0x00;
666 : 0 : value[5] = 0x00;
667 : 0 : value[6] = 0x00;
668 : :
669 [ # # ]: 0 : if (range == 200 || range == 900) { /* Do not apply any fine limit */
670 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
671 : 0 : spin_unlock_irqrestore(&entry->report_lock, flags);
672 : 0 : return;
673 : : }
674 : :
675 : : /* Construct fine limit command */
676 : 0 : start_left = (((full_range - range + 1) * 2047) / full_range);
677 : 0 : start_right = 0xfff - start_left;
678 : :
679 : 0 : value[2] = start_left >> 4;
680 : 0 : value[3] = start_right >> 4;
681 : 0 : value[4] = 0xff;
682 : 0 : value[5] = (start_right & 0xe) << 4 | (start_left & 0xe);
683 : 0 : value[6] = 0xff;
684 : :
685 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
686 : 0 : spin_unlock_irqrestore(&entry->report_lock, flags);
687 : : }
688 : :
689 : 0 : static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id)
690 : : {
691 [ # # # # : 0 : switch (real_product_id) {
# # ]
692 : 0 : case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
693 [ # # ]: 0 : switch (target_product_id) {
694 : : case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
695 : : return &lg4ff_mode_switch_ext01_dfp;
696 : : /* DFP can only be switched to its native mode */
697 : 0 : default:
698 : 0 : return NULL;
699 : : }
700 : 0 : break;
701 : 0 : case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
702 [ # # # ]: 0 : switch (target_product_id) {
703 : : case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
704 : : return &lg4ff_mode_switch_ext01_dfp;
705 : 0 : case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
706 : 0 : return &lg4ff_mode_switch_ext16_g25;
707 : : /* G25 can only be switched to DFP mode or its native mode */
708 : 0 : default:
709 : 0 : return NULL;
710 : : }
711 : 0 : break;
712 : 0 : case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
713 [ # # ]: 0 : switch (target_product_id) {
714 : : case USB_DEVICE_ID_LOGITECH_WHEEL:
715 : : return &lg4ff_mode_switch_ext09_dfex;
716 : : case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
717 : : return &lg4ff_mode_switch_ext09_dfp;
718 : : case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
719 : : return &lg4ff_mode_switch_ext09_g25;
720 : : case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
721 : : return &lg4ff_mode_switch_ext09_g27;
722 : : /* G27 can only be switched to DF-EX, DFP, G25 or its native mode */
723 : : default:
724 : : return NULL;
725 : : }
726 : 0 : break;
727 : 0 : case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
728 [ # # # # : 0 : switch (target_product_id) {
# # ]
729 : : case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
730 : : return &lg4ff_mode_switch_ext09_dfp;
731 : 0 : case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
732 : 0 : return &lg4ff_mode_switch_ext09_dfgt;
733 : 0 : case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
734 : 0 : return &lg4ff_mode_switch_ext09_g25;
735 : 0 : case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
736 : 0 : return &lg4ff_mode_switch_ext09_g27;
737 : 0 : case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
738 : 0 : return &lg4ff_mode_switch_ext09_g29;
739 : : /* G29 can only be switched to DF-EX, DFP, DFGT, G25, G27 or its native mode */
740 : 0 : default:
741 : 0 : return NULL;
742 : : }
743 : 0 : break;
744 : 0 : case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
745 [ # # ]: 0 : switch (target_product_id) {
746 : : case USB_DEVICE_ID_LOGITECH_WHEEL:
747 : : return &lg4ff_mode_switch_ext09_dfex;
748 : : case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
749 : : return &lg4ff_mode_switch_ext09_dfp;
750 : : case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
751 : : return &lg4ff_mode_switch_ext09_dfgt;
752 : : /* DFGT can only be switched to DF-EX, DFP or its native mode */
753 : : default:
754 : : return NULL;
755 : : }
756 : : break;
757 : : /* No other wheels have multiple modes */
758 : : default:
759 : : return NULL;
760 : : }
761 : : }
762 : :
763 : 0 : static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s)
764 : : {
765 : 0 : struct lg4ff_device_entry *entry;
766 : 0 : struct lg_drv_data *drv_data;
767 : 0 : unsigned long flags;
768 : 0 : s32 *value;
769 : 0 : u8 i;
770 : :
771 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
772 [ # # ]: 0 : if (!drv_data) {
773 : 0 : hid_err(hid, "Private driver data not found!\n");
774 : 0 : return -EINVAL;
775 : : }
776 : :
777 : 0 : entry = drv_data->device_props;
778 [ # # ]: 0 : if (!entry) {
779 : 0 : hid_err(hid, "Device properties not found!\n");
780 : 0 : return -EINVAL;
781 : : }
782 : 0 : value = entry->report->field[0]->value;
783 : :
784 : 0 : spin_lock_irqsave(&entry->report_lock, flags);
785 [ # # ]: 0 : for (i = 0; i < s->cmd_count; i++) {
786 : : u8 j;
787 : :
788 [ # # ]: 0 : for (j = 0; j < 7; j++)
789 : 0 : value[j] = s->cmd[j + (7*i)];
790 : :
791 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
792 : : }
793 : 0 : spin_unlock_irqrestore(&entry->report_lock, flags);
794 [ # # ]: 0 : hid_hw_wait(hid);
795 : : return 0;
796 : : }
797 : :
798 : 0 : static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf)
799 : : {
800 : 0 : struct hid_device *hid = to_hid_device(dev);
801 : 0 : struct lg4ff_device_entry *entry;
802 : 0 : struct lg_drv_data *drv_data;
803 : 0 : ssize_t count = 0;
804 : 0 : int i;
805 : :
806 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
807 [ # # ]: 0 : if (!drv_data) {
808 : 0 : hid_err(hid, "Private driver data not found!\n");
809 : 0 : return 0;
810 : : }
811 : :
812 : 0 : entry = drv_data->device_props;
813 [ # # ]: 0 : if (!entry) {
814 : 0 : hid_err(hid, "Device properties not found!\n");
815 : 0 : return 0;
816 : : }
817 : :
818 [ # # ]: 0 : if (!entry->wdata.real_name) {
819 : 0 : hid_err(hid, "NULL pointer to string\n");
820 : 0 : return 0;
821 : : }
822 : :
823 [ # # ]: 0 : for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
824 [ # # ]: 0 : if (entry->wdata.alternate_modes & BIT(i)) {
825 : : /* Print tag and full name */
826 : 0 : count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s",
827 : : lg4ff_alternate_modes[i].tag,
828 [ # # ]: 0 : !lg4ff_alternate_modes[i].product_id ? entry->wdata.real_name : lg4ff_alternate_modes[i].name);
829 [ # # ]: 0 : if (count >= PAGE_SIZE - 1)
830 : 0 : return count;
831 : :
832 : : /* Mark the currently active mode with an asterisk */
833 [ # # # # ]: 0 : if (lg4ff_alternate_modes[i].product_id == entry->wdata.product_id ||
834 [ # # ]: 0 : (lg4ff_alternate_modes[i].product_id == 0 && entry->wdata.product_id == entry->wdata.real_product_id))
835 : 0 : count += scnprintf(buf + count, PAGE_SIZE - count, " *\n");
836 : : else
837 : 0 : count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
838 : :
839 [ # # ]: 0 : if (count >= PAGE_SIZE - 1)
840 : 0 : return count;
841 : : }
842 : : }
843 : :
844 : : return count;
845 : : }
846 : :
847 : 0 : static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
848 : : {
849 : 0 : struct hid_device *hid = to_hid_device(dev);
850 : 0 : struct lg4ff_device_entry *entry;
851 : 0 : struct lg_drv_data *drv_data;
852 : 0 : const struct lg4ff_compat_mode_switch *s;
853 : 0 : u16 target_product_id = 0;
854 : 0 : int i, ret;
855 : 0 : char *lbuf;
856 : :
857 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
858 [ # # ]: 0 : if (!drv_data) {
859 : 0 : hid_err(hid, "Private driver data not found!\n");
860 : 0 : return -EINVAL;
861 : : }
862 : :
863 : 0 : entry = drv_data->device_props;
864 [ # # ]: 0 : if (!entry) {
865 : 0 : hid_err(hid, "Device properties not found!\n");
866 : 0 : return -EINVAL;
867 : : }
868 : :
869 : : /* Allow \n at the end of the input parameter */
870 : 0 : lbuf = kasprintf(GFP_KERNEL, "%s", buf);
871 [ # # ]: 0 : if (!lbuf)
872 : : return -ENOMEM;
873 : :
874 : 0 : i = strlen(lbuf);
875 [ # # ]: 0 : if (lbuf[i-1] == '\n') {
876 [ # # ]: 0 : if (i == 1) {
877 : 0 : kfree(lbuf);
878 : 0 : return -EINVAL;
879 : : }
880 : 0 : lbuf[i-1] = '\0';
881 : : }
882 : :
883 [ # # ]: 0 : for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
884 : 0 : const u16 mode_product_id = lg4ff_alternate_modes[i].product_id;
885 : 0 : const char *tag = lg4ff_alternate_modes[i].tag;
886 : :
887 [ # # ]: 0 : if (entry->wdata.alternate_modes & BIT(i)) {
888 [ # # ]: 0 : if (!strcmp(tag, lbuf)) {
889 [ # # ]: 0 : if (!mode_product_id)
890 : 0 : target_product_id = entry->wdata.real_product_id;
891 : : else
892 : : target_product_id = mode_product_id;
893 : : break;
894 : : }
895 : : }
896 : : }
897 : :
898 [ # # ]: 0 : if (i == LG4FF_MODE_MAX_IDX) {
899 : 0 : hid_info(hid, "Requested mode \"%s\" is not supported by the device\n", lbuf);
900 : 0 : kfree(lbuf);
901 : 0 : return -EINVAL;
902 : : }
903 : 0 : kfree(lbuf); /* Not needed anymore */
904 : :
905 [ # # ]: 0 : if (target_product_id == entry->wdata.product_id) /* Nothing to do */
906 : 0 : return count;
907 : :
908 : : /* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */
909 [ # # # # ]: 0 : if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) {
910 : 0 : hid_info(hid, "\"%s\" cannot be switched to \"DF-EX\" mode. Load the \"hid_logitech\" module with \"lg4ff_no_autoswitch=1\" parameter set and try again\n",
911 : : entry->wdata.real_name);
912 : 0 : return -EINVAL;
913 : : }
914 : :
915 : : /* Take care of hardware limitations */
916 [ # # # # ]: 0 : if ((entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) &&
917 : : entry->wdata.product_id > target_product_id) {
918 : 0 : hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->wdata.real_name, lg4ff_alternate_modes[i].name);
919 : 0 : return -EINVAL;
920 : : }
921 : :
922 : 0 : s = lg4ff_get_mode_switch_command(entry->wdata.real_product_id, target_product_id);
923 [ # # ]: 0 : if (!s) {
924 : 0 : hid_err(hid, "Invalid target product ID %X\n", target_product_id);
925 : 0 : return -EINVAL;
926 : : }
927 : :
928 : 0 : ret = lg4ff_switch_compatibility_mode(hid, s);
929 [ # # ]: 0 : return (ret == 0 ? count : ret);
930 : : }
931 : : static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store);
932 : :
933 : 0 : static ssize_t lg4ff_combine_show(struct device *dev, struct device_attribute *attr,
934 : : char *buf)
935 : : {
936 : 0 : struct hid_device *hid = to_hid_device(dev);
937 : 0 : struct lg4ff_device_entry *entry;
938 : 0 : struct lg_drv_data *drv_data;
939 : 0 : size_t count;
940 : :
941 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
942 [ # # ]: 0 : if (!drv_data) {
943 : 0 : hid_err(hid, "Private driver data not found!\n");
944 : 0 : return 0;
945 : : }
946 : :
947 : 0 : entry = drv_data->device_props;
948 [ # # ]: 0 : if (!entry) {
949 : 0 : hid_err(hid, "Device properties not found!\n");
950 : 0 : return 0;
951 : : }
952 : :
953 : 0 : count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.combine);
954 : 0 : return count;
955 : : }
956 : :
957 : 0 : static ssize_t lg4ff_combine_store(struct device *dev, struct device_attribute *attr,
958 : : const char *buf, size_t count)
959 : : {
960 : 0 : struct hid_device *hid = to_hid_device(dev);
961 : 0 : struct lg4ff_device_entry *entry;
962 : 0 : struct lg_drv_data *drv_data;
963 : 0 : u16 combine = simple_strtoul(buf, NULL, 10);
964 : :
965 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
966 [ # # ]: 0 : if (!drv_data) {
967 : 0 : hid_err(hid, "Private driver data not found!\n");
968 : 0 : return -EINVAL;
969 : : }
970 : :
971 : 0 : entry = drv_data->device_props;
972 [ # # ]: 0 : if (!entry) {
973 : 0 : hid_err(hid, "Device properties not found!\n");
974 : 0 : return -EINVAL;
975 : : }
976 : :
977 : 0 : if (combine > 1)
978 : : combine = 1;
979 : :
980 : 0 : entry->wdata.combine = combine;
981 : 0 : return count;
982 : : }
983 : : static DEVICE_ATTR(combine_pedals, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_combine_show, lg4ff_combine_store);
984 : :
985 : : /* Export the currently set range of the wheel */
986 : 0 : static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr,
987 : : char *buf)
988 : : {
989 : 0 : struct hid_device *hid = to_hid_device(dev);
990 : 0 : struct lg4ff_device_entry *entry;
991 : 0 : struct lg_drv_data *drv_data;
992 : 0 : size_t count;
993 : :
994 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
995 [ # # ]: 0 : if (!drv_data) {
996 : 0 : hid_err(hid, "Private driver data not found!\n");
997 : 0 : return 0;
998 : : }
999 : :
1000 : 0 : entry = drv_data->device_props;
1001 [ # # ]: 0 : if (!entry) {
1002 : 0 : hid_err(hid, "Device properties not found!\n");
1003 : 0 : return 0;
1004 : : }
1005 : :
1006 : 0 : count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.range);
1007 : 0 : return count;
1008 : : }
1009 : :
1010 : : /* Set range to user specified value, call appropriate function
1011 : : * according to the type of the wheel */
1012 : 0 : static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr,
1013 : : const char *buf, size_t count)
1014 : : {
1015 : 0 : struct hid_device *hid = to_hid_device(dev);
1016 : 0 : struct lg4ff_device_entry *entry;
1017 : 0 : struct lg_drv_data *drv_data;
1018 : 0 : u16 range = simple_strtoul(buf, NULL, 10);
1019 : :
1020 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
1021 [ # # ]: 0 : if (!drv_data) {
1022 : 0 : hid_err(hid, "Private driver data not found!\n");
1023 : 0 : return -EINVAL;
1024 : : }
1025 : :
1026 : 0 : entry = drv_data->device_props;
1027 [ # # ]: 0 : if (!entry) {
1028 : 0 : hid_err(hid, "Device properties not found!\n");
1029 : 0 : return -EINVAL;
1030 : : }
1031 : :
1032 [ # # ]: 0 : if (range == 0)
1033 : 0 : range = entry->wdata.max_range;
1034 : :
1035 : : /* Check if the wheel supports range setting
1036 : : * and that the range is within limits for the wheel */
1037 [ # # # # : 0 : if (entry->wdata.set_range && range >= entry->wdata.min_range && range <= entry->wdata.max_range) {
# # ]
1038 : 0 : entry->wdata.set_range(hid, range);
1039 : 0 : entry->wdata.range = range;
1040 : : }
1041 : :
1042 : 0 : return count;
1043 : : }
1044 : : static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_range_show, lg4ff_range_store);
1045 : :
1046 : 0 : static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *attr, char *buf)
1047 : : {
1048 : 0 : struct hid_device *hid = to_hid_device(dev);
1049 : 0 : struct lg4ff_device_entry *entry;
1050 : 0 : struct lg_drv_data *drv_data;
1051 : 0 : size_t count;
1052 : :
1053 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
1054 [ # # ]: 0 : if (!drv_data) {
1055 : 0 : hid_err(hid, "Private driver data not found!\n");
1056 : 0 : return 0;
1057 : : }
1058 : :
1059 : 0 : entry = drv_data->device_props;
1060 [ # # ]: 0 : if (!entry) {
1061 : 0 : hid_err(hid, "Device properties not found!\n");
1062 : 0 : return 0;
1063 : : }
1064 : :
1065 [ # # # # ]: 0 : if (!entry->wdata.real_tag || !entry->wdata.real_name) {
1066 : 0 : hid_err(hid, "NULL pointer to string\n");
1067 : 0 : return 0;
1068 : : }
1069 : :
1070 : 0 : count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->wdata.real_tag, entry->wdata.real_name);
1071 : 0 : return count;
1072 : : }
1073 : :
1074 : 0 : static ssize_t lg4ff_real_id_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
1075 : : {
1076 : : /* Real ID is a read-only value */
1077 : 0 : return -EPERM;
1078 : : }
1079 : : static DEVICE_ATTR(real_id, S_IRUGO, lg4ff_real_id_show, lg4ff_real_id_store);
1080 : :
1081 : : #ifdef CONFIG_LEDS_CLASS
1082 : 0 : static void lg4ff_set_leds(struct hid_device *hid, u8 leds)
1083 : : {
1084 : 0 : struct lg_drv_data *drv_data;
1085 : 0 : struct lg4ff_device_entry *entry;
1086 : 0 : unsigned long flags;
1087 : 0 : s32 *value;
1088 : :
1089 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
1090 [ # # ]: 0 : if (!drv_data) {
1091 : 0 : hid_err(hid, "Private driver data not found!\n");
1092 : 0 : return;
1093 : : }
1094 : :
1095 : 0 : entry = drv_data->device_props;
1096 [ # # ]: 0 : if (!entry) {
1097 : 0 : hid_err(hid, "Device properties not found!\n");
1098 : 0 : return;
1099 : : }
1100 : 0 : value = entry->report->field[0]->value;
1101 : :
1102 : 0 : spin_lock_irqsave(&entry->report_lock, flags);
1103 : 0 : value[0] = 0xf8;
1104 : 0 : value[1] = 0x12;
1105 : 0 : value[2] = leds;
1106 : 0 : value[3] = 0x00;
1107 : 0 : value[4] = 0x00;
1108 : 0 : value[5] = 0x00;
1109 : 0 : value[6] = 0x00;
1110 : 0 : hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
1111 : 0 : spin_unlock_irqrestore(&entry->report_lock, flags);
1112 : : }
1113 : :
1114 : 0 : static void lg4ff_led_set_brightness(struct led_classdev *led_cdev,
1115 : : enum led_brightness value)
1116 : : {
1117 : 0 : struct device *dev = led_cdev->dev->parent;
1118 : 0 : struct hid_device *hid = to_hid_device(dev);
1119 [ # # ]: 0 : struct lg_drv_data *drv_data = hid_get_drvdata(hid);
1120 : 0 : struct lg4ff_device_entry *entry;
1121 : 0 : int i, state = 0;
1122 : :
1123 [ # # ]: 0 : if (!drv_data) {
1124 : 0 : hid_err(hid, "Device data not found.");
1125 : 0 : return;
1126 : : }
1127 : :
1128 : 0 : entry = drv_data->device_props;
1129 : :
1130 [ # # ]: 0 : if (!entry) {
1131 : 0 : hid_err(hid, "Device properties not found.");
1132 : 0 : return;
1133 : : }
1134 : :
1135 [ # # ]: 0 : for (i = 0; i < 5; i++) {
1136 [ # # ]: 0 : if (led_cdev != entry->wdata.led[i])
1137 : 0 : continue;
1138 : 0 : state = (entry->wdata.led_state >> i) & 1;
1139 [ # # ]: 0 : if (value == LED_OFF && state) {
1140 : 0 : entry->wdata.led_state &= ~(1 << i);
1141 : 0 : lg4ff_set_leds(hid, entry->wdata.led_state);
1142 [ # # ]: 0 : } else if (value != LED_OFF && !state) {
1143 : 0 : entry->wdata.led_state |= 1 << i;
1144 : 0 : lg4ff_set_leds(hid, entry->wdata.led_state);
1145 : : }
1146 : : break;
1147 : : }
1148 : : }
1149 : :
1150 : 0 : static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cdev)
1151 : : {
1152 : 0 : struct device *dev = led_cdev->dev->parent;
1153 : 0 : struct hid_device *hid = to_hid_device(dev);
1154 [ # # ]: 0 : struct lg_drv_data *drv_data = hid_get_drvdata(hid);
1155 : 0 : struct lg4ff_device_entry *entry;
1156 : 0 : int i, value = 0;
1157 : :
1158 [ # # ]: 0 : if (!drv_data) {
1159 : 0 : hid_err(hid, "Device data not found.");
1160 : 0 : return LED_OFF;
1161 : : }
1162 : :
1163 : 0 : entry = drv_data->device_props;
1164 : :
1165 [ # # ]: 0 : if (!entry) {
1166 : 0 : hid_err(hid, "Device properties not found.");
1167 : 0 : return LED_OFF;
1168 : : }
1169 : :
1170 [ # # ]: 0 : for (i = 0; i < 5; i++)
1171 [ # # ]: 0 : if (led_cdev == entry->wdata.led[i]) {
1172 : 0 : value = (entry->wdata.led_state >> i) & 1;
1173 : 0 : break;
1174 : : }
1175 : :
1176 [ # # ]: 0 : return value ? LED_FULL : LED_OFF;
1177 : : }
1178 : : #endif
1179 : :
1180 : 0 : static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice)
1181 : : {
1182 : 0 : u32 current_mode;
1183 : 0 : int i;
1184 : :
1185 : : /* identify current mode from USB PID */
1186 [ # # ]: 0 : for (i = 1; i < ARRAY_SIZE(lg4ff_alternate_modes); i++) {
1187 [ # # ]: 0 : dbg_hid("Testing whether PID is %X\n", lg4ff_alternate_modes[i].product_id);
1188 [ # # ]: 0 : if (reported_product_id == lg4ff_alternate_modes[i].product_id)
1189 : : break;
1190 : : }
1191 : :
1192 [ # # ]: 0 : if (i == ARRAY_SIZE(lg4ff_alternate_modes))
1193 : : return 0;
1194 : :
1195 : 0 : current_mode = BIT(i);
1196 : :
1197 [ # # ]: 0 : for (i = 0; i < ARRAY_SIZE(lg4ff_main_checklist); i++) {
1198 : 0 : const u16 mask = lg4ff_main_checklist[i]->mask;
1199 : 0 : const u16 result = lg4ff_main_checklist[i]->result;
1200 : 0 : const u16 real_product_id = lg4ff_main_checklist[i]->real_product_id;
1201 : :
1202 [ # # ]: 0 : if ((current_mode & lg4ff_main_checklist[i]->modes) && \
1203 [ # # ]: 0 : (bcdDevice & mask) == result) {
1204 [ # # ]: 0 : dbg_hid("Found wheel with real PID %X whose reported PID is %X\n", real_product_id, reported_product_id);
1205 : 0 : return real_product_id;
1206 : : }
1207 : : }
1208 : :
1209 : : /* No match found. This is either Driving Force or an unknown
1210 : : * wheel model, do not touch it */
1211 [ # # ]: 0 : dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice);
1212 : : return 0;
1213 : : }
1214 : :
1215 : 0 : static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, const u16 bcdDevice)
1216 : : {
1217 : 0 : const u16 reported_product_id = hid->product;
1218 : 0 : int ret;
1219 : :
1220 : 0 : *real_product_id = lg4ff_identify_multimode_wheel(hid, reported_product_id, bcdDevice);
1221 : : /* Probed wheel is not a multimode wheel */
1222 [ # # ]: 0 : if (!*real_product_id) {
1223 : 0 : *real_product_id = reported_product_id;
1224 [ # # ]: 0 : dbg_hid("Wheel is not a multimode wheel\n");
1225 : 0 : return LG4FF_MMODE_NOT_MULTIMODE;
1226 : : }
1227 : :
1228 : : /* Switch from "Driving Force" mode to native mode automatically.
1229 : : * Otherwise keep the wheel in its current mode */
1230 [ # # # # ]: 0 : if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
1231 : 0 : reported_product_id != *real_product_id &&
1232 [ # # ]: 0 : !lg4ff_no_autoswitch) {
1233 : 0 : const struct lg4ff_compat_mode_switch *s = lg4ff_get_mode_switch_command(*real_product_id, *real_product_id);
1234 : :
1235 [ # # ]: 0 : if (!s) {
1236 : 0 : hid_err(hid, "Invalid product id %X\n", *real_product_id);
1237 : 0 : return LG4FF_MMODE_NOT_MULTIMODE;
1238 : : }
1239 : :
1240 : 0 : ret = lg4ff_switch_compatibility_mode(hid, s);
1241 [ # # ]: 0 : if (ret) {
1242 : : /* Wheel could not have been switched to native mode,
1243 : : * leave it in "Driving Force" mode and continue */
1244 : 0 : hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret);
1245 : 0 : return LG4FF_MMODE_IS_MULTIMODE;
1246 : : }
1247 : : return LG4FF_MMODE_SWITCHED;
1248 : : }
1249 : :
1250 : : return LG4FF_MMODE_IS_MULTIMODE;
1251 : : }
1252 : :
1253 : :
1254 : 0 : int lg4ff_init(struct hid_device *hid)
1255 : : {
1256 : 0 : struct hid_input *hidinput;
1257 : 0 : struct input_dev *dev;
1258 : 0 : struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
1259 : 0 : struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
1260 : 0 : const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
1261 : 0 : const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
1262 : 0 : const struct lg4ff_multimode_wheel *mmode_wheel = NULL;
1263 : 0 : struct lg4ff_device_entry *entry;
1264 : 0 : struct lg_drv_data *drv_data;
1265 : 0 : int error, i, j;
1266 : 0 : int mmode_ret, mmode_idx = -1;
1267 : 0 : u16 real_product_id;
1268 : :
1269 [ # # ]: 0 : if (list_empty(&hid->inputs)) {
1270 : 0 : hid_err(hid, "no inputs found\n");
1271 : 0 : return -ENODEV;
1272 : : }
1273 : 0 : hidinput = list_entry(hid->inputs.next, struct hid_input, list);
1274 : 0 : dev = hidinput->input;
1275 : :
1276 : : /* Check that the report looks ok */
1277 [ # # ]: 0 : if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
1278 : : return -1;
1279 : :
1280 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
1281 [ # # ]: 0 : if (!drv_data) {
1282 : 0 : hid_err(hid, "Cannot add device, private driver data not allocated\n");
1283 : 0 : return -1;
1284 : : }
1285 : 0 : entry = kzalloc(sizeof(*entry), GFP_KERNEL);
1286 [ # # ]: 0 : if (!entry)
1287 : : return -ENOMEM;
1288 : 0 : spin_lock_init(&entry->report_lock);
1289 : 0 : entry->report = report;
1290 : 0 : drv_data->device_props = entry;
1291 : :
1292 : : /* Check if a multimode wheel has been connected and
1293 : : * handle it appropriately */
1294 : 0 : mmode_ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice);
1295 : :
1296 : : /* Wheel has been told to switch to native mode. There is no point in going on
1297 : : * with the initialization as the wheel will do a USB reset when it switches mode
1298 : : */
1299 [ # # ]: 0 : if (mmode_ret == LG4FF_MMODE_SWITCHED)
1300 : : return 0;
1301 [ # # ]: 0 : else if (mmode_ret < 0) {
1302 : 0 : hid_err(hid, "Unable to switch device mode during initialization, errno %d\n", mmode_ret);
1303 : 0 : error = mmode_ret;
1304 : 0 : goto err_init;
1305 : : }
1306 : :
1307 : : /* Check what wheel has been connected */
1308 [ # # ]: 0 : for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) {
1309 [ # # ]: 0 : if (hid->product == lg4ff_devices[i].product_id) {
1310 [ # # ]: 0 : dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id);
1311 : : break;
1312 : : }
1313 : : }
1314 : :
1315 [ # # ]: 0 : if (i == ARRAY_SIZE(lg4ff_devices)) {
1316 : 0 : hid_err(hid, "This device is flagged to be handled by the lg4ff module but this module does not know how to handle it. "
1317 : : "Please report this as a bug to LKML, Simon Wood <simon@mungewell.org> or "
1318 : : "Michal Maly <madcatxster@devoid-pointer.net>\n");
1319 : 0 : error = -1;
1320 : 0 : goto err_init;
1321 : : }
1322 : :
1323 [ # # ]: 0 : if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
1324 [ # # ]: 0 : for (mmode_idx = 0; mmode_idx < ARRAY_SIZE(lg4ff_multimode_wheels); mmode_idx++) {
1325 [ # # ]: 0 : if (real_product_id == lg4ff_multimode_wheels[mmode_idx].product_id)
1326 : : break;
1327 : : }
1328 : :
1329 [ # # ]: 0 : if (mmode_idx == ARRAY_SIZE(lg4ff_multimode_wheels)) {
1330 : 0 : hid_err(hid, "Device product ID %X is not listed as a multimode wheel", real_product_id);
1331 : 0 : error = -1;
1332 : 0 : goto err_init;
1333 : : }
1334 : : }
1335 : :
1336 : : /* Set supported force feedback capabilities */
1337 [ # # ]: 0 : for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++)
1338 : 0 : set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit);
1339 : :
1340 : 0 : error = input_ff_create_memless(dev, NULL, lg4ff_play);
1341 : :
1342 [ # # ]: 0 : if (error)
1343 : 0 : goto err_init;
1344 : :
1345 : : /* Initialize device properties */
1346 [ # # ]: 0 : if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
1347 [ # # ]: 0 : BUG_ON(mmode_idx == -1);
1348 : 0 : mmode_wheel = &lg4ff_multimode_wheels[mmode_idx];
1349 : : }
1350 : 0 : lg4ff_init_wheel_data(&entry->wdata, &lg4ff_devices[i], mmode_wheel, real_product_id);
1351 : :
1352 : : /* Check if autocentering is available and
1353 : : * set the centering force to zero by default */
1354 [ # # ]: 0 : if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
1355 : : /* Formula Force EX expects different autocentering command */
1356 [ # # # # ]: 0 : if ((bcdDevice >> 8) == LG4FF_FFEX_REV_MAJ &&
1357 : : (bcdDevice & 0xff) == LG4FF_FFEX_REV_MIN)
1358 : 0 : dev->ff->set_autocenter = lg4ff_set_autocenter_ffex;
1359 : : else
1360 : 0 : dev->ff->set_autocenter = lg4ff_set_autocenter_default;
1361 : :
1362 : 0 : dev->ff->set_autocenter(dev, 0);
1363 : : }
1364 : :
1365 : : /* Create sysfs interface */
1366 : 0 : error = device_create_file(&hid->dev, &dev_attr_combine_pedals);
1367 [ # # ]: 0 : if (error)
1368 : 0 : hid_warn(hid, "Unable to create sysfs interface for \"combine\", errno %d\n", error);
1369 : 0 : error = device_create_file(&hid->dev, &dev_attr_range);
1370 [ # # ]: 0 : if (error)
1371 : 0 : hid_warn(hid, "Unable to create sysfs interface for \"range\", errno %d\n", error);
1372 [ # # ]: 0 : if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
1373 : 0 : error = device_create_file(&hid->dev, &dev_attr_real_id);
1374 [ # # ]: 0 : if (error)
1375 : 0 : hid_warn(hid, "Unable to create sysfs interface for \"real_id\", errno %d\n", error);
1376 : 0 : error = device_create_file(&hid->dev, &dev_attr_alternate_modes);
1377 [ # # ]: 0 : if (error)
1378 : 0 : hid_warn(hid, "Unable to create sysfs interface for \"alternate_modes\", errno %d\n", error);
1379 : : }
1380 [ # # ]: 0 : dbg_hid("sysfs interface created\n");
1381 : :
1382 : : /* Set the maximum range to start with */
1383 : 0 : entry->wdata.range = entry->wdata.max_range;
1384 [ # # ]: 0 : if (entry->wdata.set_range)
1385 : 0 : entry->wdata.set_range(hid, entry->wdata.range);
1386 : :
1387 : : #ifdef CONFIG_LEDS_CLASS
1388 : : /* register led subsystem - G27/G29 only */
1389 : 0 : entry->wdata.led_state = 0;
1390 [ # # ]: 0 : for (j = 0; j < 5; j++)
1391 : 0 : entry->wdata.led[j] = NULL;
1392 : :
1393 [ # # ]: 0 : if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL ||
1394 : : lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G29_WHEEL) {
1395 : 0 : struct led_classdev *led;
1396 : 0 : size_t name_sz;
1397 : 0 : char *name;
1398 : :
1399 : 0 : lg4ff_set_leds(hid, 0);
1400 : :
1401 [ # # ]: 0 : name_sz = strlen(dev_name(&hid->dev)) + 8;
1402 : :
1403 [ # # ]: 0 : for (j = 0; j < 5; j++) {
1404 : 0 : led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
1405 [ # # ]: 0 : if (!led) {
1406 : 0 : hid_err(hid, "can't allocate memory for LED %d\n", j);
1407 : 0 : goto err_leds;
1408 : : }
1409 : :
1410 : 0 : name = (void *)(&led[1]);
1411 [ # # ]: 0 : snprintf(name, name_sz, "%s::RPM%d", dev_name(&hid->dev), j+1);
1412 : 0 : led->name = name;
1413 : 0 : led->brightness = 0;
1414 : 0 : led->max_brightness = 1;
1415 : 0 : led->brightness_get = lg4ff_led_get_brightness;
1416 : 0 : led->brightness_set = lg4ff_led_set_brightness;
1417 : :
1418 : 0 : entry->wdata.led[j] = led;
1419 : 0 : error = led_classdev_register(&hid->dev, led);
1420 : :
1421 [ # # ]: 0 : if (error) {
1422 : 0 : hid_err(hid, "failed to register LED %d. Aborting.\n", j);
1423 : : err_leds:
1424 : : /* Deregister LEDs (if any) */
1425 [ # # ]: 0 : for (j = 0; j < 5; j++) {
1426 : 0 : led = entry->wdata.led[j];
1427 : 0 : entry->wdata.led[j] = NULL;
1428 [ # # ]: 0 : if (!led)
1429 : 0 : continue;
1430 : 0 : led_classdev_unregister(led);
1431 : 0 : kfree(led);
1432 : : }
1433 : 0 : goto out; /* Let the driver continue without LEDs */
1434 : : }
1435 : : }
1436 : : }
1437 : 0 : out:
1438 : : #endif
1439 : 0 : hid_info(hid, "Force feedback support for Logitech Gaming Wheels\n");
1440 : 0 : return 0;
1441 : :
1442 : 0 : err_init:
1443 : 0 : drv_data->device_props = NULL;
1444 : 0 : kfree(entry);
1445 : 0 : return error;
1446 : : }
1447 : :
1448 : 0 : int lg4ff_deinit(struct hid_device *hid)
1449 : : {
1450 : 0 : struct lg4ff_device_entry *entry;
1451 : 0 : struct lg_drv_data *drv_data;
1452 : :
1453 [ # # ]: 0 : drv_data = hid_get_drvdata(hid);
1454 [ # # ]: 0 : if (!drv_data) {
1455 : 0 : hid_err(hid, "Error while deinitializing device, no private driver data.\n");
1456 : 0 : return -1;
1457 : : }
1458 : 0 : entry = drv_data->device_props;
1459 [ # # ]: 0 : if (!entry)
1460 : 0 : goto out; /* Nothing more to do */
1461 : :
1462 : : /* Multimode devices will have at least the "MODE_NATIVE" bit set */
1463 [ # # ]: 0 : if (entry->wdata.alternate_modes) {
1464 : 0 : device_remove_file(&hid->dev, &dev_attr_real_id);
1465 : 0 : device_remove_file(&hid->dev, &dev_attr_alternate_modes);
1466 : : }
1467 : :
1468 : 0 : device_remove_file(&hid->dev, &dev_attr_combine_pedals);
1469 : 0 : device_remove_file(&hid->dev, &dev_attr_range);
1470 : : #ifdef CONFIG_LEDS_CLASS
1471 : : {
1472 : 0 : int j;
1473 : 0 : struct led_classdev *led;
1474 : :
1475 : : /* Deregister LEDs (if any) */
1476 [ # # ]: 0 : for (j = 0; j < 5; j++) {
1477 : :
1478 : 0 : led = entry->wdata.led[j];
1479 : 0 : entry->wdata.led[j] = NULL;
1480 [ # # ]: 0 : if (!led)
1481 : 0 : continue;
1482 : 0 : led_classdev_unregister(led);
1483 : 0 : kfree(led);
1484 : : }
1485 : : }
1486 : : #endif
1487 : 0 : drv_data->device_props = NULL;
1488 : :
1489 : 0 : kfree(entry);
1490 : 0 : out:
1491 [ # # ]: 0 : dbg_hid("Device successfully unregistered\n");
1492 : : return 0;
1493 : : }
|