aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid
diff options
context:
space:
mode:
authorMichal Malý <madcatxster@devoid-pointer.net>2015-02-18 11:59:23 -0500
committerJiri Kosina <jkosina@suse.cz>2015-02-18 15:14:54 -0500
commitf31a2de3fe3680223a0dc93e484c491cc09473d3 (patch)
tree84cbbaaabe685e2c6d24ad4554e79c1f5ade8911 /drivers/hid
parenta54dc7795efceb9a458457540c69450c995a2772 (diff)
HID: hid-lg4ff: Allow switching of Logitech gaming wheels between compatibility modes
Allow switching of Logitech gaming wheels between available compatibility modes through sysfs. This only applies to multimode wheels. Signed-off-by: Michal Malý <madcatxster@devoid-pointer.net> Tested-by: Simon Wood <simon@mungewell.org> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/hid-lg4ff.c204
1 files changed, 175 insertions, 29 deletions
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index dd307724965f..854982be3194 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -201,26 +201,47 @@ static const struct lg4ff_wheel_ident_checklist lg4ff_main_checklist = {
201}; 201};
202 202
203/* Compatibility mode switching commands */ 203/* Compatibility mode switching commands */
204static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_dfp = { 204/* EXT_CMD9 - Understood by G27 and DFGT */
205 1, 205static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfex = {
206 {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00} 206 2,
207 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
208 0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DF-EX with detach */
207}; 209};
208 210
209static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_dfgt = { 211static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfp = {
210 2, 212 2,
211 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ 213 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
212 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ 214 0xf8, 0x09, 0x01, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFP with detach */
213}; 215};
214 216
215static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_g25 = { 217static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g25 = {
216 1, 218 2,
217 {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00} 219 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
220 0xf8, 0x09, 0x02, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G25 with detach */
218}; 221};
219 222
220static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_g27 = { 223static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfgt = {
221 2, 224 2,
222 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */ 225 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
223 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */ 226 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFGT with detach */
227};
228
229static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g27 = {
230 2,
231 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
232 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G27 with detach */
233};
234
235/* EXT_CMD1 - Understood by DFP, G25, G27 and DFGT */
236static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext01_dfp = {
237 1,
238 {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
239};
240
241/* EXT_CMD16 - Understood by G25 and G27 */
242static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext16_g25 = {
243 1,
244 {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
224}; 245};
225 246
226/* Recalculates X axis value accordingly to currently selected range */ 247/* Recalculates X axis value accordingly to currently selected range */
@@ -489,6 +510,63 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
489 hid_hw_request(hid, report, HID_REQ_SET_REPORT); 510 hid_hw_request(hid, report, HID_REQ_SET_REPORT);
490} 511}
491 512
513static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id)
514{
515 switch (real_product_id) {
516 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
517 switch (target_product_id) {
518 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
519 return &lg4ff_mode_switch_ext01_dfp;
520 /* DFP can only be switched to its native mode */
521 default:
522 return NULL;
523 }
524 break;
525 case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
526 switch (target_product_id) {
527 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
528 return &lg4ff_mode_switch_ext01_dfp;
529 case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
530 return &lg4ff_mode_switch_ext16_g25;
531 /* G25 can only be switched to DFP mode or its native mode */
532 default:
533 return NULL;
534 }
535 break;
536 case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
537 switch (target_product_id) {
538 case USB_DEVICE_ID_LOGITECH_WHEEL:
539 return &lg4ff_mode_switch_ext09_dfex;
540 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
541 return &lg4ff_mode_switch_ext09_dfp;
542 case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
543 return &lg4ff_mode_switch_ext09_g25;
544 case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
545 return &lg4ff_mode_switch_ext09_g27;
546 /* G27 can only be switched to DF-EX, DFP, G25 or its native mode */
547 default:
548 return NULL;
549 }
550 break;
551 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
552 switch (target_product_id) {
553 case USB_DEVICE_ID_LOGITECH_WHEEL:
554 return &lg4ff_mode_switch_ext09_dfex;
555 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
556 return &lg4ff_mode_switch_ext09_dfp;
557 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
558 return &lg4ff_mode_switch_ext09_dfgt;
559 /* DFGT can only be switched to DF-EX, DFP or its native mode */
560 default:
561 return NULL;
562 }
563 break;
564 /* No other wheels have multiple modes */
565 default:
566 return NULL;
567 }
568}
569
492static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s) 570static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s)
493{ 571{
494 struct usb_device *usbdev = hid_to_usb_dev(hid); 572 struct usb_device *usbdev = hid_to_usb_dev(hid);
@@ -558,7 +636,87 @@ static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attr
558 636
559static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) 637static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
560{ 638{
561 return -ENOSYS; 639 struct hid_device *hid = to_hid_device(dev);
640 struct lg4ff_device_entry *entry;
641 struct lg_drv_data *drv_data;
642 const struct lg4ff_compat_mode_switch *s;
643 u16 target_product_id = 0;
644 int i, ret;
645 char *lbuf;
646
647 drv_data = hid_get_drvdata(hid);
648 if (!drv_data) {
649 hid_err(hid, "Private driver data not found!\n");
650 return -EINVAL;
651 }
652
653 entry = drv_data->device_props;
654 if (!entry) {
655 hid_err(hid, "Device properties not found!\n");
656 return -EINVAL;
657 }
658
659 /* Allow \n at the end of the input parameter */
660 lbuf = kasprintf(GFP_KERNEL, "%s", buf);
661 if (!lbuf)
662 return -ENOMEM;
663
664 i = strlen(lbuf);
665 if (lbuf[i-1] == '\n') {
666 if (i == 1) {
667 kfree(lbuf);
668 return -EINVAL;
669 }
670 lbuf[i-1] = '\0';
671 }
672
673 for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
674 const u16 mode_product_id = lg4ff_alternate_modes[i].product_id;
675 const char *tag = lg4ff_alternate_modes[i].tag;
676
677 if (entry->alternate_modes & BIT(i)) {
678 if (!strcmp(tag, lbuf)) {
679 if (!mode_product_id)
680 target_product_id = entry->real_product_id;
681 else
682 target_product_id = mode_product_id;
683 break;
684 }
685 }
686 }
687
688 if (i == LG4FF_MODE_MAX_IDX) {
689 hid_info(hid, "Requested mode \"%s\" is not supported by the device\n", lbuf);
690 kfree(lbuf);
691 return -EINVAL;
692 }
693 kfree(lbuf); /* Not needed anymore */
694
695 if (target_product_id == entry->product_id) /* Nothing to do */
696 return count;
697
698 /* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */
699 if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) {
700 hid_info(hid, "\"%s\" cannot be switched to \"DF-EX\" mode. Load the \"hid_logitech\" module with \"lg4ff_no_autoswitch=1\" parameter set and try again\n",
701 entry->real_name);
702 return -EINVAL;
703 }
704
705 /* Take care of hardware limitations */
706 if ((entry->real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) &&
707 entry->product_id > target_product_id) {
708 hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->real_name, lg4ff_alternate_modes[i].name);
709 return -EINVAL;
710 }
711
712 s = lg4ff_get_mode_switch_command(entry->real_product_id, target_product_id);
713 if (!s) {
714 hid_err(hid, "Invalid target product ID %X\n", target_product_id);
715 return -EINVAL;
716 }
717
718 ret = lg4ff_switch_compatibility_mode(hid, s);
719 return (ret == 0 ? count : ret);
562} 720}
563static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store); 721static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store);
564 722
@@ -783,7 +941,8 @@ static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 repo
783 } 941 }
784 } 942 }
785 943
786 /* No match found. This is an unknown wheel model, do not touch it */ 944 /* No match found. This is either Driving Force or an unknown
945 * wheel model, do not touch it */
787 dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice); 946 dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice);
788 return 0; 947 return 0;
789} 948}
@@ -806,22 +965,9 @@ static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_produc
806 if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && 965 if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
807 reported_product_id != *real_product_id && 966 reported_product_id != *real_product_id &&
808 !lg4ff_no_autoswitch) { 967 !lg4ff_no_autoswitch) {
809 const struct lg4ff_compat_mode_switch *s; 968 const struct lg4ff_compat_mode_switch *s = lg4ff_get_mode_switch_command(*real_product_id, *real_product_id);
810 969
811 switch (*real_product_id) { 970 if (!s) {
812 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
813 s = &lg4ff_mode_switch_dfp;
814 break;
815 case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
816 s = &lg4ff_mode_switch_g25;
817 break;
818 case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
819 s = &lg4ff_mode_switch_g27;
820 break;
821 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
822 s = &lg4ff_mode_switch_dfgt;
823 break;
824 default:
825 hid_err(hid, "Invalid product id %X\n", *real_product_id); 971 hid_err(hid, "Invalid product id %X\n", *real_product_id);
826 return LG4FF_MMODE_NOT_MULTIMODE; 972 return LG4FF_MMODE_NOT_MULTIMODE;
827 } 973 }