Blame view

drivers/hid/hid-lg4ff.c 41.2 KB
1a59d1b8e   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-or-later
32c88cbc3   Simon Wood   HID: Add support ...
2
  /*
640138008   Simon Wood   HID: hid-lg4ff: U...
3
   *  Force feedback support for Logitech Gaming Wheels
32c88cbc3   Simon Wood   HID: Add support ...
4
   *
640138008   Simon Wood   HID: hid-lg4ff: U...
5
6
   *  Including G27, G25, DFP, DFGT, FFEX, Momo, Momo2 &
   *  Speed Force Wireless (WiiWheel)
32c88cbc3   Simon Wood   HID: Add support ...
7
8
9
10
11
   *
   *  Copyright (c) 2010 Simon Wood <simon@mungewell.org>
   */
  
  /*
32c88cbc3   Simon Wood   HID: Add support ...
12
13
14
15
16
17
18
19
20
   */
  
  
  #include <linux/input.h>
  #include <linux/usb.h>
  #include <linux/hid.h>
  
  #include "usbhid/usbhid.h"
  #include "hid-lg.h"
a54dc7795   Michal Malý   HID: hid-lg4ff: I...
21
  #include "hid-lg4ff.h"
7362cd228   Michal Malý   HID: lg4ff - Move...
22
  #include "hid-ids.h"
32c88cbc3   Simon Wood   HID: Add support ...
23

b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
24
  #define LG4FF_MMODE_IS_MULTIMODE 0
e7c234496   Michal Malý   HID: hid-lg4ff: I...
25
26
  #define LG4FF_MMODE_SWITCHED 1
  #define LG4FF_MMODE_NOT_MULTIMODE 2
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
27
28
29
30
31
32
  #define LG4FF_MODE_NATIVE_IDX 0
  #define LG4FF_MODE_DFEX_IDX 1
  #define LG4FF_MODE_DFP_IDX 2
  #define LG4FF_MODE_G25_IDX 3
  #define LG4FF_MODE_DFGT_IDX 4
  #define LG4FF_MODE_G27_IDX 5
29fae1c85   Simon Wood   HID: logitech: Ad...
33
34
  #define LG4FF_MODE_G29_IDX 6
  #define LG4FF_MODE_MAX_IDX 7
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
35
36
37
38
39
40
41
  
  #define LG4FF_MODE_NATIVE BIT(LG4FF_MODE_NATIVE_IDX)
  #define LG4FF_MODE_DFEX BIT(LG4FF_MODE_DFEX_IDX)
  #define LG4FF_MODE_DFP BIT(LG4FF_MODE_DFP_IDX)
  #define LG4FF_MODE_G25 BIT(LG4FF_MODE_G25_IDX)
  #define LG4FF_MODE_DFGT BIT(LG4FF_MODE_DFGT_IDX)
  #define LG4FF_MODE_G27 BIT(LG4FF_MODE_G27_IDX)
29fae1c85   Simon Wood   HID: logitech: Ad...
42
  #define LG4FF_MODE_G29 BIT(LG4FF_MODE_G29_IDX)
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
43
44
45
46
47
48
49
50
51
  
  #define LG4FF_DFEX_TAG "DF-EX"
  #define LG4FF_DFEX_NAME "Driving Force / Formula EX"
  #define LG4FF_DFP_TAG "DFP"
  #define LG4FF_DFP_NAME "Driving Force Pro"
  #define LG4FF_G25_TAG "G25"
  #define LG4FF_G25_NAME "G25 Racing Wheel"
  #define LG4FF_G27_TAG "G27"
  #define LG4FF_G27_NAME "G27 Racing Wheel"
29fae1c85   Simon Wood   HID: logitech: Ad...
52
53
  #define LG4FF_G29_TAG "G29"
  #define LG4FF_G29_NAME "G29 Racing Wheel"
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
54
55
  #define LG4FF_DFGT_TAG "DFGT"
  #define LG4FF_DFGT_NAME "Driving Force GT"
e7c234496   Michal Malý   HID: hid-lg4ff: I...
56
57
  #define LG4FF_FFEX_REV_MAJ 0x21
  #define LG4FF_FFEX_REV_MIN 0x00
d0afd8489   Michal Malý   HID: hid-lg4ff: R...
58
59
  static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
  static void lg4ff_set_range_g25(struct hid_device *hid, u16 range);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
60

72529c65a   Michal Malý   HID: hid-lg4ff: M...
61
  struct lg4ff_wheel_data {
5d9d60ad3   Michal Malý   HID: hid-lg4ff: C...
62
  	const u32 product_id;
961af46f8   Simon Wood   HID: hid-logitech...
63
  	u16 combine;
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
64
  	u16 range;
5d9d60ad3   Michal Malý   HID: hid-lg4ff: C...
65
66
  	const u16 min_range;
  	const u16 max_range;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
67
  #ifdef CONFIG_LEDS_CLASS
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
68
  	u8  led_state;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
69
70
  	struct led_classdev *led[5];
  #endif
5d9d60ad3   Michal Malý   HID: hid-lg4ff: C...
71
72
73
74
  	const u32 alternate_modes;
  	const char * const real_tag;
  	const char * const real_name;
  	const u16 real_product_id;
72529c65a   Michal Malý   HID: hid-lg4ff: M...
75

30bb75d71   Michal Malý   HID: lg4ff - Add ...
76
77
  	void (*set_range)(struct hid_device *hid, u16 range);
  };
72529c65a   Michal Malý   HID: hid-lg4ff: M...
78
  struct lg4ff_device_entry {
c918fe781   Michal Malý   HID: hid-lg4ff: P...
79
  	spinlock_t report_lock; /* Protect output HID report */
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
80
  	struct hid_report *report;
72529c65a   Michal Malý   HID: hid-lg4ff: M...
81
82
  	struct lg4ff_wheel_data wdata;
  };
7362cd228   Michal Malý   HID: lg4ff - Move...
83
  static const signed short lg4ff_wheel_effects[] = {
32c88cbc3   Simon Wood   HID: Add support ...
84
85
86
87
  	FF_CONSTANT,
  	FF_AUTOCENTER,
  	-1
  };
e41b3cdaf   Jarrad Whitaker   HID: fix Logitech...
88
89
90
  static const signed short no_wheel_effects[] = {
  	-1
  };
7362cd228   Michal Malý   HID: lg4ff - Move...
91
  struct lg4ff_wheel {
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
92
  	const u32 product_id;
7362cd228   Michal Malý   HID: lg4ff - Move...
93
  	const signed short *ff_effects;
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
94
95
  	const u16 min_range;
  	const u16 max_range;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
96
  	void (*set_range)(struct hid_device *hid, u16 range);
7362cd228   Michal Malý   HID: lg4ff - Move...
97
  };
e7c234496   Michal Malý   HID: hid-lg4ff: I...
98
  struct lg4ff_compat_mode_switch {
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
99
100
  	const u8 cmd_count;	/* Number of commands to send */
  	const u8 cmd[];
e7c234496   Michal Malý   HID: hid-lg4ff: I...
101
102
103
  };
  
  struct lg4ff_wheel_ident_info {
bbec1bd0f   Simon Wood   HID: logitech: Si...
104
  	const u32 modes;
e7c234496   Michal Malý   HID: hid-lg4ff: I...
105
106
107
108
  	const u16 mask;
  	const u16 result;
  	const u16 real_product_id;
  };
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
109
110
111
112
113
114
115
116
117
118
119
120
  struct lg4ff_multimode_wheel {
  	const u16 product_id;
  	const u32 alternate_modes;
  	const char *real_tag;
  	const char *real_name;
  };
  
  struct lg4ff_alternate_mode {
  	const u16 product_id;
  	const char *tag;
  	const char *name;
  };
