Blame view

drivers/hid/hid-lg4ff.c 19.1 KB
32c88cbc3   Simon Wood   HID: Add support ...
1
  /*
640138008   Simon Wood   HID: hid-lg4ff: U...
2
   *  Force feedback support for Logitech Gaming Wheels
32c88cbc3   Simon Wood   HID: Add support ...
3
   *
640138008   Simon Wood   HID: hid-lg4ff: U...
4
5
   *  Including G27, G25, DFP, DFGT, FFEX, Momo, Momo2 &
   *  Speed Force Wireless (WiiWheel)
32c88cbc3   Simon Wood   HID: Add support ...
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
   *
   *  Copyright (c) 2010 Simon Wood <simon@mungewell.org>
   */
  
  /*
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 2 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
   */
  
  
  #include <linux/input.h>
  #include <linux/usb.h>
  #include <linux/hid.h>
  
  #include "usbhid/usbhid.h"
  #include "hid-lg.h"
7362cd228   Michal Malý   HID: lg4ff - Move...
33
  #include "hid-ids.h"
32c88cbc3   Simon Wood   HID: Add support ...
34

96440c8a0   Michal Malý   HID: lg4ff - Add ...
35
36
  #define DFGT_REV_MAJ 0x13
  #define DFGT_REV_MIN 0x22
d991938a8   Simon Wood   HID: hid-lg4ff ad...
37
  #define DFGT2_REV_MIN 0x26
96440c8a0   Michal Malý   HID: lg4ff - Add ...
38
39
40
41
42
43
44
45
  #define DFP_REV_MAJ 0x11
  #define DFP_REV_MIN 0x06
  #define FFEX_REV_MAJ 0x21
  #define FFEX_REV_MIN 0x00
  #define G25_REV_MAJ 0x12
  #define G25_REV_MIN 0x22
  #define G27_REV_MAJ 0x12
  #define G27_REV_MIN 0x38
30bb75d71   Michal Malý   HID: lg4ff - Add ...
46
47
48
49
50
51
52
53
  #define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
  
  static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
  static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range);
  static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf);
  static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
  
  static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IRWXO, lg4ff_range_show, lg4ff_range_store);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
54
  struct lg4ff_device_entry {
2b24a9600   Michal Malý   HID: hid-lg4ff: A...
55
  	__u32 product_id;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
56
57
58
  	__u16 range;
  	__u16 min_range;
  	__u16 max_range;
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
59
60
61
62
  #ifdef CONFIG_LEDS_CLASS
  	__u8  led_state;
  	struct led_classdev *led[5];
  #endif
30bb75d71   Michal Malý   HID: lg4ff - Add ...
63
64
65
  	struct list_head list;
  	void (*set_range)(struct hid_device *hid, u16 range);
  };
7362cd228   Michal Malý   HID: lg4ff - Move...
66
  static const signed short lg4ff_wheel_effects[] = {
32c88cbc3   Simon Wood   HID: Add support ...
67
68
69
70
  	FF_CONSTANT,
  	FF_AUTOCENTER,
  	-1
  };
