diff options
author | Michal Malý <madcatxster@devoid-pointer.net> | 2015-02-18 11:59:23 -0500 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2015-02-18 15:14:54 -0500 |
commit | f31a2de3fe3680223a0dc93e484c491cc09473d3 (patch) | |
tree | 84cbbaaabe685e2c6d24ad4554e79c1f5ade8911 /drivers/hid | |
parent | a54dc7795efceb9a458457540c69450c995a2772 (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.c | 204 |
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 */ |
204 | static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_dfp = { | 204 | /* EXT_CMD9 - Understood by G27 and DFGT */ |
205 | 1, | 205 | static 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 | ||
209 | static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_dfgt = { | 211 | static 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 | ||
215 | static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_g25 = { | 217 | static 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 | ||
220 | static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_g27 = { | 223 | static 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 | |||
229 | static 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 */ | ||
236 | static 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 */ | ||
242 | static 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 | ||
513 | static 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 | |||
492 | static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s) | 570 | static 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 | ||
559 | static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) | 637 | static 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 | } |
563 | static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store); | 721 | static 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 | } |