Blame view

drivers/hid/hid-lg4ff.c 15.2 KB
32c88cbc3   Simon Wood   HID: Add support ...
1
2
3
4
5
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
  /*
   *  Force feedback support for Logitech Speed Force Wireless
   *
   *  http://wiibrew.org/wiki/Logitech_USB_steering_wheel
   *
   *  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...
32
  #include "hid-ids.h"
32c88cbc3   Simon Wood   HID: Add support ...
33

96440c8a0   Michal Malý   HID: lg4ff - Add ...
34
35
36
37
38
39
40
41
42
43
  #define DFGT_REV_MAJ 0x13
  #define DFGT_REV_MIN 0x22
  #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 ...
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
  #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);
  
  static bool list_inited;
  
  struct lg4ff_device_entry {
  	char  *device_id;	/* Use name in respective kobject structure's address as the ID */
  	__u16 range;
  	__u16 min_range;
  	__u16 max_range;
  	__u8  leds;
  	struct list_head list;
  	void (*set_range)(struct hid_device *hid, u16 range);
  };
  
  static struct lg4ff_device_entry device_list;
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
124
125
126
127
  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 */
  	{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 */
  };
30bb75d71   Michal Malý   HID: lg4ff - Add ...
128
  static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
32c88cbc3   Simon Wood   HID: Add support ...
129
130
131
132
133
134
135
136
137
138
139
140
141
  {
  	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);
  	int x;
  
  #define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff
  
  	switch (effect->type) {
  	case FF_CONSTANT:
  		x = effect->u.ramp.start_level + 0x80;	/* 0x80 is no force */
  		CLAMP(x);
  		report->field[0]->value[0] = 0x11;	/* Slot 1 */
7362cd228   Michal Malý   HID: lg4ff - Move...
142
  		report->field[0]->value[1] = 0x08;
32c88cbc3   Simon Wood   HID: Add support ...
143
  		report->field[0]->value[2] = x;
7362cd228   Michal Malý   HID: lg4ff - Move...
144
  		report->field[0]->value[3] = 0x80;
32c88cbc3   Simon Wood   HID: Add support ...
145
  		report->field[0]->value[4] = 0x00;
7362cd228   Michal Malý   HID: lg4ff - Move...
146
  		report->field[0]->value[5] = 0x00;
32c88cbc3   Simon Wood   HID: Add support ...
147
  		report->field[0]->value[6] = 0x00;
32c88cbc3   Simon Wood   HID: Add support ...
148
149
150
151
152
153
  
  		usbhid_submit_report(hid, report, USB_DIR_OUT);
  		break;
  	}
  	return 0;
  }
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
154
155
156
  /* 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 ...
157
158
159
160
  {
  	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);
32c88cbc3   Simon Wood   HID: Add support ...
161

7362cd228   Michal Malý   HID: lg4ff - Move...
162
163
164
165
166
167
168
  	report->field[0]->value[0] = 0xfe;
  	report->field[0]->value[1] = 0x0d;
  	report->field[0]->value[2] = magnitude >> 13;
  	report->field[0]->value[3] = magnitude >> 13;
  	report->field[0]->value[4] = magnitude >> 8;
  	report->field[0]->value[5] = 0x00;
  	report->field[0]->value[6] = 0x00;
32c88cbc3   Simon Wood   HID: Add support ...
169
170
171
  
  	usbhid_submit_report(hid, report, USB_DIR_OUT);
  }
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
  /* 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);
  	magnitude = magnitude * 90 / 65535;
  	
  
  	report->field[0]->value[0] = 0xfe;
  	report->field[0]->value[1] = 0x03;
  	report->field[0]->value[2] = magnitude >> 14;
  	report->field[0]->value[3] = magnitude >> 14;
  	report->field[0]->value[4] = magnitude;
  	report->field[0]->value[5] = 0x00;
  	report->field[0]->value[6] = 0x00;
  
  	usbhid_submit_report(hid, report, USB_DIR_OUT);
  }
30bb75d71   Michal Malý   HID: lg4ff - Add ...
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
  /* 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);
  	dbg_hid("G25/G27/DFGT: setting range to %u
  ", range);
  
  	report->field[0]->value[0] = 0xf8;
  	report->field[0]->value[1] = 0x81;
  	report->field[0]->value[2] = range & 0x00ff;
  	report->field[0]->value[3] = (range & 0xff00) >> 8;
  	report->field[0]->value[4] = 0x00;
  	report->field[0]->value[5] = 0x00;
  	report->field[0]->value[6] = 0x00;
  
  	usbhid_submit_report(hid, report, USB_DIR_OUT);
  }
  
  /* 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;
  	dbg_hid("Driving Force Pro: setting range to %u
  ", range);
  
  	/* Prepare "coarse" limit command */
  	report->field[0]->value[0] = 0xf8;
  	report->field[0]->value[1] = 0x00; 	/* Set later */
  	report->field[0]->value[2] = 0x00;
  	report->field[0]->value[3] = 0x00;
  	report->field[0]->value[4] = 0x00;
  	report->field[0]->value[5] = 0x00;
  	report->field[0]->value[6] = 0x00;
  
  	if (range > 200) {
  		report->field[0]->value[1] = 0x03;
  		full_range = 900;
  	} else {
  		report->field[0]->value[1] = 0x02;
  		full_range = 200;
  	}
  	usbhid_submit_report(hid, report, USB_DIR_OUT);
  
  	/* Prepare "fine" limit command */
  	report->field[0]->value[0] = 0x81;
  	report->field[0]->value[1] = 0x0b;
  	report->field[0]->value[2] = 0x00;
  	report->field[0]->value[3] = 0x00;
  	report->field[0]->value[4] = 0x00;
  	report->field[0]->value[5] = 0x00;
  	report->field[0]->value[6] = 0x00;
  
  	if (range == 200 || range == 900) {	/* Do not apply any fine limit */
  		usbhid_submit_report(hid, report, USB_DIR_OUT);
  		return;
  	}
  
  	/* Construct fine limit command */
  	start_left = (((full_range - range + 1) * 2047) / full_range);
  	start_right = 0xfff - start_left;
  
  	report->field[0]->value[2] = start_left >> 4;
  	report->field[0]->value[3] = start_right >> 4;
  	report->field[0]->value[4] = 0xff;
  	report->field[0]->value[5] = (start_right & 0xe) << 4 | (start_left & 0xe);
  	report->field[0]->value[6] = 0xff;
  
  	usbhid_submit_report(hid, report, USB_DIR_OUT);
  }
