Support for some Logitech devices which breaks less or more
HID specification.
+config LOGITECH_FF
+ bool "Logitech force feedback"
+ depends on HID_LOGITECH
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here if you have one of these devices:
+ - Logitech WingMan Cordless RumblePad
+ - Logitech WingMan Cordless RumblePad 2
+ - Logitech WingMan Force 3D
+ - Logitech Formula Force EX
+ - Logitech MOMO Force wheel
+
+ and if you want to enable force feedback for them.
+ Note: if you say N here, this device will still be supported, but without
+ force feedback.
+
+config LOGIRUMBLEPAD2_FF
+ bool "Logitech Rumblepad 2 force feedback"
+ depends on HID_LOGITECH
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here if you want to enable force feedback support for Logitech
+ Rumblepad 2 devices.
+
config HID_MICROSOFT
tristate "Microsoft"
default m
obj-m += hid-dummy.o
endif
+hid-logitech-objs := hid-lg.o
+ifdef CONFIG_LOGITECH_FF
+ hid-logitech-objs += hid-lgff.o
+endif
+ifdef CONFIG_LOGIRUMBLEPAD2_FF
+ hid-logitech-objs += hid-lg2ff.o
+endif
+
obj-$(CONFIG_HID_A4TECH) += hid-a4tech.o
obj-$(CONFIG_HID_APPLE) += hid-apple.o
obj-$(CONFIG_HID_BELKIN) += hid-belkin.o
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_V150) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_F3D) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FORCE3D_PRO) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_LK6K) },
#define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101
#define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST 0xc110
#define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f
+#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD 0xc211
#define USB_DEVICE_ID_LOGITECH_EXTREME_3D 0xc215
+#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2 0xc218
+#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2 0xc219
+#define USB_DEVICE_ID_LOGITECH_WINGMAN_F3D 0xc283
+#define USB_DEVICE_ID_LOGITECH_FORCE3D_PRO 0xc286
#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294
+#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL 0xc295
#define USB_DEVICE_ID_LOGITECH_ELITE_KBD 0xc30a
#define USB_DEVICE_ID_LOGITECH_KBD 0xc311
#define USB_DEVICE_ID_S510_RECEIVER 0xc50c
#define USB_DEVICE_ID_DINOVO_DESKTOP 0xc704
#define USB_DEVICE_ID_DINOVO_EDGE 0xc714
#define USB_DEVICE_ID_DINOVO_MINI 0xc71f
+#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2 0xca03
#define USB_VENDOR_ID_MCC 0x09db
#define USB_DEVICE_ID_MCC_PMD1024LS 0x0076
--- /dev/null
+/*
+ * HID driver for some logitech "special" devices
+ *
+ * Copyright (c) 1999 Andreas Gal
+ * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ * Copyright (c) 2006-2007 Jiri Kosina
+ * Copyright (c) 2007 Paul Walmsley
+ * Copyright (c) 2008 Jiri Slaby
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+#include "hid-lg.h"
+
+#define LG_RDESC 0x001
+#define LG_BAD_RELATIVE_KEYS 0x002
+#define LG_DUPLICATE_USAGES 0x004
+#define LG_RESET_LEDS 0x008
+#define LG_EXPANDED_KEYMAP 0x010
+#define LG_IGNORE_DOUBLED_WHEEL 0x020
+#define LG_WIRELESS 0x040
+#define LG_INVERT_HWHEEL 0x080
+#define LG_NOGET 0x100
+#define LG_FF 0x200
+#define LG_FF2 0x400
+
+/*
+ * Certain Logitech keyboards send in report #3 keys which are far
+ * above the logical maximum described in descriptor. This extends
+ * the original value of 0x28c of logical maximum to 0x104d
+ */
+static void lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int rsize)
+{
+ unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+
+ if ((quirks & LG_RDESC) && rsize >= 90 && rdesc[83] == 0x26 &&
+ rdesc[84] == 0x8c && rdesc[85] == 0x02) {
+ dev_info(&hdev->dev, "fixing up Logitech keyboard report "
+ "descriptor\n");
+ rdesc[84] = rdesc[89] = 0x4d;
+ rdesc[85] = rdesc[90] = 0x10;
+ }
+}
+
+#define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
+ EV_KEY, (c))
+
+static int lg_ultrax_remote_mapping(struct hid_input *hi,
+ struct hid_usage *usage, unsigned long **bit, int *max)
+{
+ if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
+ return 0;
+
+ set_bit(EV_REP, hi->input->evbit);
+ switch (usage->hid & HID_USAGE) {
+ /* Reported on Logitech Ultra X Media Remote */
+ case 0x004: lg_map_key_clear(KEY_AGAIN); break;
+ case 0x00d: lg_map_key_clear(KEY_HOME); break;
+ case 0x024: lg_map_key_clear(KEY_SHUFFLE); break;
+ case 0x025: lg_map_key_clear(KEY_TV); break;
+ case 0x026: lg_map_key_clear(KEY_MENU); break;
+ case 0x031: lg_map_key_clear(KEY_AUDIO); break;
+ case 0x032: lg_map_key_clear(KEY_TEXT); break;
+ case 0x033: lg_map_key_clear(KEY_LAST); break;
+ case 0x047: lg_map_key_clear(KEY_MP3); break;
+ case 0x048: lg_map_key_clear(KEY_DVD); break;
+ case 0x049: lg_map_key_clear(KEY_MEDIA); break;
+ case 0x04a: lg_map_key_clear(KEY_VIDEO); break;
+ case 0x04b: lg_map_key_clear(KEY_ANGLE); break;
+ case 0x04c: lg_map_key_clear(KEY_LANGUAGE); break;
+ case 0x04d: lg_map_key_clear(KEY_SUBTITLE); break;
+ case 0x051: lg_map_key_clear(KEY_RED); break;
+ case 0x052: lg_map_key_clear(KEY_CLOSE); break;
+
+ default:
+ return 0;
+ }
+ return 1;
+}
+
+static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
+ return 0;
+
+ switch (usage->hid & HID_USAGE) {
+ case 0x1001: lg_map_key_clear(KEY_MESSENGER); break;
+ case 0x1003: lg_map_key_clear(KEY_SOUND); break;
+ case 0x1004: lg_map_key_clear(KEY_VIDEO); break;
+ case 0x1005: lg_map_key_clear(KEY_AUDIO); break;
+ case 0x100a: lg_map_key_clear(KEY_DOCUMENTS); break;
+ case 0x1011: lg_map_key_clear(KEY_PREVIOUSSONG); break;
+ case 0x1012: lg_map_key_clear(KEY_NEXTSONG); break;
+ case 0x1013: lg_map_key_clear(KEY_CAMERA); break;
+ case 0x1014: lg_map_key_clear(KEY_MESSENGER); break;
+ case 0x1015: lg_map_key_clear(KEY_RECORD); break;
+ case 0x1016: lg_map_key_clear(KEY_PLAYER); break;
+ case 0x1017: lg_map_key_clear(KEY_EJECTCD); break;
+ case 0x1018: lg_map_key_clear(KEY_MEDIA); break;
+ case 0x1019: lg_map_key_clear(KEY_PROG1); break;
+ case 0x101a: lg_map_key_clear(KEY_PROG2); break;
+ case 0x101b: lg_map_key_clear(KEY_PROG3); break;
+ case 0x101f: lg_map_key_clear(KEY_ZOOMIN); break;
+ case 0x1020: lg_map_key_clear(KEY_ZOOMOUT); break;
+ case 0x1021: lg_map_key_clear(KEY_ZOOMRESET); break;
+ case 0x1023: lg_map_key_clear(KEY_CLOSE); break;
+ case 0x1027: lg_map_key_clear(KEY_MENU); break;
+ /* this one is marked as 'Rotate' */
+ case 0x1028: lg_map_key_clear(KEY_ANGLE); break;
+ case 0x1029: lg_map_key_clear(KEY_SHUFFLE); break;
+ case 0x102a: lg_map_key_clear(KEY_BACK); break;
+ case 0x102b: lg_map_key_clear(KEY_CYCLEWINDOWS); break;
+ case 0x1041: lg_map_key_clear(KEY_BATTERY); break;
+ case 0x1042: lg_map_key_clear(KEY_WORDPROCESSOR); break;
+ case 0x1043: lg_map_key_clear(KEY_SPREADSHEET); break;
+ case 0x1044: lg_map_key_clear(KEY_PRESENTATION); break;
+ case 0x1045: lg_map_key_clear(KEY_UNDO); break;
+ case 0x1046: lg_map_key_clear(KEY_REDO); break;
+ case 0x1047: lg_map_key_clear(KEY_PRINT); break;
+ case 0x1048: lg_map_key_clear(KEY_SAVE); break;
+ case 0x1049: lg_map_key_clear(KEY_PROG1); break;
+ case 0x104a: lg_map_key_clear(KEY_PROG2); break;
+ case 0x104b: lg_map_key_clear(KEY_PROG3); break;
+ case 0x104c: lg_map_key_clear(KEY_PROG4); break;
+
+ default:
+ return 0;
+ }
+ return 1;
+}
+
+static int lg_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+ struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ /* extended mapping for certain Logitech hardware (Logitech cordless
+ desktop LX500) */
+ static const u8 e_keymap[] = {
+ 0,216, 0,213,175,156, 0, 0, 0, 0,
+ 144, 0, 0, 0, 0, 0, 0, 0, 0,212,
+ 174,167,152,161,112, 0, 0, 0,154, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,183,184,185,186,187,
+ 188,189,190,191,192,193,194, 0, 0, 0
+ };
+ unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+ unsigned int hid = usage->hid;
+
+ if (hdev->product == USB_DEVICE_ID_LOGITECH_RECEIVER &&
+ lg_ultrax_remote_mapping(hi, usage, bit, max))
+ return 1;
+
+ if ((quirks & LG_WIRELESS) && lg_wireless_mapping(hi, usage, bit, max))
+ return 1;
+
+ if ((hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
+ return 0;
+
+ hid &= HID_USAGE;
+
+ /* Special handling for Logitech Cordless Desktop */
+ if (field->application == HID_GD_MOUSE) {
+ if ((quirks & LG_IGNORE_DOUBLED_WHEEL) &&
+ (hid == 7 || hid == 8))
+ return -1;
+ } else {
+ if ((quirks & LG_EXPANDED_KEYMAP) &&
+ hid < ARRAY_SIZE(e_keymap) &&
+ e_keymap[hid] != 0) {
+ hid_map_usage(hi, usage, bit, max, EV_KEY,
+ e_keymap[hid]);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int lg_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+ struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+
+ if ((quirks & LG_BAD_RELATIVE_KEYS) && usage->type == EV_KEY &&
+ (field->flags & HID_MAIN_ITEM_RELATIVE))
+ field->flags &= ~HID_MAIN_ITEM_RELATIVE;
+
+ if ((quirks & LG_DUPLICATE_USAGES) && (usage->type == EV_KEY ||
+ usage->type == EV_REL || usage->type == EV_ABS))
+ clear_bit(usage->code, *bit);
+
+ return 0;
+}
+
+static int lg_event(struct hid_device *hdev, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+
+ if ((quirks & LG_INVERT_HWHEEL) && usage->code == REL_HWHEEL) {
+ input_event(field->hidinput->input, usage->type, usage->code,
+ -value);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ unsigned long quirks = id->driver_data;
+ unsigned int connect_mask = HID_CONNECT_DEFAULT;
+ int ret;
+
+ hid_set_drvdata(hdev, (void *)quirks);
+
+ if (quirks & LG_NOGET)
+ hdev->quirks |= HID_QUIRK_NOGET;
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ dev_err(&hdev->dev, "parse failed\n");
+ goto err_free;
+ }
+
+ if (quirks & (LG_FF | LG_FF2))
+ connect_mask &= ~HID_CONNECT_FF;
+
+ ret = hid_hw_start(hdev, connect_mask);
+ if (ret) {
+ dev_err(&hdev->dev, "hw start failed\n");
+ goto err_free;
+ }
+
+ if (quirks & LG_RESET_LEDS)
+ usbhid_set_leds(hdev);
+
+ if (quirks & LG_FF)
+ lgff_init(hdev);
+ if (quirks & LG_FF2)
+ lg2ff_init(hdev);
+
+ return 0;
+err_free:
+ return ret;
+}
+
+static const struct hid_device_id lg_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
+ .driver_data = LG_RDESC | LG_WIRELESS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER),
+ .driver_data = LG_RDESC | LG_WIRELESS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2),
+ .driver_data = LG_RDESC | LG_WIRELESS },
+
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER),
+ .driver_data = LG_BAD_RELATIVE_KEYS },
+
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP),
+ .driver_data = LG_DUPLICATE_USAGES },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE),
+ .driver_data = LG_DUPLICATE_USAGES },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI),
+ .driver_data = LG_DUPLICATE_USAGES },
+
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_KBD),
+ .driver_data = LG_RESET_LEDS },
+
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD),
+ .driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500),
+ .driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },
+
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_LX3),
+ .driver_data = LG_INVERT_HWHEEL },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_V150),
+ .driver_data = LG_INVERT_HWHEEL },
+
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D),
+ .driver_data = LG_NOGET },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL),
+ .driver_data = LG_NOGET | LG_FF },
+
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD),
+ .driver_data = LG_FF },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2),
+ .driver_data = LG_FF },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_F3D),
+ .driver_data = LG_FF },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FORCE3D_PRO),
+ .driver_data = LG_FF },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL),
+ .driver_data = LG_FF },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2),
+ .driver_data = LG_FF },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2),
+ .driver_data = LG_FF2 },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, lg_devices);
+
+static struct hid_driver lg_driver = {
+ .name = "logitech",
+ .id_table = lg_devices,
+ .report_fixup = lg_report_fixup,
+ .input_mapping = lg_input_mapping,
+ .input_mapped = lg_input_mapped,
+ .event = lg_event,
+ .probe = lg_probe,
+};
+
+static int lg_init(void)
+{
+ return hid_register_driver(&lg_driver);
+}
+
+static void lg_exit(void)
+{
+ hid_unregister_driver(&lg_driver);
+}
+
+module_init(lg_init);
+module_exit(lg_exit);
+MODULE_LICENSE("GPL");
+
+HID_COMPAT_LOAD_DRIVER(logitech);
--- /dev/null
+#ifndef __HID_LG_H
+#define __HID_LG_H
+
+#include <linux/autoconf.h>
+
+#ifdef CONFIG_LOGITECH_FF
+int lgff_init(struct hid_device *hdev);
+#else
+static inline int lgff_init(struct hid_device *hdev) { return -1; }
+#endif
+
+#ifdef CONFIG_LOGIRUMBLEPAD2_FF
+int lg2ff_init(struct hid_device *hdev);
+#else
+static inline int lg2ff_init(struct hid_device *hdev) { return -1; }
+#endif
+
+#endif
--- /dev/null
+/*
+ * Force feedback support for Logitech Rumblepad 2
+ *
+ * Copyright (c) 2008 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+
+#include "usbhid/usbhid.h"
+#include "hid-lg.h"
+
+struct lg2ff_device {
+ struct hid_report *report;
+};
+
+static int play_effect(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct hid_device *hid = input_get_drvdata(dev);
+ struct lg2ff_device *lg2ff = data;
+ int weak, strong;
+
+ strong = effect->u.rumble.strong_magnitude;
+ weak = effect->u.rumble.weak_magnitude;
+
+ if (weak || strong) {
+ weak = weak * 0xff / 0xffff;
+ strong = strong * 0xff / 0xffff;
+
+ lg2ff->report->field[0]->value[0] = 0x51;
+ lg2ff->report->field[0]->value[2] = weak;
+ lg2ff->report->field[0]->value[4] = strong;
+ } else {
+ lg2ff->report->field[0]->value[0] = 0xf3;
+ lg2ff->report->field[0]->value[2] = 0x00;
+ lg2ff->report->field[0]->value[4] = 0x00;
+ }
+
+ usbhid_submit_report(hid, lg2ff->report, USB_DIR_OUT);
+ return 0;
+}
+
+int lg2ff_init(struct hid_device *hid)
+{
+ struct lg2ff_device *lg2ff;
+ struct hid_report *report;
+ struct hid_input *hidinput = list_entry(hid->inputs.next,
+ struct hid_input, list);
+ struct list_head *report_list =
+ &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+ struct input_dev *dev = hidinput->input;
+ int error;
+
+ if (list_empty(report_list)) {
+ printk(KERN_ERR "hid-lg2ff: no output report found\n");
+ return -ENODEV;
+ }
+
+ report = list_entry(report_list->next, struct hid_report, list);
+
+ if (report->maxfield < 1) {
+ printk(KERN_ERR "hid-lg2ff: output report is empty\n");
+ return -ENODEV;
+ }
+ if (report->field[0]->report_count < 7) {
+ printk(KERN_ERR "hid-lg2ff: not enough values in the field\n");
+ return -ENODEV;
+ }
+
+ lg2ff = kmalloc(sizeof(struct lg2ff_device), GFP_KERNEL);
+ if (!lg2ff)
+ return -ENOMEM;
+
+ set_bit(FF_RUMBLE, dev->ffbit);
+
+ error = input_ff_create_memless(dev, lg2ff, play_effect);
+ if (error) {
+ kfree(lg2ff);
+ return error;
+ }
+
+ lg2ff->report = report;
+ report->field[0]->value[0] = 0xf3;
+ report->field[0]->value[1] = 0x00;
+ report->field[0]->value[2] = 0x00;
+ report->field[0]->value[3] = 0x00;
+ report->field[0]->value[4] = 0x00;
+ report->field[0]->value[5] = 0x00;
+ report->field[0]->value[6] = 0x00;
+
+ usbhid_submit_report(hid, report, USB_DIR_OUT);
+
+ printk(KERN_INFO "Force feedback for Logitech Rumblepad 2 by "
+ "Anssi Hannula <anssi.hannula@gmail.com>\n");
+
+ return 0;
+}
--- /dev/null
+/*
+ * Force feedback support for hid-compliant for some of the devices from
+ * Logitech, namely:
+ * - WingMan Cordless RumblePad
+ * - WingMan Force 3D
+ *
+ * Copyright (c) 2002-2004 Johann Deneux
+ * Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by
+ * e-mail - mail your message to <johann.deneux@it.uu.se>
+ */
+
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+
+#include "usbhid/usbhid.h"
+#include "hid-lg.h"
+
+struct dev_type {
+ u16 idVendor;
+ u16 idProduct;
+ const signed short *ff;
+};
+
+static const signed short ff_rumble[] = {
+ FF_RUMBLE,
+ -1
+};
+
+static const signed short ff_joystick[] = {
+ FF_CONSTANT,
+ -1
+};
+
+static const struct dev_type devices[] = {
+ { 0x046d, 0xc211, ff_rumble },
+ { 0x046d, 0xc219, ff_rumble },
+ { 0x046d, 0xc283, ff_joystick },
+ { 0x046d, 0xc286, ff_joystick },
+ { 0x046d, 0xc294, ff_joystick },
+ { 0x046d, 0xc295, ff_joystick },
+ { 0x046d, 0xca03, ff_joystick },
+};
+
+static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+ struct hid_device *hid = input_get_drvdata(dev);
+ struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+ struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+ int x, y;
+ unsigned int left, right;
+
+#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff
+
+ switch (effect->type) {
+ case FF_CONSTANT:
+ x = effect->u.ramp.start_level + 0x7f; /* 0x7f is center */
+ y = effect->u.ramp.end_level + 0x7f;
+ CLAMP(x);
+ CLAMP(y);
+ report->field[0]->value[0] = 0x51;
+ report->field[0]->value[1] = 0x08;
+ report->field[0]->value[2] = x;
+ report->field[0]->value[3] = y;
+ dbg_hid("(x, y)=(%04x, %04x)\n", x, y);
+ usbhid_submit_report(hid, report, USB_DIR_OUT);
+ break;
+
+ case FF_RUMBLE:
+ right = effect->u.rumble.strong_magnitude;
+ left = effect->u.rumble.weak_magnitude;
+ right = right * 0xff / 0xffff;
+ left = left * 0xff / 0xffff;
+ CLAMP(left);
+ CLAMP(right);
+ report->field[0]->value[0] = 0x42;
+ report->field[0]->value[1] = 0x00;
+ report->field[0]->value[2] = left;
+ report->field[0]->value[3] = right;
+ dbg_hid("(left, right)=(%04x, %04x)\n", left, right);
+ usbhid_submit_report(hid, report, USB_DIR_OUT);
+ break;
+ }
+ return 0;
+}
+
+int lgff_init(struct hid_device* hid)
+{
+ struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+ struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+ struct input_dev *dev = hidinput->input;
+ struct hid_report *report;
+ struct hid_field *field;
+ const signed short *ff_bits = ff_joystick;
+ int error;
+ int i;
+
+ /* Find the report to use */
+ if (list_empty(report_list)) {
+ err_hid("No output report found");
+ return -1;
+ }
+
+ /* Check that the report looks ok */
+ report = list_entry(report_list->next, struct hid_report, list);
+ if (!report) {
+ err_hid("NULL output report");
+ return -1;
+ }
+
+ field = report->field[0];
+ if (!field) {
+ err_hid("NULL field");
+ return -1;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(devices); i++) {
+ if (dev->id.vendor == devices[i].idVendor &&
+ dev->id.product == devices[i].idProduct) {
+ ff_bits = devices[i].ff;
+ break;
+ }
+ }
+
+ for (i = 0; ff_bits[i] >= 0; i++)
+ set_bit(ff_bits[i], dev->ffbit);
+
+ error = input_ff_create_memless(dev, NULL, hid_lgff_play);
+ if (error)
+ return error;
+
+ printk(KERN_INFO "Force feedback for Logitech force feedback devices by Johann Deneux <johann.deneux@it.uu.se>\n");
+
+ return 0;
+}
+++ /dev/null
-/*
- * HID driver for some logitech "special" devices
- *
- * Copyright (c) 1999 Andreas Gal
- * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
- * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
- * Copyright (c) 2006-2007 Jiri Kosina
- * Copyright (c) 2007 Paul Walmsley
- * Copyright (c) 2008 Jiri Slaby
- */
-
-/*
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the Free
- * Software Foundation; either version 2 of the License, or (at your option)
- * any later version.
- */
-
-#include <linux/device.h>
-#include <linux/hid.h>
-#include <linux/module.h>
-
-#include "hid-ids.h"
-
-#define LG_RDESC 0x001
-#define LG_BAD_RELATIVE_KEYS 0x002
-#define LG_DUPLICATE_USAGES 0x004
-#define LG_RESET_LEDS 0x008
-#define LG_EXPANDED_KEYMAP 0x010
-#define LG_IGNORE_DOUBLED_WHEEL 0x020
-#define LG_WIRELESS 0x040
-#define LG_INVERT_HWHEEL 0x080
-#define LG_NOGET 0x100
-
-/*
- * Certain Logitech keyboards send in report #3 keys which are far
- * above the logical maximum described in descriptor. This extends
- * the original value of 0x28c of logical maximum to 0x104d
- */
-static void lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int rsize)
-{
- unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
-
- if ((quirks & LG_RDESC) && rsize >= 90 && rdesc[83] == 0x26 &&
- rdesc[84] == 0x8c && rdesc[85] == 0x02) {
- dev_info(&hdev->dev, "fixing up Logitech keyboard report "
- "descriptor\n");
- rdesc[84] = rdesc[89] = 0x4d;
- rdesc[85] = rdesc[90] = 0x10;
- }
-}
-
-#define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \
- EV_KEY, (c))
-
-static int lg_ultrax_remote_mapping(struct hid_input *hi,
- struct hid_usage *usage, unsigned long **bit, int *max)
-{
- if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
- return 0;
-
- set_bit(EV_REP, hi->input->evbit);
- switch (usage->hid & HID_USAGE) {
- /* Reported on Logitech Ultra X Media Remote */
- case 0x004: lg_map_key_clear(KEY_AGAIN); break;
- case 0x00d: lg_map_key_clear(KEY_HOME); break;
- case 0x024: lg_map_key_clear(KEY_SHUFFLE); break;
- case 0x025: lg_map_key_clear(KEY_TV); break;
- case 0x026: lg_map_key_clear(KEY_MENU); break;
- case 0x031: lg_map_key_clear(KEY_AUDIO); break;
- case 0x032: lg_map_key_clear(KEY_TEXT); break;
- case 0x033: lg_map_key_clear(KEY_LAST); break;
- case 0x047: lg_map_key_clear(KEY_MP3); break;
- case 0x048: lg_map_key_clear(KEY_DVD); break;
- case 0x049: lg_map_key_clear(KEY_MEDIA); break;
- case 0x04a: lg_map_key_clear(KEY_VIDEO); break;
- case 0x04b: lg_map_key_clear(KEY_ANGLE); break;
- case 0x04c: lg_map_key_clear(KEY_LANGUAGE); break;
- case 0x04d: lg_map_key_clear(KEY_SUBTITLE); break;
- case 0x051: lg_map_key_clear(KEY_RED); break;
- case 0x052: lg_map_key_clear(KEY_CLOSE); break;
-
- default:
- return 0;
- }
- return 1;
-}
-
-static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage,
- unsigned long **bit, int *max)
-{
- if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
- return 0;
-
- switch (usage->hid & HID_USAGE) {
- case 0x1001: lg_map_key_clear(KEY_MESSENGER); break;
- case 0x1003: lg_map_key_clear(KEY_SOUND); break;
- case 0x1004: lg_map_key_clear(KEY_VIDEO); break;
- case 0x1005: lg_map_key_clear(KEY_AUDIO); break;
- case 0x100a: lg_map_key_clear(KEY_DOCUMENTS); break;
- case 0x1011: lg_map_key_clear(KEY_PREVIOUSSONG); break;
- case 0x1012: lg_map_key_clear(KEY_NEXTSONG); break;
- case 0x1013: lg_map_key_clear(KEY_CAMERA); break;
- case 0x1014: lg_map_key_clear(KEY_MESSENGER); break;
- case 0x1015: lg_map_key_clear(KEY_RECORD); break;
- case 0x1016: lg_map_key_clear(KEY_PLAYER); break;
- case 0x1017: lg_map_key_clear(KEY_EJECTCD); break;
- case 0x1018: lg_map_key_clear(KEY_MEDIA); break;
- case 0x1019: lg_map_key_clear(KEY_PROG1); break;
- case 0x101a: lg_map_key_clear(KEY_PROG2); break;
- case 0x101b: lg_map_key_clear(KEY_PROG3); break;
- case 0x101f: lg_map_key_clear(KEY_ZOOMIN); break;
- case 0x1020: lg_map_key_clear(KEY_ZOOMOUT); break;
- case 0x1021: lg_map_key_clear(KEY_ZOOMRESET); break;
- case 0x1023: lg_map_key_clear(KEY_CLOSE); break;
- case 0x1027: lg_map_key_clear(KEY_MENU); break;
- /* this one is marked as 'Rotate' */
- case 0x1028: lg_map_key_clear(KEY_ANGLE); break;
- case 0x1029: lg_map_key_clear(KEY_SHUFFLE); break;
- case 0x102a: lg_map_key_clear(KEY_BACK); break;
- case 0x102b: lg_map_key_clear(KEY_CYCLEWINDOWS); break;
- case 0x1041: lg_map_key_clear(KEY_BATTERY); break;
- case 0x1042: lg_map_key_clear(KEY_WORDPROCESSOR); break;
- case 0x1043: lg_map_key_clear(KEY_SPREADSHEET); break;
- case 0x1044: lg_map_key_clear(KEY_PRESENTATION); break;
- case 0x1045: lg_map_key_clear(KEY_UNDO); break;
- case 0x1046: lg_map_key_clear(KEY_REDO); break;
- case 0x1047: lg_map_key_clear(KEY_PRINT); break;
- case 0x1048: lg_map_key_clear(KEY_SAVE); break;
- case 0x1049: lg_map_key_clear(KEY_PROG1); break;
- case 0x104a: lg_map_key_clear(KEY_PROG2); break;
- case 0x104b: lg_map_key_clear(KEY_PROG3); break;
- case 0x104c: lg_map_key_clear(KEY_PROG4); break;
-
- default:
- return 0;
- }
- return 1;
-}
-
-static int lg_input_mapping(struct hid_device *hdev, struct hid_input *hi,
- struct hid_field *field, struct hid_usage *usage,
- unsigned long **bit, int *max)
-{
- /* extended mapping for certain Logitech hardware (Logitech cordless
- desktop LX500) */
- static const u8 e_keymap[] = {
- 0,216, 0,213,175,156, 0, 0, 0, 0,
- 144, 0, 0, 0, 0, 0, 0, 0, 0,212,
- 174,167,152,161,112, 0, 0, 0,154, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,183,184,185,186,187,
- 188,189,190,191,192,193,194, 0, 0, 0
- };
- unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
- unsigned int hid = usage->hid;
-
- if (hdev->product == USB_DEVICE_ID_LOGITECH_RECEIVER &&
- lg_ultrax_remote_mapping(hi, usage, bit, max))
- return 1;
-
- if ((quirks & LG_WIRELESS) && lg_wireless_mapping(hi, usage, bit, max))
- return 1;
-
- if ((hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
- return 0;
-
- hid &= HID_USAGE;
-
- /* Special handling for Logitech Cordless Desktop */
- if (field->application == HID_GD_MOUSE) {
- if ((quirks & LG_IGNORE_DOUBLED_WHEEL) &&
- (hid == 7 || hid == 8))
- return -1;
- } else {
- if ((quirks & LG_EXPANDED_KEYMAP) &&
- hid < ARRAY_SIZE(e_keymap) &&
- e_keymap[hid] != 0) {
- hid_map_usage(hi, usage, bit, max, EV_KEY,
- e_keymap[hid]);
- return 1;
- }
- }
-
- return 0;
-}
-
-static int lg_input_mapped(struct hid_device *hdev, struct hid_input *hi,
- struct hid_field *field, struct hid_usage *usage,
- unsigned long **bit, int *max)
-{
- unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
-
- if ((quirks & LG_BAD_RELATIVE_KEYS) && usage->type == EV_KEY &&
- (field->flags & HID_MAIN_ITEM_RELATIVE))
- field->flags &= ~HID_MAIN_ITEM_RELATIVE;
-
- if ((quirks & LG_DUPLICATE_USAGES) && (usage->type == EV_KEY ||
- usage->type == EV_REL || usage->type == EV_ABS))
- clear_bit(usage->code, *bit);
-
- return 0;
-}
-
-static int lg_event(struct hid_device *hdev, struct hid_field *field,
- struct hid_usage *usage, __s32 value)
-{
- unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
-
- if ((quirks & LG_INVERT_HWHEEL) && usage->code == REL_HWHEEL) {
- input_event(field->hidinput->input, usage->type, usage->code,
- -value);
- return 1;
- }
-
- return 0;
-}
-
-static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
-{
- unsigned long quirks = id->driver_data;
- int ret;
-
- hid_set_drvdata(hdev, (void *)quirks);
-
- if (quirks & LG_NOGET)
- hdev->quirks |= HID_QUIRK_NOGET;
-
- ret = hid_parse(hdev);
- if (ret) {
- dev_err(&hdev->dev, "parse failed\n");
- goto err_free;
- }
-
- ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
- if (ret) {
- dev_err(&hdev->dev, "hw start failed\n");
- goto err_free;
- }
-
- if (quirks & LG_RESET_LEDS)
- usbhid_set_leds(hdev);
-
- return 0;
-err_free:
- return ret;
-}
-
-static const struct hid_device_id lg_devices[] = {
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
- .driver_data = LG_RDESC | LG_WIRELESS },
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER),
- .driver_data = LG_RDESC | LG_WIRELESS },
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2),
- .driver_data = LG_RDESC | LG_WIRELESS },
-
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER),
- .driver_data = LG_BAD_RELATIVE_KEYS },
-
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP),
- .driver_data = LG_DUPLICATE_USAGES },
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE),
- .driver_data = LG_DUPLICATE_USAGES },
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI),
- .driver_data = LG_DUPLICATE_USAGES },
-
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_KBD),
- .driver_data = LG_RESET_LEDS },
-
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD),
- .driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500),
- .driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },
-
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_LX3),
- .driver_data = LG_INVERT_HWHEEL },
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_V150),
- .driver_data = LG_INVERT_HWHEEL },
-
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D),
- .driver_data = LG_NOGET },
- { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL),
- .driver_data = LG_NOGET },
- { }
-};
-MODULE_DEVICE_TABLE(hid, lg_devices);
-
-static struct hid_driver lg_driver = {
- .name = "logitech",
- .id_table = lg_devices,
- .report_fixup = lg_report_fixup,
- .input_mapping = lg_input_mapping,
- .input_mapped = lg_input_mapped,
- .event = lg_event,
- .probe = lg_probe,
-};
-
-static int lg_init(void)
-{
- return hid_register_driver(&lg_driver);
-}
-
-static void lg_exit(void)
-{
- hid_unregister_driver(&lg_driver);
-}
-
-module_init(lg_init);
-module_exit(lg_exit);
-MODULE_LICENSE("GPL");
-
-HID_COMPAT_LOAD_DRIVER(logitech);
feedback for it. Microsoft Sidewinder Force Feedback 2 is one of such
devices.
-config LOGITECH_FF
- bool "Logitech devices support"
- depends on HID_FF
- select INPUT_FF_MEMLESS if USB_HID
- help
- Say Y here if you have one of these devices:
- - Logitech WingMan Cordless RumblePad
- - Logitech WingMan Cordless RumblePad 2
- - Logitech WingMan Force 3D
- - Logitech Formula Force EX
- - Logitech MOMO Force wheel
-
- and if you want to enable force feedback for them.
- Note: if you say N here, this device will still be supported, but without
- force feedback.
-
-config LOGIRUMBLEPAD2_FF
- bool "Logitech Rumblepad 2 support"
- depends on HID_FF
- select INPUT_FF_MEMLESS if USB_HID
- help
- Say Y here if you want to enable force feedback support for Logitech
- Rumblepad 2 devices.
-
config PANTHERLORD_FF
bool "PantherLord/GreenAsia based device support"
depends on HID_FF
ifeq ($(CONFIG_HID_PID),y)
usbhid-objs += hid-pidff.o
endif
-ifeq ($(CONFIG_LOGITECH_FF),y)
- usbhid-objs += hid-lgff.o
-endif
-ifeq ($(CONFIG_LOGIRUMBLEPAD2_FF),y)
- usbhid-objs += hid-lg2ff.o
-endif
ifeq ($(CONFIG_PANTHERLORD_FF),y)
usbhid-objs += hid-plff.o
endif
spin_unlock_irqrestore(&usbhid->ctrllock, flags);
}
+EXPORT_SYMBOL_GPL(usbhid_submit_report);
static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
* be a PID device
*/
static struct hid_ff_initializer inits[] = {
-#ifdef CONFIG_LOGITECH_FF
- { 0x46d, 0xc211, hid_lgff_init }, /* Logitech Cordless rumble pad */
- { 0x46d, 0xc219, hid_lgff_init }, /* Logitech Cordless rumble pad 2 */
- { 0x46d, 0xc283, hid_lgff_init }, /* Logitech Wingman Force 3d */
- { 0x46d, 0xc286, hid_lgff_init }, /* Logitech Force 3D Pro Joystick */
- { 0x46d, 0xc294, hid_lgff_init }, /* Logitech Formula Force EX */
- { 0x46d, 0xc295, hid_lgff_init }, /* Logitech MOMO force wheel */
- { 0x46d, 0xca03, hid_lgff_init }, /* Logitech MOMO force wheel */
-#endif
-#ifdef CONFIG_LOGIRUMBLEPAD2_FF
- { 0x46d, 0xc218, hid_lg2ff_init }, /* Logitech Rumblepad 2 */
-#endif
#ifdef CONFIG_PANTHERLORD_FF
{ 0x810, 0x0001, hid_plff_init }, /* "Twin USB Joystick" */
{ 0xe8f, 0x0003, hid_plff_init }, /* "GreenAsia Inc. USB Joystick " */
+++ /dev/null
-/*
- * Force feedback support for Logitech Rumblepad 2
- *
- * Copyright (c) 2008 Anssi Hannula <anssi.hannula@gmail.com>
- */
-
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-
-#include <linux/input.h>
-#include <linux/usb.h>
-#include <linux/hid.h>
-#include "usbhid.h"
-
-struct lg2ff_device {
- struct hid_report *report;
-};
-
-static int play_effect(struct input_dev *dev, void *data,
- struct ff_effect *effect)
-{
- struct hid_device *hid = input_get_drvdata(dev);
- struct lg2ff_device *lg2ff = data;
- int weak, strong;
-
- strong = effect->u.rumble.strong_magnitude;
- weak = effect->u.rumble.weak_magnitude;
-
- if (weak || strong) {
- weak = weak * 0xff / 0xffff;
- strong = strong * 0xff / 0xffff;
-
- lg2ff->report->field[0]->value[0] = 0x51;
- lg2ff->report->field[0]->value[2] = weak;
- lg2ff->report->field[0]->value[4] = strong;
- } else {
- lg2ff->report->field[0]->value[0] = 0xf3;
- lg2ff->report->field[0]->value[2] = 0x00;
- lg2ff->report->field[0]->value[4] = 0x00;
- }
-
- usbhid_submit_report(hid, lg2ff->report, USB_DIR_OUT);
- return 0;
-}
-
-int hid_lg2ff_init(struct hid_device *hid)
-{
- struct lg2ff_device *lg2ff;
- struct hid_report *report;
- struct hid_input *hidinput = list_entry(hid->inputs.next,
- struct hid_input, list);
- struct list_head *report_list =
- &hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct input_dev *dev = hidinput->input;
- int error;
-
- if (list_empty(report_list)) {
- printk(KERN_ERR "hid-lg2ff: no output report found\n");
- return -ENODEV;
- }
-
- report = list_entry(report_list->next, struct hid_report, list);
-
- if (report->maxfield < 1) {
- printk(KERN_ERR "hid-lg2ff: output report is empty\n");
- return -ENODEV;
- }
- if (report->field[0]->report_count < 7) {
- printk(KERN_ERR "hid-lg2ff: not enough values in the field\n");
- return -ENODEV;
- }
-
- lg2ff = kmalloc(sizeof(struct lg2ff_device), GFP_KERNEL);
- if (!lg2ff)
- return -ENOMEM;
-
- set_bit(FF_RUMBLE, dev->ffbit);
-
- error = input_ff_create_memless(dev, lg2ff, play_effect);
- if (error) {
- kfree(lg2ff);
- return error;
- }
-
- lg2ff->report = report;
- report->field[0]->value[0] = 0xf3;
- report->field[0]->value[1] = 0x00;
- report->field[0]->value[2] = 0x00;
- report->field[0]->value[3] = 0x00;
- report->field[0]->value[4] = 0x00;
- report->field[0]->value[5] = 0x00;
- report->field[0]->value[6] = 0x00;
-
- usbhid_submit_report(hid, report, USB_DIR_OUT);
-
- printk(KERN_INFO "Force feedback for Logitech Rumblepad 2 by "
- "Anssi Hannula <anssi.hannula@gmail.com>\n");
-
- return 0;
-}
+++ /dev/null
-/*
- * Force feedback support for hid-compliant for some of the devices from
- * Logitech, namely:
- * - WingMan Cordless RumblePad
- * - WingMan Force 3D
- *
- * Copyright (c) 2002-2004 Johann Deneux
- * Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com>
- */
-
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- * Should you need to contact me, the author, you can do so by
- * e-mail - mail your message to <johann.deneux@it.uu.se>
- */
-
-#include <linux/input.h>
-#include <linux/usb.h>
-#include <linux/hid.h>
-#include "usbhid.h"
-
-struct dev_type {
- u16 idVendor;
- u16 idProduct;
- const signed short *ff;
-};
-
-static const signed short ff_rumble[] = {
- FF_RUMBLE,
- -1
-};
-
-static const signed short ff_joystick[] = {
- FF_CONSTANT,
- -1
-};
-
-static const struct dev_type devices[] = {
- { 0x046d, 0xc211, ff_rumble },
- { 0x046d, 0xc219, ff_rumble },
- { 0x046d, 0xc283, ff_joystick },
- { 0x046d, 0xc286, ff_joystick },
- { 0x046d, 0xc294, ff_joystick },
- { 0x046d, 0xc295, ff_joystick },
- { 0x046d, 0xca03, ff_joystick },
-};
-
-static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
-{
- struct hid_device *hid = input_get_drvdata(dev);
- struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
- int x, y;
- unsigned int left, right;
-
-#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff
-
- switch (effect->type) {
- case FF_CONSTANT:
- x = effect->u.ramp.start_level + 0x7f; /* 0x7f is center */
- y = effect->u.ramp.end_level + 0x7f;
- CLAMP(x);
- CLAMP(y);
- report->field[0]->value[0] = 0x51;
- report->field[0]->value[1] = 0x08;
- report->field[0]->value[2] = x;
- report->field[0]->value[3] = y;
- dbg_hid("(x, y)=(%04x, %04x)\n", x, y);
- usbhid_submit_report(hid, report, USB_DIR_OUT);
- break;
-
- case FF_RUMBLE:
- right = effect->u.rumble.strong_magnitude;
- left = effect->u.rumble.weak_magnitude;
- right = right * 0xff / 0xffff;
- left = left * 0xff / 0xffff;
- CLAMP(left);
- CLAMP(right);
- report->field[0]->value[0] = 0x42;
- report->field[0]->value[1] = 0x00;
- report->field[0]->value[2] = left;
- report->field[0]->value[3] = right;
- dbg_hid("(left, right)=(%04x, %04x)\n", left, right);
- usbhid_submit_report(hid, report, USB_DIR_OUT);
- break;
- }
- return 0;
-}
-
-int hid_lgff_init(struct hid_device* hid)
-{
- struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
- struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
- struct input_dev *dev = hidinput->input;
- struct hid_report *report;
- struct hid_field *field;
- const signed short *ff_bits = ff_joystick;
- int error;
- int i;
-
- /* Find the report to use */
- if (list_empty(report_list)) {
- err_hid("No output report found");
- return -1;
- }
-
- /* Check that the report looks ok */
- report = list_entry(report_list->next, struct hid_report, list);
- if (!report) {
- err_hid("NULL output report");
- return -1;
- }
-
- field = report->field[0];
- if (!field) {
- err_hid("NULL field");
- return -1;
- }
-
- for (i = 0; i < ARRAY_SIZE(devices); i++) {
- if (dev->id.vendor == devices[i].idVendor &&
- dev->id.product == devices[i].idProduct) {
- ff_bits = devices[i].ff;
- break;
- }
- }
-
- for (i = 0; ff_bits[i] >= 0; i++)
- set_bit(ff_bits[i], dev->ffbit);
-
- error = input_ff_create_memless(dev, NULL, hid_lgff_play);
- if (error)
- return error;
-
- printk(KERN_INFO "Force feedback for Logitech force feedback devices by Johann Deneux <johann.deneux@it.uu.se>\n");
-
- return 0;
-}
#ifdef CONFIG_HID_FF
int hid_ff_init(struct hid_device *hid);
-int hid_lgff_init(struct hid_device *hid);
-int hid_lg2ff_init(struct hid_device *hid);
int hid_plff_init(struct hid_device *hid);
int hid_tmff_init(struct hid_device *hid);
int hid_zpff_init(struct hid_device *hid);