diff options
| -rw-r--r-- | Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff | 20 | ||||
| -rw-r--r-- | drivers/hid/hid-lg4ff.c | 205 |
2 files changed, 218 insertions, 7 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff index 167d9032b970..60f24a1d8119 100644 --- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff +++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff | |||
| @@ -5,3 +5,23 @@ Contact: Michal Malý <madcatxster@gmail.com> | |||
| 5 | Description: Display minimum, maximum and current range of the steering | 5 | Description: Display minimum, maximum and current range of the steering |
| 6 | wheel. Writing a value within min and max boundaries sets the | 6 | wheel. Writing a value within min and max boundaries sets the |
| 7 | range of the wheel. | 7 | range of the wheel. |
| 8 | |||
| 9 | What: /sys/bus/hid/drivers/logitech/<dev>/alternate_modes | ||
| 10 | Date: Feb 2015 | ||
| 11 | KernelVersion: 4.1 | ||
| 12 | Contact: Michal Malý <madcatxster@gmail.com> | ||
| 13 | Description: Displays a set of alternate modes supported by a wheel. Each | ||
| 14 | mode is listed as follows: | ||
| 15 | Tag: Mode Name | ||
| 16 | Currently active mode is marked with an asterisk. List also | ||
| 17 | contains an abstract item "native" which always denotes the | ||
| 18 | native mode of the wheel. | ||
| 19 | |||
| 20 | What: /sys/bus/hid/drivers/logitech/<dev>/real_id | ||
| 21 | Date: Feb 2015 | ||
| 22 | KernelVersion: 4.1 | ||
| 23 | Contact: Michal Malý <madcatxster@gmail.com> | ||
| 24 | Description: Displays the real model of the wheel regardless of any | ||
| 25 | alternate mode the wheel might be switched to. | ||
| 26 | It is a read-only value. | ||
| 27 | This entry is not created for devices that have only one mode. | ||
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index 190c5e3f46ce..a64a35ed291f 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c | |||
| @@ -34,10 +34,36 @@ | |||
| 34 | 34 | ||
| 35 | #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) |
| 36 | 36 | ||
| 37 | #define LG4FF_MMODE_DONE 0 | 37 | #define LG4FF_MMODE_IS_MULTIMODE 0 |
| 38 | #define LG4FF_MMODE_SWITCHED 1 | 38 | #define LG4FF_MMODE_SWITCHED 1 |
| 39 | #define LG4FF_MMODE_NOT_MULTIMODE 2 | 39 | #define LG4FF_MMODE_NOT_MULTIMODE 2 |
| 40 | 40 | ||
| 41 | #define LG4FF_MODE_NATIVE_IDX 0 | ||
| 42 | #define LG4FF_MODE_DFEX_IDX 1 | ||
| 43 | #define LG4FF_MODE_DFP_IDX 2 | ||
| 44 | #define LG4FF_MODE_G25_IDX 3 | ||
| 45 | #define LG4FF_MODE_DFGT_IDX 4 | ||
| 46 | #define LG4FF_MODE_G27_IDX 5 | ||
| 47 | #define LG4FF_MODE_MAX_IDX 6 | ||
| 48 | |||
| 49 | #define LG4FF_MODE_NATIVE BIT(LG4FF_MODE_NATIVE_IDX) | ||
| 50 | #define LG4FF_MODE_DFEX BIT(LG4FF_MODE_DFEX_IDX) | ||
| 51 | #define LG4FF_MODE_DFP BIT(LG4FF_MODE_DFP_IDX) | ||
| 52 | #define LG4FF_MODE_G25 BIT(LG4FF_MODE_G25_IDX) | ||
| 53 | #define LG4FF_MODE_DFGT BIT(LG4FF_MODE_DFGT_IDX) | ||
| 54 | #define LG4FF_MODE_G27 BIT(LG4FF_MODE_G27_IDX) | ||
| 55 | |||
| 56 | #define LG4FF_DFEX_TAG "DF-EX" | ||
| 57 | #define LG4FF_DFEX_NAME "Driving Force / Formula EX" | ||
| 58 | #define LG4FF_DFP_TAG "DFP" | ||
| 59 | #define LG4FF_DFP_NAME "Driving Force Pro" | ||
| 60 | #define LG4FF_G25_TAG "G25" | ||
| 61 | #define LG4FF_G25_NAME "G25 Racing Wheel" | ||
| 62 | #define LG4FF_G27_TAG "G27" | ||
| 63 | #define LG4FF_G27_NAME "G27 Racing Wheel" | ||
| 64 | #define LG4FF_DFGT_TAG "DFGT" | ||
| 65 | #define LG4FF_DFGT_NAME "Driving Force GT" | ||
| 66 | |||
| 41 | #define LG4FF_FFEX_REV_MAJ 0x21 | 67 | #define LG4FF_FFEX_REV_MAJ 0x21 |
| 42 | #define LG4FF_FFEX_REV_MIN 0x00 | 68 | #define LG4FF_FFEX_REV_MIN 0x00 |
| 43 | 69 | ||
| @@ -53,6 +79,10 @@ struct lg4ff_device_entry { | |||
| 53 | __u8 led_state; | 79 | __u8 led_state; |
| 54 | struct led_classdev *led[5]; | 80 | struct led_classdev *led[5]; |
| 55 | #endif | 81 | #endif |
| 82 | u32 alternate_modes; | ||
| 83 | const char *real_tag; | ||
| 84 | const char *real_name; | ||
| 85 | u16 real_product_id; | ||
| 56 | struct list_head list; | 86 | struct list_head list; |
| 57 | void (*set_range)(struct hid_device *hid, u16 range); | 87 | void (*set_range)(struct hid_device *hid, u16 range); |
| 58 | }; | 88 | }; |
| @@ -87,6 +117,19 @@ struct lg4ff_wheel_ident_checklist { | |||
| 87 | const struct lg4ff_wheel_ident_info *models[]; | 117 | const struct lg4ff_wheel_ident_info *models[]; |
| 88 | }; | 118 | }; |
| 89 | 119 | ||
| 120 | struct lg4ff_multimode_wheel { | ||
| 121 | const u16 product_id; | ||
| 122 | const u32 alternate_modes; | ||
| 123 | const char *real_tag; | ||
| 124 | const char *real_name; | ||
| 125 | }; | ||
| 126 | |||
| 127 | struct lg4ff_alternate_mode { | ||
| 128 | const u16 product_id; | ||
| 129 | const char *tag; | ||
| 130 | const char *name; | ||
| 131 | }; | ||
| 132 | |||
| 90 | static const struct lg4ff_wheel lg4ff_devices[] = { | 133 | static const struct lg4ff_wheel lg4ff_devices[] = { |
| 91 | {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, | 134 | {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, |
| 92 | {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, | 135 | {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, |
| @@ -98,6 +141,30 @@ static const struct lg4ff_wheel lg4ff_devices[] = { | |||
| 98 | {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL} | 141 | {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL} |
| 99 | }; | 142 | }; |
| 100 | 143 | ||
| 144 | static const struct lg4ff_multimode_wheel lg4ff_multimode_wheels[] = { | ||
| 145 | {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, | ||
| 146 | LG4FF_MODE_NATIVE | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, | ||
| 147 | LG4FF_DFP_TAG, LG4FF_DFP_NAME}, | ||
| 148 | {USB_DEVICE_ID_LOGITECH_G25_WHEEL, | ||
| 149 | LG4FF_MODE_NATIVE | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, | ||
| 150 | LG4FF_G25_TAG, LG4FF_G25_NAME}, | ||
| 151 | {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, | ||
| 152 | LG4FF_MODE_NATIVE | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, | ||
| 153 | LG4FF_DFGT_TAG, LG4FF_DFGT_NAME}, | ||
| 154 | {USB_DEVICE_ID_LOGITECH_G27_WHEEL, | ||
| 155 | LG4FF_MODE_NATIVE | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX, | ||
| 156 | LG4FF_G27_TAG, LG4FF_G27_NAME}, | ||
| 157 | }; | ||
| 158 | |||
| 159 | static const struct lg4ff_alternate_mode lg4ff_alternate_modes[] = { | ||
| 160 | [LG4FF_MODE_NATIVE_IDX] = {0, "native", ""}, | ||
| 161 | [LG4FF_MODE_DFEX_IDX] = {USB_DEVICE_ID_LOGITECH_WHEEL, LG4FF_DFEX_TAG, LG4FF_DFEX_NAME}, | ||
| 162 | [LG4FF_MODE_DFP_IDX] = {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, LG4FF_DFP_TAG, LG4FF_DFP_NAME}, | ||
| 163 | [LG4FF_MODE_G25_IDX] = {USB_DEVICE_ID_LOGITECH_G25_WHEEL, LG4FF_G25_TAG, LG4FF_G25_NAME}, | ||
| 164 | [LG4FF_MODE_DFGT_IDX] = {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, LG4FF_DFGT_TAG, LG4FF_DFGT_NAME}, | ||
| 165 | [LG4FF_MODE_G27_IDX] = {USB_DEVICE_ID_LOGITECH_G27_WHEEL, LG4FF_G27_TAG, LG4FF_G27_NAME} | ||
| 166 | }; | ||
| 167 | |||
| 101 | /* Multimode wheel identificators */ | 168 | /* Multimode wheel identificators */ |
| 102 | static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = { | 169 | static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = { |
| 103 | 0xf000, | 170 | 0xf000, |
| @@ -439,6 +506,61 @@ static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct | |||
| 439 | return 0; | 506 | return 0; |
| 440 | } | 507 | } |
| 441 | 508 | ||
| 509 | static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf) | ||
| 510 | { | ||
| 511 | struct hid_device *hid = to_hid_device(dev); | ||
| 512 | struct lg4ff_device_entry *entry; | ||
| 513 | struct lg_drv_data *drv_data; | ||
| 514 | ssize_t count = 0; | ||
| 515 | int i; | ||
| 516 | |||
| 517 | drv_data = hid_get_drvdata(hid); | ||
| 518 | if (!drv_data) { | ||
| 519 | hid_err(hid, "Private driver data not found!\n"); | ||
| 520 | return 0; | ||
| 521 | } | ||
| 522 | |||
| 523 | entry = drv_data->device_props; | ||
| 524 | if (!entry) { | ||
| 525 | hid_err(hid, "Device properties not found!\n"); | ||
| 526 | return 0; | ||
| 527 | } | ||
| 528 | |||
| 529 | if (!entry->real_name) { | ||
| 530 | hid_err(hid, "NULL pointer to string\n"); | ||
| 531 | return 0; | ||
| 532 | } | ||
| 533 | |||
| 534 | for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) { | ||
| 535 | if (entry->alternate_modes & BIT(i)) { | ||
| 536 | /* Print tag and full name */ | ||
| 537 | count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s", | ||
| 538 | lg4ff_alternate_modes[i].tag, | ||
| 539 | !lg4ff_alternate_modes[i].product_id ? entry->real_name : lg4ff_alternate_modes[i].name); | ||
| 540 | if (count >= PAGE_SIZE - 1) | ||
| 541 | return count; | ||
| 542 | |||
| 543 | /* Mark the currently active mode with an asterisk */ | ||
| 544 | if (lg4ff_alternate_modes[i].product_id == entry->product_id || | ||
| 545 | (lg4ff_alternate_modes[i].product_id == 0 && entry->product_id == entry->real_product_id)) | ||
| 546 | count += scnprintf(buf + count, PAGE_SIZE - count, " *\n"); | ||
| 547 | else | ||
| 548 | count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); | ||
| 549 | |||
| 550 | if (count >= PAGE_SIZE - 1) | ||
| 551 | return count; | ||
| 552 | } | ||
| 553 | } | ||
| 554 | |||
| 555 | return count; | ||
| 556 | } | ||
| 557 | |||
| 558 | static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) | ||
| 559 | { | ||
| 560 | return -ENOSYS; | ||
| 561 | } | ||
| 562 | static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store); | ||
| 563 | |||
| 442 | /* Read current range and display it in terminal */ | 564 | /* Read current range and display it in terminal */ |
| 443 | static ssize_t range_show(struct device *dev, struct device_attribute *attr, | 565 | static ssize_t range_show(struct device *dev, struct device_attribute *attr, |
| 444 | char *buf) | 566 | char *buf) |
| @@ -500,6 +622,41 @@ static ssize_t range_store(struct device *dev, struct device_attribute *attr, | |||
| 500 | } | 622 | } |
| 501 | static DEVICE_ATTR_RW(range); | 623 | static DEVICE_ATTR_RW(range); |
| 502 | 624 | ||
| 625 | static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *attr, char *buf) | ||
| 626 | { | ||
| 627 | struct hid_device *hid = to_hid_device(dev); | ||
| 628 | struct lg4ff_device_entry *entry; | ||
| 629 | struct lg_drv_data *drv_data; | ||
| 630 | size_t count; | ||
| 631 | |||
| 632 | drv_data = hid_get_drvdata(hid); | ||
| 633 | if (!drv_data) { | ||
| 634 | hid_err(hid, "Private driver data not found!\n"); | ||
| 635 | return 0; | ||
| 636 | } | ||
| 637 | |||
| 638 | entry = drv_data->device_props; | ||
| 639 | if (!entry) { | ||
| 640 | hid_err(hid, "Device properties not found!\n"); | ||
| 641 | return 0; | ||
| 642 | } | ||
| 643 | |||
| 644 | if (!entry->real_tag || !entry->real_name) { | ||
| 645 | hid_err(hid, "NULL pointer to string\n"); | ||
| 646 | return 0; | ||
| 647 | } | ||
| 648 | |||
| 649 | count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->real_tag, entry->real_name); | ||
| 650 | return count; | ||
| 651 | } | ||
| 652 | |||
| 653 | static ssize_t lg4ff_real_id_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) | ||
| 654 | { | ||
| 655 | /* Real ID is a read-only value */ | ||
| 656 | return -EPERM; | ||
| 657 | } | ||
| 658 | static DEVICE_ATTR(real_id, S_IRUGO, lg4ff_real_id_show, lg4ff_real_id_store); | ||
| 659 | |||
| 503 | #ifdef CONFIG_LEDS_CLASS | 660 | #ifdef CONFIG_LEDS_CLASS |
| 504 | static void lg4ff_set_leds(struct hid_device *hid, __u8 leds) | 661 | static void lg4ff_set_leds(struct hid_device *hid, __u8 leds) |
| 505 | { | 662 | { |
| @@ -664,7 +821,7 @@ static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_produc | |||
| 664 | break; | 821 | break; |
| 665 | default: | 822 | default: |
| 666 | hid_err(hid, "Invalid product id %X\n", *real_product_id); | 823 | hid_err(hid, "Invalid product id %X\n", *real_product_id); |
| 667 | return LG4FF_MMODE_DONE; | 824 | return LG4FF_MMODE_NOT_MULTIMODE; |
| 668 | } | 825 | } |
| 669 | 826 | ||
| 670 | ret = lg4ff_switch_compatibility_mode(hid, s); | 827 | ret = lg4ff_switch_compatibility_mode(hid, s); |
| @@ -672,12 +829,12 @@ static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_produc | |||
| 672 | /* Wheel could not have been switched to native mode, | 829 | /* Wheel could not have been switched to native mode, |
| 673 | * leave it in "Driving Force" mode and continue */ | 830 | * leave it in "Driving Force" mode and continue */ |
| 674 | hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret); | 831 | hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret); |
| 675 | return LG4FF_MMODE_DONE; | 832 | return LG4FF_MMODE_IS_MULTIMODE; |
| 676 | } | 833 | } |
| 677 | return LG4FF_MMODE_SWITCHED; | 834 | return LG4FF_MMODE_SWITCHED; |
| 678 | } | 835 | } |
| 679 | 836 | ||
| 680 | return LG4FF_MMODE_DONE; | 837 | return LG4FF_MMODE_IS_MULTIMODE; |
| 681 | } | 838 | } |
| 682 | 839 | ||
| 683 | 840 | ||
| @@ -689,7 +846,8 @@ int lg4ff_init(struct hid_device *hid) | |||
| 689 | const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice); | 846 | const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice); |
| 690 | struct lg4ff_device_entry *entry; | 847 | struct lg4ff_device_entry *entry; |
| 691 | struct lg_drv_data *drv_data; | 848 | struct lg_drv_data *drv_data; |
| 692 | int error, i, j, ret; | 849 | int error, i, j; |
| 850 | int mmode_ret, mmode_idx = -1; | ||
| 693 | u16 real_product_id; | 851 | u16 real_product_id; |
| 694 | 852 | ||
| 695 | /* Check that the report looks ok */ | 853 | /* Check that the report looks ok */ |
| @@ -698,12 +856,12 @@ int lg4ff_init(struct hid_device *hid) | |||
| 698 | 856 | ||
| 699 | /* Check if a multimode wheel has been connected and | 857 | /* Check if a multimode wheel has been connected and |
| 700 | * handle it appropriately */ | 858 | * handle it appropriately */ |
| 701 | ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice); | 859 | mmode_ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice); |
| 702 | 860 | ||
| 703 | /* Wheel has been told to switch to native mode. There is no point in going on | 861 | /* 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 | 862 | * with the initialization as the wheel will do a USB reset when it switches mode |
| 705 | */ | 863 | */ |
| 706 | if (ret == LG4FF_MMODE_SWITCHED) | 864 | if (mmode_ret == LG4FF_MMODE_SWITCHED) |
| 707 | return 0; | 865 | return 0; |
| 708 | 866 | ||
| 709 | /* Check what wheel has been connected */ | 867 | /* Check what wheel has been connected */ |
| @@ -720,6 +878,18 @@ int lg4ff_init(struct hid_device *hid) | |||
| 720 | return -1; | 878 | return -1; |
| 721 | } | 879 | } |
| 722 | 880 | ||
| 881 | if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) { | ||
| 882 | for (mmode_idx = 0; mmode_idx < ARRAY_SIZE(lg4ff_multimode_wheels); mmode_idx++) { | ||
| 883 | if (real_product_id == lg4ff_multimode_wheels[mmode_idx].product_id) | ||
| 884 | break; | ||
| 885 | } | ||
| 886 | |||
| 887 | if (mmode_idx == ARRAY_SIZE(lg4ff_multimode_wheels)) { | ||
| 888 | hid_err(hid, "Device product ID %X is not listed as a multimode wheel", real_product_id); | ||
| 889 | return -1; | ||
| 890 | } | ||
| 891 | } | ||
| 892 | |||
| 723 | /* Set supported force feedback capabilities */ | 893 | /* Set supported force feedback capabilities */ |
| 724 | for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++) | 894 | for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++) |
| 725 | set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit); | 895 | set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit); |
| @@ -745,9 +915,16 @@ int lg4ff_init(struct hid_device *hid) | |||
| 745 | drv_data->device_props = entry; | 915 | drv_data->device_props = entry; |
| 746 | 916 | ||
| 747 | entry->product_id = lg4ff_devices[i].product_id; | 917 | entry->product_id = lg4ff_devices[i].product_id; |
| 918 | entry->real_product_id = real_product_id; | ||
| 748 | entry->min_range = lg4ff_devices[i].min_range; | 919 | entry->min_range = lg4ff_devices[i].min_range; |
| 749 | entry->max_range = lg4ff_devices[i].max_range; | 920 | entry->max_range = lg4ff_devices[i].max_range; |
| 750 | entry->set_range = lg4ff_devices[i].set_range; | 921 | entry->set_range = lg4ff_devices[i].set_range; |
| 922 | if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) { | ||
| 923 | BUG_ON(mmode_idx == -1); | ||
| 924 | entry->alternate_modes = lg4ff_multimode_wheels[mmode_idx].alternate_modes; | ||
| 925 | entry->real_tag = lg4ff_multimode_wheels[mmode_idx].real_tag; | ||
| 926 | entry->real_name = lg4ff_multimode_wheels[mmode_idx].real_name; | ||
| 927 | } | ||
| 751 | 928 | ||
| 752 | /* Check if autocentering is available and | 929 | /* Check if autocentering is available and |
| 753 | * set the centering force to zero by default */ | 930 | * set the centering force to zero by default */ |
| @@ -766,6 +943,14 @@ int lg4ff_init(struct hid_device *hid) | |||
| 766 | error = device_create_file(&hid->dev, &dev_attr_range); | 943 | error = device_create_file(&hid->dev, &dev_attr_range); |
| 767 | if (error) | 944 | if (error) |
| 768 | return error; | 945 | return error; |
| 946 | if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) { | ||
| 947 | error = device_create_file(&hid->dev, &dev_attr_real_id); | ||
| 948 | if (error) | ||
| 949 | return error; | ||
| 950 | error = device_create_file(&hid->dev, &dev_attr_alternate_modes); | ||
| 951 | if (error) | ||
| 952 | return error; | ||
| 953 | } | ||
| 769 | dbg_hid("sysfs interface created\n"); | 954 | dbg_hid("sysfs interface created\n"); |
| 770 | 955 | ||
| 771 | /* Set the maximum range to start with */ | 956 | /* Set the maximum range to start with */ |
| @@ -844,6 +1029,12 @@ int lg4ff_deinit(struct hid_device *hid) | |||
| 844 | 1029 | ||
| 845 | device_remove_file(&hid->dev, &dev_attr_range); | 1030 | device_remove_file(&hid->dev, &dev_attr_range); |
| 846 | 1031 | ||
| 1032 | /* Multimode devices will have at least the "MODE_NATIVE" bit set */ | ||
| 1033 | if (entry->alternate_modes) { | ||
| 1034 | device_remove_file(&hid->dev, &dev_attr_real_id); | ||
| 1035 | device_remove_file(&hid->dev, &dev_attr_alternate_modes); | ||
| 1036 | } | ||
| 1037 | |||
| 847 | #ifdef CONFIG_LEDS_CLASS | 1038 | #ifdef CONFIG_LEDS_CLASS |
| 848 | { | 1039 | { |
| 849 | int j; | 1040 | int j; |