7362cd228   Michal Malý   HID: lg4ff - Move...
121
  static const struct lg4ff_wheel lg4ff_devices[] = {
e41b3cdaf   Jarrad Whitaker   HID: fix Logitech...
122
  	{USB_DEVICE_ID_LOGITECH_WINGMAN_FG,  no_wheel_effects,    40, 180, NULL},
560bea30f   Simon Wood   HID: hid-logitech...
123
  	{USB_DEVICE_ID_LOGITECH_WINGMAN_FFG, lg4ff_wheel_effects, 40, 180, NULL},
30bb75d71   Michal Malý   HID: lg4ff - Add ...
124
125
  	{USB_DEVICE_ID_LOGITECH_WHEEL,       lg4ff_wheel_effects, 40, 270, NULL},
  	{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL,  lg4ff_wheel_effects, 40, 270, NULL},
d0afd8489   Michal Malý   HID: hid-lg4ff: R...
126
127
128
129
  	{USB_DEVICE_ID_LOGITECH_DFP_WHEEL,   lg4ff_wheel_effects, 40, 900, lg4ff_set_range_dfp},
  	{USB_DEVICE_ID_LOGITECH_G25_WHEEL,   lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
  	{USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,  lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
  	{USB_DEVICE_ID_LOGITECH_G27_WHEEL,   lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
29fae1c85   Simon Wood   HID: logitech: Ad...
130
  	{USB_DEVICE_ID_LOGITECH_G29_WHEEL,   lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
30bb75d71   Michal Malý   HID: lg4ff - Add ...
131
132
  	{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270, NULL},
  	{USB_DEVICE_ID_LOGITECH_WII_WHEEL,   lg4ff_wheel_effects, 40, 270, NULL}
7362cd228   Michal Malý   HID: lg4ff - Move...
133
  };
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
134
135
136
137
138
139
140
141
142
143
144
145
146
  static const struct lg4ff_multimode_wheel lg4ff_multimode_wheels[] = {
  	{USB_DEVICE_ID_LOGITECH_DFP_WHEEL,
  	 LG4FF_MODE_NATIVE | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
  	 LG4FF_DFP_TAG, LG4FF_DFP_NAME},
  	{USB_DEVICE_ID_LOGITECH_G25_WHEEL,
  	 LG4FF_MODE_NATIVE | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
  	 LG4FF_G25_TAG, LG4FF_G25_NAME},
  	{USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,
  	 LG4FF_MODE_NATIVE | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
  	 LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
  	{USB_DEVICE_ID_LOGITECH_G27_WHEEL,
  	 LG4FF_MODE_NATIVE | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
  	 LG4FF_G27_TAG, LG4FF_G27_NAME},
29fae1c85   Simon Wood   HID: logitech: Ad...
147
148
149
  	{USB_DEVICE_ID_LOGITECH_G29_WHEEL,
  	 LG4FF_MODE_NATIVE | LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
  	 LG4FF_G29_TAG, LG4FF_G29_NAME},
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
150
151
152
153
154
155
156
157
  };
  
  static const struct lg4ff_alternate_mode lg4ff_alternate_modes[] = {
  	[LG4FF_MODE_NATIVE_IDX] = {0, "native", ""},
  	[LG4FF_MODE_DFEX_IDX] = {USB_DEVICE_ID_LOGITECH_WHEEL, LG4FF_DFEX_TAG, LG4FF_DFEX_NAME},
  	[LG4FF_MODE_DFP_IDX] = {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, LG4FF_DFP_TAG, LG4FF_DFP_NAME},
  	[LG4FF_MODE_G25_IDX] = {USB_DEVICE_ID_LOGITECH_G25_WHEEL, LG4FF_G25_TAG, LG4FF_G25_NAME},
  	[LG4FF_MODE_DFGT_IDX] = {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
29fae1c85   Simon Wood   HID: logitech: Ad...
158
159
  	[LG4FF_MODE_G27_IDX] = {USB_DEVICE_ID_LOGITECH_G27_WHEEL, LG4FF_G27_TAG, LG4FF_G27_NAME},
  	[LG4FF_MODE_G29_IDX] = {USB_DEVICE_ID_LOGITECH_G29_WHEEL, LG4FF_G29_TAG, LG4FF_G29_NAME},
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
160
  };
e7c234496   Michal Malý   HID: hid-lg4ff: I...
161
162
  /* Multimode wheel identificators */
  static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = {
bbec1bd0f   Simon Wood   HID: logitech: Si...
163
  	LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
e7c234496   Michal Malý   HID: hid-lg4ff: I...
164
165
166
167
168
169
  	0xf000,
  	0x1000,
  	USB_DEVICE_ID_LOGITECH_DFP_WHEEL
  };
  
  static const struct lg4ff_wheel_ident_info lg4ff_g25_ident_info = {
bbec1bd0f   Simon Wood   HID: logitech: Si...
170
  	LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
e7c234496   Michal Malý   HID: hid-lg4ff: I...
171
172
173
174
175
176
  	0xff00,
  	0x1200,
  	USB_DEVICE_ID_LOGITECH_G25_WHEEL
  };
  
  static const struct lg4ff_wheel_ident_info lg4ff_g27_ident_info = {
bbec1bd0f   Simon Wood   HID: logitech: Si...
177
  	LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
e7c234496   Michal Malý   HID: hid-lg4ff: I...
178
179
180
  	0xfff0,
  	0x1230,
  	USB_DEVICE_ID_LOGITECH_G27_WHEEL
96440c8a0   Michal Malý   HID: lg4ff - Add ...
181
  };
e7c234496   Michal Malý   HID: hid-lg4ff: I...
182
  static const struct lg4ff_wheel_ident_info lg4ff_dfgt_ident_info = {
bbec1bd0f   Simon Wood   HID: logitech: Si...
183
  	LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
e7c234496   Michal Malý   HID: hid-lg4ff: I...
184
185
186
  	0xff00,
  	0x1300,
  	USB_DEVICE_ID_LOGITECH_DFGT_WHEEL
96440c8a0   Michal Malý   HID: lg4ff - Add ...
187
  };
29fae1c85   Simon Wood   HID: logitech: Ad...
188
189
190
191
192
193
194
195
196
197
198
199
200
  static const struct lg4ff_wheel_ident_info lg4ff_g29_ident_info = {
  	LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
  	0xfff8,
  	0x1350,
  	USB_DEVICE_ID_LOGITECH_G29_WHEEL
  };
  
  static const struct lg4ff_wheel_ident_info lg4ff_g29_ident_info2 = {
  	LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
  	0xff00,
  	0x8900,
  	USB_DEVICE_ID_LOGITECH_G29_WHEEL
  };
e7c234496   Michal Malý   HID: hid-lg4ff: I...
201
  /* Multimode wheel identification checklists */
bbec1bd0f   Simon Wood   HID: logitech: Si...
202
  static const struct lg4ff_wheel_ident_info *lg4ff_main_checklist[] = {
29fae1c85   Simon Wood   HID: logitech: Ad...
203
204
  	&lg4ff_g29_ident_info,
  	&lg4ff_g29_ident_info2,
bbec1bd0f   Simon Wood   HID: logitech: Si...
205
206
207
208
  	&lg4ff_dfgt_ident_info,
  	&lg4ff_g27_ident_info,
  	&lg4ff_g25_ident_info,
  	&lg4ff_dfp_ident_info
e7c234496   Michal Malý   HID: hid-lg4ff: I...
209
210
211
  };
  
  /* Compatibility mode switching commands */
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
212
213
214
215
216
  /* EXT_CMD9 - Understood by G27 and DFGT */
  static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfex = {
  	2,
  	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
  	 0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00}	/* Switch mode to DF-EX with detach */
96440c8a0   Michal Malý   HID: lg4ff - Add ...
217
  };
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
218
  static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfp = {
96440c8a0   Michal Malý   HID: lg4ff - Add ...
219
  	2,
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
220
221
  	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
  	 0xf8, 0x09, 0x01, 0x01, 0x00, 0x00, 0x00}	/* Switch mode to DFP with detach */
96440c8a0   Michal Malý   HID: lg4ff - Add ...
222
  };
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
223
224
225
226
  static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g25 = {
  	2,
  	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
  	 0xf8, 0x09, 0x02, 0x01, 0x00, 0x00, 0x00}	/* Switch mode to G25 with detach */
96440c8a0   Michal Malý   HID: lg4ff - Add ...
227
  };
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
228
  static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfgt = {
96440c8a0   Michal Malý   HID: lg4ff - Add ...
229
  	2,
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
230
231
232
233
234
235
236
237
238
  	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
  	 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00}	/* Switch mode to DFGT with detach */
  };
  
  static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g27 = {
  	2,
  	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
  	 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00}	/* Switch mode to G27 with detach */
  };
29fae1c85   Simon Wood   HID: logitech: Ad...
239
240
241
242
243
  static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g29 = {
  	2,
  	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
  	 0xf8, 0x09, 0x05, 0x01, 0x01, 0x00, 0x00}	/* Switch mode to G29 with detach */
  };
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
244
245
246
247
248
249
250
251
252
253
  /* EXT_CMD1 - Understood by DFP, G25, G27 and DFGT */
  static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext01_dfp = {
  	1,
  	{0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
  };
  
  /* EXT_CMD16 - Understood by G25 and G27 */
  static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext16_g25 = {
  	1,
  	{0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
96440c8a0   Michal Malý   HID: lg4ff - Add ...
254
  };
2b24a9600   Michal Malý   HID: hid-lg4ff: A...
255
  /* Recalculates X axis value accordingly to currently selected range */
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
256
  static s32 lg4ff_adjust_dfp_x_axis(s32 value, u16 range)
2b24a9600   Michal Malý   HID: hid-lg4ff: A...
257
  {
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
258
259
  	u16 max_range;
  	s32 new_value;
2b24a9600   Michal Malý   HID: hid-lg4ff: A...
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
  
  	if (range == 900)
  		return value;
  	else if (range == 200)
  		return value;
  	else if (range < 200)
  		max_range = 200;
  	else
  		max_range = 900;
  
  	new_value = 8192 + mult_frac(value - 8192, max_range, range);
  	if (new_value < 0)
  		return 0;
  	else if (new_value > 16383)
  		return 16383;
  	else
  		return new_value;
  }
  
  int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
280
  			     struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data)
2b24a9600   Michal Malý   HID: hid-lg4ff: A...
281
282
  {
  	struct lg4ff_device_entry *entry = drv_data->device_props;
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
283
  	s32 new_value = 0;
2b24a9600   Michal Malý   HID: hid-lg4ff: A...
284
285
286
287
288
  
  	if (!entry) {
  		hid_err(hid, "Device properties not found");
  		return 0;
  	}
72529c65a   Michal Malý   HID: hid-lg4ff: M...
289
  	switch (entry->wdata.product_id) {
2b24a9600   Michal Malý   HID: hid-lg4ff: A...
290
291
292
  	case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
  		switch (usage->code) {
  		case ABS_X:
72529c65a   Michal Malý   HID: hid-lg4ff: M...
293
  			new_value = lg4ff_adjust_dfp_x_axis(value, entry->wdata.range);
2b24a9600   Michal Malý   HID: hid-lg4ff: A...
294
295
296
297
298
299
300
301
302
  			input_event(field->hidinput->input, usage->type, usage->code, new_value);
  			return 1;
  		default:
  			return 0;
  		}
  	default:
  		return 0;
  	}
  }
c832f86ef   Simon Wood   HID: hid-logitech...
303
304
305
  int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report,
  		u8 *rd, int size, struct lg_drv_data *drv_data)
  {
b456634b8   Simon Wood   HID: hid-logitech...
306
  	int offset;
c832f86ef   Simon Wood   HID: hid-logitech...
307
308
309
310
311
312
313
314
315
316
317
318
  	struct lg4ff_device_entry *entry = drv_data->device_props;
  
  	if (!entry)
  		return 0;
  
  	/* adjust HID report present combined pedals data */
  	if (entry->wdata.combine) {
  		switch (entry->wdata.product_id) {
  		case USB_DEVICE_ID_LOGITECH_WHEEL:
  			rd[5] = rd[3];
  			rd[6] = 0x7F;
  			return 1;
e41b3cdaf   Jarrad Whitaker   HID: fix Logitech...
319
  		case USB_DEVICE_ID_LOGITECH_WINGMAN_FG:
560bea30f   Simon Wood   HID: hid-logitech...
320
  		case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG:
c832f86ef   Simon Wood   HID: hid-logitech...
321
322
323
324
325
326
327
328
329
  		case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
  		case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
  			rd[4] = rd[3];
  			rd[5] = 0x7F;
  			return 1;
  		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
  			rd[5] = rd[4];
  			rd[6] = 0x7F;
  			return 1;
b456634b8   Simon Wood   HID: hid-logitech...
330
331
332
333
334
335
336
337
338
339
340
  		case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
  		case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
  			offset = 5;
  			break;
  		case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
  		case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
  			offset = 6;
  			break;
  		case USB_DEVICE_ID_LOGITECH_WII_WHEEL:
  			offset = 3;
  			break;
c832f86ef   Simon Wood   HID: hid-logitech...
341
342
343
  		default:
  			return 0;
  		}
b456634b8   Simon Wood   HID: hid-logitech...
344
345
346
347
348
  
  		/* Compute a combined axis when wheel does not supply it */
  		rd[offset] = (0xFF + rd[offset] - rd[offset+1]) >> 1;
  		rd[offset+1] = 0x7F;
  		return 1;
c832f86ef   Simon Wood   HID: hid-logitech...
349
350
351
352
  	}
  
  	return 0;
  }
