aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichal Malý <madcatxster@devoid-pointer.net>2015-02-18 11:59:20 -0500
committerJiri Kosina <jkosina@suse.cz>2015-02-18 15:14:53 -0500
commite7c234496d01c90a4b042d899a65e10f1f63ebc1 (patch)
tree05c9a6e3b3ca1427e176f18376752dcaa519a566
parent870fd0f5df4e131467612cc46db46fc3b69fd706 (diff)
HID: hid-lg4ff: Identify Logitech gaming wheels in compatibility modes
Identify Logitech gaming wheels in compatibility modes accordingly to Logitech specifications. Logitech specification contains a general method of identifying various models of their gaming wheels while they are in "compatibility" mode. This patch implements the method instead of checking against known values of bcdDevice. Handling of the mode switch upon initialization is also adjusted so that the driver does not have to go through the entire initialization routine because the wheels are set to perform a USB detach before they reappear in "native" mode. Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net> Tested-by: Simon Wood <simon@mungewell.org> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
-rw-r--r--drivers/hid/hid-lg4ff.c266
1 files changed, 190 insertions, 76 deletions
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index db0dd9b17e53..190c5e3f46ce 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -32,21 +32,15 @@
32#include "hid-lg.h" 32#include "hid-lg.h"
33#include "hid-ids.h" 33#include "hid-ids.h"
34 34
35#define DFGT_REV_MAJ 0x13
36#define DFGT_REV_MIN 0x22
37#define DFGT2_REV_MIN 0x26
38#define DFP_REV_MAJ 0x11
39#define DFP_REV_MIN 0x06
40#define FFEX_REV_MAJ 0x21
41#define FFEX_REV_MIN 0x00
42#define G25_REV_MAJ 0x12
43#define G25_REV_MIN 0x22
44#define G27_REV_MAJ 0x12
45#define G27_REV_MIN 0x38
46#define G27_2_REV_MIN 0x39
47
48#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev) 35#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
49 36
37#define LG4FF_MMODE_DONE 0
38#define LG4FF_MMODE_SWITCHED 1
39#define LG4FF_MMODE_NOT_MULTIMODE 2
40
41#define LG4FF_FFEX_REV_MAJ 0x21
42#define LG4FF_FFEX_REV_MIN 0x00
43
50static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range); 44static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
51static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range); 45static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range);
52 46
@@ -77,6 +71,22 @@ struct lg4ff_wheel {
77 void (*set_range)(struct hid_device *hid, u16 range); 71 void (*set_range)(struct hid_device *hid, u16 range);
78}; 72};
79 73
74struct lg4ff_compat_mode_switch {
75 const __u8 cmd_count; /* Number of commands to send */
76 const __u8 cmd[];
77};
78
79struct lg4ff_wheel_ident_info {
80 const u16 mask;
81 const u16 result;
82 const u16 real_product_id;
83};
84
85struct lg4ff_wheel_ident_checklist {
86 const u32 count;
87 const struct lg4ff_wheel_ident_info *models[];
88};
89
80static const struct lg4ff_wheel lg4ff_devices[] = { 90static const struct lg4ff_wheel lg4ff_devices[] = {
81 {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, 91 {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
82 {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, 92 {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
@@ -88,48 +98,63 @@ static const struct lg4ff_wheel lg4ff_devices[] = {
88 {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL} 98 {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}
89}; 99};
90 100
91struct lg4ff_native_cmd { 101/* Multimode wheel identificators */
92 const __u8 cmd_num; /* Number of commands to send */ 102static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = {
93 const __u8 cmd[]; 103 0xf000,
104 0x1000,
105 USB_DEVICE_ID_LOGITECH_DFP_WHEEL
106};
107
108static const struct lg4ff_wheel_ident_info lg4ff_g25_ident_info = {
109 0xff00,
110 0x1200,
111 USB_DEVICE_ID_LOGITECH_G25_WHEEL
112};
113
114static const struct lg4ff_wheel_ident_info lg4ff_g27_ident_info = {
115 0xfff0,
116 0x1230,
117 USB_DEVICE_ID_LOGITECH_G27_WHEEL
94}; 118};
95 119
96struct lg4ff_usb_revision { 120static const struct lg4ff_wheel_ident_info lg4ff_dfgt_ident_info = {
97 const __u16 rev_maj; 121 0xff00,
98 const __u16 rev_min; 122 0x1300,
99 const struct lg4ff_native_cmd *command; 123 USB_DEVICE_ID_LOGITECH_DFGT_WHEEL
100}; 124};
101 125
102static const struct lg4ff_native_cmd native_dfp = { 126/* Multimode wheel identification checklists */
127static const struct lg4ff_wheel_ident_checklist lg4ff_main_checklist = {
128 4,
129 {&lg4ff_dfgt_ident_info,
130 &lg4ff_g27_ident_info,
131 &lg4ff_g25_ident_info,
132 &lg4ff_dfp_ident_info}
133};
134
135/* Compatibility mode switching commands */
136static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_dfp = {
103 1, 137 1,
104 {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00} 138 {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
105}; 139};
106 140
107static const struct lg4ff_native_cmd native_dfgt = { 141static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_dfgt = {
108 2, 142 2,
109 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ 143 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */
110 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ 144 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */
111}; 145};
112 146
113static const struct lg4ff_native_cmd native_g25 = { 147static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_g25 = {
114 1, 148 1,
115 {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00} 149 {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
116}; 150};
117 151
118static const struct lg4ff_native_cmd native_g27 = { 152static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_g27 = {
119 2, 153 2,
120 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ 154 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */
121 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ 155 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */
122}; 156};
123 157
124static const struct lg4ff_usb_revision lg4ff_revs[] = {
125 {DFGT_REV_MAJ, DFGT_REV_MIN, &native_dfgt}, /* Driving Force GT */
126 {DFGT_REV_MAJ, DFGT2_REV_MIN, &native_dfgt}, /* Driving Force GT v2 */
127 {DFP_REV_MAJ, DFP_REV_MIN, &native_dfp}, /* Driving Force Pro */
128 {G25_REV_MAJ, G25_REV_MIN, &native_g25}, /* G25 */
129 {G27_REV_MAJ, G27_REV_MIN, &native_g27}, /* G27 */
130 {G27_REV_MAJ, G27_2_REV_MIN, &native_g27}, /* G27 v2 */
131};
132
133/* Recalculates X axis value accordingly to currently selected range */ 158/* Recalculates X axis value accordingly to currently selected range */
134static __s32 lg4ff_adjust_dfp_x_axis(__s32 value, __u16 range) 159static __s32 lg4ff_adjust_dfp_x_axis(__s32 value, __u16 range)
135{ 160{
@@ -396,19 +421,22 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
396 hid_hw_request(hid, report, HID_REQ_SET_REPORT); 421 hid_hw_request(hid, report, HID_REQ_SET_REPORT);
397} 422}
398 423
399static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd) 424static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s)
400{ 425{
401 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; 426 struct usb_device *usbdev = hid_to_usb_dev(hid);
402 struct hid_report *report = list_entry(report_list->next, struct hid_report, list); 427 struct usbhid_device *usbhid = hid->driver_data;
403 __u8 i, j; 428 u8 i;
404 429
405 j = 0; 430 for (i = 0; i < s->cmd_count; i++) {
406 while (j < 7*cmd->cmd_num) { 431 int xferd, ret;
407 for (i = 0; i < 7; i++) 432 u8 data[7];
408 report->field[0]->value[i] = cmd->cmd[j++];
409 433
410 hid_hw_request(hid, report, HID_REQ_SET_REPORT); 434 memcpy(data, s->cmd + (7*i), 7);
435 ret = usb_interrupt_msg(usbdev, usbhid->urbout->pipe, data, 7, &xferd, USB_CTRL_SET_TIMEOUT);
436 if (ret)
437 return ret;
411 } 438 }
439 return 0;
412} 440}
413 441
414/* Read current range and display it in terminal */ 442/* Read current range and display it in terminal */
@@ -555,20 +583,129 @@ static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cde
555} 583}
556#endif 584#endif
557 585
586static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice)
587{
588 const struct lg4ff_wheel_ident_checklist *checklist;
589 int i, from_idx, to_idx;
590
591 switch (reported_product_id) {
592 case USB_DEVICE_ID_LOGITECH_WHEEL:
593 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
594 checklist = &lg4ff_main_checklist;
595 from_idx = 0;
596 to_idx = checklist->count - 1;
597 break;
598 case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
599 checklist = &lg4ff_main_checklist;
600 from_idx = 0;
601 to_idx = checklist->count - 2; /* End identity check at G25 */
602 break;
603 case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
604 checklist = &lg4ff_main_checklist;
605 from_idx = 1; /* Start identity check at G27 */
606 to_idx = checklist->count - 3; /* End identity check at G27 */
607 break;
608 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
609 checklist = &lg4ff_main_checklist;
610 from_idx = 0;
611 to_idx = checklist->count - 4; /* End identity check at DFGT */
612 break;
613 default:
614 return 0;
615 }
616
617 for (i = from_idx; i <= to_idx; i++) {
618 const u16 mask = checklist->models[i]->mask;
619 const u16 result = checklist->models[i]->result;
620 const u16 real_product_id = checklist->models[i]->real_product_id;
621
622 if ((bcdDevice & mask) == result) {
623 dbg_hid("Found wheel with real PID %X whose reported PID is %X\n", real_product_id, reported_product_id);
624 return real_product_id;
625 }
626 }
627
628 /* No match found. This is an unknown wheel model, do not touch it */
629 dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice);
630 return 0;
631}
632
633static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, const u16 bcdDevice)
634{
635 const u16 reported_product_id = hid->product;
636 int ret;
637
638 *real_product_id = lg4ff_identify_multimode_wheel(hid, reported_product_id, bcdDevice);
639 /* Probed wheel is not a multimode wheel */
640 if (!*real_product_id) {
641 *real_product_id = reported_product_id;
642 dbg_hid("Wheel is not a multimode wheel\n");
643 return LG4FF_MMODE_NOT_MULTIMODE;
644 }
645
646 /* Switch from "Driving Force" mode to native mode automatically.
647 * Otherwise keep the wheel in its current mode */
648 if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
649 reported_product_id != *real_product_id) {
650 const struct lg4ff_compat_mode_switch *s;
651
652 switch (*real_product_id) {
653 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
654 s = &lg4ff_mode_switch_dfp;
655 break;
656 case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
657 s = &lg4ff_mode_switch_g25;
658 break;
659 case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
660 s = &lg4ff_mode_switch_g27;
661 break;
662 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
663 s = &lg4ff_mode_switch_dfgt;
664 break;
665 default:
666 hid_err(hid, "Invalid product id %X\n", *real_product_id);
667 return LG4FF_MMODE_DONE;
668 }
669
670 ret = lg4ff_switch_compatibility_mode(hid, s);
671 if (ret) {
672 /* Wheel could not have been switched to native mode,
673 * leave it in "Driving Force" mode and continue */
674 hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret);
675 return LG4FF_MMODE_DONE;
676 }
677 return LG4FF_MMODE_SWITCHED;
678 }
679
680 return LG4FF_MMODE_DONE;
681}
682
683
558int lg4ff_init(struct hid_device *hid) 684int lg4ff_init(struct hid_device *hid)
559{ 685{
560 struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); 686 struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
561 struct input_dev *dev = hidinput->input; 687 struct input_dev *dev = hidinput->input;
688 const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
689 const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
562 struct lg4ff_device_entry *entry; 690 struct lg4ff_device_entry *entry;
563 struct lg_drv_data *drv_data; 691 struct lg_drv_data *drv_data;
564 struct usb_device_descriptor *udesc; 692 int error, i, j, ret;
565 int error, i, j; 693 u16 real_product_id;
566 __u16 bcdDevice, rev_maj, rev_min;
567 694
568 /* Check that the report looks ok */ 695 /* Check that the report looks ok */
569 if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7)) 696 if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
570 return -1; 697 return -1;
571 698
699 /* Check if a multimode wheel has been connected and
700 * handle it appropriately */
701 ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice);
702
703 /* Wheel has been told to switch to native mode. There is no point in going on
704 * with the initialization as the wheel will do a USB reset when it switches mode
705 */
706 if (ret == LG4FF_MMODE_SWITCHED)
707 return 0;
708
572 /* Check what wheel has been connected */ 709 /* Check what wheel has been connected */
573 for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) { 710 for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) {
574 if (hid->product == lg4ff_devices[i].product_id) { 711 if (hid->product == lg4ff_devices[i].product_id) {
@@ -583,28 +720,6 @@ int lg4ff_init(struct hid_device *hid)
583 return -1; 720 return -1;
584 } 721 }
585 722
586 /* Attempt to switch wheel to native mode when applicable */
587 udesc = &(hid_to_usb_dev(hid)->descriptor);
588 if (!udesc) {
589 hid_err(hid, "NULL USB device descriptor\n");
590 return -1;
591 }
592 bcdDevice = le16_to_cpu(udesc->bcdDevice);
593 rev_maj = bcdDevice >> 8;
594 rev_min = bcdDevice & 0xff;
595
596 if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_WHEEL) {
597 dbg_hid("Generic wheel detected, can it do native?\n");
598 dbg_hid("USB revision: %2x.%02x\n", rev_maj, rev_min);
599
600 for (j = 0; j < ARRAY_SIZE(lg4ff_revs); j++) {
601 if (lg4ff_revs[j].rev_maj == rev_maj && lg4ff_revs[j].rev_min == rev_min) {
602 hid_lg4ff_switch_native(hid, lg4ff_revs[j].command);
603 hid_info(hid, "Switched to native mode\n");
604 }
605 }
606 }
607
608 /* Set supported force feedback capabilities */ 723 /* Set supported force feedback capabilities */
609 for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++) 724 for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++)
610 set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit); 725 set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit);
@@ -637,7 +752,9 @@ int lg4ff_init(struct hid_device *hid)
637 /* Check if autocentering is available and 752 /* Check if autocentering is available and
638 * set the centering force to zero by default */ 753 * set the centering force to zero by default */
639 if (test_bit(FF_AUTOCENTER, dev->ffbit)) { 754 if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
640 if (rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN) /* Formula Force EX expects different autocentering command */ 755 /* Formula Force EX expects different autocentering command */
756 if ((bcdDevice >> 8) == LG4FF_FFEX_REV_MAJ &&
757 (bcdDevice & 0xff) == LG4FF_FFEX_REV_MIN)
641 dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex; 758 dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex;
642 else 759 else
643 dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default; 760 dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default;
@@ -711,25 +828,21 @@ out:
711 return 0; 828 return 0;
712} 829}
713 830
714
715
716int lg4ff_deinit(struct hid_device *hid) 831int lg4ff_deinit(struct hid_device *hid)
717{ 832{
718 struct lg4ff_device_entry *entry; 833 struct lg4ff_device_entry *entry;
719 struct lg_drv_data *drv_data; 834 struct lg_drv_data *drv_data;
720 835
721 device_remove_file(&hid->dev, &dev_attr_range);
722
723 drv_data = hid_get_drvdata(hid); 836 drv_data = hid_get_drvdata(hid);
724 if (!drv_data) { 837 if (!drv_data) {
725 hid_err(hid, "Error while deinitializing device, no private driver data.\n"); 838 hid_err(hid, "Error while deinitializing device, no private driver data.\n");
726 return -1; 839 return -1;
727 } 840 }
728 entry = drv_data->device_props; 841 entry = drv_data->device_props;
729 if (!entry) { 842 if (!entry)
730 hid_err(hid, "Error while deinitializing device, no device properties data.\n"); 843 goto out; /* Nothing more to do */
731 return -1; 844
732 } 845 device_remove_file(&hid->dev, &dev_attr_range);
733 846
734#ifdef CONFIG_LEDS_CLASS 847#ifdef CONFIG_LEDS_CLASS
735 { 848 {
@@ -752,6 +865,7 @@ int lg4ff_deinit(struct hid_device *hid)
752 /* Deallocate memory */ 865 /* Deallocate memory */
753 kfree(entry); 866 kfree(entry);
754 867
868out:
755 dbg_hid("Device successfully unregistered\n"); 869 dbg_hid("Device successfully unregistered\n");
756 return 0; 870 return 0;
757} 871}