7362cd228   Michal Malý   HID: lg4ff - Move...
71
72
73
74
75
  struct lg4ff_wheel {
  	const __u32 product_id;
  	const signed short *ff_effects;
  	const __u16 min_range;
  	const __u16 max_range;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
76
  	void (*set_range)(struct hid_device *hid, u16 range);
7362cd228   Michal Malý   HID: lg4ff - Move...
77
78
79
  };
  
  static const struct lg4ff_wheel lg4ff_devices[] = {
30bb75d71   Michal Malý   HID: lg4ff - Add ...
80
81
82
83
84
85
86
87
  	{USB_DEVICE_ID_LOGITECH_WHEEL,       lg4ff_wheel_effects, 40, 270, NULL},
  	{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL,  lg4ff_wheel_effects, 40, 270, NULL},
  	{USB_DEVICE_ID_LOGITECH_DFP_WHEEL,   lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_dfp},
  	{USB_DEVICE_ID_LOGITECH_G25_WHEEL,   lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25},
  	{USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,  lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25},
  	{USB_DEVICE_ID_LOGITECH_G27_WHEEL,   lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25},
  	{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...
88
  };
96440c8a0   Michal Malý   HID: lg4ff - Add ...
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
  struct lg4ff_native_cmd {
  	const __u8 cmd_num;	/* Number of commands to send */
  	const __u8 cmd[];
  };
  
  struct lg4ff_usb_revision {
  	const __u16 rev_maj;
  	const __u16 rev_min;
  	const struct lg4ff_native_cmd *command;
  };
  
  static const struct lg4ff_native_cmd native_dfp = {
  	1,
  	{0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
  };
  
  static const struct lg4ff_native_cmd native_dfgt = {
  	2,
  	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 1st command */
  	 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00}	/* 2nd command */
  };
  
  static const struct lg4ff_native_cmd native_g25 = {
  	1,
  	{0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
  };
  
  static const struct lg4ff_native_cmd native_g27 = {
  	2,
  	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 1st command */
  	 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00}	/* 2nd command */
  };
  
  static const struct lg4ff_usb_revision lg4ff_revs[] = {
  	{DFGT_REV_MAJ, DFGT_REV_MIN, &native_dfgt},	/* Driving Force GT */
d991938a8   Simon Wood   HID: hid-lg4ff ad...
124
  	{DFGT_REV_MAJ, DFGT2_REV_MIN, &native_dfgt},	/* Driving Force GT v2 */
96440c8a0   Michal Malý   HID: lg4ff - Add ...
125
126
127
128
  	{DFP_REV_MAJ,  DFP_REV_MIN,  &native_dfp},	/* Driving Force Pro */
  	{G25_REV_MAJ,  G25_REV_MIN,  &native_g25},	/* G25 */
  	{G27_REV_MAJ,  G27_REV_MIN,  &native_g27},	/* G27 */
  };
2b24a9600   Michal Malý   HID: hid-lg4ff: A...
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
  /* Recalculates X axis value accordingly to currently selected range */
  static __s32 lg4ff_adjust_dfp_x_axis(__s32 value, __u16 range)
  {
  	__u16 max_range;
  	__s32 new_value;
  
  	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,
  			     struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data)
  {
  	struct lg4ff_device_entry *entry = drv_data->device_props;
  	__s32 new_value = 0;
  
  	if (!entry) {
  		hid_err(hid, "Device properties not found");
  		return 0;
  	}
  
  	switch (entry->product_id) {
  	case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
  		switch (usage->code) {
  		case ABS_X:
  			new_value = lg4ff_adjust_dfp_x_axis(value, entry->range);
  			input_event(field->hidinput->input, usage->type, usage->code, new_value);
  			return 1;
  		default:
  			return 0;
  		}
  	default:
  		return 0;
  	}
  }
30bb75d71   Michal Malý   HID: lg4ff - Add ...
178
  static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
32c88cbc3   Simon Wood   HID: Add support ...
179
180
181
182
  {
  	struct hid_device *hid = input_get_drvdata(dev);
  	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);
74479ba86   Michal Malý   HID: hid-lg4ff: M...
183
  	__s32 *value = report->field[0]->value;
32c88cbc3   Simon Wood   HID: Add support ...
184
  	int x;
a80fe5d6e   Michal Malý   HID: lg4ff: Minor...
185
  #define CLAMP(x) do { if (x < 0) x = 0; else if (x > 0xff) x = 0xff; } while (0)
32c88cbc3   Simon Wood   HID: Add support ...
186
187
188
189
190
  
  	switch (effect->type) {
  	case FF_CONSTANT:
  		x = effect->u.ramp.start_level + 0x80;	/* 0x80 is no force */
  		CLAMP(x);
74479ba86   Michal Malý   HID: hid-lg4ff: M...
191
192
193
194
195
196
197
  		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 ...
198

d88142725   Benjamin Tissoires   HID: use hid_hw_r...
199
  		hid_hw_request(hid, report, HID_REQ_SET_REPORT);
32c88cbc3   Simon Wood   HID: Add support ...
200
201
202
203
  		break;
  	}
  	return 0;
  }
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
204
205
206
  /* Sends default autocentering command compatible with
   * all wheels except Formula Force EX */
  static void hid_lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude)
32c88cbc3   Simon Wood   HID: Add support ...
207
208
209
210
  {
  	struct hid_device *hid = input_get_drvdata(dev);
  	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);
74479ba86   Michal Malý   HID: hid-lg4ff: M...
211
  	__s32 *value = report->field[0]->value;
32c88cbc3   Simon Wood   HID: Add support ...
212

