diff options
Diffstat (limited to 'drivers/hid/hid-lg4ff.c')
-rw-r--r-- | drivers/hid/hid-lg4ff.c | 403 |
1 files changed, 378 insertions, 25 deletions
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index fa550c8e1d1b..103f30d93f76 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c | |||
@@ -29,19 +29,108 @@ | |||
29 | 29 | ||
30 | #include "usbhid/usbhid.h" | 30 | #include "usbhid/usbhid.h" |
31 | #include "hid-lg.h" | 31 | #include "hid-lg.h" |
32 | #include "hid-ids.h" | ||
32 | 33 | ||
33 | struct lg4ff_device { | 34 | #define DFGT_REV_MAJ 0x13 |
34 | struct hid_report *report; | 35 | #define DFGT_REV_MIN 0x22 |
36 | #define DFP_REV_MAJ 0x11 | ||
37 | #define DFP_REV_MIN 0x06 | ||
38 | #define FFEX_REV_MAJ 0x21 | ||
39 | #define FFEX_REV_MIN 0x00 | ||
40 | #define G25_REV_MAJ 0x12 | ||
41 | #define G25_REV_MIN 0x22 | ||
42 | #define G27_REV_MAJ 0x12 | ||
43 | #define G27_REV_MIN 0x38 | ||
44 | |||
45 | #define to_hid_device(pdev) container_of(pdev, struct hid_device, dev) | ||
46 | |||
47 | static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range); | ||
48 | static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range); | ||
49 | static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf); | ||
50 | static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); | ||
51 | |||
52 | static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IRWXO, lg4ff_range_show, lg4ff_range_store); | ||
53 | |||
54 | static bool list_inited; | ||
55 | |||
56 | struct lg4ff_device_entry { | ||
57 | char *device_id; /* Use name in respective kobject structure's address as the ID */ | ||
58 | __u16 range; | ||
59 | __u16 min_range; | ||
60 | __u16 max_range; | ||
61 | __u8 leds; | ||
62 | struct list_head list; | ||
63 | void (*set_range)(struct hid_device *hid, u16 range); | ||
35 | }; | 64 | }; |
36 | 65 | ||
37 | static const signed short ff4_wheel_ac[] = { | 66 | static struct lg4ff_device_entry device_list; |
67 | |||
68 | static const signed short lg4ff_wheel_effects[] = { | ||
38 | FF_CONSTANT, | 69 | FF_CONSTANT, |
39 | FF_AUTOCENTER, | 70 | FF_AUTOCENTER, |
40 | -1 | 71 | -1 |
41 | }; | 72 | }; |
42 | 73 | ||
43 | static int hid_lg4ff_play(struct input_dev *dev, void *data, | 74 | struct lg4ff_wheel { |
44 | struct ff_effect *effect) | 75 | const __u32 product_id; |
76 | const signed short *ff_effects; | ||
77 | const __u16 min_range; | ||
78 | const __u16 max_range; | ||
79 | void (*set_range)(struct hid_device *hid, u16 range); | ||
80 | }; | ||
81 | |||
82 | static const struct lg4ff_wheel lg4ff_devices[] = { | ||
83 | {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, | ||
84 | {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, | ||
85 | {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_dfp}, | ||
86 | {USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, | ||
87 | {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, | ||
88 | {USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, | ||
89 | {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270, NULL}, | ||
90 | {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL} | ||
91 | }; | ||
92 | |||
93 | struct lg4ff_native_cmd { | ||
94 | const __u8 cmd_num; /* Number of commands to send */ | ||
95 | const __u8 cmd[]; | ||
96 | }; | ||
97 | |||
98 | struct lg4ff_usb_revision { | ||
99 | const __u16 rev_maj; | ||
100 | const __u16 rev_min; | ||
101 | const struct lg4ff_native_cmd *command; | ||
102 | }; | ||
103 | |||
104 | static const struct lg4ff_native_cmd native_dfp = { | ||
105 | 1, | ||
106 | {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00} | ||
107 | }; | ||
108 | |||
109 | static const struct lg4ff_native_cmd native_dfgt = { | ||
110 | 2, | ||
111 | {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ | ||
112 | 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ | ||
113 | }; | ||
114 | |||
115 | static const struct lg4ff_native_cmd native_g25 = { | ||
116 | 1, | ||
117 | {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00} | ||
118 | }; | ||
119 | |||
120 | static const struct lg4ff_native_cmd native_g27 = { | ||
121 | 2, | ||
122 | {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ | ||
123 | 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ | ||
124 | }; | ||
125 | |||
126 | static const struct lg4ff_usb_revision lg4ff_revs[] = { | ||
127 | {DFGT_REV_MAJ, DFGT_REV_MIN, &native_dfgt}, /* Driving Force GT */ | ||
128 | {DFP_REV_MAJ, DFP_REV_MIN, &native_dfp}, /* Driving Force Pro */ | ||
129 | {G25_REV_MAJ, G25_REV_MIN, &native_g25}, /* G25 */ | ||
130 | {G27_REV_MAJ, G27_REV_MIN, &native_g27}, /* G27 */ | ||
131 | }; | ||
132 | |||
133 | static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect) | ||
45 | { | 134 | { |
46 | struct hid_device *hid = input_get_drvdata(dev); | 135 | struct hid_device *hid = input_get_drvdata(dev); |
47 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | 136 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; |
@@ -55,13 +144,12 @@ static int hid_lg4ff_play(struct input_dev *dev, void *data, | |||
55 | x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */ | 144 | x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */ |
56 | CLAMP(x); | 145 | CLAMP(x); |
57 | report->field[0]->value[0] = 0x11; /* Slot 1 */ | 146 | report->field[0]->value[0] = 0x11; /* Slot 1 */ |
58 | report->field[0]->value[1] = 0x10; | 147 | report->field[0]->value[1] = 0x08; |
59 | report->field[0]->value[2] = x; | 148 | report->field[0]->value[2] = x; |
60 | report->field[0]->value[3] = 0x00; | 149 | report->field[0]->value[3] = 0x80; |
61 | report->field[0]->value[4] = 0x00; | 150 | report->field[0]->value[4] = 0x00; |
62 | report->field[0]->value[5] = 0x08; | 151 | report->field[0]->value[5] = 0x00; |
63 | report->field[0]->value[6] = 0x00; | 152 | report->field[0]->value[6] = 0x00; |
64 | dbg_hid("Autocenter, x=0x%02X\n", x); | ||
65 | 153 | ||
66 | usbhid_submit_report(hid, report, USB_DIR_OUT); | 154 | usbhid_submit_report(hid, report, USB_DIR_OUT); |
67 | break; | 155 | break; |
@@ -69,24 +157,184 @@ static int hid_lg4ff_play(struct input_dev *dev, void *data, | |||
69 | return 0; | 157 | return 0; |
70 | } | 158 | } |
71 | 159 | ||
72 | static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude) | 160 | /* Sends default autocentering command compatible with |
161 | * all wheels except Formula Force EX */ | ||
162 | static void hid_lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) | ||
73 | { | 163 | { |
74 | struct hid_device *hid = input_get_drvdata(dev); | 164 | struct hid_device *hid = input_get_drvdata(dev); |
75 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | 165 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; |
76 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | 166 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); |
77 | __s32 *value = report->field[0]->value; | ||
78 | 167 | ||
79 | *value++ = 0xfe; | 168 | report->field[0]->value[0] = 0xfe; |
80 | *value++ = 0x0d; | 169 | report->field[0]->value[1] = 0x0d; |
81 | *value++ = 0x07; | 170 | report->field[0]->value[2] = magnitude >> 13; |
82 | *value++ = 0x07; | 171 | report->field[0]->value[3] = magnitude >> 13; |
83 | *value++ = (magnitude >> 8) & 0xff; | 172 | report->field[0]->value[4] = magnitude >> 8; |
84 | *value++ = 0x00; | 173 | report->field[0]->value[5] = 0x00; |
85 | *value = 0x00; | 174 | report->field[0]->value[6] = 0x00; |
175 | |||
176 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
177 | } | ||
178 | |||
179 | /* Sends autocentering command compatible with Formula Force EX */ | ||
180 | static void hid_lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude) | ||
181 | { | ||
182 | struct hid_device *hid = input_get_drvdata(dev); | ||
183 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | ||
184 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | ||
185 | magnitude = magnitude * 90 / 65535; | ||
186 | |||
187 | |||
188 | report->field[0]->value[0] = 0xfe; | ||
189 | report->field[0]->value[1] = 0x03; | ||
190 | report->field[0]->value[2] = magnitude >> 14; | ||
191 | report->field[0]->value[3] = magnitude >> 14; | ||
192 | report->field[0]->value[4] = magnitude; | ||
193 | report->field[0]->value[5] = 0x00; | ||
194 | report->field[0]->value[6] = 0x00; | ||
195 | |||
196 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
197 | } | ||
198 | |||
199 | /* Sends command to set range compatible with G25/G27/Driving Force GT */ | ||
200 | static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range) | ||
201 | { | ||
202 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | ||
203 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | ||
204 | dbg_hid("G25/G27/DFGT: setting range to %u\n", range); | ||
205 | |||
206 | report->field[0]->value[0] = 0xf8; | ||
207 | report->field[0]->value[1] = 0x81; | ||
208 | report->field[0]->value[2] = range & 0x00ff; | ||
209 | report->field[0]->value[3] = (range & 0xff00) >> 8; | ||
210 | report->field[0]->value[4] = 0x00; | ||
211 | report->field[0]->value[5] = 0x00; | ||
212 | report->field[0]->value[6] = 0x00; | ||
213 | |||
214 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
215 | } | ||
216 | |||
217 | /* Sends commands to set range compatible with Driving Force Pro wheel */ | ||
218 | static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range) | ||
219 | { | ||
220 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | ||
221 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | ||
222 | int start_left, start_right, full_range; | ||
223 | dbg_hid("Driving Force Pro: setting range to %u\n", range); | ||
224 | |||
225 | /* Prepare "coarse" limit command */ | ||
226 | report->field[0]->value[0] = 0xf8; | ||
227 | report->field[0]->value[1] = 0x00; /* Set later */ | ||
228 | report->field[0]->value[2] = 0x00; | ||
229 | report->field[0]->value[3] = 0x00; | ||
230 | report->field[0]->value[4] = 0x00; | ||
231 | report->field[0]->value[5] = 0x00; | ||
232 | report->field[0]->value[6] = 0x00; | ||
233 | |||
234 | if (range > 200) { | ||
235 | report->field[0]->value[1] = 0x03; | ||
236 | full_range = 900; | ||
237 | } else { | ||
238 | report->field[0]->value[1] = 0x02; | ||
239 | full_range = 200; | ||
240 | } | ||
241 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
242 | |||
243 | /* Prepare "fine" limit command */ | ||
244 | report->field[0]->value[0] = 0x81; | ||
245 | report->field[0]->value[1] = 0x0b; | ||
246 | report->field[0]->value[2] = 0x00; | ||
247 | report->field[0]->value[3] = 0x00; | ||
248 | report->field[0]->value[4] = 0x00; | ||
249 | report->field[0]->value[5] = 0x00; | ||
250 | report->field[0]->value[6] = 0x00; | ||
251 | |||
252 | if (range == 200 || range == 900) { /* Do not apply any fine limit */ | ||
253 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
254 | return; | ||
255 | } | ||
256 | |||
257 | /* Construct fine limit command */ | ||
258 | start_left = (((full_range - range + 1) * 2047) / full_range); | ||
259 | start_right = 0xfff - start_left; | ||
260 | |||
261 | report->field[0]->value[2] = start_left >> 4; | ||
262 | report->field[0]->value[3] = start_right >> 4; | ||
263 | report->field[0]->value[4] = 0xff; | ||
264 | report->field[0]->value[5] = (start_right & 0xe) << 4 | (start_left & 0xe); | ||
265 | report->field[0]->value[6] = 0xff; | ||
86 | 266 | ||
87 | usbhid_submit_report(hid, report, USB_DIR_OUT); | 267 | usbhid_submit_report(hid, report, USB_DIR_OUT); |
88 | } | 268 | } |
89 | 269 | ||
270 | static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd) | ||
271 | { | ||
272 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | ||
273 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | ||
274 | __u8 i, j; | ||
275 | |||
276 | j = 0; | ||
277 | while (j < 7*cmd->cmd_num) { | ||
278 | for (i = 0; i < 7; i++) | ||
279 | report->field[0]->value[i] = cmd->cmd[j++]; | ||
280 | |||
281 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
282 | } | ||
283 | } | ||
284 | |||
285 | /* Read current range and display it in terminal */ | ||
286 | static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf) | ||
287 | { | ||
288 | struct lg4ff_device_entry *uninitialized_var(entry); | ||
289 | struct list_head *h; | ||
290 | struct hid_device *hid = to_hid_device(dev); | ||
291 | size_t count; | ||
292 | |||
293 | list_for_each(h, &device_list.list) { | ||
294 | entry = list_entry(h, struct lg4ff_device_entry, list); | ||
295 | if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0) | ||
296 | break; | ||
297 | } | ||
298 | if (h == &device_list.list) { | ||
299 | dbg_hid("Device not found!"); | ||
300 | return 0; | ||
301 | } | ||
302 | |||
303 | count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->range); | ||
304 | return count; | ||
305 | } | ||
306 | |||
307 | /* Set range to user specified value, call appropriate function | ||
308 | * according to the type of the wheel */ | ||
309 | static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) | ||
310 | { | ||
311 | struct lg4ff_device_entry *uninitialized_var(entry); | ||
312 | struct list_head *h; | ||
313 | struct hid_device *hid = to_hid_device(dev); | ||
314 | __u16 range = simple_strtoul(buf, NULL, 10); | ||
315 | |||
316 | list_for_each(h, &device_list.list) { | ||
317 | entry = list_entry(h, struct lg4ff_device_entry, list); | ||
318 | if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0) | ||
319 | break; | ||
320 | } | ||
321 | if (h == &device_list.list) { | ||
322 | dbg_hid("Device not found!"); | ||
323 | return count; | ||
324 | } | ||
325 | |||
326 | if (range == 0) | ||
327 | range = entry->max_range; | ||
328 | |||
329 | /* Check if the wheel supports range setting | ||
330 | * and that the range is within limits for the wheel */ | ||
331 | if (entry->set_range != NULL && range >= entry->min_range && range <= entry->max_range) { | ||
332 | entry->set_range(hid, range); | ||
333 | entry->range = range; | ||
334 | } | ||
335 | |||
336 | return count; | ||
337 | } | ||
90 | 338 | ||
91 | int lg4ff_init(struct hid_device *hid) | 339 | int lg4ff_init(struct hid_device *hid) |
92 | { | 340 | { |
@@ -95,9 +343,10 @@ int lg4ff_init(struct hid_device *hid) | |||
95 | struct input_dev *dev = hidinput->input; | 343 | struct input_dev *dev = hidinput->input; |
96 | struct hid_report *report; | 344 | struct hid_report *report; |
97 | struct hid_field *field; | 345 | struct hid_field *field; |
98 | const signed short *ff_bits = ff4_wheel_ac; | 346 | struct lg4ff_device_entry *entry; |
99 | int error; | 347 | struct usb_device_descriptor *udesc; |
100 | int i; | 348 | int error, i, j; |
349 | __u16 bcdDevice, rev_maj, rev_min; | ||
101 | 350 | ||
102 | /* Find the report to use */ | 351 | /* Find the report to use */ |
103 | if (list_empty(report_list)) { | 352 | if (list_empty(report_list)) { |
@@ -118,18 +367,122 @@ int lg4ff_init(struct hid_device *hid) | |||
118 | return -1; | 367 | return -1; |
119 | } | 368 | } |
120 | 369 | ||
121 | for (i = 0; ff_bits[i] >= 0; i++) | 370 | /* Check what wheel has been connected */ |
122 | set_bit(ff_bits[i], dev->ffbit); | 371 | for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) { |
372 | if (hid->product == lg4ff_devices[i].product_id) { | ||
373 | dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id); | ||
374 | break; | ||
375 | } | ||
376 | } | ||
377 | |||
378 | if (i == ARRAY_SIZE(lg4ff_devices)) { | ||
379 | hid_err(hid, "Device is not supported by lg4ff driver. If you think it should be, consider reporting a bug to" | ||
380 | "LKML, Simon Wood <simon@mungewell.org> or Michal Maly <madcatxster@gmail.com>\n"); | ||
381 | return -1; | ||
382 | } | ||
383 | |||
384 | /* Attempt to switch wheel to native mode when applicable */ | ||
385 | udesc = &(hid_to_usb_dev(hid)->descriptor); | ||
386 | if (!udesc) { | ||
387 | hid_err(hid, "NULL USB device descriptor\n"); | ||
388 | return -1; | ||
389 | } | ||
390 | bcdDevice = le16_to_cpu(udesc->bcdDevice); | ||
391 | rev_maj = bcdDevice >> 8; | ||
392 | rev_min = bcdDevice & 0xff; | ||
393 | |||
394 | if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_WHEEL) { | ||
395 | dbg_hid("Generic wheel detected, can it do native?\n"); | ||
396 | dbg_hid("USB revision: %2x.%02x\n", rev_maj, rev_min); | ||
397 | |||
398 | for (j = 0; j < ARRAY_SIZE(lg4ff_revs); j++) { | ||
399 | if (lg4ff_revs[j].rev_maj == rev_maj && lg4ff_revs[j].rev_min == rev_min) { | ||
400 | hid_lg4ff_switch_native(hid, lg4ff_revs[j].command); | ||
401 | hid_info(hid, "Switched to native mode\n"); | ||
402 | } | ||
403 | } | ||
404 | } | ||
405 | |||
406 | /* Set supported force feedback capabilities */ | ||
407 | for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++) | ||
408 | set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit); | ||
123 | 409 | ||
124 | error = input_ff_create_memless(dev, NULL, hid_lg4ff_play); | 410 | error = input_ff_create_memless(dev, NULL, hid_lg4ff_play); |
125 | 411 | ||
126 | if (error) | 412 | if (error) |
127 | return error; | 413 | return error; |
128 | 414 | ||
129 | if (test_bit(FF_AUTOCENTER, dev->ffbit)) | 415 | /* Check if autocentering is available and |
130 | dev->ff->set_autocenter = hid_lg4ff_set_autocenter; | 416 | * set the centering force to zero by default */ |
417 | if (test_bit(FF_AUTOCENTER, dev->ffbit)) { | ||
418 | if(rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN) /* Formula Force EX expects different autocentering command */ | ||
419 | dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex; | ||
420 | else | ||
421 | dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default; | ||
422 | |||
423 | dev->ff->set_autocenter(dev, 0); | ||
424 | } | ||
425 | |||
426 | /* Initialize device_list if this is the first device to handle by lg4ff */ | ||
427 | if (!list_inited) { | ||
428 | INIT_LIST_HEAD(&device_list.list); | ||
429 | list_inited = 1; | ||
430 | } | ||
431 | |||
432 | /* Add the device to device_list */ | ||
433 | entry = (struct lg4ff_device_entry *)kzalloc(sizeof(struct lg4ff_device_entry), GFP_KERNEL); | ||
434 | if (!entry) { | ||
435 | hid_err(hid, "Cannot add device, insufficient memory.\n"); | ||
436 | return -ENOMEM; | ||
437 | } | ||
438 | entry->device_id = kstrdup((&hid->dev)->kobj.name, GFP_KERNEL); | ||
439 | if (!entry->device_id) { | ||
440 | hid_err(hid, "Cannot set device_id, insufficient memory.\n"); | ||
441 | kfree(entry); | ||
442 | return -ENOMEM; | ||
443 | } | ||
444 | entry->min_range = lg4ff_devices[i].min_range; | ||
445 | entry->max_range = lg4ff_devices[i].max_range; | ||
446 | entry->set_range = lg4ff_devices[i].set_range; | ||
447 | list_add(&entry->list, &device_list.list); | ||
448 | |||
449 | /* Create sysfs interface */ | ||
450 | error = device_create_file(&hid->dev, &dev_attr_range); | ||
451 | if (error) | ||
452 | return error; | ||
453 | dbg_hid("sysfs interface created\n"); | ||
454 | |||
455 | /* Set the maximum range to start with */ | ||
456 | entry->range = entry->max_range; | ||
457 | if (entry->set_range != NULL) | ||
458 | entry->set_range(hid, entry->range); | ||
131 | 459 | ||
132 | hid_info(hid, "Force feedback for Logitech Speed Force Wireless by Simon Wood <simon@mungewell.org>\n"); | 460 | hid_info(hid, "Force feedback for Logitech Speed Force Wireless by Simon Wood <simon@mungewell.org>\n"); |
133 | return 0; | 461 | return 0; |
134 | } | 462 | } |
135 | 463 | ||
464 | int lg4ff_deinit(struct hid_device *hid) | ||
465 | { | ||
466 | bool found = 0; | ||
467 | struct lg4ff_device_entry *entry; | ||
468 | struct list_head *h, *g; | ||
469 | list_for_each_safe(h, g, &device_list.list) { | ||
470 | entry = list_entry(h, struct lg4ff_device_entry, list); | ||
471 | if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0) { | ||
472 | list_del(h); | ||
473 | kfree(entry->device_id); | ||
474 | kfree(entry); | ||
475 | found = 1; | ||
476 | break; | ||
477 | } | ||
478 | } | ||
479 | |||
480 | if (!found) { | ||
481 | dbg_hid("Device entry not found!\n"); | ||
482 | return -1; | ||
483 | } | ||
484 | |||
485 | device_remove_file(&hid->dev, &dev_attr_range); | ||
486 | dbg_hid("Device successfully unregistered\n"); | ||
487 | return 0; | ||
488 | } | ||