5d9d60ad3   Michal Malý   HID: hid-lg4ff: C...
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
  static void lg4ff_init_wheel_data(struct lg4ff_wheel_data * const wdata, const struct lg4ff_wheel *wheel,
  				  const struct lg4ff_multimode_wheel *mmode_wheel,
  				  const u16 real_product_id)
  {
  	u32 alternate_modes = 0;
  	const char *real_tag = NULL;
  	const char *real_name = NULL;
  
  	if (mmode_wheel) {
  		alternate_modes = mmode_wheel->alternate_modes;
  		real_tag = mmode_wheel->real_tag;
  		real_name = mmode_wheel->real_name;
  	}
  
  	{
  		struct lg4ff_wheel_data t_wdata =  { .product_id = wheel->product_id,
  						     .real_product_id = real_product_id,
961af46f8   Simon Wood   HID: hid-logitech...
370
  						     .combine = 0,
5d9d60ad3   Michal Malý   HID: hid-lg4ff: C...
371
372
373
374
375
376
377
378
379
380
  						     .min_range = wheel->min_range,
  						     .max_range = wheel->max_range,
  						     .set_range = wheel->set_range,
  						     .alternate_modes = alternate_modes,
  						     .real_tag = real_tag,
  						     .real_name = real_name };
  
  		memcpy(wdata, &t_wdata, sizeof(t_wdata));
  	}
  }
d0afd8489   Michal Malý   HID: hid-lg4ff: R...
381
  static int lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
32c88cbc3   Simon Wood   HID: Add support ...
382
383
  {
  	struct hid_device *hid = input_get_drvdata(dev);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
384
385
386
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
  	unsigned long flags;
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
387
  	s32 *value;
32c88cbc3   Simon Wood   HID: Add support ...
388
  	int x;
c918fe781   Michal Malý   HID: hid-lg4ff: P...
389
390
391
392
393
394
395
396
397
398
399
400
401
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return -EINVAL;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return -EINVAL;
  	}
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
402
  	value = entry->report->field[0]->value;
c918fe781   Michal Malý   HID: hid-lg4ff: P...
403

a80fe5d6e   Michal Malý   HID: lg4ff: Minor...
404
  #define CLAMP(x) do { if (x < 0) x = 0; else if (x > 0xff) x = 0xff; } while (0)
32c88cbc3   Simon Wood   HID: Add support ...
405
406
407
408
409
  
  	switch (effect->type) {
  	case FF_CONSTANT:
  		x = effect->u.ramp.start_level + 0x80;	/* 0x80 is no force */
  		CLAMP(x);
56930e7ab   Simon Wood   HID:hid-lg4ff: en...
410

c918fe781   Michal Malý   HID: hid-lg4ff: P...
411
  		spin_lock_irqsave(&entry->report_lock, flags);
56930e7ab   Simon Wood   HID:hid-lg4ff: en...
412
413
414
415
416
417
418
419
420
  		if (x == 0x80) {
  			/* De-activate force in slot-1*/
  			value[0] = 0x13;
  			value[1] = 0x00;
  			value[2] = 0x00;
  			value[3] = 0x00;
  			value[4] = 0x00;
  			value[5] = 0x00;
  			value[6] = 0x00;
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
421
  			hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
422
  			spin_unlock_irqrestore(&entry->report_lock, flags);
56930e7ab   Simon Wood   HID:hid-lg4ff: en...
423
424
  			return 0;
  		}
74479ba86   Michal Malý   HID: hid-lg4ff: M...
425
426
427
428
429
430
431
  		value[0] = 0x11;	/* Slot 1 */
  		value[1] = 0x08;
  		value[2] = x;
  		value[3] = 0x80;
  		value[4] = 0x00;
  		value[5] = 0x00;
  		value[6] = 0x00;
32c88cbc3   Simon Wood   HID: Add support ...
432

c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
433
  		hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
434
  		spin_unlock_irqrestore(&entry->report_lock, flags);
32c88cbc3   Simon Wood   HID: Add support ...
435
436
437
438
  		break;
  	}
  	return 0;
  }
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
439
440
  /* Sends default autocentering command compatible with
   * all wheels except Formula Force EX */
d0afd8489   Michal Malý   HID: hid-lg4ff: R...
441
  static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude)