74479ba86   Michal Malý   HID: hid-lg4ff: M...
213
214
215
216
217
218
219
  	value[0] = 0xfe;
  	value[1] = 0x0d;
  	value[2] = magnitude >> 13;
  	value[3] = magnitude >> 13;
  	value[4] = magnitude >> 8;
  	value[5] = 0x00;
  	value[6] = 0x00;
32c88cbc3   Simon Wood   HID: Add support ...
220

d88142725   Benjamin Tissoires   HID: use hid_hw_r...
221
  	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
32c88cbc3   Simon Wood   HID: Add support ...
222
  }
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
223
224
225
226
227
228
  /* Sends autocentering command compatible with Formula Force EX */
  static void hid_lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude)
  {
  	struct hid_device *hid = input_get_drvdata(dev);
  	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);
74479ba86   Michal Malý   HID: hid-lg4ff: M...
229
  	__s32 *value = report->field[0]->value;
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
230
  	magnitude = magnitude * 90 / 65535;
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
231

74479ba86   Michal Malý   HID: hid-lg4ff: M...
232
233
234
235
236
237
238
  	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 ...
239

d88142725   Benjamin Tissoires   HID: use hid_hw_r...
240
  	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
241
  }
30bb75d71   Michal Malý   HID: lg4ff - Add ...
242
243
244
245
246
  /* Sends command to set range compatible with G25/G27/Driving Force GT */
  static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range)
  {
  	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);
74479ba86   Michal Malý   HID: hid-lg4ff: M...
247
  	__s32 *value = report->field[0]->value;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
248
249
  	dbg_hid("G25/G27/DFGT: setting range to %u
  ", range);
74479ba86   Michal Malý   HID: hid-lg4ff: M...
250
251
252
253
254
255
256
  	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 ...
257

d88142725   Benjamin Tissoires   HID: use hid_hw_r...
258
  	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
259
260
261
262
263
264
265
266
  }
  
  /* Sends commands to set range compatible with Driving Force Pro wheel */
  static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
  {
  	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);
  	int start_left, start_right, full_range;
74479ba86   Michal Malý   HID: hid-lg4ff: M...
267
  	__s32 *value = report->field[0]->value;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
268
269
270
271
  	dbg_hid("Driving Force Pro: setting range to %u
  ", range);
  
  	/* Prepare "coarse" limit command */
74479ba86   Michal Malý   HID: hid-lg4ff: M...
272
  	value[0] = 0xf8;
a80fe5d6e   Michal Malý   HID: lg4ff: Minor...
273
  	value[1] = 0x00;	/* Set later */
74479ba86   Michal Malý   HID: hid-lg4ff: M...
274
275
276
277
278
  	value[2] = 0x00;
  	value[3] = 0x00;
  	value[4] = 0x00;
  	value[5] = 0x00;
  	value[6] = 0x00;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
279
280
281
282
283
284
285
286
  
  	if (range > 200) {
  		report->field[0]->value[1] = 0x03;
  		full_range = 900;
  	} else {
  		report->field[0]->value[1] = 0x02;
  		full_range = 200;
  	}
d88142725   Benjamin Tissoires   HID: use hid_hw_r...
287
  	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
288
289
  
  	/* Prepare "fine" limit command */
74479ba86   Michal Malý   HID: hid-lg4ff: M...
290
291
292
293
294
295
296
  	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 ...
297
298
  
  	if (range == 200 || range == 900) {	/* Do not apply any fine limit */
d88142725   Benjamin Tissoires   HID: use hid_hw_r...
299
  		hid_hw_request(hid, report, HID_REQ_SET_REPORT);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
300
301
302
303
304
305
  		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...
306
307
308
309
310
  	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 ...
311

d88142725   Benjamin Tissoires   HID: use hid_hw_r...
312
  	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
313
  }
96440c8a0   Michal Malý   HID: lg4ff - Add ...
314
315
316
317
318
319
320
321
322
323
  static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd)
  {
  	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);
  	__u8 i, j;
  
  	j = 0;
  	while (j < 7*cmd->cmd_num) {
  		for (i = 0; i < 7; i++)
  			report->field[0]->value[i] = cmd->cmd[j++];
d88142725   Benjamin Tissoires   HID: use hid_hw_r...
324
  		hid_hw_request(hid, report, HID_REQ_SET_REPORT);
96440c8a0   Michal Malý   HID: lg4ff - Add ...
325
326
  	}
  }
32c88cbc3   Simon Wood   HID: Add support ...
327