96440c8a0   Michal Malý   HID: lg4ff - Add ...
263
264
265
266
267
268
269
270
271
272
273
274
275
276
  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++];
  
  		usbhid_submit_report(hid, report, USB_DIR_OUT);
  	}
  }
32c88cbc3   Simon Wood   HID: Add support ...
277

30bb75d71   Michal Malý   HID: lg4ff - Add ...
278
279
280
  /* Read current range and display it in terminal */
  static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf)
  {
2bbaf771e   Dan Carpenter   HID: hid-lg4ff: s...
281
  	struct lg4ff_device_entry *uninitialized_var(entry);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
  	struct list_head *h;
  	struct hid_device *hid = to_hid_device(dev);
  	size_t count;
  
  	list_for_each(h, &device_list.list) {
  		entry = list_entry(h, struct lg4ff_device_entry, list);
  		if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0)
  			break;
  	}
  	if (h == &device_list.list) {
  		dbg_hid("Device not found!");
  		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)
  {
2bbaf771e   Dan Carpenter   HID: hid-lg4ff: s...
305
  	struct lg4ff_device_entry *uninitialized_var(entry);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
  	struct list_head *h;
  	struct hid_device *hid = to_hid_device(dev);
  	__u16 range = simple_strtoul(buf, NULL, 10);
  
  	list_for_each(h, &device_list.list) {
  		entry = list_entry(h, struct lg4ff_device_entry, list);
  		if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0)
  			break;
  	}
  	if (h == &device_list.list) {
  		dbg_hid("Device not found!");
  		return count;
  	}
  
  	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;
  }
32c88cbc3   Simon Wood   HID: Add support ...
332
333
334
335
336
337
338
  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 ...
339
340
  	struct lg4ff_device_entry *entry;
  	struct usb_device_descriptor *udesc;
7362cd228   Michal Malý   HID: lg4ff - Move...
341
  	int error, i, j;
96440c8a0   Michal Malý   HID: lg4ff - Add ...
342
  	__u16 bcdDevice, rev_maj, rev_min;