32c88cbc3   Simon Wood   HID: Add support ...
442
443
  {
  	struct hid_device *hid = input_get_drvdata(dev);
6cb6d98ab   Colin Ian King   HID: hid-logitech...
444
  	s32 *value;
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
445
  	u32 expand_a, expand_b;
1859762ed   Simon Wood   HID:hid-lg4ff: Co...
446
447
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
c918fe781   Michal Malý   HID: hid-lg4ff: P...
448
  	unsigned long flags;
1859762ed   Simon Wood   HID:hid-lg4ff: Co...
449
450
451
452
453
454
455
456
457
458
459
460
461
462
  
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return;
  	}
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
463
  	value = entry->report->field[0]->value;
f8c231569   Simon Wood   HID:hid-lg4ff: Sc...
464

d2c02da54   Simon Wood   HID:hid-lg4ff: Sw...
465
  	/* De-activate Auto-Center */
c918fe781   Michal Malý   HID: hid-lg4ff: P...
466
  	spin_lock_irqsave(&entry->report_lock, flags);
d2c02da54   Simon Wood   HID:hid-lg4ff: Sw...
467
468
469
470
471
472
473
474
  	if (magnitude == 0) {
  		value[0] = 0xf5;
  		value[1] = 0x00;
  		value[2] = 0x00;
  		value[3] = 0x00;
  		value[4] = 0x00;
  		value[5] = 0x00;
  		value[6] = 0x00;
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
475
  		hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
476
  		spin_unlock_irqrestore(&entry->report_lock, flags);
d2c02da54   Simon Wood   HID:hid-lg4ff: Sw...
477
478
  		return;
  	}
f8c231569   Simon Wood   HID:hid-lg4ff: Sc...
479
480
481
482
483
484
485
  	if (magnitude <= 0xaaaa) {
  		expand_a = 0x0c * magnitude;
  		expand_b = 0x80 * magnitude;
  	} else {
  		expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa);
  		expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa);
  	}
32c88cbc3   Simon Wood   HID: Add support ...
486

1859762ed   Simon Wood   HID:hid-lg4ff: Co...
487
  	/* Adjust for non-MOMO wheels */
72529c65a   Michal Malý   HID: hid-lg4ff: M...
488
  	switch (entry->wdata.product_id) {
1859762ed   Simon Wood   HID:hid-lg4ff: Co...
489
490
491
492
493
494
495
  	case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
  	case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
  		break;
  	default:
  		expand_a = expand_a >> 1;
  		break;
  	}
74479ba86   Michal Malý   HID: hid-lg4ff: M...
496
497
  	value[0] = 0xfe;
  	value[1] = 0x0d;
f8c231569   Simon Wood   HID:hid-lg4ff: Sc...
498
499
500
  	value[2] = expand_a / 0xaaaa;
  	value[3] = expand_a / 0xaaaa;
  	value[4] = expand_b / 0xaaaa;
74479ba86   Michal Malý   HID: hid-lg4ff: M...
501
502
  	value[5] = 0x00;
  	value[6] = 0x00;
32c88cbc3   Simon Wood   HID: Add support ...
503

c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
504
  	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
d2c02da54   Simon Wood   HID:hid-lg4ff: Sw...
505
506
507
508
509
510
511
512
513
  
  	/* Activate Auto-Center */
  	value[0] = 0x14;
  	value[1] = 0x00;
  	value[2] = 0x00;
  	value[3] = 0x00;
  	value[4] = 0x00;
  	value[5] = 0x00;
  	value[6] = 0x00;
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
514
  	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
515
  	spin_unlock_irqrestore(&entry->report_lock, flags);
32c88cbc3   Simon Wood   HID: Add support ...
516
  }
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
517
  /* Sends autocentering command compatible with Formula Force EX */
d0afd8489   Michal Malý   HID: hid-lg4ff: R...
518
  static void lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude)
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
519
520
  {
  	struct hid_device *hid = input_get_drvdata(dev);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
521
522
523
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
  	unsigned long flags;
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
524
  	s32 *value;
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
525
  	magnitude = magnitude * 90 / 65535;
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
526

c918fe781   Michal Malý   HID: hid-lg4ff: P...
527
528
529
530
531
532
533
534
535
536
537
538
539
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return;
  	}
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
540
  	value = entry->report->field[0]->value;
c918fe781   Michal Malý   HID: hid-lg4ff: P...
541
542
  
  	spin_lock_irqsave(&entry->report_lock, flags);
74479ba86   Michal Malý   HID: hid-lg4ff: M...
543
544
545
546
547
548
549
  	value[0] = 0xfe;
  	value[1] = 0x03;
  	value[2] = magnitude >> 14;
  	value[3] = magnitude >> 14;
  	value[4] = magnitude;
  	value[5] = 0x00;
  	value[6] = 0x00;
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
550

c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
551
  	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
552
  	spin_unlock_irqrestore(&entry->report_lock, flags);
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
553
  }
30bb75d71   Michal Malý   HID: lg4ff - Add ...
554
  /* Sends command to set range compatible with G25/G27/Driving Force GT */
d0afd8489   Michal Malý   HID: hid-lg4ff: R...
555
  static void lg4ff_set_range_g25(struct hid_device *hid, u16 range)
30bb75d71   Michal Malý   HID: lg4ff - Add ...
556
  {
c918fe781   Michal Malý   HID: hid-lg4ff: P...
557
558
559
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
  	unsigned long flags;
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
560
  	s32 *value;
74479ba86   Michal Malý   HID: hid-lg4ff: M...
561

c918fe781   Michal Malý   HID: hid-lg4ff: P...
562
563
564
565
566
567
568
569
570
571
572
573
574
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return;
  	}
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
575
  	value = entry->report->field[0]->value;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
576
577
  	dbg_hid("G25/G27/DFGT: setting range to %u
  ", range);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
578
  	spin_lock_irqsave(&entry->report_lock, flags);
74479ba86   Michal Malý   HID: hid-lg4ff: M...
579
580
581
582
583
584
585
  	value[0] = 0xf8;
  	value[1] = 0x81;
  	value[2] = range & 0x00ff;
  	value[3] = (range & 0xff00) >> 8;
  	value[4] = 0x00;
  	value[5] = 0x00;
  	value[6] = 0x00;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
586

c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
587
  	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
588
  	spin_unlock_irqrestore(&entry->report_lock, flags);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
589
590
591
  }
  
  /* Sends commands to set range compatible with Driving Force Pro wheel */
d0afd8489   Michal Malý   HID: hid-lg4ff: R...
592
  static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range)
30bb75d71   Michal Malý   HID: lg4ff - Add ...
593
  {
c918fe781   Michal Malý   HID: hid-lg4ff: P...
594
595
596
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
  	unsigned long flags;
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
597
598
  	int start_left, start_right, full_range;
  	s32 *value;
74479ba86   Michal Malý   HID: hid-lg4ff: M...
599

c918fe781   Michal Malý   HID: hid-lg4ff: P...
600
601
602
603
604
605
606
607
608
609
610
611
612
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return;
  	}
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
613
  	value = entry->report->field[0]->value;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
