diff options
| -rw-r--r-- | Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff | 27 | ||||
| -rw-r--r-- | drivers/hid/hid-lg4ff.c | 204 |
2 files changed, 201 insertions, 30 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff index 60f24a1d8119..b3f6a2ac5007 100644 --- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff +++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff | |||
| @@ -15,7 +15,32 @@ Description: Displays a set of alternate modes supported by a wheel. Each | |||
| 15 | Tag: Mode Name | 15 | Tag: Mode Name |
| 16 | Currently active mode is marked with an asterisk. List also | 16 | Currently active mode is marked with an asterisk. List also |
| 17 | contains an abstract item "native" which always denotes the | 17 | contains an abstract item "native" which always denotes the |
| 18 | native mode of the wheel. | 18 | native mode of the wheel. Echoing the mode tag switches the |
| 19 | wheel into the corresponding mode. Depending on the exact model | ||
| 20 | of the wheel not all listed modes might always be selectable. | ||
| 21 | If a wheel cannot be switched into the desired mode, -EINVAL | ||
| 22 | is returned accompanied with an explanatory message in the | ||
| 23 | kernel log. | ||
| 24 | This entry is not created for devices that have only one mode. | ||
| 25 | |||
| 26 | Currently supported mode switches: | ||
| 27 | Driving Force Pro: | ||
| 28 | DF-EX --> DFP | ||
| 29 | |||
| 30 | G25: | ||
| 31 | DF-EX --> DFP --> G25 | ||
| 32 | |||
| 33 | G27: | ||
| 34 | DF-EX <*> DFP <-> G25 <-> G27 | ||
| 35 | DF-EX <*--------> G25 <-> G27 | ||
| 36 | DF-EX <*----------------> G27 | ||
| 37 | |||
| 38 | DFGT: | ||
| 39 | DF-EX <*> DFP <-> DFGT | ||
| 40 | DF-EX <*--------> DFGT | ||
| 41 | |||
| 42 | * hid_logitech module must be loaded with lg4ff_no_autoswitch=1 | ||
| 43 | parameter set in order for the switch to DF-EX mode to work. | ||
| 19 | 44 | ||
| 20 | What: /sys/bus/hid/drivers/logitech/<dev>/real_id | 45 | What: /sys/bus/hid/drivers/logitech/<dev>/real_id |
| 21 | Date: Feb 2015 | 46 | Date: Feb 2015 |
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) { | ||
