mirror of https://github.com/nirenjan/libx52.git
Create HID driver for Saitek X52 and X52 Pro
This change replaces the old USB driver with a more modern HID driver. This uses the HID framework, which means that we don't have to deal with the USB transport, and only have to parse and report input events. [skip ci]pull/26/head
parent
3409a7bad6
commit
d3c55da89d
|
@ -1,7 +1,7 @@
|
|||
# Compiled object files
|
||||
*.ko
|
||||
*.o
|
||||
*.mod.*
|
||||
*.mod*
|
||||
|
||||
# Generated objects (source, executables, tarballs, etc.)
|
||||
a.out
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
obj-m := saitek_x52.o
|
||||
saitek_x52-objs := x52joy.o x52joy_commands.o x52joy_input.o
|
||||
saitek_x52-objs := hid-saitek-x52.o
|
||||
|
||||
KDIR := /lib/modules/$(shell uname -r)/build
|
||||
PWD := $(shell pwd)
|
||||
|
|
|
@ -1,9 +1,29 @@
|
|||
Kernel driver for Saitek X52 Pro
|
||||
================================
|
||||
Kernel driver for Saitek X52 and X52 Pro
|
||||
========================================
|
||||
|
||||
This folder contains a loadable kernel module for the Saitek X52Pro HOTAS.
|
||||
Note that the standard usbhid driver already supports this joystick as an
|
||||
input device, and the additional functionality of the LEDs and MFD can be
|
||||
handled by a userspace library.
|
||||
This folder contains a loadable kernel module for the Saitek X52 and X52 Pro
|
||||
HOTAS. This improves upon the standard `hid-generic` driver by reporting both
|
||||
the left-right and up-down motion of the thumb stick.
|
||||
|
||||
**This module is only a proof-of-concept and not suitable for production**
|
||||
However, it changes the buttons that are reported by the joystick, and thus,
|
||||
may not be suitable for all applications.
|
||||
|
||||
# Building
|
||||
|
||||
This directory is deliberately not integrated with the top level Autotools
|
||||
based build framework.
|
||||
|
||||
Install the Linux headers for the currently running kernel. On Ubuntu, this
|
||||
can be done by running `sudo apt-get install -y linux-headers-$(uname -r)`.
|
||||
|
||||
Run `make`. This will build the module from source.
|
||||
|
||||
# Installing the kernel module
|
||||
|
||||
Once you have built the kernel module, run `sudo insmod saitek_x52.ko` from
|
||||
the current directory. With a recent enough kernel, the driver should switch
|
||||
automatically. Otherwise, simply disconnect and reconnect your X52.
|
||||
|
||||
# Reporting issues
|
||||
|
||||
Please report any issues seen as a [Github issue](https://github.com/nirenjan/x52pro-linux/issues).
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* HID driver for Saitek X52 HOTAS
|
||||
*
|
||||
* Supported devices:
|
||||
* - Saitek X52
|
||||
* - Saitek X52 Pro
|
||||
*
|
||||
* Copyright (c) 2020 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_X52_1 0x0255
|
||||
#define DEV_X52_2 0x075c
|
||||
#define DEV_X52_PRO 0x0762
|
||||
|
||||
static void _parse_axis_report(struct input_dev *input_dev,
|
||||
int is_pro, u8 *data, int len)
|
||||
{
|
||||
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[len - 2]) >> 4;
|
||||
|
||||
u32 axis = (data[3] << 24) |
|
||||
(data[2] << 16) |
|
||||
(data[1] << 8) |
|
||||
data[0];
|
||||
|
||||
if (is_pro) {
|
||||
input_report_abs(input_dev, ABS_X, (axis & 0x3ff));
|
||||
input_report_abs(input_dev, ABS_Y, ((axis >> 10) & 0x3ff));
|
||||
} else {
|
||||
input_report_abs(input_dev, ABS_X, (axis & 0x7ff));
|
||||
input_report_abs(input_dev, ABS_Y, ((axis >> 11) & 0x7ff));
|
||||
}
|
||||
|
||||
input_report_abs(input_dev, ABS_RZ, ((axis >> 22) & 0x3ff));
|
||||
input_report_abs(input_dev, ABS_Z, data[4]);
|
||||
input_report_abs(input_dev, ABS_RX, data[5]);
|
||||
input_report_abs(input_dev, ABS_RY, data[5]);
|
||||
input_report_abs(input_dev, ABS_MISC, data[6]);
|
||||
|
||||
/* Mouse stick is always the last byte of the report */
|
||||
input_report_abs(input_dev, ABS_TILT_X, data[len-1] & 0xf);
|
||||
input_report_abs(input_dev, ABS_TILT_Y, data[len-1] >> 4);
|
||||
|
||||
/* Hat is always the upper nibble of the penultimate byte of the report */
|
||||
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, int num_buttons)
|
||||
{
|
||||
int i;
|
||||
int idx;
|
||||
int btn;
|
||||
|
||||
for (i = 0; i < num_buttons; i++) {
|
||||
idx = 8 + (i / BITS_PER_BYTE);
|
||||
btn = !!(data[idx] & (1 << (i % BITS_PER_BYTE)));
|
||||
input_report_key(input_dev, BTN_TRIGGER_HAPPY + i, btn);
|
||||
}
|
||||
}
|
||||
|
||||
static int _parse_x52_report(struct input_dev *input_dev,
|
||||
u8 *data, int len)
|
||||
{
|
||||
if (len != 14) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
_parse_axis_report(input_dev, 0, data, len);
|
||||
_parse_button_report(input_dev, data, 34);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _parse_x52pro_report(struct input_dev *input_dev,
|
||||
u8 *data, int len)
|
||||
{
|
||||
if (len != 15) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
_parse_axis_report(input_dev, 1, data, len);
|
||||
_parse_button_report(input_dev, data, 39);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int x52_raw_event(struct hid_device *dev,
|
||||
struct hid_report *report, u8 *data, int len)
|
||||
{
|
||||
struct input_dev *input_dev = hid_get_drvdata(dev);
|
||||
int is_pro = (dev->product == DEV_X52_PRO);
|
||||
int ret;
|
||||
|
||||
if (is_pro) {
|
||||
ret = _parse_x52pro_report(input_dev, data, len);
|
||||
} else {
|
||||
ret = _parse_x52_report(input_dev, data, len);
|
||||
}
|
||||
input_sync(input_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int x52_input_configured(struct hid_device *dev,
|
||||
struct hid_input *input)
|
||||
{
|
||||
struct input_dev *input_dev = input->input;
|
||||
int i;
|
||||
int max_btn;
|
||||
int is_pro = (dev->product == DEV_X52_PRO);
|
||||
int max_stick;
|
||||
|
||||
hid_set_drvdata(dev, input_dev);
|
||||
|
||||
set_bit(EV_KEY, input_dev->evbit);
|
||||
set_bit(EV_ABS, input_dev->evbit);
|
||||
|
||||
/*
|
||||
* X52 has only 34 buttons, X52 Pro has 39. The first 34 buttons are common
|
||||
* although the button order differs between the two.
|
||||
*/
|
||||
max_btn = is_pro ? 39 : 34 ;
|
||||
|
||||
for (i = 0; i < max_btn; i++) {
|
||||
set_bit(BTN_TRIGGER_HAPPY1 + i, input_dev->keybit);
|
||||
}
|
||||
|
||||
/* Both X52 and X52 Pro have the same number of axes, only the ranges vary */
|
||||
|
||||
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_RZ, input_dev->absbit);
|
||||
set_bit(ABS_HAT0X, input_dev->absbit);
|
||||
set_bit(ABS_HAT0Y, input_dev->absbit);
|
||||
set_bit(ABS_TILT_X, input_dev->absbit);
|
||||
set_bit(ABS_TILT_Y, input_dev->absbit);
|
||||
set_bit(ABS_MISC, input_dev->absbit);
|
||||
|
||||
max_stick = is_pro ? 1023 : 2047;
|
||||
input_set_abs_params(input_dev, ABS_X, 0, max_stick, max_stick >> 8, max_stick >> 4);
|
||||
input_set_abs_params(input_dev, ABS_Y, 0, max_stick, max_stick >> 8, max_stick >> 4);
|
||||
input_set_abs_params(input_dev, ABS_RZ, 0, 1023, 3, 63);
|
||||
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_MISC, 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_TILT_X, 0, 15, 0, 0);
|
||||
input_set_abs_params(input_dev, ABS_TILT_Y, 0, 15, 0, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int x52_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 x52_raw_event.
|
||||
* Skip the hid-input processing.
|
||||
*/
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int x52_probe(struct hid_device *dev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
hid_set_drvdata(dev, NULL);
|
||||
ret = hid_parse(dev);
|
||||
if (ret != 0) {
|
||||
hid_err(dev, "parse failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(dev, HID_CONNECT_DEFAULT);
|
||||
if (ret != 0) {
|
||||
hid_err(dev, "hw start failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void x52_remove(struct hid_device *dev)
|
||||
{
|
||||
hid_hw_stop(dev);
|
||||
}
|
||||
|
||||
static const struct hid_device_id x52_devices[] = {
|
||||
{ HID_USB_DEVICE(VENDOR_SAITEK, DEV_X52_1) },
|
||||
{ HID_USB_DEVICE(VENDOR_SAITEK, DEV_X52_2) },
|
||||
{ HID_USB_DEVICE(VENDOR_SAITEK, DEV_X52_PRO) },
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(hid, x52_devices);
|
||||
|
||||
static struct hid_driver x52_driver = {
|
||||
.name = "saitek-x52",
|
||||
.id_table = x52_devices,
|
||||
.probe = x52_probe,
|
||||
.remove = x52_remove,
|
||||
.input_mapping = x52_input_mapping,
|
||||
.input_configured = x52_input_configured,
|
||||
.raw_event = x52_raw_event,
|
||||
};
|
||||
|
||||
module_hid_driver(x52_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Nirenjan Krishnan");
|
||||
MODULE_DESCRIPTION("HID driver for Saitek X52 HOTAS devices");
|
Loading…
Reference in New Issue