614
615
616
617
  	dbg_hid("Driving Force Pro: setting range to %u
  ", range);
  
  	/* Prepare "coarse" limit command */
c918fe781   Michal Malý   HID: hid-lg4ff: P...
618
  	spin_lock_irqsave(&entry->report_lock, flags);
74479ba86   Michal Malý   HID: hid-lg4ff: M...
619
  	value[0] = 0xf8;
a80fe5d6e   Michal Malý   HID: lg4ff: Minor...
620
  	value[1] = 0x00;	/* Set later */
74479ba86   Michal Malý   HID: hid-lg4ff: M...
621
622
623
624
625
  	value[2] = 0x00;
  	value[3] = 0x00;
  	value[4] = 0x00;
  	value[5] = 0x00;
  	value[6] = 0x00;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
626
627
  
  	if (range > 200) {
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
628
  		value[1] = 0x03;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
629
630
  		full_range = 900;
  	} else {
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
631
  		value[1] = 0x02;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
632
633
  		full_range = 200;
  	}
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
634
  	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
635
636
  
  	/* Prepare "fine" limit command */
74479ba86   Michal Malý   HID: hid-lg4ff: M...
637
638
639
640
641
642
643
  	value[0] = 0x81;
  	value[1] = 0x0b;
  	value[2] = 0x00;
  	value[3] = 0x00;
  	value[4] = 0x00;
  	value[5] = 0x00;
  	value[6] = 0x00;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
644
645
  
  	if (range == 200 || range == 900) {	/* Do not apply any fine limit */
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
646
  		hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
647
  		spin_unlock_irqrestore(&entry->report_lock, flags);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
648
649
650
651
652
653
  		return;
  	}
  
  	/* Construct fine limit command */
  	start_left = (((full_range - range + 1) * 2047) / full_range);
  	start_right = 0xfff - start_left;
74479ba86   Michal Malý   HID: hid-lg4ff: M...
654
655
656
657
658
  	value[2] = start_left >> 4;
  	value[3] = start_right >> 4;
  	value[4] = 0xff;
  	value[5] = (start_right & 0xe) << 4 | (start_left & 0xe);
  	value[6] = 0xff;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
659

c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
660
  	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
661
  	spin_unlock_irqrestore(&entry->report_lock, flags);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
662
  }
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
  static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id)
  {
  	switch (real_product_id) {
  	case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
  		switch (target_product_id) {
  		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
  			return &lg4ff_mode_switch_ext01_dfp;
  		/* DFP can only be switched to its native mode */
  		default:
  			return NULL;
  		}
  		break;
  	case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
  		switch (target_product_id) {
  		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
  			return &lg4ff_mode_switch_ext01_dfp;
  		case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
  			return &lg4ff_mode_switch_ext16_g25;
  		/* G25 can only be switched to DFP mode or its native mode */
  		default:
  			return NULL;
  		}
  		break;
  	case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
  		switch (target_product_id) {
  		case USB_DEVICE_ID_LOGITECH_WHEEL:
  			return &lg4ff_mode_switch_ext09_dfex;
  		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
  			return &lg4ff_mode_switch_ext09_dfp;
  		case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
  			return &lg4ff_mode_switch_ext09_g25;
  		case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
  			return &lg4ff_mode_switch_ext09_g27;
  		/* G27 can only be switched to DF-EX, DFP, G25 or its native mode */
  		default:
  			return NULL;
  		}
  		break;
29fae1c85   Simon Wood   HID: logitech: Ad...
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
  	case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
  		switch (target_product_id) {
  		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
  			return &lg4ff_mode_switch_ext09_dfp;
  		case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
  			return &lg4ff_mode_switch_ext09_dfgt;
  		case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
  			return &lg4ff_mode_switch_ext09_g25;
  		case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
  			return &lg4ff_mode_switch_ext09_g27;
  		case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
  			return &lg4ff_mode_switch_ext09_g29;
  		/* G29 can only be switched to DF-EX, DFP, DFGT, G25, G27 or its native mode */
  		default:
  			return NULL;
  		}
  		break;
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
  	case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
  		switch (target_product_id) {
  		case USB_DEVICE_ID_LOGITECH_WHEEL:
  			return &lg4ff_mode_switch_ext09_dfex;
  		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
  			return &lg4ff_mode_switch_ext09_dfp;
  		case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
  			return &lg4ff_mode_switch_ext09_dfgt;
  		/* DFGT can only be switched to DF-EX, DFP or its native mode */
  		default:
  			return NULL;
  		}
  		break;
  	/* No other wheels have multiple modes */
  	default:
  		return NULL;
  	}
  }
e7c234496   Michal Malý   HID: hid-lg4ff: I...
736
  static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s)
96440c8a0   Michal Malý   HID: lg4ff - Add ...
737
  {
c918fe781   Michal Malý   HID: hid-lg4ff: P...
738
739
740
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
  	unsigned long flags;
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
741
  	s32 *value;
e7c234496   Michal Malý   HID: hid-lg4ff: I...
742
  	u8 i;
96440c8a0   Michal Malý   HID: lg4ff - Add ...
743

c918fe781   Michal Malý   HID: hid-lg4ff: P...
744
745
746
747
748
749
750
751
752
753
754
755
756
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return -EINVAL;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return -EINVAL;
  	}
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
757
  	value = entry->report->field[0]->value;
c918fe781   Michal Malý   HID: hid-lg4ff: P...
758
759
  
  	spin_lock_irqsave(&entry->report_lock, flags);
e7c234496   Michal Malý   HID: hid-lg4ff: I...
760
  	for (i = 0; i < s->cmd_count; i++) {
c1740d13e   Michal Malý   HID: hid-lg4ff: F...
761
  		u8 j;
96440c8a0   Michal Malý   HID: lg4ff - Add ...
762

c1740d13e   Michal Malý   HID: hid-lg4ff: F...
763
764
  		for (j = 0; j < 7; j++)
  			value[j] = s->cmd[j + (7*i)];
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
765
  		hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
96440c8a0   Michal Malý   HID: lg4ff - Add ...
766
  	}
c918fe781   Michal Malý   HID: hid-lg4ff: P...
767
  	spin_unlock_irqrestore(&entry->report_lock, flags);
c1740d13e   Michal Malý   HID: hid-lg4ff: F...
768
  	hid_hw_wait(hid);
e7c234496   Michal Malý   HID: hid-lg4ff: I...
769
  	return 0;
96440c8a0   Michal Malý   HID: lg4ff - Add ...
770
  }
32c88cbc3   Simon Wood   HID: Add support ...
771

b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
  static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf)
  {
  	struct hid_device *hid = to_hid_device(dev);
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
  	ssize_t count = 0;
  	int i;
  
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return 0;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return 0;
  	}
72529c65a   Michal Malý   HID: hid-lg4ff: M...
793
  	if (!entry->wdata.real_name) {
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
794
795
796
797
798
799
  		hid_err(hid, "NULL pointer to string
  ");
  		return 0;
  	}
  
  	for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
72529c65a   Michal Malý   HID: hid-lg4ff: M...
800
  		if (entry->wdata.alternate_modes & BIT(i)) {
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
801
802
803
  			/* Print tag and full name */
  			count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s",
  					   lg4ff_alternate_modes[i].tag,
72529c65a   Michal Malý   HID: hid-lg4ff: M...
804
  					   !lg4ff_alternate_modes[i].product_id ? entry->wdata.real_name : lg4ff_alternate_modes[i].name);
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
805
806
807
808
  			if (count >= PAGE_SIZE - 1)
  				return count;
  
  			/* Mark the currently active mode with an asterisk */
72529c65a   Michal Malý   HID: hid-lg4ff: M...
809
810
  			if (lg4ff_alternate_modes[i].product_id == entry->wdata.product_id ||
  			    (lg4ff_alternate_modes[i].product_id == 0 && entry->wdata.product_id == entry->wdata.real_product_id))
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
  				count += scnprintf(buf + count, PAGE_SIZE - count, " *
  ");
  			else
  				count += scnprintf(buf + count, PAGE_SIZE - count, "
  ");
  
  			if (count >= PAGE_SIZE - 1)
  				return count;
  		}
  	}
  
  	return count;
  }
  
  static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
  {
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
  	struct hid_device *hid = to_hid_device(dev);
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
  	const struct lg4ff_compat_mode_switch *s;
  	u16 target_product_id = 0;
  	int i, ret;
  	char *lbuf;
  
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return -EINVAL;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return -EINVAL;
  	}
  
  	/* Allow 
   at the end of the input parameter */
  	lbuf = kasprintf(GFP_KERNEL, "%s", buf);
  	if (!lbuf)
  		return -ENOMEM;
  
  	i = strlen(lbuf);
  	if (lbuf[i-1] == '
  ') {
  		if (i == 1) {
  			kfree(lbuf);
  			return -EINVAL;
  		}
  		lbuf[i-1] = '\0';
  	}
  
  	for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
  		const u16 mode_product_id = lg4ff_alternate_modes[i].product_id;
  		const char *tag = lg4ff_alternate_modes[i].tag;
72529c65a   Michal Malý   HID: hid-lg4ff: M...
868
  		if (entry->wdata.alternate_modes & BIT(i)) {
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
869
870
  			if (!strcmp(tag, lbuf)) {
  				if (!mode_product_id)
72529c65a   Michal Malý   HID: hid-lg4ff: M...
871
  					target_product_id = entry->wdata.real_product_id;
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
872
873
874
875
876
877
878
879
880
881
882
883
884
885
  				else
  					target_product_id = mode_product_id;
  				break;
  			}
  		}
  	}
  
  	if (i == LG4FF_MODE_MAX_IDX) {
  		hid_info(hid, "Requested mode \"%s\" is not supported by the device
  ", lbuf);
  		kfree(lbuf);
  		return -EINVAL;
  	}
  	kfree(lbuf); /* Not needed anymore */
72529c65a   Michal Malý   HID: hid-lg4ff: M...
886
  	if (target_product_id == entry->wdata.product_id) /* Nothing to do */
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
887
888
889
890
891
892
  		return count;
  
  	/* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */
  	if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) {
  		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
  ",
72529c65a   Michal Malý   HID: hid-lg4ff: M...
893
  			 entry->wdata.real_name);
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
894
895
896
897
  		return -EINVAL;
  	}
  
  	/* Take care of hardware limitations */