32c88cbc3   Simon Wood   HID: Add support ...
343
344
345
  
  	/* Find the report to use */
  	if (list_empty(report_list)) {
4291ee305   Joe Perches   HID: Add and use ...
346
347
  		hid_err(hid, "No output report found
  ");
32c88cbc3   Simon Wood   HID: Add support ...
348
349
350
351
352
353
  		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 ...
354
355
  		hid_err(hid, "NULL output report
  ");
32c88cbc3   Simon Wood   HID: Add support ...
356
357
358
359
360
  		return -1;
  	}
  
  	field = report->field[0];
  	if (!field) {
4291ee305   Joe Perches   HID: Add and use ...
361
362
  		hid_err(hid, "NULL field
  ");
32c88cbc3   Simon Wood   HID: Add support ...
363
364
  		return -1;
  	}
30bb75d71   Michal Malý   HID: lg4ff - Add ...
365

7362cd228   Michal Malý   HID: lg4ff - Move...
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
  	/* 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 ...
381

96440c8a0   Michal Malý   HID: lg4ff - Add ...
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
  	/* 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...
407
408
409
  	/* 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 ...
410
411
412
413
414
  
  	error = input_ff_create_memless(dev, NULL, hid_lg4ff_play);
  
  	if (error)
  		return error;
6e2de8e0a   Michal Malý   HID: lg4ff - Add ...
415
416
417
418
419
420
421
422
423
424
  	/* Check if autocentering is available and
  	 * set the centering force to zero by default */
  	if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
  		if(rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN)	/* Formula Force EX expects different autocentering command */
  			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 ...
425

30bb75d71   Michal Malý   HID: lg4ff - Add ...
426
427
428
429
430
431
432
  		/* Initialize device_list if this is the first device to handle by lg4ff */
  	if (!list_inited) {
  		INIT_LIST_HEAD(&device_list.list);
  		list_inited = 1;
  	}
  
  	/* Add the device to device_list */
8383c6bf9   Thomas Meyer   HID: hid-lg4ff: C...
433
  	entry = kzalloc(sizeof(struct lg4ff_device_entry), GFP_KERNEL);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
434
435
436
437
438
  	if (!entry) {
  		hid_err(hid, "Cannot add device, insufficient memory.
  ");
  		return -ENOMEM;
  	}
8f2522902   Dan Carpenter   HID: hid-lg4ff: a...
439
  	entry->device_id = kstrdup((&hid->dev)->kobj.name, GFP_KERNEL);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
440
441
442
  	if (!entry->device_id) {
  		hid_err(hid, "Cannot set device_id, insufficient memory.
  ");
8f2522902   Dan Carpenter   HID: hid-lg4ff: a...
443
  		kfree(entry);
30bb75d71   Michal Malý   HID: lg4ff - Add ...
444
445
  		return -ENOMEM;
  	}
30bb75d71   Michal Malý   HID: lg4ff - Add ...
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
  	entry->min_range = lg4ff_devices[i].min_range;
  	entry->max_range = lg4ff_devices[i].max_range;
  	entry->set_range = lg4ff_devices[i].set_range;
  	list_add(&entry->list, &device_list.list);
  
  	/* 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);
4291ee305   Joe Perches   HID: Add and use ...
462
463
  	hid_info(hid, "Force feedback for Logitech Speed Force Wireless by Simon Wood <simon@mungewell.org>
  ");
32c88cbc3   Simon Wood   HID: Add support ...
464
465
  	return 0;
  }
30bb75d71   Michal Malý   HID: lg4ff - Add ...
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
  int lg4ff_deinit(struct hid_device *hid)
  {
  	bool found = 0;
  	struct lg4ff_device_entry *entry;
  	struct list_head *h, *g;
  	list_for_each_safe(h, g, &device_list.list) {
  		entry = list_entry(h, struct lg4ff_device_entry, list);
  		if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0) {
  			list_del(h);
  			kfree(entry->device_id);
  			kfree(entry);
  			found = 1;
  			break;
  		}
  	}
  
  	if (!found) {
  		dbg_hid("Device entry not found!
  ");
  		return -1;
  	}
  
  	device_remove_file(&hid->dev, &dev_attr_range);
  	dbg_hid("Device successfully unregistered
  ");
  	return 0;
  }