mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-18 18:23:53 +08:00
Merge branches 'acrux', 'logitech', 'multitouch', 'roccat' and 'wiimote' into for-linus
This commit is contained in:
commit
b0eae38ceb
@ -0,0 +1,7 @@
|
||||
What: /sys/module/hid_logitech/drivers/hid:logitech/<dev>/range.
|
||||
Date: July 2011
|
||||
KernelVersion: 3.2
|
||||
Contact: Michal Malý <madcatxster@gmail.com>
|
||||
Description: Display minimum, maximum and current range of the steering
|
||||
wheel. Writing a value within min and max boundaries sets the
|
||||
range of the wheel.
|
@ -7142,6 +7142,12 @@ L: linux-scsi@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/scsi/wd7000.c
|
||||
|
||||
WIIMOTE HID DRIVER
|
||||
M: David Herrmann <dh.herrmann@googlemail.com>
|
||||
L: linux-input@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hid/hid-wiimote*
|
||||
|
||||
WINBOND CIR DRIVER
|
||||
M: David Härdeman <david@hardeman.nu>
|
||||
S: Maintained
|
||||
|
@ -69,7 +69,7 @@ config HID_ACRUX
|
||||
Say Y here if you want to enable support for ACRUX game controllers.
|
||||
|
||||
config HID_ACRUX_FF
|
||||
tristate "ACRUX force feedback support"
|
||||
bool "ACRUX force feedback support"
|
||||
depends on HID_ACRUX
|
||||
select INPUT_FF_MEMLESS
|
||||
---help---
|
||||
@ -245,6 +245,15 @@ config HID_LOGITECH
|
||||
---help---
|
||||
Support for Logitech devices that are not fully compliant with HID standard.
|
||||
|
||||
config HID_LOGITECH_DJ
|
||||
tristate "Logitech Unifying receivers full support"
|
||||
depends on HID_LOGITECH
|
||||
default m
|
||||
---help---
|
||||
Say Y if you want support for Logitech Unifying receivers and devices.
|
||||
Unifying receivers are capable of pairing up to 6 Logitech compliant
|
||||
devices to the same receiver.
|
||||
|
||||
config LOGITECH_FF
|
||||
bool "Logitech force feedback support"
|
||||
depends on HID_LOGITECH
|
||||
@ -278,13 +287,21 @@ config LOGIG940_FF
|
||||
Say Y here if you want to enable force feedback support for Logitech
|
||||
Flight System G940 devices.
|
||||
|
||||
config LOGIWII_FF
|
||||
bool "Logitech Speed Force Wireless force feedback support"
|
||||
config LOGIWHEELS_FF
|
||||
bool "Logitech wheels configuration and force feedback support"
|
||||
depends on HID_LOGITECH
|
||||
select INPUT_FF_MEMLESS
|
||||
default LOGITECH_FF
|
||||
help
|
||||
Say Y here if you want to enable force feedback support for Logitech
|
||||
Speed Force Wireless (Wii) devices.
|
||||
Say Y here if you want to enable force feedback and range setting
|
||||
support for following Logitech wheels:
|
||||
- Logitech Driving Force
|
||||
- Logitech Driving Force Pro
|
||||
- Logitech Driving Force GT
|
||||
- Logitech G25
|
||||
- Logitech G27
|
||||
- Logitech MOMO/MOMO 2
|
||||
- Logitech Formula Force EX
|
||||
|
||||
config HID_MAGICMOUSE
|
||||
tristate "Apple MagicMouse multi-touch support"
|
||||
@ -328,6 +345,7 @@ config HID_MULTITOUCH
|
||||
- Hanvon dual touch panels
|
||||
- Ilitek dual touch panels
|
||||
- IrTouch Infrared USB panels
|
||||
- LG Display panels (Dell ST2220Tc)
|
||||
- Lumio CrystalTouch panels
|
||||
- MosArt dual-touch panels
|
||||
- PenMount dual touch panels
|
||||
@ -590,6 +608,7 @@ config HID_WIIMOTE
|
||||
tristate "Nintendo Wii Remote support"
|
||||
depends on BT_HIDP
|
||||
depends on LEDS_CLASS
|
||||
select POWER_SUPPLY
|
||||
---help---
|
||||
Support for the Nintendo Wii Remote bluetooth device.
|
||||
|
||||
|
@ -21,7 +21,7 @@ endif
|
||||
ifdef CONFIG_LOGIG940_FF
|
||||
hid-logitech-y += hid-lg3ff.o
|
||||
endif
|
||||
ifdef CONFIG_LOGIWII_FF
|
||||
ifdef CONFIG_LOGIWHEELS_FF
|
||||
hid-logitech-y += hid-lg4ff.o
|
||||
endif
|
||||
|
||||
@ -43,6 +43,7 @@ obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o
|
||||
obj-$(CONFIG_HID_KYE) += hid-kye.o
|
||||
obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o
|
||||
obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o
|
||||
obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.o
|
||||
obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o
|
||||
obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o
|
||||
obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o
|
||||
|
@ -6,7 +6,7 @@
|
||||
* Xbox 360 controller.
|
||||
*
|
||||
* 1a34:0802 "ACRUX USB GAMEPAD 8116"
|
||||
* - tested with a EXEQ EQ-PCU-02090 game controller.
|
||||
* - tested with an EXEQ EQ-PCU-02090 game controller.
|
||||
*
|
||||
* Copyright (c) 2010 Sergei Kolzun <x0r@dv-life.ru>
|
||||
*/
|
||||
@ -45,7 +45,10 @@ static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect
|
||||
{
|
||||
struct hid_device *hid = input_get_drvdata(dev);
|
||||
struct axff_device *axff = data;
|
||||
struct hid_report *report = axff->report;
|
||||
int field_count = 0;
|
||||
int left, right;
|
||||
int i, j;
|
||||
|
||||
left = effect->u.rumble.strong_magnitude;
|
||||
right = effect->u.rumble.weak_magnitude;
|
||||
@ -55,10 +58,14 @@ static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect
|
||||
left = left * 0xff / 0xffff;
|
||||
right = right * 0xff / 0xffff;
|
||||
|
||||
axff->report->field[0]->value[0] = left;
|
||||
axff->report->field[1]->value[0] = right;
|
||||
axff->report->field[2]->value[0] = left;
|
||||
axff->report->field[3]->value[0] = right;
|
||||
for (i = 0; i < report->maxfield; i++) {
|
||||
for (j = 0; j < report->field[i]->report_count; j++) {
|
||||
report->field[i]->value[j] =
|
||||
field_count % 2 ? right : left;
|
||||
field_count++;
|
||||
}
|
||||
}
|
||||
|
||||
dbg_hid("running with 0x%02x 0x%02x", left, right);
|
||||
usbhid_submit_report(hid, axff->report, USB_DIR_OUT);
|
||||
|
||||
@ -72,6 +79,8 @@ static int axff_init(struct hid_device *hid)
|
||||
struct hid_input *hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
|
||||
struct list_head *report_list =&hid->report_enum[HID_OUTPUT_REPORT].report_list;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int field_count = 0;
|
||||
int i, j;
|
||||
int error;
|
||||
|
||||
if (list_empty(report_list)) {
|
||||
@ -80,9 +89,16 @@ static int axff_init(struct hid_device *hid)
|
||||
}
|
||||
|
||||
report = list_first_entry(report_list, struct hid_report, list);
|
||||
for (i = 0; i < report->maxfield; i++) {
|
||||
for (j = 0; j < report->field[i]->report_count; j++) {
|
||||
report->field[i]->value[j] = 0x00;
|
||||
field_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (report->maxfield < 4) {
|
||||
hid_err(hid, "no fields in the report: %d\n", report->maxfield);
|
||||
if (field_count < 4) {
|
||||
hid_err(hid, "not enough fields in the report: %d\n",
|
||||
field_count);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
@ -97,13 +113,9 @@ static int axff_init(struct hid_device *hid)
|
||||
goto err_free_mem;
|
||||
|
||||
axff->report = report;
|
||||
axff->report->field[0]->value[0] = 0x00;
|
||||
axff->report->field[1]->value[0] = 0x00;
|
||||
axff->report->field[2]->value[0] = 0x00;
|
||||
axff->report->field[3]->value[0] = 0x00;
|
||||
usbhid_submit_report(hid, axff->report, USB_DIR_OUT);
|
||||
|
||||
hid_info(hid, "Force Feedback for ACRUX game controllers by Sergei Kolzun<x0r@dv-life.ru>\n");
|
||||
hid_info(hid, "Force Feedback for ACRUX game controllers by Sergei Kolzun <x0r@dv-life.ru>\n");
|
||||
|
||||
return 0;
|
||||
|
||||
|
@ -1212,6 +1212,12 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
|
||||
if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev,
|
||||
connect_mask & HID_CONNECT_HIDINPUT_FORCE))
|
||||
hdev->claimed |= HID_CLAIMED_INPUT;
|
||||
if (hdev->quirks & HID_QUIRK_MULTITOUCH) {
|
||||
/* this device should be handled by hid-multitouch, skip it */
|
||||
hdev->quirks &= ~HID_QUIRK_MULTITOUCH;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if ((connect_mask & HID_CONNECT_HIDDEV) && hdev->hiddev_connect &&
|
||||
!hdev->hiddev_connect(hdev,
|
||||
connect_mask & HID_CONNECT_HIDDEV_FORCE))
|
||||
@ -1391,6 +1397,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_3) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HANVON, USB_DEVICE_ID_HANVON_MULTITOUCH) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6650) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ILITEK, USB_DEVICE_ID_ILITEK_MULTITOUCH) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_IRTOUCHSYSTEMS, USB_DEVICE_ID_IRTOUCH_INFRARED_USB) },
|
||||
@ -1399,6 +1406,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LG, USB_DEVICE_ID_LG_MULTITOUCH) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2) },
|
||||
@ -1420,8 +1428,11 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
{ 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_DFP_WHEEL) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFGT_WHEEL) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G27_WHEEL) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER) },
|
||||
|
@ -351,6 +351,9 @@
|
||||
#define USB_DEVICE_ID_UGCI_FLYING 0x0020
|
||||
#define USB_DEVICE_ID_UGCI_FIGHTING 0x0030
|
||||
|
||||
#define USB_VENDOR_ID_IDEACOM 0x1cb6
|
||||
#define USB_DEVICE_ID_IDEACOM_IDC6650 0x6650
|
||||
|
||||
#define USB_VENDOR_ID_ILITEK 0x222a
|
||||
#define USB_DEVICE_ID_ILITEK_MULTITOUCH 0x0001
|
||||
|
||||
@ -423,6 +426,9 @@
|
||||
#define USB_DEVICE_ID_LD_HYBRID 0x2090
|
||||
#define USB_DEVICE_ID_LD_HEATCONTROL 0x20A0
|
||||
|
||||
#define USB_VENDOR_ID_LG 0x1fd2
|
||||
#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064
|
||||
|
||||
#define USB_VENDOR_ID_LOGITECH 0x046d
|
||||
#define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101
|
||||
#define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST 0xc110
|
||||
@ -440,6 +446,7 @@
|
||||
#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL 0xc295
|
||||
#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298
|
||||
#define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299
|
||||
#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a
|
||||
#define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b
|
||||
#define USB_DEVICE_ID_LOGITECH_WII_WHEEL 0xc29c
|
||||
#define USB_DEVICE_ID_LOGITECH_ELITE_KBD 0xc30a
|
||||
@ -447,6 +454,8 @@
|
||||
#define USB_DEVICE_ID_S510_RECEIVER_2 0xc517
|
||||
#define USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500 0xc512
|
||||
#define USB_DEVICE_ID_MX3000_RECEIVER 0xc513
|
||||
#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER 0xc52b
|
||||
#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2 0xc532
|
||||
#define USB_DEVICE_ID_SPACETRAVELLER 0xc623
|
||||
#define USB_DEVICE_ID_SPACENAVIGATOR 0xc626
|
||||
#define USB_DEVICE_ID_DINOVO_DESKTOP 0xc704
|
||||
|
@ -474,6 +474,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
|
||||
map_key_clear(BTN_STYLUS2);
|
||||
break;
|
||||
|
||||
case 0x51: /* ContactID */
|
||||
device->quirks |= HID_QUIRK_MULTITOUCH;
|
||||
goto unknown;
|
||||
|
||||
default: goto unknown;
|
||||
}
|
||||
break;
|
||||
@ -978,6 +982,13 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
|
||||
}
|
||||
}
|
||||
|
||||
if (hid->quirks & HID_QUIRK_MULTITOUCH) {
|
||||
/* generic hid does not know how to handle multitouch devices */
|
||||
if (hidinput)
|
||||
goto out_cleanup;
|
||||
goto out_unwind;
|
||||
}
|
||||
|
||||
if (hidinput && input_register_device(hidinput->input))
|
||||
goto out_cleanup;
|
||||
|
||||
|
@ -363,7 +363,7 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
if (quirks & (LG_FF | LG_FF2 | LG_FF3))
|
||||
if (quirks & (LG_FF | LG_FF2 | LG_FF3 | LG_FF4))
|
||||
connect_mask &= ~HID_CONNECT_FF;
|
||||
|
||||
ret = hid_hw_start(hdev, connect_mask);
|
||||
@ -372,7 +372,8 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
if (quirks & LG_FF4) {
|
||||
/* Setup wireless link with Logitech Wii wheel */
|
||||
if(hdev->product == USB_DEVICE_ID_LOGITECH_WII_WHEEL) {
|
||||
unsigned char buf[] = { 0x00, 0xAF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
ret = hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT);
|
||||
@ -405,6 +406,15 @@ err_free:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void lg_remove(struct hid_device *hdev)
|
||||
{
|
||||
unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
|
||||
if(quirks & LG_FF4)
|
||||
lg4ff_deinit(hdev);
|
||||
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
||||
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 },
|
||||
@ -431,7 +441,7 @@ static const struct hid_device_id lg_devices[] = {
|
||||
{ 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 },
|
||||
.driver_data = LG_NOGET | LG_FF4 },
|
||||
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD),
|
||||
.driver_data = LG_FF2 },
|
||||
@ -444,15 +454,17 @@ static const struct hid_device_id lg_devices[] = {
|
||||
{ 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 },
|
||||
.driver_data = LG_FF4 },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2),
|
||||
.driver_data = LG_FF },
|
||||
.driver_data = LG_FF4 },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL),
|
||||
.driver_data = LG_FF },
|
||||
.driver_data = LG_FF4 },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFGT_WHEEL),
|
||||
.driver_data = LG_FF4 },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G27_WHEEL),
|
||||
.driver_data = LG_FF },
|
||||
.driver_data = LG_FF4 },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFP_WHEEL),
|
||||
.driver_data = LG_NOGET | LG_FF },
|
||||
.driver_data = LG_NOGET | LG_FF4 },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL),
|
||||
.driver_data = LG_FF4 },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG ),
|
||||
@ -478,6 +490,7 @@ static struct hid_driver lg_driver = {
|
||||
.input_mapped = lg_input_mapped,
|
||||
.event = lg_event,
|
||||
.probe = lg_probe,
|
||||
.remove = lg_remove,
|
||||
};
|
||||
|
||||
static int __init lg_init(void)
|
||||
|
@ -19,10 +19,12 @@ int lg3ff_init(struct hid_device *hdev);
|
||||
static inline int lg3ff_init(struct hid_device *hdev) { return -1; }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LOGIWII_FF
|
||||
#ifdef CONFIG_LOGIWHEELS_FF
|
||||
int lg4ff_init(struct hid_device *hdev);
|
||||
int lg4ff_deinit(struct hid_device *hdev);
|
||||
#else
|
||||
static inline int lg4ff_init(struct hid_device *hdev) { return -1; }
|
||||
static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -29,19 +29,108 @@
|
||||
|
||||
#include "usbhid/usbhid.h"
|
||||
#include "hid-lg.h"
|
||||
#include "hid-ids.h"
|
||||
|
||||
struct lg4ff_device {
|
||||
struct hid_report *report;
|
||||
#define DFGT_REV_MAJ 0x13
|
||||
#define DFGT_REV_MIN 0x22
|
||||
#define DFP_REV_MAJ 0x11
|
||||
#define DFP_REV_MIN 0x06
|
||||
#define FFEX_REV_MAJ 0x21
|
||||
#define FFEX_REV_MIN 0x00
|
||||
#define G25_REV_MAJ 0x12
|
||||
#define G25_REV_MIN 0x22
|
||||
#define G27_REV_MAJ 0x12
|
||||
#define G27_REV_MIN 0x38
|
||||
|
||||
#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
|
||||
|
||||
static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
|
||||
static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range);
|
||||
static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf);
|
||||
static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
|
||||
|
||||
static DEVICE_ATTR(range, S_IRWXU | S_IRWXG | S_IRWXO, lg4ff_range_show, lg4ff_range_store);
|
||||
|
||||
static bool list_inited;
|
||||
|
||||
struct lg4ff_device_entry {
|
||||
char *device_id; /* Use name in respective kobject structure's address as the ID */
|
||||
__u16 range;
|
||||
__u16 min_range;
|
||||
__u16 max_range;
|
||||
__u8 leds;
|
||||
struct list_head list;
|
||||
void (*set_range)(struct hid_device *hid, u16 range);
|
||||
};
|
||||
|
||||
static const signed short ff4_wheel_ac[] = {
|
||||
static struct lg4ff_device_entry device_list;
|
||||
|
||||
static const signed short lg4ff_wheel_effects[] = {
|
||||
FF_CONSTANT,
|
||||
FF_AUTOCENTER,
|
||||
-1
|
||||
};
|
||||
|
||||
static int hid_lg4ff_play(struct input_dev *dev, void *data,
|
||||
struct ff_effect *effect)
|
||||
struct lg4ff_wheel {
|
||||
const __u32 product_id;
|
||||
const signed short *ff_effects;
|
||||
const __u16 min_range;
|
||||
const __u16 max_range;
|
||||
void (*set_range)(struct hid_device *hid, u16 range);
|
||||
};
|
||||
|
||||
static const struct lg4ff_wheel lg4ff_devices[] = {
|
||||
{USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
|
||||
{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
|
||||
{USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_dfp},
|
||||
{USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25},
|
||||
{USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25},
|
||||
{USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, 40, 900, hid_lg4ff_set_range_g25},
|
||||
{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270, NULL},
|
||||
{USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}
|
||||
};
|
||||
|
||||
struct lg4ff_native_cmd {
|
||||
const __u8 cmd_num; /* Number of commands to send */
|
||||
const __u8 cmd[];
|
||||
};
|
||||
|
||||
struct lg4ff_usb_revision {
|
||||
const __u16 rev_maj;
|
||||
const __u16 rev_min;
|
||||
const struct lg4ff_native_cmd *command;
|
||||
};
|
||||
|
||||
static const struct lg4ff_native_cmd native_dfp = {
|
||||
1,
|
||||
{0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
};
|
||||
|
||||
static const struct lg4ff_native_cmd native_dfgt = {
|
||||
2,
|
||||
{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */
|
||||
0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* 2nd command */
|
||||
};
|
||||
|
||||
static const struct lg4ff_native_cmd native_g25 = {
|
||||
1,
|
||||
{0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
};
|
||||
|
||||
static const struct lg4ff_native_cmd native_g27 = {
|
||||
2,
|
||||
{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1st command */
|
||||
0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* 2nd command */
|
||||
};
|
||||
|
||||
static const struct lg4ff_usb_revision lg4ff_revs[] = {
|
||||
{DFGT_REV_MAJ, DFGT_REV_MIN, &native_dfgt}, /* Driving Force GT */
|
||||
{DFP_REV_MAJ, DFP_REV_MIN, &native_dfp}, /* Driving Force Pro */
|
||||
{G25_REV_MAJ, G25_REV_MIN, &native_g25}, /* G25 */
|
||||
{G27_REV_MAJ, G27_REV_MIN, &native_g27}, /* G27 */
|
||||
};
|
||||
|
||||
static int hid_lg4ff_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;
|
||||
@ -55,13 +144,12 @@ static int hid_lg4ff_play(struct input_dev *dev, void *data,
|
||||
x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */
|
||||
CLAMP(x);
|
||||
report->field[0]->value[0] = 0x11; /* Slot 1 */
|
||||
report->field[0]->value[1] = 0x10;
|
||||
report->field[0]->value[1] = 0x08;
|
||||
report->field[0]->value[2] = x;
|
||||
report->field[0]->value[3] = 0x00;
|
||||
report->field[0]->value[3] = 0x80;
|
||||
report->field[0]->value[4] = 0x00;
|
||||
report->field[0]->value[5] = 0x08;
|
||||
report->field[0]->value[5] = 0x00;
|
||||
report->field[0]->value[6] = 0x00;
|
||||
dbg_hid("Autocenter, x=0x%02X\n", x);
|
||||
|
||||
usbhid_submit_report(hid, report, USB_DIR_OUT);
|
||||
break;
|
||||
@ -69,24 +157,184 @@ static int hid_lg4ff_play(struct input_dev *dev, void *data,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hid_lg4ff_set_autocenter(struct input_dev *dev, u16 magnitude)
|
||||
/* Sends default autocentering command compatible with
|
||||
* all wheels except Formula Force EX */
|
||||
static void hid_lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude)
|
||||
{
|
||||
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);
|
||||
__s32 *value = report->field[0]->value;
|
||||
|
||||
*value++ = 0xfe;
|
||||
*value++ = 0x0d;
|
||||
*value++ = 0x07;
|
||||
*value++ = 0x07;
|
||||
*value++ = (magnitude >> 8) & 0xff;
|
||||
*value++ = 0x00;
|
||||
*value = 0x00;
|
||||
report->field[0]->value[0] = 0xfe;
|
||||
report->field[0]->value[1] = 0x0d;
|
||||
report->field[0]->value[2] = magnitude >> 13;
|
||||
report->field[0]->value[3] = magnitude >> 13;
|
||||
report->field[0]->value[4] = magnitude >> 8;
|
||||
report->field[0]->value[5] = 0x00;
|
||||
report->field[0]->value[6] = 0x00;
|
||||
|
||||
usbhid_submit_report(hid, report, USB_DIR_OUT);
|
||||
}
|
||||
|
||||
/* Sends autocentering command compatible with Formula Force EX */
|
||||
static void hid_lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude)
|
||||
{
|
||||
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);
|
||||
magnitude = magnitude * 90 / 65535;
|
||||
|
||||
|
||||
report->field[0]->value[0] = 0xfe;
|
||||
report->field[0]->value[1] = 0x03;
|
||||
report->field[0]->value[2] = magnitude >> 14;
|
||||
report->field[0]->value[3] = magnitude >> 14;
|
||||
report->field[0]->value[4] = magnitude;
|
||||
report->field[0]->value[5] = 0x00;
|
||||
report->field[0]->value[6] = 0x00;
|
||||
|
||||
usbhid_submit_report(hid, report, USB_DIR_OUT);
|
||||
}
|
||||
|
||||
/* Sends command to set range compatible with G25/G27/Driving Force GT */
|
||||
static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range)
|
||||
{
|
||||
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);
|
||||
dbg_hid("G25/G27/DFGT: setting range to %u\n", range);
|
||||
|
||||
report->field[0]->value[0] = 0xf8;
|
||||
report->field[0]->value[1] = 0x81;
|
||||
report->field[0]->value[2] = range & 0x00ff;
|
||||
report->field[0]->value[3] = (range & 0xff00) >> 8;
|
||||
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);
|
||||
}
|
||||
|
||||
/* Sends commands to set range compatible with Driving Force Pro wheel */
|
||||
static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
|
||||
{
|
||||
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 start_left, start_right, full_range;
|
||||
dbg_hid("Driving Force Pro: setting range to %u\n", range);
|
||||
|
||||
/* Prepare "coarse" limit command */
|
||||
report->field[0]->value[0] = 0xf8;
|
||||
report->field[0]->value[1] = 0x00; /* Set later */
|
||||
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;
|
||||
|
||||
if (range > 200) {
|
||||
report->field[0]->value[1] = 0x03;
|
||||
full_range = 900;
|
||||
} else {
|
||||
report->field[0]->value[1] = 0x02;
|
||||
full_range = 200;
|
||||
}
|
||||
usbhid_submit_report(hid, report, USB_DIR_OUT);
|
||||
|
||||
/* Prepare "fine" limit command */
|
||||
report->field[0]->value[0] = 0x81;
|
||||
report->field[0]->value[1] = 0x0b;
|
||||
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;
|
||||
|
||||
if (range == 200 || range == 900) { /* Do not apply any fine limit */
|
||||
usbhid_submit_report(hid, report, USB_DIR_OUT);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Construct fine limit command */
|
||||
start_left = (((full_range - range + 1) * 2047) / full_range);
|
||||
start_right = 0xfff - start_left;
|
||||
|
||||
report->field[0]->value[2] = start_left >> 4;
|
||||
report->field[0]->value[3] = start_right >> 4;
|
||||
report->field[0]->value[4] = 0xff;
|
||||
report->field[0]->value[5] = (start_right & 0xe) << 4 | (start_left & 0xe);
|
||||
report->field[0]->value[6] = 0xff;
|
||||
|
||||
usbhid_submit_report(hid, report, USB_DIR_OUT);
|
||||
}
|
||||
|
||||
static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd)
|
||||
{
|
||||
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);
|
||||
__u8 i, j;
|
||||
|
||||
j = 0;
|
||||
while (j < 7*cmd->cmd_num) {
|
||||
for (i = 0; i < 7; i++)
|
||||
report->field[0]->value[i] = cmd->cmd[j++];
|
||||
|
||||
usbhid_submit_report(hid, report, USB_DIR_OUT);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read current range and display it in terminal */
|
||||
static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct lg4ff_device_entry *uninitialized_var(entry);
|
||||
struct list_head *h;
|
||||
struct hid_device *hid = to_hid_device(dev);
|
||||
size_t count;
|
||||
|
||||
list_for_each(h, &device_list.list) {
|
||||
entry = list_entry(h, struct lg4ff_device_entry, list);
|
||||
if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0)
|
||||
break;
|
||||
}
|
||||
if (h == &device_list.list) {
|
||||
dbg_hid("Device not found!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->range);
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Set range to user specified value, call appropriate function
|
||||
* according to the type of the wheel */
|
||||
static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct lg4ff_device_entry *uninitialized_var(entry);
|
||||
struct list_head *h;
|
||||
struct hid_device *hid = to_hid_device(dev);
|
||||
__u16 range = simple_strtoul(buf, NULL, 10);
|
||||
|
||||
list_for_each(h, &device_list.list) {
|
||||
entry = list_entry(h, struct lg4ff_device_entry, list);
|
||||
if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0)
|
||||
break;
|
||||
}
|
||||
if (h == &device_list.list) {
|
||||
dbg_hid("Device not found!");
|
||||
return count;
|
||||
}
|
||||
|
||||
if (range == 0)
|
||||
range = entry->max_range;
|
||||
|
||||
/* Check if the wheel supports range setting
|
||||
* and that the range is within limits for the wheel */
|
||||
if (entry->set_range != NULL && range >= entry->min_range && range <= entry->max_range) {
|
||||
entry->set_range(hid, range);
|
||||
entry->range = range;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int lg4ff_init(struct hid_device *hid)
|
||||
{
|
||||
@ -95,9 +343,10 @@ int lg4ff_init(struct hid_device *hid)
|
||||
struct input_dev *dev = hidinput->input;
|
||||
struct hid_report *report;
|
||||
struct hid_field *field;
|
||||
const signed short *ff_bits = ff4_wheel_ac;
|
||||
int error;
|
||||
int i;
|
||||
struct lg4ff_device_entry *entry;
|
||||
struct usb_device_descriptor *udesc;
|
||||
int error, i, j;
|
||||
__u16 bcdDevice, rev_maj, rev_min;
|
||||
|
||||
/* Find the report to use */
|
||||
if (list_empty(report_list)) {
|
||||
@ -118,18 +367,122 @@ int lg4ff_init(struct hid_device *hid)
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; ff_bits[i] >= 0; i++)
|
||||
set_bit(ff_bits[i], dev->ffbit);
|
||||
/* Check what wheel has been connected */
|
||||
for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) {
|
||||
if (hid->product == lg4ff_devices[i].product_id) {
|
||||
dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == ARRAY_SIZE(lg4ff_devices)) {
|
||||
hid_err(hid, "Device is not supported by lg4ff driver. If you think it should be, consider reporting a bug to"
|
||||
"LKML, Simon Wood <simon@mungewell.org> or Michal Maly <madcatxster@gmail.com>\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Attempt to switch wheel to native mode when applicable */
|
||||
udesc = &(hid_to_usb_dev(hid)->descriptor);
|
||||
if (!udesc) {
|
||||
hid_err(hid, "NULL USB device descriptor\n");
|
||||
return -1;
|
||||
}
|
||||
bcdDevice = le16_to_cpu(udesc->bcdDevice);
|
||||
rev_maj = bcdDevice >> 8;
|
||||
rev_min = bcdDevice & 0xff;
|
||||
|
||||
if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_WHEEL) {
|
||||
dbg_hid("Generic wheel detected, can it do native?\n");
|
||||
dbg_hid("USB revision: %2x.%02x\n", rev_maj, rev_min);
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(lg4ff_revs); j++) {
|
||||
if (lg4ff_revs[j].rev_maj == rev_maj && lg4ff_revs[j].rev_min == rev_min) {
|
||||
hid_lg4ff_switch_native(hid, lg4ff_revs[j].command);
|
||||
hid_info(hid, "Switched to native mode\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set supported force feedback capabilities */
|
||||
for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++)
|
||||
set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, NULL, hid_lg4ff_play);
|
||||
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
if (test_bit(FF_AUTOCENTER, dev->ffbit))
|
||||
dev->ff->set_autocenter = hid_lg4ff_set_autocenter;
|
||||
/* Check if autocentering is available and
|
||||
* set the centering force to zero by default */
|
||||
if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
|
||||
if(rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN) /* Formula Force EX expects different autocentering command */
|
||||
dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex;
|
||||
else
|
||||
dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default;
|
||||
|
||||
dev->ff->set_autocenter(dev, 0);
|
||||
}
|
||||
|
||||
/* Initialize device_list if this is the first device to handle by lg4ff */
|
||||
if (!list_inited) {
|
||||
INIT_LIST_HEAD(&device_list.list);
|
||||
list_inited = 1;
|
||||
}
|
||||
|
||||
/* Add the device to device_list */
|
||||
entry = (struct lg4ff_device_entry *)kzalloc(sizeof(struct lg4ff_device_entry), GFP_KERNEL);
|
||||
if (!entry) {
|
||||
hid_err(hid, "Cannot add device, insufficient memory.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
entry->device_id = kstrdup((&hid->dev)->kobj.name, GFP_KERNEL);
|
||||
if (!entry->device_id) {
|
||||
hid_err(hid, "Cannot set device_id, insufficient memory.\n");
|
||||
kfree(entry);
|
||||
return -ENOMEM;
|
||||
}
|
||||
entry->min_range = lg4ff_devices[i].min_range;
|
||||
entry->max_range = lg4ff_devices[i].max_range;
|
||||
entry->set_range = lg4ff_devices[i].set_range;
|
||||
list_add(&entry->list, &device_list.list);
|
||||
|
||||
/* Create sysfs interface */
|
||||
error = device_create_file(&hid->dev, &dev_attr_range);
|
||||
if (error)
|
||||
return error;
|
||||
dbg_hid("sysfs interface created\n");
|
||||
|
||||
/* Set the maximum range to start with */
|
||||
entry->range = entry->max_range;
|
||||
if (entry->set_range != NULL)
|
||||
entry->set_range(hid, entry->range);
|
||||
|
||||
hid_info(hid, "Force feedback for Logitech Speed Force Wireless by Simon Wood <simon@mungewell.org>\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lg4ff_deinit(struct hid_device *hid)
|
||||
{
|
||||
bool found = 0;
|
||||
struct lg4ff_device_entry *entry;
|
||||
struct list_head *h, *g;
|
||||
list_for_each_safe(h, g, &device_list.list) {
|
||||
entry = list_entry(h, struct lg4ff_device_entry, list);
|
||||
if (strcmp(entry->device_id, (&hid->dev)->kobj.name) == 0) {
|
||||
list_del(h);
|
||||
kfree(entry->device_id);
|
||||
kfree(entry);
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
dbg_hid("Device entry not found!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
device_remove_file(&hid->dev, &dev_attr_range);
|
||||
dbg_hid("Device successfully unregistered\n");
|
||||
return 0;
|
||||
}
|
||||
|
@ -58,12 +58,6 @@ static const signed short ff_joystick_ac[] = {
|
||||
-1
|
||||
};
|
||||
|
||||
static const signed short ff_wheel[] = {
|
||||
FF_CONSTANT,
|
||||
FF_AUTOCENTER,
|
||||
-1
|
||||
};
|
||||
|
||||
static const struct dev_type devices[] = {
|
||||
{ 0x046d, 0xc211, ff_rumble },
|
||||
{ 0x046d, 0xc219, ff_rumble },
|
||||
@ -71,14 +65,7 @@ static const struct dev_type devices[] = {
|
||||
{ 0x046d, 0xc286, ff_joystick_ac },
|
||||
{ 0x046d, 0xc287, ff_joystick_ac },
|
||||
{ 0x046d, 0xc293, ff_joystick },
|
||||
{ 0x046d, 0xc294, ff_wheel },
|
||||
{ 0x046d, 0xc298, ff_wheel },
|
||||
{ 0x046d, 0xc299, ff_wheel },
|
||||
{ 0x046d, 0xc29b, ff_wheel },
|
||||
{ 0x046d, 0xc295, ff_joystick },
|
||||
{ 0x046d, 0xc298, ff_wheel },
|
||||
{ 0x046d, 0xc299, ff_wheel },
|
||||
{ 0x046d, 0xca03, ff_wheel },
|
||||
};
|
||||
|
||||
static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
|
||||
|
922
drivers/hid/hid-logitech-dj.c
Normal file
922
drivers/hid/hid-logitech-dj.c
Normal file
@ -0,0 +1,922 @@
|
||||
/*
|
||||
* HID driver for Logitech Unifying receivers
|
||||
*
|
||||
* Copyright (c) 2011 Logitech
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
|
||||
*
|
||||
* 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/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
#include "usbhid/usbhid.h"
|
||||
#include "hid-ids.h"
|
||||
#include "hid-logitech-dj.h"
|
||||
|
||||
/* Keyboard descriptor (1) */
|
||||
static const char kbd_descriptor[] = {
|
||||
0x05, 0x01, /* USAGE_PAGE (generic Desktop) */
|
||||
0x09, 0x06, /* USAGE (Keyboard) */
|
||||
0xA1, 0x01, /* COLLECTION (Application) */
|
||||
0x85, 0x01, /* REPORT_ID (1) */
|
||||
0x95, 0x08, /* REPORT_COUNT (8) */
|
||||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
|
||||
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
|
||||
0x19, 0xE0, /* USAGE_MINIMUM (Left Control) */
|
||||
0x29, 0xE7, /* USAGE_MAXIMUM (Right GUI) */
|
||||
0x81, 0x02, /* INPUT (Data,Var,Abs) */
|
||||
0x95, 0x05, /* REPORT COUNT (5) */
|
||||
0x05, 0x08, /* USAGE PAGE (LED page) */
|
||||
0x19, 0x01, /* USAGE MINIMUM (1) */
|
||||
0x29, 0x05, /* USAGE MAXIMUM (5) */
|
||||
0x91, 0x02, /* OUTPUT (Data, Variable, Absolute) */
|
||||
0x95, 0x01, /* REPORT COUNT (1) */
|
||||
0x75, 0x03, /* REPORT SIZE (3) */
|
||||
0x91, 0x01, /* OUTPUT (Constant) */
|
||||
0x95, 0x06, /* REPORT_COUNT (6) */
|
||||
0x75, 0x08, /* REPORT_SIZE (8) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x26, 0xFF, 0x00, /* LOGICAL_MAXIMUM (255) */
|
||||
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
|
||||
0x19, 0x00, /* USAGE_MINIMUM (no event) */
|
||||
0x2A, 0xFF, 0x00, /* USAGE_MAXIMUM (reserved) */
|
||||
0x81, 0x00, /* INPUT (Data,Ary,Abs) */
|
||||
0xC0
|
||||
};
|
||||
|
||||
/* Mouse descriptor (2) */
|
||||
static const char mse_descriptor[] = {
|
||||
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
|
||||
0x09, 0x02, /* USAGE (Mouse) */
|
||||
0xA1, 0x01, /* COLLECTION (Application) */
|
||||
0x85, 0x02, /* REPORT_ID = 2 */
|
||||
0x09, 0x01, /* USAGE (pointer) */
|
||||
0xA1, 0x00, /* COLLECTION (physical) */
|
||||
0x05, 0x09, /* USAGE_PAGE (buttons) */
|
||||
0x19, 0x01, /* USAGE_MIN (1) */
|
||||
0x29, 0x10, /* USAGE_MAX (16) */
|
||||
0x15, 0x00, /* LOGICAL_MIN (0) */
|
||||
0x25, 0x01, /* LOGICAL_MAX (1) */
|
||||
0x95, 0x10, /* REPORT_COUNT (16) */
|
||||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
0x81, 0x02, /* INPUT (data var abs) */
|
||||
0x05, 0x01, /* USAGE_PAGE (generic desktop) */
|
||||
0x16, 0x01, 0xF8, /* LOGICAL_MIN (-2047) */
|
||||
0x26, 0xFF, 0x07, /* LOGICAL_MAX (2047) */
|
||||
0x75, 0x0C, /* REPORT_SIZE (12) */
|
||||
0x95, 0x02, /* REPORT_COUNT (2) */
|
||||
0x09, 0x30, /* USAGE (X) */
|
||||
0x09, 0x31, /* USAGE (Y) */
|
||||
0x81, 0x06, /* INPUT */
|
||||
0x15, 0x81, /* LOGICAL_MIN (-127) */
|
||||
0x25, 0x7F, /* LOGICAL_MAX (127) */
|
||||
0x75, 0x08, /* REPORT_SIZE (8) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x09, 0x38, /* USAGE (wheel) */
|
||||
0x81, 0x06, /* INPUT */
|
||||
0x05, 0x0C, /* USAGE_PAGE(consumer) */
|
||||
0x0A, 0x38, 0x02, /* USAGE(AC Pan) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x81, 0x06, /* INPUT */
|
||||
0xC0, /* END_COLLECTION */
|
||||
0xC0, /* END_COLLECTION */
|
||||
};
|
||||
|
||||
/* Consumer Control descriptor (3) */
|
||||
static const char consumer_descriptor[] = {
|
||||
0x05, 0x0C, /* USAGE_PAGE (Consumer Devices) */
|
||||
0x09, 0x01, /* USAGE (Consumer Control) */
|
||||
0xA1, 0x01, /* COLLECTION (Application) */
|
||||
0x85, 0x03, /* REPORT_ID = 3 */
|
||||
0x75, 0x10, /* REPORT_SIZE (16) */
|
||||
0x95, 0x02, /* REPORT_COUNT (2) */
|
||||
0x15, 0x01, /* LOGICAL_MIN (1) */
|
||||
0x26, 0x8C, 0x02, /* LOGICAL_MAX (652) */
|
||||
0x19, 0x01, /* USAGE_MIN (1) */
|
||||
0x2A, 0x8C, 0x02, /* USAGE_MAX (652) */
|
||||
0x81, 0x00, /* INPUT (Data Ary Abs) */
|
||||
0xC0, /* END_COLLECTION */
|
||||
}; /* */
|
||||
|
||||
/* System control descriptor (4) */
|
||||
static const char syscontrol_descriptor[] = {
|
||||
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
|
||||
0x09, 0x80, /* USAGE (System Control) */
|
||||
0xA1, 0x01, /* COLLECTION (Application) */
|
||||
0x85, 0x04, /* REPORT_ID = 4 */
|
||||
0x75, 0x02, /* REPORT_SIZE (2) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x15, 0x01, /* LOGICAL_MIN (1) */
|
||||
0x25, 0x03, /* LOGICAL_MAX (3) */
|
||||
0x09, 0x82, /* USAGE (System Sleep) */
|
||||
0x09, 0x81, /* USAGE (System Power Down) */
|
||||
0x09, 0x83, /* USAGE (System Wake Up) */
|
||||
0x81, 0x60, /* INPUT (Data Ary Abs NPrf Null) */
|
||||
0x75, 0x06, /* REPORT_SIZE (6) */
|
||||
0x81, 0x03, /* INPUT (Cnst Var Abs) */
|
||||
0xC0, /* END_COLLECTION */
|
||||
};
|
||||
|
||||
/* Media descriptor (8) */
|
||||
static const char media_descriptor[] = {
|
||||
0x06, 0xbc, 0xff, /* Usage Page 0xffbc */
|
||||
0x09, 0x88, /* Usage 0x0088 */
|
||||
0xa1, 0x01, /* BeginCollection */
|
||||
0x85, 0x08, /* Report ID 8 */
|
||||
0x19, 0x01, /* Usage Min 0x0001 */
|
||||
0x29, 0xff, /* Usage Max 0x00ff */
|
||||
0x15, 0x01, /* Logical Min 1 */
|
||||
0x26, 0xff, 0x00, /* Logical Max 255 */
|
||||
0x75, 0x08, /* Report Size 8 */
|
||||
0x95, 0x01, /* Report Count 1 */
|
||||
0x81, 0x00, /* Input */
|
||||
0xc0, /* EndCollection */
|
||||
}; /* */
|
||||
|
||||
/* Maximum size of all defined hid reports in bytes (including report id) */
|
||||
#define MAX_REPORT_SIZE 8
|
||||
|
||||
/* Number of possible hid report types that can be created by this driver.
|
||||
*
|
||||
* Right now, RF report types have the same report types (or report id's)
|
||||
* than the hid report created from those RF reports. In the future
|
||||
* this doesnt have to be true.
|
||||
*
|
||||
* For instance, RF report type 0x01 which has a size of 8 bytes, corresponds
|
||||
* to hid report id 0x01, this is standard keyboard. Same thing applies to mice
|
||||
* reports and consumer control, etc. If a new RF report is created, it doesn't
|
||||
* has to have the same report id as its corresponding hid report, so an
|
||||
* translation may have to take place for future report types.
|
||||
*/
|
||||
#define NUMBER_OF_HID_REPORTS 32
|
||||
static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = {
|
||||
[1] = 8, /* Standard keyboard */
|
||||
[2] = 8, /* Standard mouse */
|
||||
[3] = 5, /* Consumer control */
|
||||
[4] = 2, /* System control */
|
||||
[8] = 2, /* Media Center */
|
||||
};
|
||||
|
||||
|
||||
#define LOGITECH_DJ_INTERFACE_NUMBER 0x02
|
||||
|
||||
static struct hid_ll_driver logi_dj_ll_driver;
|
||||
|
||||
static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf,
|
||||
size_t count,
|
||||
unsigned char report_type);
|
||||
|
||||
static void logi_dj_recv_destroy_djhid_device(struct dj_receiver_dev *djrcv_dev,
|
||||
struct dj_report *dj_report)
|
||||
{
|
||||
/* Called in delayed work context */
|
||||
struct dj_device *dj_dev;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&djrcv_dev->lock, flags);
|
||||
dj_dev = djrcv_dev->paired_dj_devices[dj_report->device_index];
|
||||
djrcv_dev->paired_dj_devices[dj_report->device_index] = NULL;
|
||||
spin_unlock_irqrestore(&djrcv_dev->lock, flags);
|
||||
|
||||
if (dj_dev != NULL) {
|
||||
hid_destroy_device(dj_dev->hdev);
|
||||
kfree(dj_dev);
|
||||
} else {
|
||||
dev_err(&djrcv_dev->hdev->dev, "%s: can't destroy a NULL device\n",
|
||||
__func__);
|
||||
}
|
||||
}
|
||||
|
||||
static void logi_dj_recv_add_djhid_device(struct dj_receiver_dev *djrcv_dev,
|
||||
struct dj_report *dj_report)
|
||||
{
|
||||
/* Called in delayed work context */
|
||||
struct hid_device *djrcv_hdev = djrcv_dev->hdev;
|
||||
struct usb_interface *intf = to_usb_interface(djrcv_hdev->dev.parent);
|
||||
struct usb_device *usbdev = interface_to_usbdev(intf);
|
||||
struct hid_device *dj_hiddev;
|
||||
struct dj_device *dj_dev;
|
||||
|
||||
/* Device index goes from 1 to 6, we need 3 bytes to store the
|
||||
* semicolon, the index, and a null terminator
|
||||
*/
|
||||
unsigned char tmpstr[3];
|
||||
|
||||
if (dj_report->report_params[DEVICE_PAIRED_PARAM_SPFUNCTION] &
|
||||
SPFUNCTION_DEVICE_LIST_EMPTY) {
|
||||
dbg_hid("%s: device list is empty\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((dj_report->device_index < DJ_DEVICE_INDEX_MIN) ||
|
||||
(dj_report->device_index > DJ_DEVICE_INDEX_MAX)) {
|
||||
dev_err(&djrcv_hdev->dev, "%s: invalid device index:%d\n",
|
||||
__func__, dj_report->device_index);
|
||||
return;
|
||||
}
|
||||
|
||||
dj_hiddev = hid_allocate_device();
|
||||
if (IS_ERR(dj_hiddev)) {
|
||||
dev_err(&djrcv_hdev->dev, "%s: hid_allocate_device failed\n",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
dj_hiddev->ll_driver = &logi_dj_ll_driver;
|
||||
dj_hiddev->hid_output_raw_report = logi_dj_output_hidraw_report;
|
||||
|
||||
dj_hiddev->dev.parent = &djrcv_hdev->dev;
|
||||
dj_hiddev->bus = BUS_USB;
|
||||
dj_hiddev->vendor = le16_to_cpu(usbdev->descriptor.idVendor);
|
||||
dj_hiddev->product = le16_to_cpu(usbdev->descriptor.idProduct);
|
||||
snprintf(dj_hiddev->name, sizeof(dj_hiddev->name),
|
||||
"Logitech Unifying Device. Wireless PID:%02x%02x",
|
||||
dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_MSB],
|
||||
dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_LSB]);
|
||||
|
||||
usb_make_path(usbdev, dj_hiddev->phys, sizeof(dj_hiddev->phys));
|
||||
snprintf(tmpstr, sizeof(tmpstr), ":%d", dj_report->device_index);
|
||||
strlcat(dj_hiddev->phys, tmpstr, sizeof(dj_hiddev->phys));
|
||||
|
||||
dj_dev = kzalloc(sizeof(struct dj_device), GFP_KERNEL);
|
||||
|
||||
if (!dj_dev) {
|
||||
dev_err(&djrcv_hdev->dev, "%s: failed allocating dj_device\n",
|
||||
__func__);
|
||||
goto dj_device_allocate_fail;
|
||||
}
|
||||
|
||||
dj_dev->reports_supported = le32_to_cpu(
|
||||
dj_report->report_params[DEVICE_PAIRED_RF_REPORT_TYPE]);
|
||||
dj_dev->hdev = dj_hiddev;
|
||||
dj_dev->dj_receiver_dev = djrcv_dev;
|
||||
dj_dev->device_index = dj_report->device_index;
|
||||
dj_hiddev->driver_data = dj_dev;
|
||||
|
||||
djrcv_dev->paired_dj_devices[dj_report->device_index] = dj_dev;
|
||||
|
||||
if (hid_add_device(dj_hiddev)) {
|
||||
dev_err(&djrcv_hdev->dev, "%s: failed adding dj_device\n",
|
||||
__func__);
|
||||
goto hid_add_device_fail;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
hid_add_device_fail:
|
||||
djrcv_dev->paired_dj_devices[dj_report->device_index] = NULL;
|
||||
kfree(dj_dev);
|
||||
dj_device_allocate_fail:
|
||||
hid_destroy_device(dj_hiddev);
|
||||
}
|
||||
|
||||
static void delayedwork_callback(struct work_struct *work)
|
||||
{
|
||||
struct dj_receiver_dev *djrcv_dev =
|
||||
container_of(work, struct dj_receiver_dev, work);
|
||||
|
||||
struct dj_report dj_report;
|
||||
unsigned long flags;
|
||||
int count;
|
||||
|
||||
dbg_hid("%s\n", __func__);
|
||||
|
||||
spin_lock_irqsave(&djrcv_dev->lock, flags);
|
||||
|
||||
count = kfifo_out(&djrcv_dev->notif_fifo, &dj_report,
|
||||
sizeof(struct dj_report));
|
||||
|
||||
if (count != sizeof(struct dj_report)) {
|
||||
dev_err(&djrcv_dev->hdev->dev, "%s: workitem triggered without "
|
||||
"notifications available\n", __func__);
|
||||
spin_unlock_irqrestore(&djrcv_dev->lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!kfifo_is_empty(&djrcv_dev->notif_fifo)) {
|
||||
if (schedule_work(&djrcv_dev->work) == 0) {
|
||||
dbg_hid("%s: did not schedule the work item, was "
|
||||
"already queued\n", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&djrcv_dev->lock, flags);
|
||||
|
||||
switch (dj_report.report_type) {
|
||||
case REPORT_TYPE_NOTIF_DEVICE_PAIRED:
|
||||
logi_dj_recv_add_djhid_device(djrcv_dev, &dj_report);
|
||||
break;
|
||||
case REPORT_TYPE_NOTIF_DEVICE_UNPAIRED:
|
||||
logi_dj_recv_destroy_djhid_device(djrcv_dev, &dj_report);
|
||||
break;
|
||||
default:
|
||||
dbg_hid("%s: unexpected report type\n", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
static void logi_dj_recv_queue_notification(struct dj_receiver_dev *djrcv_dev,
|
||||
struct dj_report *dj_report)
|
||||
{
|
||||
/* We are called from atomic context (tasklet && djrcv->lock held) */
|
||||
|
||||
kfifo_in(&djrcv_dev->notif_fifo, dj_report, sizeof(struct dj_report));
|
||||
|
||||
if (schedule_work(&djrcv_dev->work) == 0) {
|
||||
dbg_hid("%s: did not schedule the work item, was already "
|
||||
"queued\n", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
static void logi_dj_recv_forward_null_report(struct dj_receiver_dev *djrcv_dev,
|
||||
struct dj_report *dj_report)
|
||||
{
|
||||
/* We are called from atomic context (tasklet && djrcv->lock held) */
|
||||
unsigned int i;
|
||||
u8 reportbuffer[MAX_REPORT_SIZE];
|
||||
struct dj_device *djdev;
|
||||
|
||||
djdev = djrcv_dev->paired_dj_devices[dj_report->device_index];
|
||||
|
||||
if (!djdev) {
|
||||
dbg_hid("djrcv_dev->paired_dj_devices[dj_report->device_index]"
|
||||
" is NULL, index %d\n", dj_report->device_index);
|
||||
return;
|
||||
}
|
||||
|
||||
memset(reportbuffer, 0, sizeof(reportbuffer));
|
||||
|
||||
for (i = 0; i < NUMBER_OF_HID_REPORTS; i++) {
|
||||
if (djdev->reports_supported & (1 << i)) {
|
||||
reportbuffer[0] = i;
|
||||
if (hid_input_report(djdev->hdev,
|
||||
HID_INPUT_REPORT,
|
||||
reportbuffer,
|
||||
hid_reportid_size_map[i], 1)) {
|
||||
dbg_hid("hid_input_report error sending null "
|
||||
"report\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void logi_dj_recv_forward_report(struct dj_receiver_dev *djrcv_dev,
|
||||
struct dj_report *dj_report)
|
||||
{
|
||||
/* We are called from atomic context (tasklet && djrcv->lock held) */
|
||||
struct dj_device *dj_device;
|
||||
|
||||
dj_device = djrcv_dev->paired_dj_devices[dj_report->device_index];
|
||||
|
||||
if (dj_device == NULL) {
|
||||
dbg_hid("djrcv_dev->paired_dj_devices[dj_report->device_index]"
|
||||
" is NULL, index %d\n", dj_report->device_index);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((dj_report->report_type > ARRAY_SIZE(hid_reportid_size_map) - 1) ||
|
||||
(hid_reportid_size_map[dj_report->report_type] == 0)) {
|
||||
dbg_hid("invalid report type:%x\n", dj_report->report_type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hid_input_report(dj_device->hdev,
|
||||
HID_INPUT_REPORT, &dj_report->report_type,
|
||||
hid_reportid_size_map[dj_report->report_type], 1)) {
|
||||
dbg_hid("hid_input_report error\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int logi_dj_recv_send_report(struct dj_receiver_dev *djrcv_dev,
|
||||
struct dj_report *dj_report)
|
||||
{
|
||||
struct hid_device *hdev = djrcv_dev->hdev;
|
||||
int sent_bytes;
|
||||
|
||||
if (!hdev->hid_output_raw_report) {
|
||||
dev_err(&hdev->dev, "%s:"
|
||||
"hid_output_raw_report is null\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
sent_bytes = hdev->hid_output_raw_report(hdev, (u8 *) dj_report,
|
||||
sizeof(struct dj_report),
|
||||
HID_OUTPUT_REPORT);
|
||||
|
||||
return (sent_bytes < 0) ? sent_bytes : 0;
|
||||
}
|
||||
|
||||
static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev)
|
||||
{
|
||||
struct dj_report dj_report;
|
||||
|
||||
memset(&dj_report, 0, sizeof(dj_report));
|
||||
dj_report.report_id = REPORT_ID_DJ_SHORT;
|
||||
dj_report.device_index = 0xFF;
|
||||
dj_report.report_type = REPORT_TYPE_CMD_GET_PAIRED_DEVICES;
|
||||
return logi_dj_recv_send_report(djrcv_dev, &dj_report);
|
||||
}
|
||||
|
||||
static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
|
||||
unsigned timeout)
|
||||
{
|
||||
struct dj_report dj_report;
|
||||
|
||||
memset(&dj_report, 0, sizeof(dj_report));
|
||||
dj_report.report_id = REPORT_ID_DJ_SHORT;
|
||||
dj_report.device_index = 0xFF;
|
||||
dj_report.report_type = REPORT_TYPE_CMD_SWITCH;
|
||||
dj_report.report_params[CMD_SWITCH_PARAM_DEVBITFIELD] = 0x1F;
|
||||
dj_report.report_params[CMD_SWITCH_PARAM_TIMEOUT_SECONDS] = (u8)timeout;
|
||||
return logi_dj_recv_send_report(djrcv_dev, &dj_report);
|
||||
}
|
||||
|
||||
|
||||
static int logi_dj_ll_open(struct hid_device *hid)
|
||||
{
|
||||
dbg_hid("%s:%s\n", __func__, hid->phys);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static void logi_dj_ll_close(struct hid_device *hid)
|
||||
{
|
||||
dbg_hid("%s:%s\n", __func__, hid->phys);
|
||||
}
|
||||
|
||||
static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf,
|
||||
size_t count,
|
||||
unsigned char report_type)
|
||||
{
|
||||
/* Called by hid raw to send data */
|
||||
dbg_hid("%s\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int logi_dj_ll_parse(struct hid_device *hid)
|
||||
{
|
||||
struct dj_device *djdev = hid->driver_data;
|
||||
int retval;
|
||||
|
||||
dbg_hid("%s\n", __func__);
|
||||
|
||||
djdev->hdev->version = 0x0111;
|
||||
djdev->hdev->country = 0x00;
|
||||
|
||||
if (djdev->reports_supported & STD_KEYBOARD) {
|
||||
dbg_hid("%s: sending a kbd descriptor, reports_supported: %x\n",
|
||||
__func__, djdev->reports_supported);
|
||||
retval = hid_parse_report(hid,
|
||||
(u8 *) kbd_descriptor,
|
||||
sizeof(kbd_descriptor));
|
||||
if (retval) {
|
||||
dbg_hid("%s: sending a kbd descriptor, hid_parse failed"
|
||||
" error: %d\n", __func__, retval);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
if (djdev->reports_supported & STD_MOUSE) {
|
||||
dbg_hid("%s: sending a mouse descriptor, reports_supported: "
|
||||
"%x\n", __func__, djdev->reports_supported);
|
||||
retval = hid_parse_report(hid,
|
||||
(u8 *) mse_descriptor,
|
||||
sizeof(mse_descriptor));
|
||||
if (retval) {
|
||||
dbg_hid("%s: sending a mouse descriptor, hid_parse "
|
||||
"failed error: %d\n", __func__, retval);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
if (djdev->reports_supported & MULTIMEDIA) {
|
||||
dbg_hid("%s: sending a multimedia report descriptor: %x\n",
|
||||
__func__, djdev->reports_supported);
|
||||
retval = hid_parse_report(hid,
|
||||
(u8 *) consumer_descriptor,
|
||||
sizeof(consumer_descriptor));
|
||||
if (retval) {
|
||||
dbg_hid("%s: sending a consumer_descriptor, hid_parse "
|
||||
"failed error: %d\n", __func__, retval);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
if (djdev->reports_supported & POWER_KEYS) {
|
||||
dbg_hid("%s: sending a power keys report descriptor: %x\n",
|
||||
__func__, djdev->reports_supported);
|
||||
retval = hid_parse_report(hid,
|
||||
(u8 *) syscontrol_descriptor,
|
||||
sizeof(syscontrol_descriptor));
|
||||
if (retval) {
|
||||
dbg_hid("%s: sending a syscontrol_descriptor, "
|
||||
"hid_parse failed error: %d\n",
|
||||
__func__, retval);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
if (djdev->reports_supported & MEDIA_CENTER) {
|
||||
dbg_hid("%s: sending a media center report descriptor: %x\n",
|
||||
__func__, djdev->reports_supported);
|
||||
retval = hid_parse_report(hid,
|
||||
(u8 *) media_descriptor,
|
||||
sizeof(media_descriptor));
|
||||
if (retval) {
|
||||
dbg_hid("%s: sending a media_descriptor, hid_parse "
|
||||
"failed error: %d\n", __func__, retval);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
if (djdev->reports_supported & KBD_LEDS) {
|
||||
dbg_hid("%s: need to send kbd leds report descriptor: %x\n",
|
||||
__func__, djdev->reports_supported);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int logi_dj_ll_input_event(struct input_dev *dev, unsigned int type,
|
||||
unsigned int code, int value)
|
||||
{
|
||||
/* Sent by the input layer to handle leds and Force Feedback */
|
||||
struct hid_device *dj_hiddev = input_get_drvdata(dev);
|
||||
struct dj_device *dj_dev = dj_hiddev->driver_data;
|
||||
|
||||
struct dj_receiver_dev *djrcv_dev =
|
||||
dev_get_drvdata(dj_hiddev->dev.parent);
|
||||
struct hid_device *dj_rcv_hiddev = djrcv_dev->hdev;
|
||||
struct hid_report_enum *output_report_enum;
|
||||
|
||||
struct hid_field *field;
|
||||
struct hid_report *report;
|
||||
unsigned char data[8];
|
||||
int offset;
|
||||
|
||||
dbg_hid("%s: %s, type:%d | code:%d | value:%d\n",
|
||||
__func__, dev->phys, type, code, value);
|
||||
|
||||
if (type != EV_LED)
|
||||
return -1;
|
||||
|
||||
offset = hidinput_find_field(dj_hiddev, type, code, &field);
|
||||
|
||||
if (offset == -1) {
|
||||
dev_warn(&dev->dev, "event field not found\n");
|
||||
return -1;
|
||||
}
|
||||
hid_set_field(field, offset, value);
|
||||
hid_output_report(field->report, &data[0]);
|
||||
|
||||
output_report_enum = &dj_rcv_hiddev->report_enum[HID_OUTPUT_REPORT];
|
||||
report = output_report_enum->report_id_hash[REPORT_ID_DJ_SHORT];
|
||||
hid_set_field(report->field[0], 0, dj_dev->device_index);
|
||||
hid_set_field(report->field[0], 1, REPORT_TYPE_LEDS);
|
||||
hid_set_field(report->field[0], 2, data[1]);
|
||||
|
||||
usbhid_submit_report(dj_rcv_hiddev, report, USB_DIR_OUT);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static int logi_dj_ll_start(struct hid_device *hid)
|
||||
{
|
||||
dbg_hid("%s\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void logi_dj_ll_stop(struct hid_device *hid)
|
||||
{
|
||||
dbg_hid("%s\n", __func__);
|
||||
}
|
||||
|
||||
|
||||
static struct hid_ll_driver logi_dj_ll_driver = {
|
||||
.parse = logi_dj_ll_parse,
|
||||
.start = logi_dj_ll_start,
|
||||
.stop = logi_dj_ll_stop,
|
||||
.open = logi_dj_ll_open,
|
||||
.close = logi_dj_ll_close,
|
||||
.hidinput_input_event = logi_dj_ll_input_event,
|
||||
};
|
||||
|
||||
|
||||
static int logi_dj_raw_event(struct hid_device *hdev,
|
||||
struct hid_report *report, u8 *data,
|
||||
int size)
|
||||
{
|
||||
struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev);
|
||||
struct dj_report *dj_report = (struct dj_report *) data;
|
||||
unsigned long flags;
|
||||
bool report_processed = false;
|
||||
|
||||
dbg_hid("%s, size:%d\n", __func__, size);
|
||||
|
||||
/* Here we receive all data coming from iface 2, there are 4 cases:
|
||||
*
|
||||
* 1) Data should continue its normal processing i.e. data does not
|
||||
* come from the DJ collection, in which case we do nothing and
|
||||
* return 0, so hid-core can continue normal processing (will forward
|
||||
* to associated hidraw device)
|
||||
*
|
||||
* 2) Data is from DJ collection, and is intended for this driver i. e.
|
||||
* data contains arrival, departure, etc notifications, in which case
|
||||
* we queue them for delayed processing by the work queue. We return 1
|
||||
* to hid-core as no further processing is required from it.
|
||||
*
|
||||
* 3) Data is from DJ collection, and informs a connection change,
|
||||
* if the change means rf link loss, then we must send a null report
|
||||
* to the upper layer to discard potentially pressed keys that may be
|
||||
* repeated forever by the input layer. Return 1 to hid-core as no
|
||||
* further processing is required.
|
||||
*
|
||||
* 4) Data is from DJ collection and is an actual input event from
|
||||
* a paired DJ device in which case we forward it to the correct hid
|
||||
* device (via hid_input_report() ) and return 1 so hid-core does not do
|
||||
* anything else with it.
|
||||
*/
|
||||
|
||||
spin_lock_irqsave(&djrcv_dev->lock, flags);
|
||||
if (dj_report->report_id == REPORT_ID_DJ_SHORT) {
|
||||
switch (dj_report->report_type) {
|
||||
case REPORT_TYPE_NOTIF_DEVICE_PAIRED:
|
||||
case REPORT_TYPE_NOTIF_DEVICE_UNPAIRED:
|
||||
logi_dj_recv_queue_notification(djrcv_dev, dj_report);
|
||||
break;
|
||||
case REPORT_TYPE_NOTIF_CONNECTION_STATUS:
|
||||
if (dj_report->report_params[CONNECTION_STATUS_PARAM_STATUS] ==
|
||||
STATUS_LINKLOSS) {
|
||||
logi_dj_recv_forward_null_report(djrcv_dev, dj_report);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logi_dj_recv_forward_report(djrcv_dev, dj_report);
|
||||
}
|
||||
report_processed = true;
|
||||
}
|
||||
spin_unlock_irqrestore(&djrcv_dev->lock, flags);
|
||||
|
||||
return report_processed;
|
||||
}
|
||||
|
||||
static int logi_dj_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
struct dj_receiver_dev *djrcv_dev;
|
||||
int retval;
|
||||
|
||||
if (is_dj_device((struct dj_device *)hdev->driver_data))
|
||||
return -ENODEV;
|
||||
|
||||
dbg_hid("%s called for ifnum %d\n", __func__,
|
||||
intf->cur_altsetting->desc.bInterfaceNumber);
|
||||
|
||||
/* Ignore interfaces 0 and 1, they will not carry any data, dont create
|
||||
* any hid_device for them */
|
||||
if (intf->cur_altsetting->desc.bInterfaceNumber !=
|
||||
LOGITECH_DJ_INTERFACE_NUMBER) {
|
||||
dbg_hid("%s: ignoring ifnum %d\n", __func__,
|
||||
intf->cur_altsetting->desc.bInterfaceNumber);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Treat interface 2 */
|
||||
|
||||
djrcv_dev = kzalloc(sizeof(struct dj_receiver_dev), GFP_KERNEL);
|
||||
if (!djrcv_dev) {
|
||||
dev_err(&hdev->dev,
|
||||
"%s:failed allocating dj_receiver_dev\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
djrcv_dev->hdev = hdev;
|
||||
INIT_WORK(&djrcv_dev->work, delayedwork_callback);
|
||||
spin_lock_init(&djrcv_dev->lock);
|
||||
if (kfifo_alloc(&djrcv_dev->notif_fifo,
|
||||
DJ_MAX_NUMBER_NOTIFICATIONS * sizeof(struct dj_report),
|
||||
GFP_KERNEL)) {
|
||||
dev_err(&hdev->dev,
|
||||
"%s:failed allocating notif_fifo\n", __func__);
|
||||
kfree(djrcv_dev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
hid_set_drvdata(hdev, djrcv_dev);
|
||||
|
||||
/* Call to usbhid to fetch the HID descriptors of interface 2 and
|
||||
* subsequently call to the hid/hid-core to parse the fetched
|
||||
* descriptors, this will in turn create the hidraw and hiddev nodes
|
||||
* for interface 2 of the receiver */
|
||||
retval = hid_parse(hdev);
|
||||
if (retval) {
|
||||
dev_err(&hdev->dev,
|
||||
"%s:parse of interface 2 failed\n", __func__);
|
||||
goto hid_parse_fail;
|
||||
}
|
||||
|
||||
/* Starts the usb device and connects to upper interfaces hiddev and
|
||||
* hidraw */
|
||||
retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (retval) {
|
||||
dev_err(&hdev->dev,
|
||||
"%s:hid_hw_start returned error\n", __func__);
|
||||
goto hid_hw_start_fail;
|
||||
}
|
||||
|
||||
retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
|
||||
if (retval < 0) {
|
||||
dev_err(&hdev->dev,
|
||||
"%s:logi_dj_recv_switch_to_dj_mode returned error:%d\n",
|
||||
__func__, retval);
|
||||
goto switch_to_dj_mode_fail;
|
||||
}
|
||||
|
||||
/* This is enabling the polling urb on the IN endpoint */
|
||||
retval = hdev->ll_driver->open(hdev);
|
||||
if (retval < 0) {
|
||||
dev_err(&hdev->dev, "%s:hdev->ll_driver->open returned "
|
||||
"error:%d\n", __func__, retval);
|
||||
goto llopen_failed;
|
||||
}
|
||||
|
||||
retval = logi_dj_recv_query_paired_devices(djrcv_dev);
|
||||
if (retval < 0) {
|
||||
dev_err(&hdev->dev, "%s:logi_dj_recv_query_paired_devices "
|
||||
"error:%d\n", __func__, retval);
|
||||
goto logi_dj_recv_query_paired_devices_failed;
|
||||
}
|
||||
|
||||
return retval;
|
||||
|
||||
logi_dj_recv_query_paired_devices_failed:
|
||||
hdev->ll_driver->close(hdev);
|
||||
|
||||
llopen_failed:
|
||||
switch_to_dj_mode_fail:
|
||||
hid_hw_stop(hdev);
|
||||
|
||||
hid_hw_start_fail:
|
||||
hid_parse_fail:
|
||||
kfifo_free(&djrcv_dev->notif_fifo);
|
||||
kfree(djrcv_dev);
|
||||
hid_set_drvdata(hdev, NULL);
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int logi_dj_reset_resume(struct hid_device *hdev)
|
||||
{
|
||||
int retval;
|
||||
struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev);
|
||||
|
||||
retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
|
||||
if (retval < 0) {
|
||||
dev_err(&hdev->dev,
|
||||
"%s:logi_dj_recv_switch_to_dj_mode returned error:%d\n",
|
||||
__func__, retval);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void logi_dj_remove(struct hid_device *hdev)
|
||||
{
|
||||
struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev);
|
||||
struct dj_device *dj_dev;
|
||||
int i;
|
||||
|
||||
dbg_hid("%s\n", __func__);
|
||||
|
||||
cancel_work_sync(&djrcv_dev->work);
|
||||
|
||||
hdev->ll_driver->close(hdev);
|
||||
hid_hw_stop(hdev);
|
||||
|
||||
/* I suppose that at this point the only context that can access
|
||||
* the djrecv_data is this thread as the work item is guaranteed to
|
||||
* have finished and no more raw_event callbacks should arrive after
|
||||
* the remove callback was triggered so no locks are put around the
|
||||
* code below */
|
||||
for (i = 0; i < (DJ_MAX_PAIRED_DEVICES + DJ_DEVICE_INDEX_MIN); i++) {
|
||||
dj_dev = djrcv_dev->paired_dj_devices[i];
|
||||
if (dj_dev != NULL) {
|
||||
hid_destroy_device(dj_dev->hdev);
|
||||
kfree(dj_dev);
|
||||
djrcv_dev->paired_dj_devices[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
kfifo_free(&djrcv_dev->notif_fifo);
|
||||
kfree(djrcv_dev);
|
||||
hid_set_drvdata(hdev, NULL);
|
||||
}
|
||||
|
||||
static int logi_djdevice_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct dj_device *dj_dev = hdev->driver_data;
|
||||
|
||||
if (!is_dj_device(dj_dev))
|
||||
return -ENODEV;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (!ret)
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id logi_dj_receivers[] = {
|
||||
{HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER)},
|
||||
{HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2)},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(hid, logi_dj_receivers);
|
||||
|
||||
static struct hid_driver logi_djreceiver_driver = {
|
||||
.name = "logitech-djreceiver",
|
||||
.id_table = logi_dj_receivers,
|
||||
.probe = logi_dj_probe,
|
||||
.remove = logi_dj_remove,
|
||||
.raw_event = logi_dj_raw_event,
|
||||
#ifdef CONFIG_PM
|
||||
.reset_resume = logi_dj_reset_resume,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
static const struct hid_device_id logi_dj_devices[] = {
|
||||
{HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER)},
|
||||
{HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2)},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct hid_driver logi_djdevice_driver = {
|
||||
.name = "logitech-djdevice",
|
||||
.id_table = logi_dj_devices,
|
||||
.probe = logi_djdevice_probe,
|
||||
};
|
||||
|
||||
|
||||
static int __init logi_dj_init(void)
|
||||
{
|
||||
int retval;
|
||||
|
||||
dbg_hid("Logitech-DJ:%s\n", __func__);
|
||||
|
||||
retval = hid_register_driver(&logi_djreceiver_driver);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
retval = hid_register_driver(&logi_djdevice_driver);
|
||||
if (retval)
|
||||
hid_unregister_driver(&logi_djreceiver_driver);
|
||||
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
static void __exit logi_dj_exit(void)
|
||||
{
|
||||
dbg_hid("Logitech-DJ:%s\n", __func__);
|
||||
|
||||
hid_unregister_driver(&logi_djdevice_driver);
|
||||
hid_unregister_driver(&logi_djreceiver_driver);
|
||||
|
||||
}
|
||||
|
||||
module_init(logi_dj_init);
|
||||
module_exit(logi_dj_exit);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Logitech");
|
||||
MODULE_AUTHOR("Nestor Lopez Casado");
|
||||
MODULE_AUTHOR("nlopezcasad@logitech.com");
|
123
drivers/hid/hid-logitech-dj.h
Normal file
123
drivers/hid/hid-logitech-dj.h
Normal file
@ -0,0 +1,123 @@
|
||||
#ifndef __HID_LOGITECH_DJ_H
|
||||
#define __HID_LOGITECH_DJ_H
|
||||
|
||||
/*
|
||||
* HID driver for Logitech Unifying receivers
|
||||
*
|
||||
* Copyright (c) 2011 Logitech
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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/kfifo.h>
|
||||
|
||||
#define DJ_MAX_PAIRED_DEVICES 6
|
||||
#define DJ_MAX_NUMBER_NOTIFICATIONS 8
|
||||
#define DJ_DEVICE_INDEX_MIN 1
|
||||
#define DJ_DEVICE_INDEX_MAX 6
|
||||
|
||||
#define DJREPORT_SHORT_LENGTH 15
|
||||
#define DJREPORT_LONG_LENGTH 32
|
||||
|
||||
#define REPORT_ID_DJ_SHORT 0x20
|
||||
#define REPORT_ID_DJ_LONG 0x21
|
||||
|
||||
#define REPORT_TYPE_RFREPORT_FIRST 0x01
|
||||
#define REPORT_TYPE_RFREPORT_LAST 0x1F
|
||||
|
||||
/* Command Switch to DJ mode */
|
||||
#define REPORT_TYPE_CMD_SWITCH 0x80
|
||||
#define CMD_SWITCH_PARAM_DEVBITFIELD 0x00
|
||||
#define CMD_SWITCH_PARAM_TIMEOUT_SECONDS 0x01
|
||||
#define TIMEOUT_NO_KEEPALIVE 0x00
|
||||
|
||||
/* Command to Get the list of Paired devices */
|
||||
#define REPORT_TYPE_CMD_GET_PAIRED_DEVICES 0x81
|
||||
|
||||
/* Device Paired Notification */
|
||||
#define REPORT_TYPE_NOTIF_DEVICE_PAIRED 0x41
|
||||
#define SPFUNCTION_MORE_NOTIF_EXPECTED 0x01
|
||||
#define SPFUNCTION_DEVICE_LIST_EMPTY 0x02
|
||||
#define DEVICE_PAIRED_PARAM_SPFUNCTION 0x00
|
||||
#define DEVICE_PAIRED_PARAM_EQUAD_ID_LSB 0x01
|
||||
#define DEVICE_PAIRED_PARAM_EQUAD_ID_MSB 0x02
|
||||
#define DEVICE_PAIRED_RF_REPORT_TYPE 0x03
|
||||
|
||||
/* Device Un-Paired Notification */
|
||||
#define REPORT_TYPE_NOTIF_DEVICE_UNPAIRED 0x40
|
||||
|
||||
|
||||
/* Connection Status Notification */
|
||||
#define REPORT_TYPE_NOTIF_CONNECTION_STATUS 0x42
|
||||
#define CONNECTION_STATUS_PARAM_STATUS 0x00
|
||||
#define STATUS_LINKLOSS 0x01
|
||||
|
||||
/* Error Notification */
|
||||
#define REPORT_TYPE_NOTIF_ERROR 0x7F
|
||||
#define NOTIF_ERROR_PARAM_ETYPE 0x00
|
||||
#define ETYPE_KEEPALIVE_TIMEOUT 0x01
|
||||
|
||||
/* supported DJ HID && RF report types */
|
||||
#define REPORT_TYPE_KEYBOARD 0x01
|
||||
#define REPORT_TYPE_MOUSE 0x02
|
||||
#define REPORT_TYPE_CONSUMER_CONTROL 0x03
|
||||
#define REPORT_TYPE_SYSTEM_CONTROL 0x04
|
||||
#define REPORT_TYPE_MEDIA_CENTER 0x08
|
||||
#define REPORT_TYPE_LEDS 0x0E
|
||||
|
||||
/* RF Report types bitfield */
|
||||
#define STD_KEYBOARD 0x00000002
|
||||
#define STD_MOUSE 0x00000004
|
||||
#define MULTIMEDIA 0x00000008
|
||||
#define POWER_KEYS 0x00000010
|
||||
#define MEDIA_CENTER 0x00000100
|
||||
#define KBD_LEDS 0x00004000
|
||||
|
||||
struct dj_report {
|
||||
u8 report_id;
|
||||
u8 device_index;
|
||||
u8 report_type;
|
||||
u8 report_params[DJREPORT_SHORT_LENGTH - 3];
|
||||
};
|
||||
|
||||
struct dj_receiver_dev {
|
||||
struct hid_device *hdev;
|
||||
struct dj_device *paired_dj_devices[DJ_MAX_PAIRED_DEVICES +
|
||||
DJ_DEVICE_INDEX_MIN];
|
||||
struct work_struct work;
|
||||
struct kfifo notif_fifo;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
struct dj_device {
|
||||
struct hid_device *hdev;
|
||||
struct dj_receiver_dev *dj_receiver_dev;
|
||||
u32 reports_supported;
|
||||
u8 device_index;
|
||||
};
|
||||
|
||||
/**
|
||||
* is_dj_device - know if the given dj_device is not the receiver.
|
||||
* @dj_dev: the dj device to test
|
||||
*
|
||||
* This macro tests if a struct dj_device pointer is a device created
|
||||
* by the bus enumarator.
|
||||
*/
|
||||
#define is_dj_device(dj_dev) \
|
||||
(&(dj_dev)->dj_receiver_dev->hdev->dev == (dj_dev)->hdev->dev.parent)
|
||||
|
||||
#endif
|
@ -47,10 +47,11 @@ MODULE_LICENSE("GPL");
|
||||
#define MT_QUIRK_SLOT_IS_CONTACTID (1 << 1)
|
||||
#define MT_QUIRK_CYPRESS (1 << 2)
|
||||
#define MT_QUIRK_SLOT_IS_CONTACTNUMBER (1 << 3)
|
||||
#define MT_QUIRK_VALID_IS_INRANGE (1 << 4)
|
||||
#define MT_QUIRK_VALID_IS_CONFIDENCE (1 << 5)
|
||||
#define MT_QUIRK_EGALAX_XYZ_FIXUP (1 << 6)
|
||||
#define MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE (1 << 7)
|
||||
#define MT_QUIRK_ALWAYS_VALID (1 << 4)
|
||||
#define MT_QUIRK_VALID_IS_INRANGE (1 << 5)
|
||||
#define MT_QUIRK_VALID_IS_CONFIDENCE (1 << 6)
|
||||
#define MT_QUIRK_EGALAX_XYZ_FIXUP (1 << 7)
|
||||
#define MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE (1 << 8)
|
||||
|
||||
struct mt_slot {
|
||||
__s32 x, y, p, w, h;
|
||||
@ -86,11 +87,12 @@ struct mt_class {
|
||||
/* classes of device behavior */
|
||||
#define MT_CLS_DEFAULT 0x0001
|
||||
|
||||
#define MT_CLS_CONFIDENCE 0x0002
|
||||
#define MT_CLS_CONFIDENCE_MINUS_ONE 0x0003
|
||||
#define MT_CLS_DUAL_INRANGE_CONTACTID 0x0004
|
||||
#define MT_CLS_DUAL_INRANGE_CONTACTNUMBER 0x0005
|
||||
#define MT_CLS_DUAL_NSMU_CONTACTID 0x0006
|
||||
#define MT_CLS_SERIAL 0x0002
|
||||
#define MT_CLS_CONFIDENCE 0x0003
|
||||
#define MT_CLS_CONFIDENCE_MINUS_ONE 0x0004
|
||||
#define MT_CLS_DUAL_INRANGE_CONTACTID 0x0005
|
||||
#define MT_CLS_DUAL_INRANGE_CONTACTNUMBER 0x0006
|
||||
#define MT_CLS_DUAL_NSMU_CONTACTID 0x0007
|
||||
|
||||
/* vendor specific classes */
|
||||
#define MT_CLS_3M 0x0101
|
||||
@ -134,6 +136,8 @@ static int find_slot_from_contactid(struct mt_device *td)
|
||||
struct mt_class mt_classes[] = {
|
||||
{ .name = MT_CLS_DEFAULT,
|
||||
.quirks = MT_QUIRK_NOT_SEEN_MEANS_UP },
|
||||
{ .name = MT_CLS_SERIAL,
|
||||
.quirks = MT_QUIRK_ALWAYS_VALID},
|
||||
{ .name = MT_CLS_CONFIDENCE,
|
||||
.quirks = MT_QUIRK_VALID_IS_CONFIDENCE },
|
||||
{ .name = MT_CLS_CONFIDENCE_MINUS_ONE,
|
||||
@ -213,6 +217,16 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
struct mt_class *cls = td->mtclass;
|
||||
__s32 quirks = cls->quirks;
|
||||
|
||||
/* Only map fields from TouchScreen or TouchPad collections.
|
||||
* We need to ignore fields that belong to other collections
|
||||
* such as Mouse that might have the same GenericDesktop usages. */
|
||||
if (field->application == HID_DG_TOUCHSCREEN)
|
||||
set_bit(INPUT_PROP_DIRECT, hi->input->propbit);
|
||||
else if (field->application == HID_DG_TOUCHPAD)
|
||||
set_bit(INPUT_PROP_POINTER, hi->input->propbit);
|
||||
else
|
||||
return 0;
|
||||
|
||||
switch (usage->hid & HID_USAGE_PAGE) {
|
||||
|
||||
case HID_UP_GENDESK:
|
||||
@ -277,6 +291,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
td->last_slot_field = usage->hid;
|
||||
td->last_field_index = field->index;
|
||||
td->last_mt_collection = usage->collection_index;
|
||||
hdev->quirks &= ~HID_QUIRK_MULTITOUCH;
|
||||
return 1;
|
||||
case HID_DG_WIDTH:
|
||||
hid_map_usage(hi, usage, bit, max,
|
||||
@ -435,7 +450,9 @@ static int mt_event(struct hid_device *hid, struct hid_field *field,
|
||||
if (hid->claimed & HID_CLAIMED_INPUT && td->slots) {
|
||||
switch (usage->hid) {
|
||||
case HID_DG_INRANGE:
|
||||
if (quirks & MT_QUIRK_VALID_IS_INRANGE)
|
||||
if (quirks & MT_QUIRK_ALWAYS_VALID)
|
||||
td->curvalid = true;
|
||||
else if (quirks & MT_QUIRK_VALID_IS_INRANGE)
|
||||
td->curvalid = value;
|
||||
break;
|
||||
case HID_DG_TIPSWITCH:
|
||||
@ -513,12 +530,44 @@ static void mt_set_input_mode(struct hid_device *hdev)
|
||||
}
|
||||
}
|
||||
|
||||
/* a list of devices for which there is a specialized multitouch driver */
|
||||
static const struct hid_device_id mt_have_special_driver[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, 0x0001) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, 0x0006) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA,
|
||||
USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA,
|
||||
USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH) },
|
||||
{ }
|
||||
};
|
||||
|
||||
static bool mt_match_one_id(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
return id->bus == hdev->bus &&
|
||||
(id->vendor == HID_ANY_ID || id->vendor == hdev->vendor) &&
|
||||
(id->product == HID_ANY_ID || id->product == hdev->product);
|
||||
}
|
||||
|
||||
static const struct hid_device_id *mt_match_id(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
for (; id->bus; id++)
|
||||
if (mt_match_one_id(hdev, id))
|
||||
return id;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret, i;
|
||||
struct mt_device *td;
|
||||
struct mt_class *mtclass = mt_classes; /* MT_CLS_DEFAULT */
|
||||
|
||||
if (mt_match_id(hdev, mt_have_special_driver))
|
||||
return -ENODEV;
|
||||
|
||||
for (i = 0; mt_classes[i].name ; i++) {
|
||||
if (id->driver_data == mt_classes[i].name) {
|
||||
mtclass = &(mt_classes[i]);
|
||||
@ -526,10 +575,6 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
}
|
||||
}
|
||||
|
||||
/* This allows the driver to correctly support devices
|
||||
* that emit events over several HID messages.
|
||||
*/
|
||||
hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC;
|
||||
|
||||
td = kzalloc(sizeof(struct mt_device), GFP_KERNEL);
|
||||
if (!td) {
|
||||
@ -545,10 +590,16 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
if (ret != 0)
|
||||
goto fail;
|
||||
|
||||
hdev->quirks |= HID_QUIRK_MULTITOUCH;
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
/* This allows the driver to correctly support devices
|
||||
* that emit events over several HID messages.
|
||||
*/
|
||||
hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC;
|
||||
|
||||
td->slots = kzalloc(td->maxcontacts * sizeof(struct mt_slot),
|
||||
GFP_KERNEL);
|
||||
if (!td->slots) {
|
||||
@ -662,6 +713,11 @@ static const struct hid_device_id mt_devices[] = {
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_GOODTOUCH,
|
||||
USB_DEVICE_ID_GOODTOUCH_000f) },
|
||||
|
||||
/* Ideacom panel */
|
||||
{ .driver_data = MT_CLS_SERIAL,
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM,
|
||||
USB_DEVICE_ID_IDEACOM_IDC6650) },
|
||||
|
||||
/* Ilitek dual touch panel */
|
||||
{ .driver_data = MT_CLS_DEFAULT,
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_ILITEK,
|
||||
@ -672,6 +728,11 @@ static const struct hid_device_id mt_devices[] = {
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_IRTOUCHSYSTEMS,
|
||||
USB_DEVICE_ID_IRTOUCH_INFRARED_USB) },
|
||||
|
||||
/* LG Display panels */
|
||||
{ .driver_data = MT_CLS_DEFAULT,
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LG,
|
||||
USB_DEVICE_ID_LG_MULTITOUCH) },
|
||||
|
||||
/* Lumio panels */
|
||||
{ .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE,
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LUMIO,
|
||||
@ -732,6 +793,10 @@ static const struct hid_device_id mt_devices[] = {
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_XAT,
|
||||
USB_DEVICE_ID_XAT_CSR) },
|
||||
|
||||
/* Rest of the world */
|
||||
{ .driver_data = MT_CLS_DEFAULT,
|
||||
HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) },
|
||||
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, mt_devices);
|
||||
|
@ -37,6 +37,21 @@
|
||||
|
||||
static uint profile_numbers[5] = {0, 1, 2, 3, 4};
|
||||
|
||||
static void kone_profile_activated(struct kone_device *kone, uint new_profile)
|
||||
{
|
||||
kone->actual_profile = new_profile;
|
||||
kone->actual_dpi = kone->profiles[new_profile - 1].startup_dpi;
|
||||
}
|
||||
|
||||
static void kone_profile_report(struct kone_device *kone, uint new_profile)
|
||||
{
|
||||
struct kone_roccat_report roccat_report;
|
||||
roccat_report.event = kone_mouse_event_switch_profile;
|
||||
roccat_report.value = new_profile;
|
||||
roccat_report.key = 0;
|
||||
roccat_report_event(kone->chrdev_minor, (uint8_t *)&roccat_report);
|
||||
}
|
||||
|
||||
static int kone_receive(struct usb_device *usb_dev, uint usb_command,
|
||||
void *data, uint size)
|
||||
{
|
||||
@ -283,7 +298,7 @@ static ssize_t kone_sysfs_write_settings(struct file *fp, struct kobject *kobj,
|
||||
container_of(kobj, struct device, kobj)->parent->parent;
|
||||
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
|
||||
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
|
||||
int retval = 0, difference;
|
||||
int retval = 0, difference, old_profile;
|
||||
|
||||
/* I need to get my data in one piece */
|
||||
if (off != 0 || count != sizeof(struct kone_settings))
|
||||
@ -294,22 +309,21 @@ static ssize_t kone_sysfs_write_settings(struct file *fp, struct kobject *kobj,
|
||||
if (difference) {
|
||||
retval = kone_set_settings(usb_dev,
|
||||
(struct kone_settings const *)buf);
|
||||
if (!retval)
|
||||
memcpy(&kone->settings, buf,
|
||||
sizeof(struct kone_settings));
|
||||
if (retval) {
|
||||
mutex_unlock(&kone->kone_lock);
|
||||
return retval;
|
||||
}
|
||||
|
||||
old_profile = kone->settings.startup_profile;
|
||||
memcpy(&kone->settings, buf, sizeof(struct kone_settings));
|
||||
|
||||
kone_profile_activated(kone, kone->settings.startup_profile);
|
||||
|
||||
if (kone->settings.startup_profile != old_profile)
|
||||
kone_profile_report(kone, kone->settings.startup_profile);
|
||||
}
|
||||
mutex_unlock(&kone->kone_lock);
|
||||
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
/*
|
||||
* If we get here, treat settings as okay and update actual values
|
||||
* according to startup_profile
|
||||
*/
|
||||
kone->actual_profile = kone->settings.startup_profile;
|
||||
kone->actual_dpi = kone->profiles[kone->actual_profile - 1].startup_dpi;
|
||||
|
||||
return sizeof(struct kone_settings);
|
||||
}
|
||||
|
||||
@ -501,6 +515,8 @@ static ssize_t kone_sysfs_set_tcu(struct device *dev,
|
||||
goto exit_no_settings;
|
||||
goto exit_unlock;
|
||||
}
|
||||
/* calibration resets profile */
|
||||
kone_profile_activated(kone, kone->settings.startup_profile);
|
||||
}
|
||||
|
||||
retval = size;
|
||||
@ -544,16 +560,16 @@ static ssize_t kone_sysfs_set_startup_profile(struct device *dev,
|
||||
kone_set_settings_checksum(&kone->settings);
|
||||
|
||||
retval = kone_set_settings(usb_dev, &kone->settings);
|
||||
|
||||
if (retval) {
|
||||
mutex_unlock(&kone->kone_lock);
|
||||
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* changing the startup profile immediately activates this profile */
|
||||
kone->actual_profile = new_startup_profile;
|
||||
kone->actual_dpi = kone->profiles[kone->actual_profile - 1].startup_dpi;
|
||||
kone_profile_activated(kone, new_startup_profile);
|
||||
kone_profile_report(kone, new_startup_profile);
|
||||
|
||||
mutex_unlock(&kone->kone_lock);
|
||||
return size;
|
||||
}
|
||||
|
||||
@ -665,8 +681,7 @@ static int kone_init_kone_device_struct(struct usb_device *usb_dev,
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
kone->actual_profile = kone->settings.startup_profile;
|
||||
kone->actual_dpi = kone->profiles[kone->actual_profile].startup_dpi;
|
||||
kone_profile_activated(kone, kone->settings.startup_profile);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -776,10 +791,10 @@ static void kone_keep_values_up_to_date(struct kone_device *kone,
|
||||
{
|
||||
switch (event->event) {
|
||||
case kone_mouse_event_switch_profile:
|
||||
kone->actual_dpi = kone->profiles[event->value - 1].
|
||||
startup_dpi;
|
||||
case kone_mouse_event_osd_profile:
|
||||
kone->actual_profile = event->value;
|
||||
kone->actual_dpi = kone->profiles[kone->actual_profile - 1].
|
||||
startup_dpi;
|
||||
break;
|
||||
case kone_mouse_event_switch_dpi:
|
||||
case kone_mouse_event_osd_dpi:
|
||||
|
@ -323,6 +323,7 @@ static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev,
|
||||
struct usb_device *usb_dev;
|
||||
unsigned long profile;
|
||||
int retval;
|
||||
struct kovaplus_roccat_report roccat_report;
|
||||
|
||||
dev = dev->parent->parent;
|
||||
kovaplus = hid_get_drvdata(dev_get_drvdata(dev));
|
||||
@ -337,10 +338,22 @@ static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev,
|
||||
|
||||
mutex_lock(&kovaplus->kovaplus_lock);
|
||||
retval = kovaplus_set_actual_profile(usb_dev, profile);
|
||||
kovaplus_profile_activated(kovaplus, profile);
|
||||
if (retval) {
|
||||
mutex_unlock(&kovaplus->kovaplus_lock);
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
kovaplus_profile_activated(kovaplus, profile);
|
||||
|
||||
roccat_report.type = KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1;
|
||||
roccat_report.profile = profile + 1;
|
||||
roccat_report.button = 0;
|
||||
roccat_report.data1 = profile + 1;
|
||||
roccat_report.data2 = 0;
|
||||
roccat_report_event(kovaplus->chrdev_minor,
|
||||
(uint8_t const *)&roccat_report);
|
||||
|
||||
mutex_unlock(&kovaplus->kovaplus_lock);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
@ -298,6 +298,7 @@ static ssize_t pyra_sysfs_write_settings(struct file *fp,
|
||||
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
|
||||
int retval = 0;
|
||||
int difference;
|
||||
struct pyra_roccat_report roccat_report;
|
||||
|
||||
if (off != 0 || count != sizeof(struct pyra_settings))
|
||||
return -EINVAL;
|
||||
@ -307,17 +308,23 @@ static ssize_t pyra_sysfs_write_settings(struct file *fp,
|
||||
if (difference) {
|
||||
retval = pyra_set_settings(usb_dev,
|
||||
(struct pyra_settings const *)buf);
|
||||
if (!retval)
|
||||
if (retval) {
|
||||
mutex_unlock(&pyra->pyra_lock);
|
||||
return retval;
|
||||
}
|
||||
|
||||
memcpy(&pyra->settings, buf,
|
||||
sizeof(struct pyra_settings));
|
||||
}
|
||||
mutex_unlock(&pyra->pyra_lock);
|
||||
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
profile_activated(pyra, pyra->settings.startup_profile);
|
||||
|
||||
roccat_report.type = PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2;
|
||||
roccat_report.value = pyra->settings.startup_profile + 1;
|
||||
roccat_report.key = 0;
|
||||
roccat_report_event(pyra->chrdev_minor,
|
||||
(uint8_t const *)&roccat_report);
|
||||
}
|
||||
mutex_unlock(&pyra->pyra_lock);
|
||||
return sizeof(struct pyra_settings);
|
||||
}
|
||||
|
||||
|
@ -10,15 +10,18 @@
|
||||
* any later version.
|
||||
*/
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include "hid-ids.h"
|
||||
|
||||
#define WIIMOTE_VERSION "0.1"
|
||||
#define WIIMOTE_VERSION "0.2"
|
||||
#define WIIMOTE_NAME "Nintendo Wii Remote"
|
||||
#define WIIMOTE_BUFSIZE 32
|
||||
|
||||
@ -30,12 +33,26 @@ struct wiimote_buf {
|
||||
struct wiimote_state {
|
||||
spinlock_t lock;
|
||||
__u8 flags;
|
||||
__u8 accel_split[2];
|
||||
|
||||
/* synchronous cmd requests */
|
||||
struct mutex sync;
|
||||
struct completion ready;
|
||||
int cmd;
|
||||
__u32 opt;
|
||||
|
||||
/* results of synchronous requests */
|
||||
__u8 cmd_battery;
|
||||
__u8 cmd_err;
|
||||
};
|
||||
|
||||
struct wiimote_data {
|
||||
struct hid_device *hdev;
|
||||
struct input_dev *input;
|
||||
struct led_classdev *leds[4];
|
||||
struct input_dev *accel;
|
||||
struct input_dev *ir;
|
||||
struct power_supply battery;
|
||||
|
||||
spinlock_t qlock;
|
||||
__u8 head;
|
||||
@ -50,19 +67,43 @@ struct wiimote_data {
|
||||
#define WIIPROTO_FLAG_LED2 0x02
|
||||
#define WIIPROTO_FLAG_LED3 0x04
|
||||
#define WIIPROTO_FLAG_LED4 0x08
|
||||
#define WIIPROTO_FLAG_RUMBLE 0x10
|
||||
#define WIIPROTO_FLAG_ACCEL 0x20
|
||||
#define WIIPROTO_FLAG_IR_BASIC 0x40
|
||||
#define WIIPROTO_FLAG_IR_EXT 0x80
|
||||
#define WIIPROTO_FLAG_IR_FULL 0xc0 /* IR_BASIC | IR_EXT */
|
||||
#define WIIPROTO_FLAGS_LEDS (WIIPROTO_FLAG_LED1 | WIIPROTO_FLAG_LED2 | \
|
||||
WIIPROTO_FLAG_LED3 | WIIPROTO_FLAG_LED4)
|
||||
#define WIIPROTO_FLAGS_IR (WIIPROTO_FLAG_IR_BASIC | WIIPROTO_FLAG_IR_EXT | \
|
||||
WIIPROTO_FLAG_IR_FULL)
|
||||
|
||||
/* return flag for led \num */
|
||||
#define WIIPROTO_FLAG_LED(num) (WIIPROTO_FLAG_LED1 << (num - 1))
|
||||
|
||||
enum wiiproto_reqs {
|
||||
WIIPROTO_REQ_NULL = 0x0,
|
||||
WIIPROTO_REQ_RUMBLE = 0x10,
|
||||
WIIPROTO_REQ_LED = 0x11,
|
||||
WIIPROTO_REQ_DRM = 0x12,
|
||||
WIIPROTO_REQ_IR1 = 0x13,
|
||||
WIIPROTO_REQ_SREQ = 0x15,
|
||||
WIIPROTO_REQ_WMEM = 0x16,
|
||||
WIIPROTO_REQ_RMEM = 0x17,
|
||||
WIIPROTO_REQ_IR2 = 0x1a,
|
||||
WIIPROTO_REQ_STATUS = 0x20,
|
||||
WIIPROTO_REQ_DATA = 0x21,
|
||||
WIIPROTO_REQ_RETURN = 0x22,
|
||||
WIIPROTO_REQ_DRM_K = 0x30,
|
||||
WIIPROTO_REQ_DRM_KA = 0x31,
|
||||
WIIPROTO_REQ_DRM_KE = 0x32,
|
||||
WIIPROTO_REQ_DRM_KAI = 0x33,
|
||||
WIIPROTO_REQ_DRM_KEE = 0x34,
|
||||
WIIPROTO_REQ_DRM_KAE = 0x35,
|
||||
WIIPROTO_REQ_DRM_KIE = 0x36,
|
||||
WIIPROTO_REQ_DRM_KAIE = 0x37,
|
||||
WIIPROTO_REQ_DRM_E = 0x3d,
|
||||
WIIPROTO_REQ_DRM_SKAI1 = 0x3e,
|
||||
WIIPROTO_REQ_DRM_SKAI2 = 0x3f,
|
||||
};
|
||||
|
||||
enum wiiproto_keys {
|
||||
@ -94,6 +135,56 @@ static __u16 wiiproto_keymap[] = {
|
||||
BTN_MODE, /* WIIPROTO_KEY_HOME */
|
||||
};
|
||||
|
||||
static enum power_supply_property wiimote_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_CAPACITY
|
||||
};
|
||||
|
||||
/* requires the state.lock spinlock to be held */
|
||||
static inline bool wiimote_cmd_pending(struct wiimote_data *wdata, int cmd,
|
||||
__u32 opt)
|
||||
{
|
||||
return wdata->state.cmd == cmd && wdata->state.opt == opt;
|
||||
}
|
||||
|
||||
/* requires the state.lock spinlock to be held */
|
||||
static inline void wiimote_cmd_complete(struct wiimote_data *wdata)
|
||||
{
|
||||
wdata->state.cmd = WIIPROTO_REQ_NULL;
|
||||
complete(&wdata->state.ready);
|
||||
}
|
||||
|
||||
static inline int wiimote_cmd_acquire(struct wiimote_data *wdata)
|
||||
{
|
||||
return mutex_lock_interruptible(&wdata->state.sync) ? -ERESTARTSYS : 0;
|
||||
}
|
||||
|
||||
/* requires the state.lock spinlock to be held */
|
||||
static inline void wiimote_cmd_set(struct wiimote_data *wdata, int cmd,
|
||||
__u32 opt)
|
||||
{
|
||||
INIT_COMPLETION(wdata->state.ready);
|
||||
wdata->state.cmd = cmd;
|
||||
wdata->state.opt = opt;
|
||||
}
|
||||
|
||||
static inline void wiimote_cmd_release(struct wiimote_data *wdata)
|
||||
{
|
||||
mutex_unlock(&wdata->state.sync);
|
||||
}
|
||||
|
||||
static inline int wiimote_cmd_wait(struct wiimote_data *wdata)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = wait_for_completion_interruptible_timeout(&wdata->state.ready, HZ);
|
||||
if (ret < 0)
|
||||
return -ERESTARTSYS;
|
||||
else if (ret == 0)
|
||||
return -EIO;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t wiimote_hid_send(struct hid_device *hdev, __u8 *buffer,
|
||||
size_t count)
|
||||
{
|
||||
@ -172,6 +263,39 @@ static void wiimote_queue(struct wiimote_data *wdata, const __u8 *buffer,
|
||||
spin_unlock_irqrestore(&wdata->qlock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* This sets the rumble bit on the given output report if rumble is
|
||||
* currently enabled.
|
||||
* \cmd1 must point to the second byte in the output report => &cmd[1]
|
||||
* This must be called on nearly every output report before passing it
|
||||
* into the output queue!
|
||||
*/
|
||||
static inline void wiiproto_keep_rumble(struct wiimote_data *wdata, __u8 *cmd1)
|
||||
{
|
||||
if (wdata->state.flags & WIIPROTO_FLAG_RUMBLE)
|
||||
*cmd1 |= 0x01;
|
||||
}
|
||||
|
||||
static void wiiproto_req_rumble(struct wiimote_data *wdata, __u8 rumble)
|
||||
{
|
||||
__u8 cmd[2];
|
||||
|
||||
rumble = !!rumble;
|
||||
if (rumble == !!(wdata->state.flags & WIIPROTO_FLAG_RUMBLE))
|
||||
return;
|
||||
|
||||
if (rumble)
|
||||
wdata->state.flags |= WIIPROTO_FLAG_RUMBLE;
|
||||
else
|
||||
wdata->state.flags &= ~WIIPROTO_FLAG_RUMBLE;
|
||||
|
||||
cmd[0] = WIIPROTO_REQ_RUMBLE;
|
||||
cmd[1] = 0;
|
||||
|
||||
wiiproto_keep_rumble(wdata, &cmd[1]);
|
||||
wiimote_queue(wdata, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static void wiiproto_req_leds(struct wiimote_data *wdata, int leds)
|
||||
{
|
||||
__u8 cmd[2];
|
||||
@ -193,6 +317,7 @@ static void wiiproto_req_leds(struct wiimote_data *wdata, int leds)
|
||||
if (leds & WIIPROTO_FLAG_LED4)
|
||||
cmd[1] |= 0x80;
|
||||
|
||||
wiiproto_keep_rumble(wdata, &cmd[1]);
|
||||
wiimote_queue(wdata, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
@ -203,7 +328,23 @@ static void wiiproto_req_leds(struct wiimote_data *wdata, int leds)
|
||||
*/
|
||||
static __u8 select_drm(struct wiimote_data *wdata)
|
||||
{
|
||||
__u8 ir = wdata->state.flags & WIIPROTO_FLAGS_IR;
|
||||
|
||||
if (ir == WIIPROTO_FLAG_IR_BASIC) {
|
||||
if (wdata->state.flags & WIIPROTO_FLAG_ACCEL)
|
||||
return WIIPROTO_REQ_DRM_KAIE;
|
||||
else
|
||||
return WIIPROTO_REQ_DRM_KIE;
|
||||
} else if (ir == WIIPROTO_FLAG_IR_EXT) {
|
||||
return WIIPROTO_REQ_DRM_KAI;
|
||||
} else if (ir == WIIPROTO_FLAG_IR_FULL) {
|
||||
return WIIPROTO_REQ_DRM_SKAI1;
|
||||
} else {
|
||||
if (wdata->state.flags & WIIPROTO_FLAG_ACCEL)
|
||||
return WIIPROTO_REQ_DRM_KA;
|
||||
else
|
||||
return WIIPROTO_REQ_DRM_K;
|
||||
}
|
||||
}
|
||||
|
||||
static void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm)
|
||||
@ -217,9 +358,256 @@ static void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm)
|
||||
cmd[1] = 0;
|
||||
cmd[2] = drm;
|
||||
|
||||
wiiproto_keep_rumble(wdata, &cmd[1]);
|
||||
wiimote_queue(wdata, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static void wiiproto_req_status(struct wiimote_data *wdata)
|
||||
{
|
||||
__u8 cmd[2];
|
||||
|
||||
cmd[0] = WIIPROTO_REQ_SREQ;
|
||||
cmd[1] = 0;
|
||||
|
||||
wiiproto_keep_rumble(wdata, &cmd[1]);
|
||||
wiimote_queue(wdata, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static void wiiproto_req_accel(struct wiimote_data *wdata, __u8 accel)
|
||||
{
|
||||
accel = !!accel;
|
||||
if (accel == !!(wdata->state.flags & WIIPROTO_FLAG_ACCEL))
|
||||
return;
|
||||
|
||||
if (accel)
|
||||
wdata->state.flags |= WIIPROTO_FLAG_ACCEL;
|
||||
else
|
||||
wdata->state.flags &= ~WIIPROTO_FLAG_ACCEL;
|
||||
|
||||
wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
|
||||
}
|
||||
|
||||
static void wiiproto_req_ir1(struct wiimote_data *wdata, __u8 flags)
|
||||
{
|
||||
__u8 cmd[2];
|
||||
|
||||
cmd[0] = WIIPROTO_REQ_IR1;
|
||||
cmd[1] = flags;
|
||||
|
||||
wiiproto_keep_rumble(wdata, &cmd[1]);
|
||||
wiimote_queue(wdata, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
static void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags)
|
||||
{
|
||||
__u8 cmd[2];
|
||||
|
||||
cmd[0] = WIIPROTO_REQ_IR2;
|
||||
cmd[1] = flags;
|
||||
|
||||
wiiproto_keep_rumble(wdata, &cmd[1]);
|
||||
wiimote_queue(wdata, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
#define wiiproto_req_wreg(wdata, os, buf, sz) \
|
||||
wiiproto_req_wmem((wdata), false, (os), (buf), (sz))
|
||||
|
||||
#define wiiproto_req_weeprom(wdata, os, buf, sz) \
|
||||
wiiproto_req_wmem((wdata), true, (os), (buf), (sz))
|
||||
|
||||
static void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom,
|
||||
__u32 offset, const __u8 *buf, __u8 size)
|
||||
{
|
||||
__u8 cmd[22];
|
||||
|
||||
if (size > 16 || size == 0) {
|
||||
hid_warn(wdata->hdev, "Invalid length %d wmem request\n", size);
|
||||
return;
|
||||
}
|
||||
|
||||
memset(cmd, 0, sizeof(cmd));
|
||||
cmd[0] = WIIPROTO_REQ_WMEM;
|
||||
cmd[2] = (offset >> 16) & 0xff;
|
||||
cmd[3] = (offset >> 8) & 0xff;
|
||||
cmd[4] = offset & 0xff;
|
||||
cmd[5] = size;
|
||||
memcpy(&cmd[6], buf, size);
|
||||
|
||||
if (!eeprom)
|
||||
cmd[1] |= 0x04;
|
||||
|
||||
wiiproto_keep_rumble(wdata, &cmd[1]);
|
||||
wiimote_queue(wdata, cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
/* requries the cmd-mutex to be held */
|
||||
static int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset,
|
||||
const __u8 *wmem, __u8 size)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&wdata->state.lock, flags);
|
||||
wiimote_cmd_set(wdata, WIIPROTO_REQ_WMEM, 0);
|
||||
wiiproto_req_wreg(wdata, offset, wmem, size);
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
|
||||
ret = wiimote_cmd_wait(wdata);
|
||||
if (!ret && wdata->state.cmd_err)
|
||||
ret = -EIO;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wiimote_battery_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct wiimote_data *wdata = container_of(psy,
|
||||
struct wiimote_data, battery);
|
||||
int ret = 0, state;
|
||||
unsigned long flags;
|
||||
|
||||
ret = wiimote_cmd_acquire(wdata);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spin_lock_irqsave(&wdata->state.lock, flags);
|
||||
wiimote_cmd_set(wdata, WIIPROTO_REQ_SREQ, 0);
|
||||
wiiproto_req_status(wdata);
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
|
||||
ret = wiimote_cmd_wait(wdata);
|
||||
state = wdata->state.cmd_battery;
|
||||
wiimote_cmd_release(wdata);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
val->intval = state * 100 / 255;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wiimote_init_ir(struct wiimote_data *wdata, __u16 mode)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
__u8 format = 0;
|
||||
static const __u8 data_enable[] = { 0x01 };
|
||||
static const __u8 data_sens1[] = { 0x02, 0x00, 0x00, 0x71, 0x01,
|
||||
0x00, 0xaa, 0x00, 0x64 };
|
||||
static const __u8 data_sens2[] = { 0x63, 0x03 };
|
||||
static const __u8 data_fin[] = { 0x08 };
|
||||
|
||||
spin_lock_irqsave(&wdata->state.lock, flags);
|
||||
|
||||
if (mode == (wdata->state.flags & WIIPROTO_FLAGS_IR)) {
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mode == 0) {
|
||||
wdata->state.flags &= ~WIIPROTO_FLAGS_IR;
|
||||
wiiproto_req_ir1(wdata, 0);
|
||||
wiiproto_req_ir2(wdata, 0);
|
||||
wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
|
||||
ret = wiimote_cmd_acquire(wdata);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* send PIXEL CLOCK ENABLE cmd first */
|
||||
spin_lock_irqsave(&wdata->state.lock, flags);
|
||||
wiimote_cmd_set(wdata, WIIPROTO_REQ_IR1, 0);
|
||||
wiiproto_req_ir1(wdata, 0x06);
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
|
||||
ret = wiimote_cmd_wait(wdata);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
if (wdata->state.cmd_err) {
|
||||
ret = -EIO;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* enable IR LOGIC */
|
||||
spin_lock_irqsave(&wdata->state.lock, flags);
|
||||
wiimote_cmd_set(wdata, WIIPROTO_REQ_IR2, 0);
|
||||
wiiproto_req_ir2(wdata, 0x06);
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
|
||||
ret = wiimote_cmd_wait(wdata);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
if (wdata->state.cmd_err) {
|
||||
ret = -EIO;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* enable IR cam but do not make it send data, yet */
|
||||
ret = wiimote_cmd_write(wdata, 0xb00030, data_enable,
|
||||
sizeof(data_enable));
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
/* write first sensitivity block */
|
||||
ret = wiimote_cmd_write(wdata, 0xb00000, data_sens1,
|
||||
sizeof(data_sens1));
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
/* write second sensitivity block */
|
||||
ret = wiimote_cmd_write(wdata, 0xb0001a, data_sens2,
|
||||
sizeof(data_sens2));
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
/* put IR cam into desired state */
|
||||
switch (mode) {
|
||||
case WIIPROTO_FLAG_IR_FULL:
|
||||
format = 5;
|
||||
break;
|
||||
case WIIPROTO_FLAG_IR_EXT:
|
||||
format = 3;
|
||||
break;
|
||||
case WIIPROTO_FLAG_IR_BASIC:
|
||||
format = 1;
|
||||
break;
|
||||
}
|
||||
ret = wiimote_cmd_write(wdata, 0xb00033, &format, sizeof(format));
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
/* make IR cam send data */
|
||||
ret = wiimote_cmd_write(wdata, 0xb00030, data_fin, sizeof(data_fin));
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
/* request new DRM mode compatible to IR mode */
|
||||
spin_lock_irqsave(&wdata->state.lock, flags);
|
||||
wdata->state.flags &= ~WIIPROTO_FLAGS_IR;
|
||||
wdata->state.flags |= mode & WIIPROTO_FLAGS_IR;
|
||||
wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
|
||||
unlock:
|
||||
wiimote_cmd_release(wdata);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum led_brightness wiimote_leds_get(struct led_classdev *led_dev)
|
||||
{
|
||||
struct wiimote_data *wdata;
|
||||
@ -268,9 +656,28 @@ static void wiimote_leds_set(struct led_classdev *led_dev,
|
||||
}
|
||||
}
|
||||
|
||||
static int wiimote_input_event(struct input_dev *dev, unsigned int type,
|
||||
unsigned int code, int value)
|
||||
static int wiimote_ff_play(struct input_dev *dev, void *data,
|
||||
struct ff_effect *eff)
|
||||
{
|
||||
struct wiimote_data *wdata = input_get_drvdata(dev);
|
||||
__u8 value;
|
||||
unsigned long flags;
|
||||
|
||||
/*
|
||||
* The wiimote supports only a single rumble motor so if any magnitude
|
||||
* is set to non-zero then we start the rumble motor. If both are set to
|
||||
* zero, we stop the rumble motor.
|
||||
*/
|
||||
|
||||
if (eff->u.rumble.strong_magnitude || eff->u.rumble.weak_magnitude)
|
||||
value = 1;
|
||||
else
|
||||
value = 0;
|
||||
|
||||
spin_lock_irqsave(&wdata->state.lock, flags);
|
||||
wiiproto_req_rumble(wdata, value);
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -288,6 +695,61 @@ static void wiimote_input_close(struct input_dev *dev)
|
||||
hid_hw_close(wdata->hdev);
|
||||
}
|
||||
|
||||
static int wiimote_accel_open(struct input_dev *dev)
|
||||
{
|
||||
struct wiimote_data *wdata = input_get_drvdata(dev);
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
ret = hid_hw_open(wdata->hdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spin_lock_irqsave(&wdata->state.lock, flags);
|
||||
wiiproto_req_accel(wdata, true);
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wiimote_accel_close(struct input_dev *dev)
|
||||
{
|
||||
struct wiimote_data *wdata = input_get_drvdata(dev);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&wdata->state.lock, flags);
|
||||
wiiproto_req_accel(wdata, false);
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
|
||||
hid_hw_close(wdata->hdev);
|
||||
}
|
||||
|
||||
static int wiimote_ir_open(struct input_dev *dev)
|
||||
{
|
||||
struct wiimote_data *wdata = input_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = hid_hw_open(wdata->hdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = wiimote_init_ir(wdata, WIIPROTO_FLAG_IR_BASIC);
|
||||
if (ret) {
|
||||
hid_hw_close(wdata->hdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wiimote_ir_close(struct input_dev *dev)
|
||||
{
|
||||
struct wiimote_data *wdata = input_get_drvdata(dev);
|
||||
|
||||
wiimote_init_ir(wdata, 0);
|
||||
hid_hw_close(wdata->hdev);
|
||||
}
|
||||
|
||||
static void handler_keys(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
input_report_key(wdata->input, wiiproto_keymap[WIIPROTO_KEY_LEFT],
|
||||
@ -315,12 +777,100 @@ static void handler_keys(struct wiimote_data *wdata, const __u8 *payload)
|
||||
input_sync(wdata->input);
|
||||
}
|
||||
|
||||
static void handler_accel(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
__u16 x, y, z;
|
||||
|
||||
if (!(wdata->state.flags & WIIPROTO_FLAG_ACCEL))
|
||||
return;
|
||||
|
||||
/*
|
||||
* payload is: BB BB XX YY ZZ
|
||||
* Accelerometer data is encoded into 3 10bit values. XX, YY and ZZ
|
||||
* contain the upper 8 bits of each value. The lower 2 bits are
|
||||
* contained in the buttons data BB BB.
|
||||
* Bits 6 and 7 of the first buttons byte BB is the lower 2 bits of the
|
||||
* X accel value. Bit 5 of the second buttons byte is the 2nd bit of Y
|
||||
* accel value and bit 6 is the second bit of the Z value.
|
||||
* The first bit of Y and Z values is not available and always set to 0.
|
||||
* 0x200 is returned on no movement.
|
||||
*/
|
||||
|
||||
x = payload[2] << 2;
|
||||
y = payload[3] << 2;
|
||||
z = payload[4] << 2;
|
||||
|
||||
x |= (payload[0] >> 5) & 0x3;
|
||||
y |= (payload[1] >> 4) & 0x2;
|
||||
z |= (payload[1] >> 5) & 0x2;
|
||||
|
||||
input_report_abs(wdata->accel, ABS_RX, x - 0x200);
|
||||
input_report_abs(wdata->accel, ABS_RY, y - 0x200);
|
||||
input_report_abs(wdata->accel, ABS_RZ, z - 0x200);
|
||||
input_sync(wdata->accel);
|
||||
}
|
||||
|
||||
#define ir_to_input0(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \
|
||||
ABS_HAT0X, ABS_HAT0Y)
|
||||
#define ir_to_input1(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \
|
||||
ABS_HAT1X, ABS_HAT1Y)
|
||||
#define ir_to_input2(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \
|
||||
ABS_HAT2X, ABS_HAT2Y)
|
||||
#define ir_to_input3(wdata, ir, packed) __ir_to_input((wdata), (ir), (packed), \
|
||||
ABS_HAT3X, ABS_HAT3Y)
|
||||
|
||||
static void __ir_to_input(struct wiimote_data *wdata, const __u8 *ir,
|
||||
bool packed, __u8 xid, __u8 yid)
|
||||
{
|
||||
__u16 x, y;
|
||||
|
||||
if (!(wdata->state.flags & WIIPROTO_FLAGS_IR))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Basic IR data is encoded into 3 bytes. The first two bytes are the
|
||||
* upper 8 bit of the X/Y data, the 3rd byte contains the lower 2 bits
|
||||
* of both.
|
||||
* If data is packed, then the 3rd byte is put first and slightly
|
||||
* reordered. This allows to interleave packed and non-packed data to
|
||||
* have two IR sets in 5 bytes instead of 6.
|
||||
* The resulting 10bit X/Y values are passed to the ABS_HATXY input dev.
|
||||
*/
|
||||
|
||||
if (packed) {
|
||||
x = ir[1] << 2;
|
||||
y = ir[2] << 2;
|
||||
|
||||
x |= ir[0] & 0x3;
|
||||
y |= (ir[0] >> 2) & 0x3;
|
||||
} else {
|
||||
x = ir[0] << 2;
|
||||
y = ir[1] << 2;
|
||||
|
||||
x |= (ir[2] >> 4) & 0x3;
|
||||
y |= (ir[2] >> 6) & 0x3;
|
||||
}
|
||||
|
||||
input_report_abs(wdata->ir, xid, x);
|
||||
input_report_abs(wdata->ir, yid, y);
|
||||
}
|
||||
|
||||
static void handler_status(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
handler_keys(wdata, payload);
|
||||
|
||||
/* on status reports the drm is reset so we need to resend the drm */
|
||||
wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
|
||||
|
||||
if (wiimote_cmd_pending(wdata, WIIPROTO_REQ_SREQ, 0)) {
|
||||
wdata->state.cmd_battery = payload[5];
|
||||
wiimote_cmd_complete(wdata);
|
||||
}
|
||||
}
|
||||
|
||||
static void handler_data(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
handler_keys(wdata, payload);
|
||||
}
|
||||
|
||||
static void handler_return(struct wiimote_data *wdata, const __u8 *payload)
|
||||
@ -330,9 +880,105 @@ static void handler_return(struct wiimote_data *wdata, const __u8 *payload)
|
||||
|
||||
handler_keys(wdata, payload);
|
||||
|
||||
if (err)
|
||||
if (wiimote_cmd_pending(wdata, cmd, 0)) {
|
||||
wdata->state.cmd_err = err;
|
||||
wiimote_cmd_complete(wdata);
|
||||
} else if (err) {
|
||||
hid_warn(wdata->hdev, "Remote error %hhu on req %hhu\n", err,
|
||||
cmd);
|
||||
}
|
||||
}
|
||||
|
||||
static void handler_drm_KA(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
handler_keys(wdata, payload);
|
||||
handler_accel(wdata, payload);
|
||||
}
|
||||
|
||||
static void handler_drm_KE(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
handler_keys(wdata, payload);
|
||||
}
|
||||
|
||||
static void handler_drm_KAI(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
handler_keys(wdata, payload);
|
||||
handler_accel(wdata, payload);
|
||||
ir_to_input0(wdata, &payload[5], false);
|
||||
ir_to_input1(wdata, &payload[8], false);
|
||||
ir_to_input2(wdata, &payload[11], false);
|
||||
ir_to_input3(wdata, &payload[14], false);
|
||||
input_sync(wdata->ir);
|
||||
}
|
||||
|
||||
static void handler_drm_KEE(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
handler_keys(wdata, payload);
|
||||
}
|
||||
|
||||
static void handler_drm_KIE(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
handler_keys(wdata, payload);
|
||||
ir_to_input0(wdata, &payload[2], false);
|
||||
ir_to_input1(wdata, &payload[4], true);
|
||||
ir_to_input2(wdata, &payload[7], false);
|
||||
ir_to_input3(wdata, &payload[9], true);
|
||||
input_sync(wdata->ir);
|
||||
}
|
||||
|
||||
static void handler_drm_KAE(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
handler_keys(wdata, payload);
|
||||
handler_accel(wdata, payload);
|
||||
}
|
||||
|
||||
static void handler_drm_KAIE(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
handler_keys(wdata, payload);
|
||||
handler_accel(wdata, payload);
|
||||
ir_to_input0(wdata, &payload[5], false);
|
||||
ir_to_input1(wdata, &payload[7], true);
|
||||
ir_to_input2(wdata, &payload[10], false);
|
||||
ir_to_input3(wdata, &payload[12], true);
|
||||
input_sync(wdata->ir);
|
||||
}
|
||||
|
||||
static void handler_drm_E(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
}
|
||||
|
||||
static void handler_drm_SKAI1(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
handler_keys(wdata, payload);
|
||||
|
||||
wdata->state.accel_split[0] = payload[2];
|
||||
wdata->state.accel_split[1] = (payload[0] >> 1) & (0x10 | 0x20);
|
||||
wdata->state.accel_split[1] |= (payload[1] << 1) & (0x40 | 0x80);
|
||||
|
||||
ir_to_input0(wdata, &payload[3], false);
|
||||
ir_to_input1(wdata, &payload[12], false);
|
||||
input_sync(wdata->ir);
|
||||
}
|
||||
|
||||
static void handler_drm_SKAI2(struct wiimote_data *wdata, const __u8 *payload)
|
||||
{
|
||||
__u8 buf[5];
|
||||
|
||||
handler_keys(wdata, payload);
|
||||
|
||||
wdata->state.accel_split[1] |= (payload[0] >> 5) & (0x01 | 0x02);
|
||||
wdata->state.accel_split[1] |= (payload[1] >> 3) & (0x04 | 0x08);
|
||||
|
||||
buf[0] = 0;
|
||||
buf[1] = 0;
|
||||
buf[2] = wdata->state.accel_split[0];
|
||||
buf[3] = payload[2];
|
||||
buf[4] = wdata->state.accel_split[1];
|
||||
handler_accel(wdata, buf);
|
||||
|
||||
ir_to_input2(wdata, &payload[3], false);
|
||||
ir_to_input3(wdata, &payload[12], false);
|
||||
input_sync(wdata->ir);
|
||||
}
|
||||
|
||||
struct wiiproto_handler {
|
||||
@ -343,8 +989,19 @@ struct wiiproto_handler {
|
||||
|
||||
static struct wiiproto_handler handlers[] = {
|
||||
{ .id = WIIPROTO_REQ_STATUS, .size = 6, .func = handler_status },
|
||||
{ .id = WIIPROTO_REQ_DATA, .size = 21, .func = handler_data },
|
||||
{ .id = WIIPROTO_REQ_RETURN, .size = 4, .func = handler_return },
|
||||
{ .id = WIIPROTO_REQ_DRM_K, .size = 2, .func = handler_keys },
|
||||
{ .id = WIIPROTO_REQ_DRM_KA, .size = 5, .func = handler_drm_KA },
|
||||
{ .id = WIIPROTO_REQ_DRM_KE, .size = 10, .func = handler_drm_KE },
|
||||
{ .id = WIIPROTO_REQ_DRM_KAI, .size = 17, .func = handler_drm_KAI },
|
||||
{ .id = WIIPROTO_REQ_DRM_KEE, .size = 21, .func = handler_drm_KEE },
|
||||
{ .id = WIIPROTO_REQ_DRM_KAE, .size = 21, .func = handler_drm_KAE },
|
||||
{ .id = WIIPROTO_REQ_DRM_KIE, .size = 21, .func = handler_drm_KIE },
|
||||
{ .id = WIIPROTO_REQ_DRM_KAIE, .size = 21, .func = handler_drm_KAIE },
|
||||
{ .id = WIIPROTO_REQ_DRM_E, .size = 21, .func = handler_drm_E },
|
||||
{ .id = WIIPROTO_REQ_DRM_SKAI1, .size = 21, .func = handler_drm_SKAI1 },
|
||||
{ .id = WIIPROTO_REQ_DRM_SKAI2, .size = 21, .func = handler_drm_SKAI2 },
|
||||
{ .id = 0 }
|
||||
};
|
||||
|
||||
@ -355,6 +1012,7 @@ static int wiimote_hid_event(struct hid_device *hdev, struct hid_report *report,
|
||||
struct wiiproto_handler *h;
|
||||
int i;
|
||||
unsigned long flags;
|
||||
bool handled = false;
|
||||
|
||||
if (size < 1)
|
||||
return -EINVAL;
|
||||
@ -363,9 +1021,15 @@ static int wiimote_hid_event(struct hid_device *hdev, struct hid_report *report,
|
||||
|
||||
for (i = 0; handlers[i].id; ++i) {
|
||||
h = &handlers[i];
|
||||
if (h->id == raw_data[0] && h->size < size)
|
||||
if (h->id == raw_data[0] && h->size < size) {
|
||||
h->func(wdata, &raw_data[1]);
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
hid_warn(hdev, "Unhandled report %hhu size %d\n", raw_data[0],
|
||||
size);
|
||||
|
||||
spin_unlock_irqrestore(&wdata->state.lock, flags);
|
||||
|
||||
@ -434,16 +1098,13 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev)
|
||||
return NULL;
|
||||
|
||||
wdata->input = input_allocate_device();
|
||||
if (!wdata->input) {
|
||||
kfree(wdata);
|
||||
return NULL;
|
||||
}
|
||||
if (!wdata->input)
|
||||
goto err;
|
||||
|
||||
wdata->hdev = hdev;
|
||||
hid_set_drvdata(hdev, wdata);
|
||||
|
||||
input_set_drvdata(wdata->input, wdata);
|
||||
wdata->input->event = wiimote_input_event;
|
||||
wdata->input->open = wiimote_input_open;
|
||||
wdata->input->close = wiimote_input_close;
|
||||
wdata->input->dev.parent = &wdata->hdev->dev;
|
||||
@ -457,18 +1118,89 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev)
|
||||
for (i = 0; i < WIIPROTO_KEY_COUNT; ++i)
|
||||
set_bit(wiiproto_keymap[i], wdata->input->keybit);
|
||||
|
||||
set_bit(FF_RUMBLE, wdata->input->ffbit);
|
||||
if (input_ff_create_memless(wdata->input, NULL, wiimote_ff_play))
|
||||
goto err_input;
|
||||
|
||||
wdata->accel = input_allocate_device();
|
||||
if (!wdata->accel)
|
||||
goto err_input;
|
||||
|
||||
input_set_drvdata(wdata->accel, wdata);
|
||||
wdata->accel->open = wiimote_accel_open;
|
||||
wdata->accel->close = wiimote_accel_close;
|
||||
wdata->accel->dev.parent = &wdata->hdev->dev;
|
||||
wdata->accel->id.bustype = wdata->hdev->bus;
|
||||
wdata->accel->id.vendor = wdata->hdev->vendor;
|
||||
wdata->accel->id.product = wdata->hdev->product;
|
||||
wdata->accel->id.version = wdata->hdev->version;
|
||||
wdata->accel->name = WIIMOTE_NAME " Accelerometer";
|
||||
|
||||
set_bit(EV_ABS, wdata->accel->evbit);
|
||||
set_bit(ABS_RX, wdata->accel->absbit);
|
||||
set_bit(ABS_RY, wdata->accel->absbit);
|
||||
set_bit(ABS_RZ, wdata->accel->absbit);
|
||||
input_set_abs_params(wdata->accel, ABS_RX, -500, 500, 2, 4);
|
||||
input_set_abs_params(wdata->accel, ABS_RY, -500, 500, 2, 4);
|
||||
input_set_abs_params(wdata->accel, ABS_RZ, -500, 500, 2, 4);
|
||||
|
||||
wdata->ir = input_allocate_device();
|
||||
if (!wdata->ir)
|
||||
goto err_ir;
|
||||
|
||||
input_set_drvdata(wdata->ir, wdata);
|
||||
wdata->ir->open = wiimote_ir_open;
|
||||
wdata->ir->close = wiimote_ir_close;
|
||||
wdata->ir->dev.parent = &wdata->hdev->dev;
|
||||
wdata->ir->id.bustype = wdata->hdev->bus;
|
||||
wdata->ir->id.vendor = wdata->hdev->vendor;
|
||||
wdata->ir->id.product = wdata->hdev->product;
|
||||
wdata->ir->id.version = wdata->hdev->version;
|
||||
wdata->ir->name = WIIMOTE_NAME " IR";
|
||||
|
||||
set_bit(EV_ABS, wdata->ir->evbit);
|
||||
set_bit(ABS_HAT0X, wdata->ir->absbit);
|
||||
set_bit(ABS_HAT0Y, wdata->ir->absbit);
|
||||
set_bit(ABS_HAT1X, wdata->ir->absbit);
|
||||
set_bit(ABS_HAT1Y, wdata->ir->absbit);
|
||||
set_bit(ABS_HAT2X, wdata->ir->absbit);
|
||||
set_bit(ABS_HAT2Y, wdata->ir->absbit);
|
||||
set_bit(ABS_HAT3X, wdata->ir->absbit);
|
||||
set_bit(ABS_HAT3Y, wdata->ir->absbit);
|
||||
input_set_abs_params(wdata->ir, ABS_HAT0X, 0, 1023, 2, 4);
|
||||
input_set_abs_params(wdata->ir, ABS_HAT0Y, 0, 767, 2, 4);
|
||||
input_set_abs_params(wdata->ir, ABS_HAT1X, 0, 1023, 2, 4);
|
||||
input_set_abs_params(wdata->ir, ABS_HAT1Y, 0, 767, 2, 4);
|
||||
input_set_abs_params(wdata->ir, ABS_HAT2X, 0, 1023, 2, 4);
|
||||
input_set_abs_params(wdata->ir, ABS_HAT2Y, 0, 767, 2, 4);
|
||||
input_set_abs_params(wdata->ir, ABS_HAT3X, 0, 1023, 2, 4);
|
||||
input_set_abs_params(wdata->ir, ABS_HAT3Y, 0, 767, 2, 4);
|
||||
|
||||
spin_lock_init(&wdata->qlock);
|
||||
INIT_WORK(&wdata->worker, wiimote_worker);
|
||||
|
||||
spin_lock_init(&wdata->state.lock);
|
||||
init_completion(&wdata->state.ready);
|
||||
mutex_init(&wdata->state.sync);
|
||||
|
||||
return wdata;
|
||||
|
||||
err_ir:
|
||||
input_free_device(wdata->accel);
|
||||
err_input:
|
||||
input_free_device(wdata->input);
|
||||
err:
|
||||
kfree(wdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void wiimote_destroy(struct wiimote_data *wdata)
|
||||
{
|
||||
wiimote_leds_destroy(wdata);
|
||||
|
||||
power_supply_unregister(&wdata->battery);
|
||||
input_unregister_device(wdata->accel);
|
||||
input_unregister_device(wdata->ir);
|
||||
input_unregister_device(wdata->input);
|
||||
cancel_work_sync(&wdata->worker);
|
||||
hid_hw_stop(wdata->hdev);
|
||||
@ -500,12 +1232,37 @@ static int wiimote_hid_probe(struct hid_device *hdev,
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = input_register_device(wdata->input);
|
||||
ret = input_register_device(wdata->accel);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Cannot register input device\n");
|
||||
goto err_stop;
|
||||
}
|
||||
|
||||
ret = input_register_device(wdata->ir);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Cannot register input device\n");
|
||||
goto err_ir;
|
||||
}
|
||||
|
||||
ret = input_register_device(wdata->input);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Cannot register input device\n");
|
||||
goto err_input;
|
||||
}
|
||||
|
||||
wdata->battery.properties = wiimote_battery_props;
|
||||
wdata->battery.num_properties = ARRAY_SIZE(wiimote_battery_props);
|
||||
wdata->battery.get_property = wiimote_battery_get_property;
|
||||
wdata->battery.name = "wiimote_battery";
|
||||
wdata->battery.type = POWER_SUPPLY_TYPE_BATTERY;
|
||||
wdata->battery.use_for_apm = 0;
|
||||
|
||||
ret = power_supply_register(&wdata->hdev->dev, &wdata->battery);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Cannot register battery device\n");
|
||||
goto err_battery;
|
||||
}
|
||||
|
||||
ret = wiimote_leds_create(wdata);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
@ -523,9 +1280,20 @@ err_free:
|
||||
wiimote_destroy(wdata);
|
||||
return ret;
|
||||
|
||||
err_battery:
|
||||
input_unregister_device(wdata->input);
|
||||
wdata->input = NULL;
|
||||
err_input:
|
||||
input_unregister_device(wdata->ir);
|
||||
wdata->ir = NULL;
|
||||
err_ir:
|
||||
input_unregister_device(wdata->accel);
|
||||
wdata->accel = NULL;
|
||||
err_stop:
|
||||
hid_hw_stop(hdev);
|
||||
err:
|
||||
input_free_device(wdata->ir);
|
||||
input_free_device(wdata->accel);
|
||||
input_free_device(wdata->input);
|
||||
kfree(wdata);
|
||||
return ret;
|
||||
|
@ -312,6 +312,7 @@ struct hid_item {
|
||||
#define HID_QUIRK_BADPAD 0x00000020
|
||||
#define HID_QUIRK_MULTI_INPUT 0x00000040
|
||||
#define HID_QUIRK_HIDINPUT_FORCE 0x00000080
|
||||
#define HID_QUIRK_MULTITOUCH 0x00000100
|
||||
#define HID_QUIRK_SKIP_OUTPUT_REPORTS 0x00010000
|
||||
#define HID_QUIRK_FULLSPEED_INTERVAL 0x10000000
|
||||
#define HID_QUIRK_NO_INIT_REPORTS 0x20000000
|
||||
|
Loading…
Reference in New Issue
Block a user