Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 37c492c8 authored by Harald Brinkmann's avatar Harald Brinkmann Committed by Jiri Kosina
Browse files

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: default avatarHarald Brinkmann <hbrinkmann@braincalibration.de>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent f362e690
Loading
Loading
Loading
Loading
+4 −1
Original line number Original line Diff line number Diff line
@@ -608,7 +608,10 @@ config HID_SAITEK
	Support for Saitek devices that are not fully compliant with the
	Support for Saitek devices that are not fully compliant with the
	HID standard.
	HID standard.


	Currently only supports the PS1000 controller.
	Supported devices:
	- PS1000 Dual Analog Pad
	- R.A.T.7 Gaming Mouse
	- M.M.O.7 Gaming Mouse


config HID_SAMSUNG
config HID_SAMSUNG
	tristate "Samsung InfraRed remote control or keyboards"
	tristate "Samsung InfraRed remote control or keyboards"
+4 −0
Original line number Original line Diff line number Diff line
@@ -1867,7 +1867,11 @@ static const struct hid_device_id hid_have_special_driver[] = {
	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_RYOS_MK_PRO) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_RYOS_MK_PRO) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) },
#endif
#endif
#if IS_ENABLED(CONFIG_HID_SAITEK)
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_MMO7) },
#endif
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
+2 −0
Original line number Original line Diff line number Diff line
@@ -761,6 +761,8 @@
#define USB_VENDOR_ID_SAITEK		0x06a3
#define USB_VENDOR_ID_SAITEK		0x06a3
#define USB_DEVICE_ID_SAITEK_RUMBLEPAD	0xff17
#define USB_DEVICE_ID_SAITEK_RUMBLEPAD	0xff17
#define USB_DEVICE_ID_SAITEK_PS1000	0x0621
#define USB_DEVICE_ID_SAITEK_PS1000	0x0621
#define USB_DEVICE_ID_SAITEK_RAT7	0x0cd7
#define USB_DEVICE_ID_SAITEK_MMO7	0x0cd0


#define USB_VENDOR_ID_SAMSUNG		0x0419
#define USB_VENDOR_ID_SAMSUNG		0x0419
#define USB_DEVICE_ID_SAMSUNG_IR_REMOTE	0x0001
#define USB_DEVICE_ID_SAMSUNG_IR_REMOTE	0x0001
+147 −7
Original line number Original line Diff line number Diff line
/*
/*
 *  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
 *  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.
 *  clearing the constant bit on the input reports for buttons and d-pad.
 *  (This module is based on "hid-ortek".)
 *  (This module is based on "hid-ortek".)
 *
 *  Copyright (c) 2012 Andreas Hübner
 *  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"
#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,
static __u8 *saitek_report_fixup(struct hid_device *hdev, __u8 *rdesc,
		unsigned int *rsize)
		unsigned int *rsize)
{
{
	if (*rsize == 137 && rdesc[20] == 0x09 && rdesc[21] == 0x33
	struct saitek_sc *ssc = hid_get_drvdata(hdev);
			&& rdesc[94] == 0x81 && rdesc[95] == 0x03

			&& rdesc[110] == 0x81 && rdesc[111] == 0x03) {
	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");
		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;
	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[] = {
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 = {
static struct hid_driver saitek_driver = {
	.name = "saitek",
	.name = "saitek",
	.id_table = saitek_devices,
	.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);
module_hid_driver(saitek_driver);