72529c65a   Michal Malý   HID: hid-lg4ff: M...
898
899
900
901
  	if ((entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) &&
  	    entry->wdata.product_id > target_product_id) {
  		hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode
  ", entry->wdata.real_name, lg4ff_alternate_modes[i].name);
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
902
903
  		return -EINVAL;
  	}
72529c65a   Michal Malý   HID: hid-lg4ff: M...
904
  	s = lg4ff_get_mode_switch_command(entry->wdata.real_product_id, target_product_id);
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
905
906
907
908
909
910
911
912
  	if (!s) {
  		hid_err(hid, "Invalid target product ID %X
  ", target_product_id);
  		return -EINVAL;
  	}
  
  	ret = lg4ff_switch_compatibility_mode(hid, s);
  	return (ret == 0 ? count : ret);
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
913
914
  }
  static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store);
961af46f8   Simon Wood   HID: hid-logitech...
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
  static ssize_t lg4ff_combine_show(struct device *dev, struct device_attribute *attr,
  				char *buf)
  {
  	struct hid_device *hid = to_hid_device(dev);
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
  	size_t count;
  
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return 0;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return 0;
  	}
  
  	count = scnprintf(buf, PAGE_SIZE, "%u
  ", entry->wdata.combine);
  	return count;
  }
  
  static ssize_t lg4ff_combine_store(struct device *dev, struct device_attribute *attr,
  				 const char *buf, size_t count)
  {
  	struct hid_device *hid = to_hid_device(dev);
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
  	u16 combine = simple_strtoul(buf, NULL, 10);
  
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return -EINVAL;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return -EINVAL;
  	}
  
  	if (combine > 1)
  		combine = 1;
  
  	entry->wdata.combine = combine;
  	return count;
  }
  static DEVICE_ATTR(combine_pedals, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_combine_show, lg4ff_combine_store);
fbf85e2ad   Michal Malý   HID: hid-lg4ff: R...
971
972
973
  /* Export the currently set range of the wheel */
  static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr,
  				char *buf)
30bb75d71   Michal Malý   HID: lg4ff - Add ...
974
  {
30bb75d71   Michal Malý   HID: lg4ff - Add ...
975
  	struct hid_device *hid = to_hid_device(dev);
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
976
977
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
978
  	size_t count;
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
979
980
981
982
983
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return 0;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
984
  	}
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
985
986
987
988
989
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
30bb75d71   Michal Malý   HID: lg4ff - Add ...
990
991
  		return 0;
  	}
72529c65a   Michal Malý   HID: hid-lg4ff: M...
992
993
  	count = scnprintf(buf, PAGE_SIZE, "%u
  ", entry->wdata.range);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
994
995
996
997
998
  	return count;
  }
  
  /* Set range to user specified value, call appropriate function
   * according to the type of the wheel */
fbf85e2ad   Michal Malý   HID: hid-lg4ff: R...
999
1000
  static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr,
  				 const char *buf, size_t count)
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1001
  {
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1002
  	struct hid_device *hid = to_hid_device(dev);
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
1003
1004
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
1005
  	u16 range = simple_strtoul(buf, NULL, 10);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1006

3b6b17b73   Michal Malý   HID: lg4ff: Take ...
1007
1008
1009
1010
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
29ff66571   Simon Wood   HID: logitech: Pr...
1011
  		return -EINVAL;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1012
  	}
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
1013
1014
1015
1016
1017
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
29ff66571   Simon Wood   HID: logitech: Pr...
1018
  		return -EINVAL;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1019
1020
1021
  	}
  
  	if (range == 0)
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1022
  		range = entry->wdata.max_range;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1023
1024
1025
  
  	/* Check if the wheel supports range setting
  	 * and that the range is within limits for the wheel */
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1026
1027
1028
  	if (entry->wdata.set_range && range >= entry->wdata.min_range && range <= entry->wdata.max_range) {
  		entry->wdata.set_range(hid, range);
  		entry->wdata.range = range;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1029
1030
1031
1032
  	}
  
  	return count;
  }
fbf85e2ad   Michal Malý   HID: hid-lg4ff: R...
1033
  static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_range_show, lg4ff_range_store);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1034

b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
  static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *attr, char *buf)
  {
  	struct hid_device *hid = to_hid_device(dev);
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
  	size_t count;
  
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return 0;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return 0;
  	}
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1055
  	if (!entry->wdata.real_tag || !entry->wdata.real_name) {
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1056
1057
1058
1059
  		hid_err(hid, "NULL pointer to string
  ");
  		return 0;
  	}
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1060
1061
  	count = scnprintf(buf, PAGE_SIZE, "%s: %s
  ", entry->wdata.real_tag, entry->wdata.real_name);
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1062
1063
1064
1065
1066
1067
1068
1069
1070
  	return count;
  }
  
  static ssize_t lg4ff_real_id_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
  {
  	/* Real ID is a read-only value */
  	return -EPERM;
  }
  static DEVICE_ATTR(real_id, S_IRUGO, lg4ff_real_id_show, lg4ff_real_id_store);
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1071
  #ifdef CONFIG_LEDS_CLASS
2a552c30b   Michal Malý   HID: hid-lg4ff: R...
1072
  static void lg4ff_set_leds(struct hid_device *hid, u8 leds)
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1073
  {
c918fe781   Michal Malý   HID: hid-lg4ff: P...
1074
1075
1076
  	struct lg_drv_data *drv_data;
  	struct lg4ff_device_entry *entry;
  	unsigned long flags;
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
1077
  	s32 *value;
74479ba86   Michal Malý   HID: hid-lg4ff: M...
1078

c918fe781   Michal Malý   HID: hid-lg4ff: P...
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Private driver data not found!
  ");
  		return;
  	}
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return;
  	}
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
1092
  	value = entry->report->field[0]->value;
c918fe781   Michal Malý   HID: hid-lg4ff: P...
1093
1094
  
  	spin_lock_irqsave(&entry->report_lock, flags);
74479ba86   Michal Malý   HID: hid-lg4ff: M...
1095
1096
1097
1098
1099
1100
1101
  	value[0] = 0xf8;
  	value[1] = 0x12;
  	value[2] = leds;
  	value[3] = 0x00;
  	value[4] = 0x00;
  	value[5] = 0x00;
  	value[6] = 0x00;
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
1102
  	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
