Add kernel driver for Saitek X65F joystick

This commit adds the kernel driver for the Saitek X65F joystick. This is
necessary, since it has the same limitation with the thumbstick not
getting recognized on older kernels. The quirks fix has been pushed to
newer stable kernels, and therefore do not require this driver.
reverse-scroll
nirenjan 2021-09-29 12:59:34 -07:00
parent 9a39e971f1
commit 247e98c5dc
3 changed files with 194 additions and 1 deletions

View File

@ -1,5 +1,6 @@
obj-m := saitek_x52.o
obj-m := saitek_x52.o saitek_x65.o
saitek_x52-objs := hid-saitek-x52.o
saitek_x65-objs := hid-saitek-x65.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

View File

@ -35,3 +35,18 @@ automatically. Otherwise, simply disconnect and reconnect your X52.
# Reporting issues
Please report any issues seen as a [Github issue](https://github.com/nirenjan/libx52/issues).
# Notes
This folder also includes a driver for the X65F HOTAS, but it is untested as of
this writing. The same build instructions apply, but you will need to run `sudo
insmod saitek_x65.ko` instead.
As with the X52 driver, the following kernel versions have a fix for the thumb
stick, and do not require this custom driver.
* 5.13+ (from -rc3 onwards)
* 5.12.12+
* 5.10.45+
* 5.4.127+
* 4.9.196+

View File

@ -0,0 +1,177 @@
/*
* HID driver for Saitek X65 HOTAS
*
* Copyright (c) 2021 Nirenjan Krishnan
*
* SPDX-License-Identifier: GPL-2.0-only
*/
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/bits.h>
#define VENDOR_SAITEK 0x06a3
#define DEV_X65F 0x0b6a
/* X65 Report format assuming the below is one long little-endian integer
*
* |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|
* Rz | Y | X |
* |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|
* Slider | Ry | Rx | Z |
* |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|
* Buttons 1-31 |
* |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|
* | |MouseY |MouseX | Hat | Buttons 32-50
* |-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|
*/
static void _parse_axis_report(struct input_dev *input_dev, u8 *data)
{
static const s32 hat_to_axis[16][2] = {
{0, 0},
{0, -1},
{1, -1},
{1, 0},
{1, 1},
{0, 1},
{-1, 1},
{-1, 0},
{-1, -1},
};
u8 hat = (data[14] >> 3) & 0x0f;
u32 axis = (data[2] << 16) |
(data[1] << 8) |
data[0];
input_report_abs(input_dev, ABS_X, (axis & 0xfff));
input_report_abs(input_dev, ABS_Y, ((axis >> 12) & 0xfff));
input_report_abs(input_dev, ABS_RZ, ((data[4] & 0x1) << 8) | data[3]);
input_report_abs(input_dev, ABS_Z, ((data[5] & 0x1) << 8) | data[4]);
input_report_abs(input_dev, ABS_RX, ((data[6] & 0x1) << 8) | data[5]);
input_report_abs(input_dev, ABS_RY, ((data[7] & 0x1) << 8) | data[6]);
input_report_abs(input_dev, ABS_THROTTLE, ((data[8] & 0x1) << 8) | data[7]);
input_report_abs(input_dev, ABS_MISC, ((data[15] & 0x7) << 1) | ((data[14] & 0x80) >> 7));
input_report_abs(input_dev, ABS_MISC + 1, (data[15] >> 3) & 0xf);
input_report_abs(input_dev, ABS_HAT0X, hat_to_axis[hat][0]);
input_report_abs(input_dev, ABS_HAT0Y, hat_to_axis[hat][1]);
}
static void _parse_button_report(struct input_dev *input_dev, u8 *data)
{
u64 buttons;
int report_button;
int i;
buttons = ((u64)data[14] << 47) |
((u64)data[13] << 39) |
((u64)data[12] << 31) |
((u64)data[11] << 23) |
((u64)data[10] << 15) |
((u64)data[9] << 7) |
((u64)data[8] >> 1);
for (i = 0; i < 50; i++) {
if (i <= 0xf) {
report_button = BTN_JOYSTICK + i;
} else {
report_button = BTN_TRIGGER_HAPPY + i - 0x10;
}
input_report_key(input_dev, report_button, !!(buttons & (1UL << i)));
}
}
static int x65_raw_event(struct hid_device *dev,
struct hid_report *report, u8 *data, int len)
{
struct input_dev *input_dev = hid_get_drvdata(dev);
_parse_axis_report(input_dev, data);
_parse_button_report(input_dev, data);
input_sync(input_dev);
return 0;
}
static int x65_input_configured(struct hid_device *dev,
struct hid_input *input)
{
struct input_dev *input_dev = input->input;
int i;
hid_set_drvdata(dev, input_dev);
set_bit(EV_KEY, input_dev->evbit);
set_bit(EV_ABS, input_dev->evbit);
for (i = 0; i < 0x10; i++) {
set_bit(BTN_JOYSTICK + i, input_dev->keybit);
}
for (i = 0x10; i < 50; i++) {
set_bit(BTN_TRIGGER_HAPPY + i - 0x10, input_dev->keybit);
}
set_bit(ABS_X, input_dev->absbit);
set_bit(ABS_Y, input_dev->absbit);
set_bit(ABS_Z, input_dev->absbit);
set_bit(ABS_RX, input_dev->absbit);
set_bit(ABS_RY, input_dev->absbit);
set_bit(ABS_RZ, input_dev->absbit);
set_bit(ABS_THROTTLE, input_dev->absbit);
set_bit(ABS_HAT0X, input_dev->absbit);
set_bit(ABS_HAT0Y, input_dev->absbit);
set_bit(ABS_MISC, input_dev->absbit);
set_bit(ABS_MISC+1, input_dev->absbit);
input_set_abs_params(input_dev, ABS_X, 0, 4095, 15, 255);
input_set_abs_params(input_dev, ABS_Y, 0, 4095, 15, 255);
input_set_abs_params(input_dev, ABS_RZ, 0, 511, 1, 31);
input_set_abs_params(input_dev, ABS_RX, 0, 255, 0, 15);
input_set_abs_params(input_dev, ABS_RY, 0, 255, 0, 15);
input_set_abs_params(input_dev, ABS_Z, 0, 255, 0, 15);
input_set_abs_params(input_dev, ABS_THROTTLE, 0, 255, 0, 15);
input_set_abs_params(input_dev, ABS_HAT0X, -1, 1, 0, 0);
input_set_abs_params(input_dev, ABS_HAT0Y, -1, 1, 0, 0);
input_set_abs_params(input_dev, ABS_MISC, 0, 15, 0, 0);
input_set_abs_params(input_dev, ABS_MISC+1, 0, 15, 0, 0);
return 0;
}
static int x65_input_mapping(struct hid_device *dev,
struct hid_input *input,
struct hid_field *field,
struct hid_usage *usage,
unsigned long **bit,
int *max)
{
/*
* We are reporting the events in x65_raw_event.
* Skip the hid-input processing.
*/
return -1;
}
static const struct hid_device_id x65_devices[] = {
{ HID_USB_DEVICE(VENDOR_SAITEK, DEV_X65F) },
{}
};
MODULE_DEVICE_TABLE(hid, x65_devices);
static struct hid_driver x65_driver = {
.name = "saitek-x65",
.id_table = x65_devices,
.input_mapping = x65_input_mapping,
.input_configured = x65_input_configured,
.raw_event = x65_raw_event,
};
module_hid_driver(x65_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Nirenjan Krishnan");
MODULE_DESCRIPTION("HID driver for Saitek X65 HOTAS devices");