diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-17 16:15:55 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-17 16:15:55 -0500 |
commit | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (patch) | |
tree | a8f4d49d63b1ecc92f2fddceba0655b2472c5bd9 /drivers/hid/hid-lg4ff.c | |
parent | 406089d01562f1e2bf9f089fd7637009ebaad589 (diff) |
Patched in Tegra support.
Diffstat (limited to 'drivers/hid/hid-lg4ff.c')
-rw-r--r-- | drivers/hid/hid-lg4ff.c | 645 |
1 files changed, 32 insertions, 613 deletions
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index d7947c701f3..fa550c8e1d1 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c | |||
@@ -1,8 +1,7 @@ | |||
1 | /* | 1 | /* |
2 | * Force feedback support for Logitech Gaming Wheels | 2 | * Force feedback support for Logitech Speed Force Wireless |
3 | * | 3 | * |
4 | * Including G27, G25, DFP, DFGT, FFEX, Momo, Momo2 & | 4 | * http://wiibrew.org/wiki/Logitech_USB_steering_wheel |
5 | * Speed Force Wireless (WiiWheel) | ||
6 | * | 5 | * |
7 | * Copyright (c) 2010 Simon Wood <simon@mungewell.org> | 6 | * Copyright (c) 2010 Simon Wood <simon@mungewell.org> |
8 | */ | 7 | */ |
@@ -30,182 +29,39 @@ | |||
30 | 29 | ||
31 | #include "usbhid/usbhid.h" | 30 | #include "usbhid/usbhid.h" |
32 | #include "hid-lg.h" | 31 | #include "hid-lg.h" |
33 | #include "hid-ids.h" | ||
34 | 32 | ||
35 | #define DFGT_REV_MAJ 0x13 | 33 | struct lg4ff_device { |
36 | #define DFGT_REV_MIN 0x22 | 34 | struct hid_report *report; |
37 | #define DFP_REV_MAJ 0x11 | ||
38 | #define DFP_REV_MIN 0x06 | ||
39 | #define FFEX_REV_MAJ 0x21 | ||
40 | #define FFEX_REV_MIN 0x00 | ||
41 | #define G25_REV_MAJ 0x12 | ||
42 | #define G25_REV_MIN 0x22 | ||
43 | #define G27_REV_MAJ 0x12 | ||
44 | #define G27_REV_MIN 0x38 | ||
45 | |||
46 | #define DFP_X_MIN 0 | ||
47 | #define DFP_X_MAX 16383 | ||
48 | #define DFP_PEDAL_MIN 0 | ||
49 | #define DFP_PEDAL_MAX 255 | ||
50 | |||
51 | #define to_hid_device(pdev) container_of(pdev, struct hid_device, dev) | ||
52 | |||
53 | static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range); | ||
54 | static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range); | ||
55 | static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf); | ||
56 | static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); | ||
57 | |||
58 | static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IRWXO, lg4ff_range_show, lg4ff_range_store); | ||
59 | |||
60 | struct lg4ff_device_entry { | ||
61 | __u32 product_id; | ||
62 | __u16 range; | ||
63 | __u16 min_range; | ||
64 | __u16 max_range; | ||
65 | #ifdef CONFIG_LEDS_CLASS | ||
66 | __u8 led_state; | ||
67 | struct led_classdev *led[5]; | ||
68 | #endif | ||
69 | struct list_head list; | ||
70 | void (*set_range)(struct hid_device *hid, u16 range); | ||
71 | }; | 35 | }; |
72 | 36 | ||
73 | static const signed short lg4ff_wheel_effects[] = { | 37 | static const signed short ff4_wheel_ac[] = { |
74 | FF_CONSTANT, | 38 | FF_CONSTANT, |
75 | FF_AUTOCENTER, | 39 | FF_AUTOCENTER, |
76 | -1 | 40 | -1 |
77 | }; | 41 | }; |
78 | 42 | ||
79 | struct lg4ff_wheel { | 43 | static int hid_lg4ff_play(struct input_dev *dev, void *data, |
80 | const __u32 product_id; | 44 | struct ff_effect *effect) |
81 | const signed short *ff_effects; | ||
82 | const __u16 min_range; | ||
83 | const __u16 max_range; | ||
84 | void (*set_range)(struct hid_device *hid, u16 range); | ||
85 | }; | ||
86 | |||
87 | static const struct lg4ff_wheel lg4ff_devices[] = { | ||
88 | {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, | ||
89 | {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, | ||
90 | {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_dfp}, | ||
91 | {USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, | ||
92 | {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, | ||
93 | {USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25}, | ||
94 | {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270, NULL}, | ||
95 | {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL} | ||
96 | }; | ||
97 | |||
98 | struct lg4ff_native_cmd { | ||
99 | const __u8 cmd_num; /* Number of commands to send */ | ||
100 | const __u8 cmd[]; | ||
101 | }; | ||
102 | |||
103 | struct lg4ff_usb_revision { | ||
104 | const __u16 rev_maj; | ||
105 | const __u16 rev_min; | ||
106 | const struct lg4ff_native_cmd *command; | ||
107 | }; | ||
108 | |||
109 | static const struct lg4ff_native_cmd native_dfp = { | ||
110 | 1, | ||
111 | {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00} | ||
112 | }; | ||
113 | |||
114 | static const struct lg4ff_native_cmd native_dfgt = { | ||
115 | 2, | ||
116 | {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ | ||
117 | 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ | ||
118 | }; | ||
119 | |||
120 | static const struct lg4ff_native_cmd native_g25 = { | ||
121 | 1, | ||
122 | {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00} | ||
123 | }; | ||
124 | |||
125 | static const struct lg4ff_native_cmd native_g27 = { | ||
126 | 2, | ||
127 | {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ | ||
128 | 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ | ||
129 | }; | ||
130 | |||
131 | static const struct lg4ff_usb_revision lg4ff_revs[] = { | ||
132 | {DFGT_REV_MAJ, DFGT_REV_MIN, &native_dfgt}, /* Driving Force GT */ | ||
133 | {DFP_REV_MAJ, DFP_REV_MIN, &native_dfp}, /* Driving Force Pro */ | ||
134 | {G25_REV_MAJ, G25_REV_MIN, &native_g25}, /* G25 */ | ||
135 | {G27_REV_MAJ, G27_REV_MIN, &native_g27}, /* G27 */ | ||
136 | }; | ||
137 | |||
138 | /* Recalculates X axis value accordingly to currently selected range */ | ||
139 | static __s32 lg4ff_adjust_dfp_x_axis(__s32 value, __u16 range) | ||
140 | { | ||
141 | __u16 max_range; | ||
142 | __s32 new_value; | ||
143 | |||
144 | if (range == 900) | ||
145 | return value; | ||
146 | else if (range == 200) | ||
147 | return value; | ||
148 | else if (range < 200) | ||
149 | max_range = 200; | ||
150 | else | ||
151 | max_range = 900; | ||
152 | |||
153 | new_value = 8192 + mult_frac(value - 8192, max_range, range); | ||
154 | if (new_value < 0) | ||
155 | return 0; | ||
156 | else if (new_value > 16383) | ||
157 | return 16383; | ||
158 | else | ||
159 | return new_value; | ||
160 | } | ||
161 | |||
162 | int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field, | ||
163 | struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data) | ||
164 | { | ||
165 | struct lg4ff_device_entry *entry = drv_data->device_props; | ||
166 | __s32 new_value = 0; | ||
167 | |||
168 | if (!entry) { | ||
169 | hid_err(hid, "Device properties not found"); | ||
170 | return 0; | ||
171 | } | ||
172 | |||
173 | switch (entry->product_id) { | ||
174 | case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: | ||
175 | switch (usage->code) { | ||
176 | case ABS_X: | ||
177 | new_value = lg4ff_adjust_dfp_x_axis(value, entry->range); | ||
178 | input_event(field->hidinput->input, usage->type, usage->code, new_value); | ||
179 | return 1; | ||
180 | default: | ||
181 | return 0; | ||
182 | } | ||
183 | default: | ||
184 | return 0; | ||
185 | } | ||
186 | } | ||
187 | |||
188 | static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect) | ||
189 | { | 45 | { |
190 | struct hid_device *hid = input_get_drvdata(dev); | 46 | struct hid_device *hid = input_get_drvdata(dev); |
191 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | 47 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; |
192 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | 48 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); |
193 | __s32 *value = report->field[0]->value; | ||
194 | int x; | 49 | int x; |
195 | 50 | ||
196 | #define CLAMP(x) do { if (x < 0) x = 0; else if (x > 0xff) x = 0xff; } while (0) | 51 | #define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff |
197 | 52 | ||
198 | switch (effect->type) { | 53 | switch (effect->type) { |
199 | case FF_CONSTANT: | 54 | case FF_CONSTANT: |
200 | x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */ | 55 | x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */ |
201 | CLAMP(x); | 56 | CLAMP(x); |
202 | value[0] = 0x11; /* Slot 1 */ | 57 | report->field[0]->value[0] = 0x11; /* Slot 1 */ |
203 | value[1] = 0x08; | 58 | report->field[0]->value[1] = 0x10; |
204 | value[2] = x; | 59 | report->field[0]->value[2] = x; |
205 | value[3] = 0x80; | 60 | report->field[0]->value[3] = 0x00; |
206 | value[4] = 0x00; | 61 | report->field[0]->value[4] = 0x00; |
207 | value[5] = 0x00; | 62 | report->field[0]->value[5] = 0x08; |
208 | value[6] = 0x00; | 63 | report->field[0]->value[6] = 0x00; |
64 | dbg_hid("Autocenter, x=0x%02X\n", x); | ||
209 | 65 | ||
210 | usbhid_submit_report(hid, report, USB_DIR_OUT); | 66 | usbhid_submit_report(hid, report, USB_DIR_OUT); |
211 | break; | 67 | break; |
@@ -213,276 +69,24 @@ static int hid_lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *e | |||
213 | return 0; | 69 | return 0; |
214 | } | 70 | } |
215 | 71 | ||
216 | /* Sends default autocentering command compatible with | 72 | static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude) |
217 | * all wheels except Formula Force EX */ | ||
218 | static void hid_lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) | ||
219 | { | ||
220 | struct hid_device *hid = input_get_drvdata(dev); | ||
221 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | ||
222 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | ||
223 | __s32 *value = report->field[0]->value; | ||
224 | |||
225 | value[0] = 0xfe; | ||
226 | value[1] = 0x0d; | ||
227 | value[2] = magnitude >> 13; | ||
228 | value[3] = magnitude >> 13; | ||
229 | value[4] = magnitude >> 8; | ||
230 | value[5] = 0x00; | ||
231 | value[6] = 0x00; | ||
232 | |||
233 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
234 | } | ||
235 | |||
236 | /* Sends autocentering command compatible with Formula Force EX */ | ||
237 | static void hid_lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude) | ||
238 | { | 73 | { |
239 | struct hid_device *hid = input_get_drvdata(dev); | 74 | struct hid_device *hid = input_get_drvdata(dev); |
240 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | 75 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; |
241 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | 76 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); |
242 | __s32 *value = report->field[0]->value; | 77 | __s32 *value = report->field[0]->value; |
243 | magnitude = magnitude * 90 / 65535; | ||
244 | |||
245 | value[0] = 0xfe; | ||
246 | value[1] = 0x03; | ||
247 | value[2] = magnitude >> 14; | ||
248 | value[3] = magnitude >> 14; | ||
249 | value[4] = magnitude; | ||
250 | value[5] = 0x00; | ||
251 | value[6] = 0x00; | ||
252 | |||
253 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
254 | } | ||
255 | |||
256 | /* Sends command to set range compatible with G25/G27/Driving Force GT */ | ||
257 | static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range) | ||
258 | { | ||
259 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | ||
260 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | ||
261 | __s32 *value = report->field[0]->value; | ||
262 | |||
263 | dbg_hid("G25/G27/DFGT: setting range to %u\n", range); | ||
264 | |||
265 | value[0] = 0xf8; | ||
266 | value[1] = 0x81; | ||
267 | value[2] = range & 0x00ff; | ||
268 | value[3] = (range & 0xff00) >> 8; | ||
269 | value[4] = 0x00; | ||
270 | value[5] = 0x00; | ||
271 | value[6] = 0x00; | ||
272 | |||
273 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
274 | } | ||
275 | |||
276 | /* Sends commands to set range compatible with Driving Force Pro wheel */ | ||
277 | static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range) | ||
278 | { | ||
279 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | ||
280 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | ||
281 | int start_left, start_right, full_range; | ||
282 | __s32 *value = report->field[0]->value; | ||
283 | |||
284 | dbg_hid("Driving Force Pro: setting range to %u\n", range); | ||
285 | |||
286 | /* Prepare "coarse" limit command */ | ||
287 | value[0] = 0xf8; | ||
288 | value[1] = 0x00; /* Set later */ | ||
289 | value[2] = 0x00; | ||
290 | value[3] = 0x00; | ||
291 | value[4] = 0x00; | ||
292 | value[5] = 0x00; | ||
293 | value[6] = 0x00; | ||
294 | |||
295 | if (range > 200) { | ||
296 | report->field[0]->value[1] = 0x03; | ||
297 | full_range = 900; | ||
298 | } else { | ||
299 | report->field[0]->value[1] = 0x02; | ||
300 | full_range = 200; | ||
301 | } | ||
302 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
303 | |||
304 | /* Prepare "fine" limit command */ | ||
305 | value[0] = 0x81; | ||
306 | value[1] = 0x0b; | ||
307 | value[2] = 0x00; | ||
308 | value[3] = 0x00; | ||
309 | value[4] = 0x00; | ||
310 | value[5] = 0x00; | ||
311 | value[6] = 0x00; | ||
312 | |||
313 | if (range == 200 || range == 900) { /* Do not apply any fine limit */ | ||
314 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
315 | return; | ||
316 | } | ||
317 | |||
318 | /* Construct fine limit command */ | ||
319 | start_left = (((full_range - range + 1) * 2047) / full_range); | ||
320 | start_right = 0xfff - start_left; | ||
321 | |||
322 | value[2] = start_left >> 4; | ||
323 | value[3] = start_right >> 4; | ||
324 | value[4] = 0xff; | ||
325 | value[5] = (start_right & 0xe) << 4 | (start_left & 0xe); | ||
326 | value[6] = 0xff; | ||
327 | |||
328 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
329 | } | ||
330 | |||
331 | static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd) | ||
332 | { | ||
333 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | ||
334 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | ||
335 | __u8 i, j; | ||
336 | |||
337 | j = 0; | ||
338 | while (j < 7*cmd->cmd_num) { | ||
339 | for (i = 0; i < 7; i++) | ||
340 | report->field[0]->value[i] = cmd->cmd[j++]; | ||
341 | |||
342 | usbhid_submit_report(hid, report, USB_DIR_OUT); | ||
343 | } | ||
344 | } | ||
345 | |||
346 | /* Read current range and display it in terminal */ | ||
347 | static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf) | ||
348 | { | ||
349 | struct hid_device *hid = to_hid_device(dev); | ||
350 | struct lg4ff_device_entry *entry; | ||
351 | struct lg_drv_data *drv_data; | ||
352 | size_t count; | ||
353 | |||
354 | drv_data = hid_get_drvdata(hid); | ||
355 | if (!drv_data) { | ||
356 | hid_err(hid, "Private driver data not found!\n"); | ||
357 | return 0; | ||
358 | } | ||
359 | |||
360 | entry = drv_data->device_props; | ||
361 | if (!entry) { | ||
362 | hid_err(hid, "Device properties not found!\n"); | ||
363 | return 0; | ||
364 | } | ||
365 | |||
366 | count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->range); | ||
367 | return count; | ||
368 | } | ||
369 | |||
370 | /* Set range to user specified value, call appropriate function | ||
371 | * according to the type of the wheel */ | ||
372 | static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) | ||
373 | { | ||
374 | struct hid_device *hid = to_hid_device(dev); | ||
375 | struct lg4ff_device_entry *entry; | ||
376 | struct lg_drv_data *drv_data; | ||
377 | __u16 range = simple_strtoul(buf, NULL, 10); | ||
378 | |||
379 | drv_data = hid_get_drvdata(hid); | ||
380 | if (!drv_data) { | ||
381 | hid_err(hid, "Private driver data not found!\n"); | ||
382 | return 0; | ||
383 | } | ||
384 | 78 | ||
385 | entry = drv_data->device_props; | 79 | *value++ = 0xfe; |
386 | if (!entry) { | 80 | *value++ = 0x0d; |
387 | hid_err(hid, "Device properties not found!\n"); | 81 | *value++ = 0x07; |
388 | return 0; | 82 | *value++ = 0x07; |
389 | } | 83 | *value++ = (magnitude >> 8) & 0xff; |
390 | 84 | *value++ = 0x00; | |
391 | if (range == 0) | 85 | *value = 0x00; |
392 | range = entry->max_range; | ||
393 | 86 | ||
394 | /* Check if the wheel supports range setting | ||
395 | * and that the range is within limits for the wheel */ | ||
396 | if (entry->set_range != NULL && range >= entry->min_range && range <= entry->max_range) { | ||
397 | entry->set_range(hid, range); | ||
398 | entry->range = range; | ||
399 | } | ||
400 | |||
401 | return count; | ||
402 | } | ||
403 | |||
404 | #ifdef CONFIG_LEDS_CLASS | ||
405 | static void lg4ff_set_leds(struct hid_device *hid, __u8 leds) | ||
406 | { | ||
407 | struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; | ||
408 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | ||
409 | __s32 *value = report->field[0]->value; | ||
410 | |||
411 | value[0] = 0xf8; | ||
412 | value[1] = 0x12; | ||
413 | value[2] = leds; | ||
414 | value[3] = 0x00; | ||
415 | value[4] = 0x00; | ||
416 | value[5] = 0x00; | ||
417 | value[6] = 0x00; | ||
418 | usbhid_submit_report(hid, report, USB_DIR_OUT); | 87 | usbhid_submit_report(hid, report, USB_DIR_OUT); |
419 | } | 88 | } |
420 | 89 | ||
421 | static void lg4ff_led_set_brightness(struct led_classdev *led_cdev, | ||
422 | enum led_brightness value) | ||
423 | { | ||
424 | struct device *dev = led_cdev->dev->parent; | ||
425 | struct hid_device *hid = container_of(dev, struct hid_device, dev); | ||
426 | struct lg_drv_data *drv_data = hid_get_drvdata(hid); | ||
427 | struct lg4ff_device_entry *entry; | ||
428 | int i, state = 0; | ||
429 | |||
430 | if (!drv_data) { | ||
431 | hid_err(hid, "Device data not found."); | ||
432 | return; | ||
433 | } | ||
434 | |||
435 | entry = (struct lg4ff_device_entry *)drv_data->device_props; | ||
436 | |||
437 | if (!entry) { | ||
438 | hid_err(hid, "Device properties not found."); | ||
439 | return; | ||
440 | } | ||
441 | |||
442 | for (i = 0; i < 5; i++) { | ||
443 | if (led_cdev != entry->led[i]) | ||
444 | continue; | ||
445 | state = (entry->led_state >> i) & 1; | ||
446 | if (value == LED_OFF && state) { | ||
447 | entry->led_state &= ~(1 << i); | ||
448 | lg4ff_set_leds(hid, entry->led_state); | ||
449 | } else if (value != LED_OFF && !state) { | ||
450 | entry->led_state |= 1 << i; | ||
451 | lg4ff_set_leds(hid, entry->led_state); | ||
452 | } | ||
453 | break; | ||
454 | } | ||
455 | } | ||
456 | |||
457 | static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cdev) | ||
458 | { | ||
459 | struct device *dev = led_cdev->dev->parent; | ||
460 | struct hid_device *hid = container_of(dev, struct hid_device, dev); | ||
461 | struct lg_drv_data *drv_data = hid_get_drvdata(hid); | ||
462 | struct lg4ff_device_entry *entry; | ||
463 | int i, value = 0; | ||
464 | |||
465 | if (!drv_data) { | ||
466 | hid_err(hid, "Device data not found."); | ||
467 | return LED_OFF; | ||
468 | } | ||
469 | |||
470 | entry = (struct lg4ff_device_entry *)drv_data->device_props; | ||
471 | |||
472 | if (!entry) { | ||
473 | hid_err(hid, "Device properties not found."); | ||
474 | return LED_OFF; | ||
475 | } | ||
476 | |||
477 | for (i = 0; i < 5; i++) | ||
478 | if (led_cdev == entry->led[i]) { | ||
479 | value = (entry->led_state >> i) & 1; | ||
480 | break; | ||
481 | } | ||
482 | |||
483 | return value ? LED_FULL : LED_OFF; | ||
484 | } | ||
485 | #endif | ||
486 | 90 | ||
487 | int lg4ff_init(struct hid_device *hid) | 91 | int lg4ff_init(struct hid_device *hid) |
488 | { | 92 | { |
@@ -491,11 +95,9 @@ int lg4ff_init(struct hid_device *hid) | |||
491 | struct input_dev *dev = hidinput->input; | 95 | struct input_dev *dev = hidinput->input; |
492 | struct hid_report *report; | 96 | struct hid_report *report; |
493 | struct hid_field *field; | 97 | struct hid_field *field; |
494 | struct lg4ff_device_entry *entry; | 98 | const signed short *ff_bits = ff4_wheel_ac; |
495 | struct lg_drv_data *drv_data; | 99 | int error; |
496 | struct usb_device_descriptor *udesc; | 100 | int i; |
497 | int error, i, j; | ||
498 | __u16 bcdDevice, rev_maj, rev_min; | ||
499 | 101 | ||
500 | /* Find the report to use */ | 102 | /* Find the report to use */ |
501 | if (list_empty(report_list)) { | 103 | if (list_empty(report_list)) { |
@@ -516,201 +118,18 @@ int lg4ff_init(struct hid_device *hid) | |||
516 | return -1; | 118 | return -1; |
517 | } | 119 | } |
518 | 120 | ||
519 | /* Check what wheel has been connected */ | 121 | for (i = 0; ff_bits[i] >= 0; i++) |
520 | for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) { | 122 | set_bit(ff_bits[i], dev->ffbit); |
521 | if (hid->product == lg4ff_devices[i].product_id) { | ||
522 | dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id); | ||
523 | break; | ||
524 | } | ||
525 | } | ||
526 | |||
527 | if (i == ARRAY_SIZE(lg4ff_devices)) { | ||
528 | hid_err(hid, "Device is not supported by lg4ff driver. If you think it should be, consider reporting a bug to" | ||
529 | "LKML, Simon Wood <simon@mungewell.org> or Michal Maly <madcatxster@gmail.com>\n"); | ||
530 | return -1; | ||
531 | } | ||
532 | |||
533 | /* Attempt to switch wheel to native mode when applicable */ | ||
534 | udesc = &(hid_to_usb_dev(hid)->descriptor); | ||
535 | if (!udesc) { | ||
536 | hid_err(hid, "NULL USB device descriptor\n"); | ||
537 | return -1; | ||
538 | } | ||
539 | bcdDevice = le16_to_cpu(udesc->bcdDevice); | ||
540 | rev_maj = bcdDevice >> 8; | ||
541 | rev_min = bcdDevice & 0xff; | ||
542 | |||
543 | if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_WHEEL) { | ||
544 | dbg_hid("Generic wheel detected, can it do native?\n"); | ||
545 | dbg_hid("USB revision: %2x.%02x\n", rev_maj, rev_min); | ||
546 | |||
547 | for (j = 0; j < ARRAY_SIZE(lg4ff_revs); j++) { | ||
548 | if (lg4ff_revs[j].rev_maj == rev_maj && lg4ff_revs[j].rev_min == rev_min) { | ||
549 | hid_lg4ff_switch_native(hid, lg4ff_revs[j].command); | ||
550 | hid_info(hid, "Switched to native mode\n"); | ||
551 | } | ||
552 | } | ||
553 | } | ||
554 | |||
555 | /* Set supported force feedback capabilities */ | ||
556 | for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++) | ||
557 | set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit); | ||
558 | 123 | ||
559 | error = input_ff_create_memless(dev, NULL, hid_lg4ff_play); | 124 | error = input_ff_create_memless(dev, NULL, hid_lg4ff_play); |
560 | 125 | ||
561 | if (error) | 126 | if (error) |
562 | return error; | 127 | return error; |
563 | 128 | ||
564 | /* Check if autocentering is available and | 129 | if (test_bit(FF_AUTOCENTER, dev->ffbit)) |
565 | * set the centering force to zero by default */ | 130 | dev->ff->set_autocenter = hid_lg4ff_set_autocenter; |
566 | if (test_bit(FF_AUTOCENTER, dev->ffbit)) { | ||
567 | if (rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN) /* Formula Force EX expects different autocentering command */ | ||
568 | dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex; | ||
569 | else | ||
570 | dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default; | ||
571 | |||
572 | dev->ff->set_autocenter(dev, 0); | ||
573 | } | ||
574 | |||
575 | /* Get private driver data */ | ||
576 | drv_data = hid_get_drvdata(hid); | ||
577 | if (!drv_data) { | ||
578 | hid_err(hid, "Cannot add device, private driver data not allocated\n"); | ||
579 | return -1; | ||
580 | } | ||
581 | |||
582 | /* Initialize device properties */ | ||
583 | entry = kzalloc(sizeof(struct lg4ff_device_entry), GFP_KERNEL); | ||
584 | if (!entry) { | ||
585 | hid_err(hid, "Cannot add device, insufficient memory to allocate device properties.\n"); | ||
586 | return -ENOMEM; | ||
587 | } | ||
588 | drv_data->device_props = entry; | ||
589 | |||
590 | entry->product_id = lg4ff_devices[i].product_id; | ||
591 | entry->min_range = lg4ff_devices[i].min_range; | ||
592 | entry->max_range = lg4ff_devices[i].max_range; | ||
593 | entry->set_range = lg4ff_devices[i].set_range; | ||
594 | |||
595 | /* Create sysfs interface */ | ||
596 | error = device_create_file(&hid->dev, &dev_attr_range); | ||
597 | if (error) | ||
598 | return error; | ||
599 | dbg_hid("sysfs interface created\n"); | ||
600 | |||
601 | /* Set default axes parameters */ | ||
602 | switch (lg4ff_devices[i].product_id) { | ||
603 | case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: | ||
604 | dbg_hid("Setting axes parameters for Driving Force Pro\n"); | ||
605 | input_set_abs_params(dev, ABS_X, DFP_X_MIN, DFP_X_MAX, 0, 0); | ||
606 | input_set_abs_params(dev, ABS_Y, DFP_PEDAL_MIN, DFP_PEDAL_MAX, 0, 0); | ||
607 | input_set_abs_params(dev, ABS_RZ, DFP_PEDAL_MIN, DFP_PEDAL_MAX, 0, 0); | ||
608 | break; | ||
609 | default: | ||
610 | break; | ||
611 | } | ||
612 | |||
613 | /* Set the maximum range to start with */ | ||
614 | entry->range = entry->max_range; | ||
615 | if (entry->set_range != NULL) | ||
616 | entry->set_range(hid, entry->range); | ||
617 | |||
618 | #ifdef CONFIG_LEDS_CLASS | ||
619 | /* register led subsystem - G27 only */ | ||
620 | entry->led_state = 0; | ||
621 | for (j = 0; j < 5; j++) | ||
622 | entry->led[j] = NULL; | ||
623 | |||
624 | if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL) { | ||
625 | struct led_classdev *led; | ||
626 | size_t name_sz; | ||
627 | char *name; | ||
628 | 131 | ||
629 | lg4ff_set_leds(hid, 0); | 132 | hid_info(hid, "Force feedback for Logitech Speed Force Wireless by Simon Wood <simon@mungewell.org>\n"); |
630 | |||
631 | name_sz = strlen(dev_name(&hid->dev)) + 8; | ||
632 | |||
633 | for (j = 0; j < 5; j++) { | ||
634 | led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); | ||
635 | if (!led) { | ||
636 | hid_err(hid, "can't allocate memory for LED %d\n", j); | ||
637 | goto err; | ||
638 | } | ||
639 | |||
640 | name = (void *)(&led[1]); | ||
641 | snprintf(name, name_sz, "%s::RPM%d", dev_name(&hid->dev), j+1); | ||
642 | led->name = name; | ||
643 | led->brightness = 0; | ||
644 | led->max_brightness = 1; | ||
645 | led->brightness_get = lg4ff_led_get_brightness; | ||
646 | led->brightness_set = lg4ff_led_set_brightness; | ||
647 | |||
648 | entry->led[j] = led; | ||
649 | error = led_classdev_register(&hid->dev, led); | ||
650 | |||
651 | if (error) { | ||
652 | hid_err(hid, "failed to register LED %d. Aborting.\n", j); | ||
653 | err: | ||
654 | /* Deregister LEDs (if any) */ | ||
655 | for (j = 0; j < 5; j++) { | ||
656 | led = entry->led[j]; | ||
657 | entry->led[j] = NULL; | ||
658 | if (!led) | ||
659 | continue; | ||
660 | led_classdev_unregister(led); | ||
661 | kfree(led); | ||
662 | } | ||
663 | goto out; /* Let the driver continue without LEDs */ | ||
664 | } | ||
665 | } | ||
666 | } | ||
667 | out: | ||
668 | #endif | ||
669 | hid_info(hid, "Force feedback support for Logitech Gaming Wheels\n"); | ||
670 | return 0; | 133 | return 0; |
671 | } | 134 | } |
672 | 135 | ||
673 | |||
674 | |||
675 | int lg4ff_deinit(struct hid_device *hid) | ||
676 | { | ||
677 | struct lg4ff_device_entry *entry; | ||
678 | struct lg_drv_data *drv_data; | ||
679 | |||
680 | device_remove_file(&hid->dev, &dev_attr_range); | ||
681 | |||
682 | drv_data = hid_get_drvdata(hid); | ||
683 | if (!drv_data) { | ||
684 | hid_err(hid, "Error while deinitializing device, no private driver data.\n"); | ||
685 | return -1; | ||
686 | } | ||
687 | entry = drv_data->device_props; | ||
688 | if (!entry) { | ||
689 | hid_err(hid, "Error while deinitializing device, no device properties data.\n"); | ||
690 | return -1; | ||
691 | } | ||
692 | |||
693 | #ifdef CONFIG_LEDS_CLASS | ||
694 | { | ||
695 | int j; | ||
696 | struct led_classdev *led; | ||
697 | |||
698 | /* Deregister LEDs (if any) */ | ||
699 | for (j = 0; j < 5; j++) { | ||
700 | |||
701 | led = entry->led[j]; | ||
702 | entry->led[j] = NULL; | ||
703 | if (!led) | ||
704 | continue; | ||
705 | led_classdev_unregister(led); | ||
706 | kfree(led); | ||
707 | } | ||
708 | } | ||
709 | #endif | ||
710 | |||
711 | /* Deallocate memory */ | ||
712 | kfree(entry); | ||
713 | |||
714 | dbg_hid("Device successfully unregistered\n"); | ||
715 | return 0; | ||
716 | } | ||