c918fe781   Michal Malý   HID: hid-lg4ff: P...
1103
  	spin_unlock_irqrestore(&entry->report_lock, flags);
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1104
1105
1106
1107
1108
1109
  }
  
  static void lg4ff_led_set_brightness(struct led_classdev *led_cdev,
  			enum led_brightness value)
  {
  	struct device *dev = led_cdev->dev->parent;
ee79a8f84   Geliang Tang   HID: use to_hid_d...
1110
  	struct hid_device *hid = to_hid_device(dev);
4629fd160   Axel Lin   HID: lg4ff: Remov...
1111
  	struct lg_drv_data *drv_data = hid_get_drvdata(hid);
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1112
1113
1114
1115
1116
1117
1118
  	struct lg4ff_device_entry *entry;
  	int i, state = 0;
  
  	if (!drv_data) {
  		hid_err(hid, "Device data not found.");
  		return;
  	}
371a1d9e1   Michal Malý   HID: hid-lg4ff: E...
1119
  	entry = drv_data->device_props;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1120
1121
1122
1123
1124
1125
1126
  
  	if (!entry) {
  		hid_err(hid, "Device properties not found.");
  		return;
  	}
  
  	for (i = 0; i < 5; i++) {
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1127
  		if (led_cdev != entry->wdata.led[i])
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1128
  			continue;
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1129
  		state = (entry->wdata.led_state >> i) & 1;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1130
  		if (value == LED_OFF && state) {
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1131
1132
  			entry->wdata.led_state &= ~(1 << i);
  			lg4ff_set_leds(hid, entry->wdata.led_state);
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1133
  		} else if (value != LED_OFF && !state) {
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1134
1135
  			entry->wdata.led_state |= 1 << i;
  			lg4ff_set_leds(hid, entry->wdata.led_state);
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1136
1137
1138
1139
1140
1141
1142
1143
  		}
  		break;
  	}
  }
  
  static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cdev)
  {
  	struct device *dev = led_cdev->dev->parent;
ee79a8f84   Geliang Tang   HID: use to_hid_d...
1144
  	struct hid_device *hid = to_hid_device(dev);
4629fd160   Axel Lin   HID: lg4ff: Remov...
1145
  	struct lg_drv_data *drv_data = hid_get_drvdata(hid);
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1146
1147
1148
1149
1150
1151
1152
  	struct lg4ff_device_entry *entry;
  	int i, value = 0;
  
  	if (!drv_data) {
  		hid_err(hid, "Device data not found.");
  		return LED_OFF;
  	}
371a1d9e1   Michal Malý   HID: hid-lg4ff: E...
1153
  	entry = drv_data->device_props;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1154
1155
1156
1157
1158
1159
1160
  
  	if (!entry) {
  		hid_err(hid, "Device properties not found.");
  		return LED_OFF;
  	}
  
  	for (i = 0; i < 5; i++)
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1161
1162
  		if (led_cdev == entry->wdata.led[i]) {
  			value = (entry->wdata.led_state >> i) & 1;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1163
1164
1165
1166
1167
1168
  			break;
  		}
  
  	return value ? LED_FULL : LED_OFF;
  }
  #endif
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1169
1170
  static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice)
  {
bbec1bd0f   Simon Wood   HID: logitech: Si...
1171
1172
  	u32 current_mode;
  	int i;
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1173

bbec1bd0f   Simon Wood   HID: logitech: Si...
1174
1175
1176
1177
1178
1179
  	/* identify current mode from USB PID */
  	for (i = 1; i < ARRAY_SIZE(lg4ff_alternate_modes); i++) {
  		dbg_hid("Testing whether PID is %X
  ", lg4ff_alternate_modes[i].product_id);
  		if (reported_product_id == lg4ff_alternate_modes[i].product_id)
  			break;
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1180
  	}
bbec1bd0f   Simon Wood   HID: logitech: Si...
1181
1182
1183
1184
1185
1186
1187
1188
1189
  	if (i == ARRAY_SIZE(lg4ff_alternate_modes))
  		return 0;
  
  	current_mode = BIT(i);
  
  	for (i = 0; i < ARRAY_SIZE(lg4ff_main_checklist); i++) {
  		const u16 mask = lg4ff_main_checklist[i]->mask;
  		const u16 result = lg4ff_main_checklist[i]->result;
  		const u16 real_product_id = lg4ff_main_checklist[i]->real_product_id;
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1190

bbec1bd0f   Simon Wood   HID: logitech: Si...
1191
1192
  		if ((current_mode & lg4ff_main_checklist[i]->modes) && \
  				(bcdDevice & mask) == result) {
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1193
1194
1195
1196
1197
  			dbg_hid("Found wheel with real PID %X whose reported PID is %X
  ", real_product_id, reported_product_id);
  			return real_product_id;
  		}
  	}
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
1198
1199
  	/* No match found. This is either Driving Force or an unknown
  	 * wheel model, do not touch it */
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
  	dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode
  ", bcdDevice);
  	return 0;
  }
  
  static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, const u16 bcdDevice)
  {
  	const u16 reported_product_id = hid->product;
  	int ret;
  
  	*real_product_id = lg4ff_identify_multimode_wheel(hid, reported_product_id, bcdDevice);
  	/* Probed wheel is not a multimode wheel */
  	if (!*real_product_id) {
  		*real_product_id = reported_product_id;
  		dbg_hid("Wheel is not a multimode wheel
  ");
  		return LG4FF_MMODE_NOT_MULTIMODE;
  	}
  
  	/* Switch from "Driving Force" mode to native mode automatically.
  	 * Otherwise keep the wheel in its current mode */
  	if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
a54dc7795   Michal Malý   HID: hid-lg4ff: I...
1222
1223
  	    reported_product_id != *real_product_id &&
  	    !lg4ff_no_autoswitch) {
f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
1224
  		const struct lg4ff_compat_mode_switch *s = lg4ff_get_mode_switch_command(*real_product_id, *real_product_id);
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1225

f31a2de3f   Michal Malý   HID: hid-lg4ff: A...
1226
  		if (!s) {
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1227
1228
  			hid_err(hid, "Invalid product id %X
  ", *real_product_id);
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1229
  			return LG4FF_MMODE_NOT_MULTIMODE;
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1230
1231
1232
1233
1234
1235
1236
1237
  		}
  
  		ret = lg4ff_switch_compatibility_mode(hid, s);
  		if (ret) {
  			/* Wheel could not have been switched to native mode,
  			 * leave it in "Driving Force" mode and continue */
  			hid_err(hid, "Unable to switch wheel mode, errno %d
  ", ret);
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1238
  			return LG4FF_MMODE_IS_MULTIMODE;
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1239
1240
1241
  		}
  		return LG4FF_MMODE_SWITCHED;
  	}
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1242
  	return LG4FF_MMODE_IS_MULTIMODE;
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1243
  }
32c88cbc3   Simon Wood   HID: Add support ...
1244
1245
  int lg4ff_init(struct hid_device *hid)
  {
d9d4b1e46   Alan Stern   HID: Fix assumpti...
1246
1247
  	struct hid_input *hidinput;
  	struct input_dev *dev;
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
1248
1249
  	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
  	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1250
1251
  	const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
  	const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
5d9d60ad3   Michal Malý   HID: hid-lg4ff: C...
1252
  	const struct lg4ff_multimode_wheel *mmode_wheel = NULL;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1253
  	struct lg4ff_device_entry *entry;
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
1254
  	struct lg_drv_data *drv_data;
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1255
1256
  	int error, i, j;
  	int mmode_ret, mmode_idx = -1;
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1257
  	u16 real_product_id;
32c88cbc3   Simon Wood   HID: Add support ...
1258

d9d4b1e46   Alan Stern   HID: Fix assumpti...
1259
1260
1261
1262
1263
1264
1265
  	if (list_empty(&hid->inputs)) {
  		hid_err(hid, "no inputs found
  ");
  		return -ENODEV;
  	}
  	hidinput = list_entry(hid->inputs.next, struct hid_input, list);
  	dev = hidinput->input;
32c88cbc3   Simon Wood   HID: Add support ...
1266
  	/* Check that the report looks ok */
0fb6bd06e   Kees Cook   HID: LG: validate...
1267
  	if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
32c88cbc3   Simon Wood   HID: Add support ...
1268
  		return -1;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1269

72529c65a   Michal Malý   HID: hid-lg4ff: M...
1270
1271
1272
1273
1274
1275
1276
1277
1278
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Cannot add device, private driver data not allocated
  ");
  		return -1;
  	}
  	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
  	if (!entry)
  		return -ENOMEM;
c918fe781   Michal Malý   HID: hid-lg4ff: P...
1279
  	spin_lock_init(&entry->report_lock);
c28abd8c4   Michal Malý   HID: hid-lg4ff: S...
1280
  	entry->report = report;
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1281
  	drv_data->device_props = entry;
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1282
1283
  	/* Check if a multimode wheel has been connected and
  	 * handle it appropriately */
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1284
  	mmode_ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice);
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1285
1286
1287
1288
  
  	/* Wheel has been told to switch to native mode. There is no point in going on
  	 * with the initialization as the wheel will do a USB reset when it switches mode
  	 */
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1289
  	if (mmode_ret == LG4FF_MMODE_SWITCHED)
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1290
  		return 0;
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1291
1292
1293
1294
1295
1296
  	else if (mmode_ret < 0) {
  		hid_err(hid, "Unable to switch device mode during initialization, errno %d
  ", mmode_ret);
  		error = mmode_ret;
  		goto err_init;
  	}
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1297

7362cd228   Michal Malý   HID: lg4ff - Move...
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
  	/* Check what wheel has been connected */
  	for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) {
  		if (hid->product == lg4ff_devices[i].product_id) {
  			dbg_hid("Found compatible device, product ID %04X
  ", lg4ff_devices[i].product_id);
  			break;
  		}
  	}
  
  	if (i == ARRAY_SIZE(lg4ff_devices)) {
9c2a6bd11   Michal Malý   HID: hid-lg4ff: U...
1308
1309
1310
1311
  		hid_err(hid, "This device is flagged to be handled by the lg4ff module but this module does not know how to handle it. "
  			     "Please report this as a bug to LKML, Simon Wood <simon@mungewell.org> or "
  			     "Michal Maly <madcatxster@devoid-pointer.net>
  ");
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1312
1313
  		error = -1;
  		goto err_init;
7362cd228   Michal Malý   HID: lg4ff - Move...
1314
  	}
32c88cbc3   Simon Wood   HID: Add support ...
1315

b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1316
1317
1318
1319
1320
1321
1322
1323
  	if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
  		for (mmode_idx = 0; mmode_idx < ARRAY_SIZE(lg4ff_multimode_wheels); mmode_idx++) {
  			if (real_product_id == lg4ff_multimode_wheels[mmode_idx].product_id)
  				break;
  		}
  
  		if (mmode_idx == ARRAY_SIZE(lg4ff_multimode_wheels)) {
  			hid_err(hid, "Device product ID %X is not listed as a multimode wheel", real_product_id);
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1324
1325
  			error = -1;
  			goto err_init;
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1326
1327
  		}
  	}
7362cd228   Michal Malý   HID: lg4ff - Move...
1328
1329
1330
  	/* Set supported force feedback capabilities */
  	for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++)
  		set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit);
32c88cbc3   Simon Wood   HID: Add support ...
1331

d0afd8489   Michal Malý   HID: hid-lg4ff: R...
1332
  	error = input_ff_create_memless(dev, NULL, lg4ff_play);
32c88cbc3   Simon Wood   HID: Add support ...
1333
1334
  
  	if (error)
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1335
  		goto err_init;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1336

5d9d60ad3   Michal Malý   HID: hid-lg4ff: C...
1337
  	/* Initialize device properties */
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1338
1339
  	if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
  		BUG_ON(mmode_idx == -1);
5d9d60ad3   Michal Malý   HID: hid-lg4ff: C...
1340
  		mmode_wheel = &lg4ff_multimode_wheels[mmode_idx];
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1341
  	}
5d9d60ad3   Michal Malý   HID: hid-lg4ff: C...
1342
  	lg4ff_init_wheel_data(&entry->wdata, &lg4ff_devices[i], mmode_wheel, real_product_id);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1343

114a55cf9   Simon Wood   HID:hid-lg4ff: In...
1344
1345
1346
  	/* Check if autocentering is available and
  	 * set the centering force to zero by default */
  	if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1347
1348
1349
  		/* Formula Force EX expects different autocentering command */
  		if ((bcdDevice >> 8) == LG4FF_FFEX_REV_MAJ &&
  		    (bcdDevice & 0xff) == LG4FF_FFEX_REV_MIN)
d0afd8489   Michal Malý   HID: hid-lg4ff: R...
1350
  			dev->ff->set_autocenter = lg4ff_set_autocenter_ffex;
114a55cf9   Simon Wood   HID:hid-lg4ff: In...
1351
  		else
d0afd8489   Michal Malý   HID: hid-lg4ff: R...
1352
  			dev->ff->set_autocenter = lg4ff_set_autocenter_default;
114a55cf9   Simon Wood   HID:hid-lg4ff: In...
1353
1354
1355
  
  		dev->ff->set_autocenter(dev, 0);
  	}
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1356
  	/* Create sysfs interface */
