diff options
author | Harald Brinkmann <hbrinkmann@braincalibration.de> | 2014-05-20 20:28:00 +0200 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2014-05-20 21:28:42 +0200 |
commit | 37c492c8f6a99870551fe2964b2dfce5d9e87375 (patch) | |
tree | ebd0971a7181fbb0d7cdb03f5cabb613d7b86fca /drivers/hid/hid-saitek.c | |
parent | f362e690e5530dd8ace289da991bda558731678e (diff) | |
download | lwn-37c492c8f6a99870551fe2964b2dfce5d9e87375.tar.gz lwn-37c492c8f6a99870551fe2964b2dfce5d9e87375.zip |
HID: quirk for Saitek RAT7 and MMO7 mices' mode button
Some saitek mice implement a tristate button (for switching button mappings in
the original driver) by keeping one of three (non-physical)
buttons constantly pressed.
This breaks X and probably other userspace software.
This patch implements a quirk for the R.A.T.7 and M.M.O.7, tracking the mode
and generating presses of a single button if it changes. Also the missing
release event is generated instantly.
Signed-off-by: Harald Brinkmann <hbrinkmann@braincalibration.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid/hid-saitek.c')
-rw-r--r-- | drivers/hid/hid-saitek.c | 154 |
1 files changed, 147 insertions, 7 deletions
diff --git a/drivers/hid/hid-saitek.c b/drivers/hid/hid-saitek.c index 37961c7e397d..69cca1476a0c 100644 --- a/drivers/hid/hid-saitek.c +++ b/drivers/hid/hid-saitek.c @@ -1,10 +1,17 @@ /* - * HID driver for Saitek devices, currently only the PS1000 (USB gamepad). + * HID driver for Saitek devices. + * + * PS1000 (USB gamepad): * Fixes the HID report descriptor by removing a non-existent axis and * clearing the constant bit on the input reports for buttons and d-pad. * (This module is based on "hid-ortek".) - * * Copyright (c) 2012 Andreas Hübner + * + * R.A.T.7, M.M.O.7 (USB gaming mice): + * Fixes the mode button which cycles through three constantly pressed + * buttons. All three press events are mapped to one button and the + * missing release event is generated immediately. + * */ /* @@ -21,12 +28,57 @@ #include "hid-ids.h" +#define SAITEK_FIX_PS1000 0x0001 +#define SAITEK_RELEASE_MODE_RAT7 0x0002 +#define SAITEK_RELEASE_MODE_MMO7 0x0004 + +struct saitek_sc { + unsigned long quirks; + int mode; +}; + +static int saitek_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + unsigned long quirks = id->driver_data; + struct saitek_sc *ssc; + int ret; + + ssc = devm_kzalloc(&hdev->dev, sizeof(*ssc), GFP_KERNEL); + if (ssc == NULL) { + hid_err(hdev, "can't alloc saitek descriptor\n"); + return -ENOMEM; + } + + ssc->quirks = quirks; + ssc->mode = -1; + + hid_set_drvdata(hdev, ssc); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "hw start failed\n"); + return ret; + } + + return 0; +} + static __u8 *saitek_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { - if (*rsize == 137 && rdesc[20] == 0x09 && rdesc[21] == 0x33 - && rdesc[94] == 0x81 && rdesc[95] == 0x03 - && rdesc[110] == 0x81 && rdesc[111] == 0x03) { + struct saitek_sc *ssc = hid_get_drvdata(hdev); + + if ((ssc->quirks & SAITEK_FIX_PS1000) && *rsize == 137 && + rdesc[20] == 0x09 && rdesc[21] == 0x33 && + rdesc[94] == 0x81 && rdesc[95] == 0x03 && + rdesc[110] == 0x81 && rdesc[111] == 0x03) { hid_info(hdev, "Fixing up Saitek PS1000 report descriptor\n"); @@ -42,8 +94,93 @@ static __u8 *saitek_report_fixup(struct hid_device *hdev, __u8 *rdesc, return rdesc; } +static int saitek_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *raw_data, int size) +{ + struct saitek_sc *ssc = hid_get_drvdata(hdev); + + if (ssc->quirks & SAITEK_RELEASE_MODE_RAT7 && size == 7) { + /* R.A.T.7 uses bits 13, 14, 15 for the mode */ + int mode = -1; + if (raw_data[1] & 0x01) + mode = 0; + else if (raw_data[1] & 0x02) + mode = 1; + else if (raw_data[1] & 0x04) + mode = 2; + + /* clear mode bits */ + raw_data[1] &= ~0x07; + + if (mode != ssc->mode) { + hid_dbg(hdev, "entered mode %d\n", mode); + if (ssc->mode != -1) { + /* use bit 13 as the mode button */ + raw_data[1] |= 0x04; + } + ssc->mode = mode; + } + } else if (ssc->quirks & SAITEK_RELEASE_MODE_MMO7 && size == 8) { + + /* M.M.O.7 uses bits 8, 22, 23 for the mode */ + int mode = -1; + if (raw_data[1] & 0x80) + mode = 0; + else if (raw_data[2] & 0x01) + mode = 1; + else if (raw_data[2] & 0x02) + mode = 2; + + /* clear mode bits */ + raw_data[1] &= ~0x80; + raw_data[2] &= ~0x03; + + if (mode != ssc->mode) { + hid_dbg(hdev, "entered mode %d\n", mode); + if (ssc->mode != -1) { + /* use bit 8 as the mode button, bits 22 + * and 23 do not represent buttons + * according to the HID report descriptor + */ + raw_data[1] |= 0x80; + } + ssc->mode = mode; + } + } + + return 0; +} + +static int saitek_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + struct saitek_sc *ssc = hid_get_drvdata(hdev); + struct input_dev *input = field->hidinput->input; + + if (usage->type == EV_KEY && value && + (((ssc->quirks & SAITEK_RELEASE_MODE_RAT7) && + usage->code - BTN_MOUSE == 10) || + ((ssc->quirks & SAITEK_RELEASE_MODE_MMO7) && + usage->code - BTN_MOUSE == 15))) { + + input_report_key(input, usage->code, 1); + + /* report missing release event */ + input_report_key(input, usage->code, 0); + + return 1; + } + + return 0; +} + static const struct hid_device_id saitek_devices[] = { - { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000)}, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000), + .driver_data = SAITEK_FIX_PS1000 }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7), + .driver_data = SAITEK_RELEASE_MODE_RAT7 }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_MMO7), + .driver_data = SAITEK_RELEASE_MODE_MMO7 }, { } }; @@ -52,7 +189,10 @@ MODULE_DEVICE_TABLE(hid, saitek_devices); static struct hid_driver saitek_driver = { .name = "saitek", .id_table = saitek_devices, - .report_fixup = saitek_report_fixup + .probe = saitek_probe, + .report_fixup = saitek_report_fixup, + .raw_event = saitek_raw_event, + .event = saitek_event, }; module_hid_driver(saitek_driver); |