30bb75d71   Michal Malý   HID: lg4ff - Add ...
328
329
330
  /* Read current range and display it in terminal */
  static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf)
  {
30bb75d71   Michal Malý   HID: lg4ff - Add ...
331
  	struct hid_device *hid = to_hid_device(dev);
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
332
333
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
334
  	size_t count;
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
335
336
337
338
339
  	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 ...
340
  	}
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
341
342
343
344
345
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
30bb75d71   Michal Malý   HID: lg4ff - Add ...
346
347
348
349
350
351
352
353
354
355
356
357
  		return 0;
  	}
  
  	count = scnprintf(buf, PAGE_SIZE, "%u
  ", entry->range);
  	return count;
  }
  
  /* Set range to user specified value, call appropriate function
   * according to the type of the wheel */
  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 ...
358
  	struct hid_device *hid = to_hid_device(dev);
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
359
360
  	struct lg4ff_device_entry *entry;
  	struct lg_drv_data *drv_data;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
361
  	__u16 range = simple_strtoul(buf, NULL, 10);
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
362
363
364
365
366
  	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 ...
367
  	}
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
368
369
370
371
372
373
  
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Device properties not found!
  ");
  		return 0;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
374
375
376
377
378
379
380
381
382
383
384
385
386
387
  	}
  
  	if (range == 0)
  		range = entry->max_range;
  
  	/* Check if the wheel supports range setting
  	 * and that the range is within limits for the wheel */
  	if (entry->set_range != NULL && range >= entry->min_range && range <= entry->max_range) {
  		entry->set_range(hid, range);
  		entry->range = range;
  	}
  
  	return count;
  }
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
388
389
390
391
392
  #ifdef CONFIG_LEDS_CLASS
  static void lg4ff_set_leds(struct hid_device *hid, __u8 leds)
  {
  	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);
74479ba86   Michal Malý   HID: hid-lg4ff: M...
393
394
395
396
397
398
399
400
401
  	__s32 *value = report->field[0]->value;
  
  	value[0] = 0xf8;
  	value[1] = 0x12;
  	value[2] = leds;
  	value[3] = 0x00;
  	value[4] = 0x00;
  	value[5] = 0x00;
  	value[6] = 0x00;
d88142725   Benjamin Tissoires   HID: use hid_hw_r...
402
  	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
403
404
405
406
407
408
409
  }
  
  static void lg4ff_led_set_brightness(struct led_classdev *led_cdev,
  			enum led_brightness value)
  {
  	struct device *dev = led_cdev->dev->parent;
  	struct hid_device *hid = container_of(dev, struct hid_device, dev);
4629fd160   Axel Lin   HID: lg4ff: Remov...
410
  	struct lg_drv_data *drv_data = hid_get_drvdata(hid);
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
  	struct lg4ff_device_entry *entry;
  	int i, state = 0;
  
  	if (!drv_data) {
  		hid_err(hid, "Device data not found.");
  		return;
  	}
  
  	entry = (struct lg4ff_device_entry *)drv_data->device_props;
  
  	if (!entry) {
  		hid_err(hid, "Device properties not found.");
  		return;
  	}
  
  	for (i = 0; i < 5; i++) {
  		if (led_cdev != entry->led[i])
  			continue;
  		state = (entry->led_state >> i) & 1;
  		if (value == LED_OFF && state) {
  			entry->led_state &= ~(1 << i);
  			lg4ff_set_leds(hid, entry->led_state);
  		} else if (value != LED_OFF && !state) {
  			entry->led_state |= 1 << i;
  			lg4ff_set_leds(hid, entry->led_state);
  		}
  		break;
  	}
  }
  
  static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cdev)
  {
  	struct device *dev = led_cdev->dev->parent;
  	struct hid_device *hid = container_of(dev, struct hid_device, dev);
4629fd160   Axel Lin   HID: lg4ff: Remov...
445
  	struct lg_drv_data *drv_data = hid_get_drvdata(hid);
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
  	struct lg4ff_device_entry *entry;
  	int i, value = 0;
  
  	if (!drv_data) {
  		hid_err(hid, "Device data not found.");
  		return LED_OFF;
  	}
  
  	entry = (struct lg4ff_device_entry *)drv_data->device_props;
  
  	if (!entry) {
  		hid_err(hid, "Device properties not found.");
  		return LED_OFF;
  	}
  
  	for (i = 0; i < 5; i++)
  		if (led_cdev == entry->led[i]) {
  			value = (entry->led_state >> i) & 1;
  			break;
  		}
  
  	return value ? LED_FULL : LED_OFF;
  }
  #endif