961af46f8   Simon Wood   HID: hid-logitech...
1357
1358
1359
1360
  	error = device_create_file(&hid->dev, &dev_attr_combine_pedals);
  	if (error)
  		hid_warn(hid, "Unable to create sysfs interface for \"combine\", errno %d
  ", error);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1361
1362
  	error = device_create_file(&hid->dev, &dev_attr_range);
  	if (error)
d61a70ec9   Michal Malý   HID: hid-lg4ff: A...
1363
1364
  		hid_warn(hid, "Unable to create sysfs interface for \"range\", errno %d
  ", error);
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1365
1366
1367
  	if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
  		error = device_create_file(&hid->dev, &dev_attr_real_id);
  		if (error)
d61a70ec9   Michal Malý   HID: hid-lg4ff: A...
1368
1369
  			hid_warn(hid, "Unable to create sysfs interface for \"real_id\", errno %d
  ", error);
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1370
1371
  		error = device_create_file(&hid->dev, &dev_attr_alternate_modes);
  		if (error)
d61a70ec9   Michal Malý   HID: hid-lg4ff: A...
1372
1373
  			hid_warn(hid, "Unable to create sysfs interface for \"alternate_modes\", errno %d
  ", error);
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1374
  	}
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1375
1376
1377
1378
  	dbg_hid("sysfs interface created
  ");
  
  	/* Set the maximum range to start with */
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1379
1380
1381
  	entry->wdata.range = entry->wdata.max_range;
  	if (entry->wdata.set_range)
  		entry->wdata.set_range(hid, entry->wdata.range);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1382

22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1383
  #ifdef CONFIG_LEDS_CLASS
29fae1c85   Simon Wood   HID: logitech: Ad...
1384
  	/* register led subsystem - G27/G29 only */
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1385
  	entry->wdata.led_state = 0;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1386
  	for (j = 0; j < 5; j++)
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1387
  		entry->wdata.led[j] = NULL;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1388

29fae1c85   Simon Wood   HID: logitech: Ad...
1389
1390
  	if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL ||
  			lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G29_WHEEL) {
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
  		struct led_classdev *led;
  		size_t name_sz;
  		char *name;
  
  		lg4ff_set_leds(hid, 0);
  
  		name_sz = strlen(dev_name(&hid->dev)) + 8;
  
  		for (j = 0; j < 5; j++) {
  			led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
  			if (!led) {
  				hid_err(hid, "can't allocate memory for LED %d
  ", j);
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1404
  				goto err_leds;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1405
1406
1407
1408
1409
1410
1411
1412
1413
  			}
  
  			name = (void *)(&led[1]);
  			snprintf(name, name_sz, "%s::RPM%d", dev_name(&hid->dev), j+1);
  			led->name = name;
  			led->brightness = 0;
  			led->max_brightness = 1;
  			led->brightness_get = lg4ff_led_get_brightness;
  			led->brightness_set = lg4ff_led_set_brightness;
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1414
  			entry->wdata.led[j] = led;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1415
1416
1417
1418
1419
  			error = led_classdev_register(&hid->dev, led);
  
  			if (error) {
  				hid_err(hid, "failed to register LED %d. Aborting.
  ", j);
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1420
  err_leds:
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1421
1422
  				/* Deregister LEDs (if any) */
  				for (j = 0; j < 5; j++) {
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1423
1424
  					led = entry->wdata.led[j];
  					entry->wdata.led[j] = NULL;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1425
1426
1427
1428
1429
1430
1431
1432
1433
  					if (!led)
  						continue;
  					led_classdev_unregister(led);
  					kfree(led);
  				}
  				goto out;	/* Let the driver continue without LEDs */
  			}
  		}
  	}
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1434
  out:
c6e6dc87b   Jiri Kosina   HID: hid-lg4ff: r...
1435
  #endif
640138008   Simon Wood   HID: hid-lg4ff: U...
1436
1437
  	hid_info(hid, "Force feedback support for Logitech Gaming Wheels
  ");
32c88cbc3   Simon Wood   HID: Add support ...
1438
  	return 0;
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1439
1440
1441
1442
1443
  
  err_init:
  	drv_data->device_props = NULL;
  	kfree(entry);
  	return error;
32c88cbc3   Simon Wood   HID: Add support ...
1444
  }
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1445
1446
  int lg4ff_deinit(struct hid_device *hid)
  {
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1447
  	struct lg4ff_device_entry *entry;
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
1448
  	struct lg_drv_data *drv_data;
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
1449
1450
1451
1452
1453
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Error while deinitializing device, no private driver data.
  ");
  		return -1;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1454
  	}
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
1455
  	entry = drv_data->device_props;
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1456
1457
  	if (!entry)
  		goto out; /* Nothing more to do */
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1458
  	/* Multimode devices will have at least the "MODE_NATIVE" bit set */
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1459
  	if (entry->wdata.alternate_modes) {
b96d23ec6   Michal Malý   HID: hid-lg4ff: E...
1460
1461
1462
  		device_remove_file(&hid->dev, &dev_attr_real_id);
  		device_remove_file(&hid->dev, &dev_attr_alternate_modes);
  	}
961af46f8   Simon Wood   HID: hid-logitech...
1463
  	device_remove_file(&hid->dev, &dev_attr_combine_pedals);
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1464
  	device_remove_file(&hid->dev, &dev_attr_range);
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1465
1466
1467
1468
1469
1470
1471
  #ifdef CONFIG_LEDS_CLASS
  	{
  		int j;
  		struct led_classdev *led;
  
  		/* Deregister LEDs (if any) */
  		for (j = 0; j < 5; j++) {
72529c65a   Michal Malý   HID: hid-lg4ff: M...
1472
1473
  			led = entry->wdata.led[j];
  			entry->wdata.led[j] = NULL;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1474
1475
1476
1477
1478
1479
1480
  			if (!led)
  				continue;
  			led_classdev_unregister(led);
  			kfree(led);
  		}
  	}
  #endif
b211a6388   Michal Malý   HID: hid-lg4ff: S...
1481
  	drv_data->device_props = NULL;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
1482

3b6b17b73   Michal Malý   HID: lg4ff: Take ...
1483
  	kfree(entry);
e7c234496   Michal Malý   HID: hid-lg4ff: I...
1484
  out:
30bb75d71   Michal Malý   HID: lg4ff - Add ...
1485
1486
1487
1488
  	dbg_hid("Device successfully unregistered
  ");
  	return 0;
  }