32c88cbc3   Simon Wood   HID: Add support ...
470
471
472
473
474
475
476
  int lg4ff_init(struct hid_device *hid)
  {
  	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
  	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
  	struct input_dev *dev = hidinput->input;
  	struct hid_report *report;
  	struct hid_field *field;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
477
  	struct lg4ff_device_entry *entry;
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
478
  	struct lg_drv_data *drv_data;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
479
  	struct usb_device_descriptor *udesc;
7362cd228   Michal Malý   HID: lg4ff - Move...
480
  	int error, i, j;
96440c8a0   Michal Malý   HID: lg4ff - Add ...
481
  	__u16 bcdDevice, rev_maj, rev_min;
32c88cbc3   Simon Wood   HID: Add support ...
482
483
484
  
  	/* Find the report to use */
  	if (list_empty(report_list)) {
4291ee305   Joe Perches   HID: Add and use ...
485
486
  		hid_err(hid, "No output report found
  ");
32c88cbc3   Simon Wood   HID: Add support ...
487
488
489
490
491
492
  		return -1;
  	}
  
  	/* Check that the report looks ok */
  	report = list_entry(report_list->next, struct hid_report, list);
  	if (!report) {
4291ee305   Joe Perches   HID: Add and use ...
493
494
  		hid_err(hid, "NULL output report
  ");
32c88cbc3   Simon Wood   HID: Add support ...
495
496
497
498
499
  		return -1;
  	}
  
  	field = report->field[0];
  	if (!field) {
4291ee305   Joe Perches   HID: Add and use ...
500
501
  		hid_err(hid, "NULL field
  ");
32c88cbc3   Simon Wood   HID: Add support ...
502
503
  		return -1;
  	}
30bb75d71   Michal Malý   HID: lg4ff - Add ...
504

7362cd228   Michal Malý   HID: lg4ff - Move...
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
  	/* 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)) {
  		hid_err(hid, "Device is not supported by lg4ff driver. If you think it should be, consider reporting a bug to"
  			     "LKML, Simon Wood <simon@mungewell.org> or Michal Maly <madcatxster@gmail.com>
  ");
  		return -1;
  	}
32c88cbc3   Simon Wood   HID: Add support ...
520

96440c8a0   Michal Malý   HID: lg4ff - Add ...
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
  	/* Attempt to switch wheel to native mode when applicable */
  	udesc = &(hid_to_usb_dev(hid)->descriptor);
  	if (!udesc) {
  		hid_err(hid, "NULL USB device descriptor
  ");
  		return -1;
  	}
  	bcdDevice = le16_to_cpu(udesc->bcdDevice);
  	rev_maj = bcdDevice >> 8;
  	rev_min = bcdDevice & 0xff;
  
  	if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_WHEEL) {
  		dbg_hid("Generic wheel detected, can it do native?
  ");
  		dbg_hid("USB revision: %2x.%02x
  ", rev_maj, rev_min);
  
  		for (j = 0; j < ARRAY_SIZE(lg4ff_revs); j++) {
  			if (lg4ff_revs[j].rev_maj == rev_maj && lg4ff_revs[j].rev_min == rev_min) {
  				hid_lg4ff_switch_native(hid, lg4ff_revs[j].command);
  				hid_info(hid, "Switched to native mode
  ");
  			}
  		}
  	}
7362cd228   Michal Malý   HID: lg4ff - Move...
546
547
548
  	/* 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 ...
549
550
551
552
553
  
  	error = input_ff_create_memless(dev, NULL, hid_lg4ff_play);
  
  	if (error)
  		return error;
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
554
555
556
  	/* Check if autocentering is available and
  	 * set the centering force to zero by default */
  	if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
74479ba86   Michal Malý   HID: hid-lg4ff: M...
557
  		if (rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN)	/* Formula Force EX expects different autocentering command */
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
558
559
560
561
562
563
  			dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex;
  		else
  			dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default;
  
  		dev->ff->set_autocenter(dev, 0);
  	}
32c88cbc3   Simon Wood   HID: Add support ...
564

3b6b17b73   Michal Malý   HID: lg4ff: Take ...
565
566
567
568
569
570
  	/* Get private driver data */
  	drv_data = hid_get_drvdata(hid);
  	if (!drv_data) {
  		hid_err(hid, "Cannot add device, private driver data not allocated
  ");
  		return -1;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
571
  	}
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
572
  	/* Initialize device properties */
8383c6bf9   Thomas Meyer   HID: hid-lg4ff: C...
573
  	entry = kzalloc(sizeof(struct lg4ff_device_entry), GFP_KERNEL);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
574
  	if (!entry) {
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
575
576
  		hid_err(hid, "Cannot add device, insufficient memory to allocate device properties.
  ");
30bb75d71   Michal Malý   HID: lg4ff - Add ...
577
578
  		return -ENOMEM;
  	}
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
579
  	drv_data->device_props = entry;
2b24a9600   Michal Malý   HID: hid-lg4ff: A...
580
  	entry->product_id = lg4ff_devices[i].product_id;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
581
582
583
  	entry->min_range = lg4ff_devices[i].min_range;
  	entry->max_range = lg4ff_devices[i].max_range;
  	entry->set_range = lg4ff_devices[i].set_range;
30bb75d71   Michal Malý   HID: lg4ff - Add ...
584
585
586
587
588
589
590
591
592
593
594
595
  
  	/* Create sysfs interface */
  	error = device_create_file(&hid->dev, &dev_attr_range);
  	if (error)
  		return error;
  	dbg_hid("sysfs interface created
  ");
  
  	/* Set the maximum range to start with */
  	entry->range = entry->max_range;
  	if (entry->set_range != NULL)
  		entry->set_range(hid, entry->range);
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
  #ifdef CONFIG_LEDS_CLASS
  	/* register led subsystem - G27 only */
  	entry->led_state = 0;
  	for (j = 0; j < 5; j++)
  		entry->led[j] = NULL;
  
  	if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL) {
  		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);
  				goto err;
  			}
  
  			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;
  
  			entry->led[j] = led;
  			error = led_classdev_register(&hid->dev, led);
  
  			if (error) {
  				hid_err(hid, "failed to register LED %d. Aborting.
  ", j);
  err:
  				/* Deregister LEDs (if any) */
  				for (j = 0; j < 5; j++) {
  					led = entry->led[j];
  					entry->led[j] = NULL;
  					if (!led)
  						continue;
  					led_classdev_unregister(led);
  					kfree(led);
  				}
  				goto out;	/* Let the driver continue without LEDs */
  			}
  		}
  	}
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
647
  out:
c6e6dc87b   Jiri Kosina   HID: hid-lg4ff: r...
648
  #endif
640138008   Simon Wood   HID: hid-lg4ff: U...
649
650
  	hid_info(hid, "Force feedback support for Logitech Gaming Wheels
  ");
32c88cbc3   Simon Wood   HID: Add support ...
651
652
  	return 0;
  }
2b24a9600   Michal Malý   HID: hid-lg4ff: A...
653

30bb75d71   Michal Malý   HID: lg4ff - Add ...
654
655
  int lg4ff_deinit(struct hid_device *hid)
  {
30bb75d71   Michal Malý   HID: lg4ff - Add ...
656
  	struct lg4ff_device_entry *entry;
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
657
  	struct lg_drv_data *drv_data;
6a2e176b2   Michal Malý   HID: lg4ff: Remov...
658
  	device_remove_file(&hid->dev, &dev_attr_range);
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
659
660
661
662
663
  	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 ...
664
  	}
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
665
666
667
668
  	entry = drv_data->device_props;
  	if (!entry) {
  		hid_err(hid, "Error while deinitializing device, no device properties data.
  ");
30bb75d71   Michal Malý   HID: lg4ff - Add ...
669
670
  		return -1;
  	}
22bcefdc8   Simon Wood   HID: hid-lg4ff: A...
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
  
  #ifdef CONFIG_LEDS_CLASS
  	{
  		int j;
  		struct led_classdev *led;
  
  		/* Deregister LEDs (if any) */
  		for (j = 0; j < 5; j++) {
  
  			led = entry->led[j];
  			entry->led[j] = NULL;
  			if (!led)
  				continue;
  			led_classdev_unregister(led);
  			kfree(led);
  		}
  	}
  #endif
3b6b17b73   Michal Malý   HID: lg4ff: Take ...
689
690
  	/* Deallocate memory */
  	kfree(entry);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
691

30bb75d71   Michal Malý   HID: lg4ff - Add ...
692
693
694
695
  	dbg_hid("Device successfully unregistered
  ");
  	return 0;
  }