Several RC Simulator Controllers are HID compliant with similar interface. Add support for these controllers: - Phoenix RC (HID variant) - Car VRC2.0 - Real Flight G5/G6/G7 - Aero Fly, FMS - OrangeRX FrSky Signed-off-by: Marcus Folkesson <marcus.folkesson@xxxxxxxxx> --- Documentation/hid/index.rst | 1 + Documentation/hid/rcsim.rst | 142 ++++++++++++++++ drivers/hid/Kconfig | 11 ++ drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 5 + drivers/hid/hid-rcsim.c | 315 ++++++++++++++++++++++++++++++++++++ 6 files changed, 475 insertions(+) create mode 100644 Documentation/hid/rcsim.rst create mode 100644 drivers/hid/hid-rcsim.c diff --git a/Documentation/hid/index.rst b/Documentation/hid/index.rst index e50f513c579c..e5813d264f37 100644 --- a/Documentation/hid/index.rst +++ b/Documentation/hid/index.rst @@ -17,3 +17,4 @@ Human Interface Devices (HID) hid-alps intel-ish-hid amd-sfh-hid + rcsim diff --git a/Documentation/hid/rcsim.rst b/Documentation/hid/rcsim.rst new file mode 100644 index 000000000000..1a031f7189cb --- /dev/null +++ b/Documentation/hid/rcsim.rst @@ -0,0 +1,142 @@ +.. SPDX-License-Identifier: GPL-2.0 + +================================= +rcsim - RC Simulator Controllers +================================= + +:Author: Marcus Folkesson <marcus.folkesson@xxxxxxxxx> + +This driver let you use your own RC controller plugged +into your computer using an HID compatible USB dongle. + +There are several HID compatible USB dongles from different +vendors. The driver currently supports: + +- Phoenix RC (HID variant) (8ch) +- Car VRC2.0 (2ch) +- Real Flight G5/G6/G7 (6ch) +- Aero Fly, FMS (8ch) +- OrangeRX FrSky (6ch) + +Many RC controllers is able to configure which stick goes to which channel. +This is also configurable in most simulators, so a matching is not necessary. + +Supported dongles +================== + +PhoenixRC +---------- + +The PhoenixRC has one HID compatible variant which is supported by this driver. +The controller has support for 8 analog channels. + +The driver is generating the following input event for on channels: + ++---------+----------------+ +| Channel | Event | ++=========+================+ +| 1 | ABS_Y | ++---------+----------------+ +| 2 | ABS_X | ++---------+----------------+ +| 3 | ABS_RY | ++---------+----------------+ +| 4 | ABS_RX | ++---------+----------------+ +| 5 | ABS_RUDDER | ++---------+----------------+ +| 6 | ABS_THROTTLE | ++---------+----------------+ +| 7 | ABS_Z | ++---------+----------------+ +| 8 | ABS_RZ | ++---------+----------------+ + +VRC2.0 +---------- +VRC2.0 is a controller for RC Cars. +The controller has support for 2 analog channels. + +The driver is generating the following input event for on channels: + ++---------+----------------+ +| Channel | Event | ++=========+================+ +| 1 | ABS_GAS | ++---------+----------------+ +| 2 | ABS_WHEEL | ++---------+----------------+ + +RealFlight +---------- + +This driver supports Realflight G4-G7 and above +The controller has support for 4 analog channels and two buttons. + +The driver is generating the following input event for on channels: + ++---------+----------------+ +| Channel | Event | ++=========+================+ +| 1 | ABS_Y | ++---------+----------------+ +| 2 | ABS_X | ++---------+----------------+ +| 3 | ABS_RY | ++---------+----------------+ +| 4 | ABS_RX | ++---------+----------------+ +| 5 | BTN_A | ++---------+----------------+ +| 6 | BTN_B | ++---------+----------------+ + +XTR+G2+FMS Controllers +-------------------------------- + +The controllers has support for 8 analog channels. + +The driver is generating the following input event for on channels: + ++---------+----------------+ +| Channel | Event | ++=========+================+ +| 1 | ABS_Y | ++---------+----------------+ +| 2 | ABS_X | ++---------+----------------+ +| 3 | ABS_RY | ++---------+----------------+ +| 4 | ABS_RX | ++---------+----------------+ +| 5 | ABS_RUDDER | ++---------+----------------+ +| 6 | ABS_THROTTLE | ++---------+----------------+ +| 7 | ABS_Z | ++---------+----------------+ +| 8 | ABS_RZ | ++---------+----------------+ + +OrangeRX +---------- + +The controllers has support for 6 analog channels. + +The driver is generating the following input event for on channels: + ++---------+----------------+ +| Channel | Event | ++=========+================+ +| 1 | ABS_Y | ++---------+----------------+ +| 2 | ABS_X | ++---------+----------------+ +| 3 | ABS_RY | ++---------+----------------+ +| 4 | ABS_RX | ++---------+----------------+ +| 5 | ABS_RUDDER | ++---------+----------------+ +| 6 | ABS_THROTTLE | ++---------+----------------+ diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 70da5931082f..d8313d36086c 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -957,6 +957,17 @@ config HID_RAZER Support for Razer devices that are not fully compliant with the HID standard. +config HID_RCSIM + tristate "RC Simulator Controllers" + depends on HID + help + Support for several HID compatible RC Simulator Controllers including + - Phoenix RC + - Car VRC2.0 + - Real Flight + - Aero Fly, FMS + - OrangeRX FrSky + config HID_PRIMAX tristate "Primax non-fully HID-compliant devices" depends on HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index cac2cbe26d11..85d50ab352ee 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -102,6 +102,7 @@ obj-$(CONFIG_HID_PLANTRONICS) += hid-plantronics.o obj-$(CONFIG_HID_PLAYSTATION) += hid-playstation.o obj-$(CONFIG_HID_PRIMAX) += hid-primax.o obj-$(CONFIG_HID_RAZER) += hid-razer.o +obj-$(CONFIG_HID_RCSIM) += hid-rcsim.o obj-$(CONFIG_HID_REDRAGON) += hid-redragon.o obj-$(CONFIG_HID_RETRODE) += hid-retrode.o obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \ diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index d9eb676abe96..baf5f74d5bed 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1381,6 +1381,11 @@ #define USB_VENDOR_ID_MULTIPLE_1781 0x1781 #define USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD 0x0a9d +#define USB_DEVICE_ID_PHOENIXRC 0x0898 +#define USB_DEVICE_ID_REALFLIGHT 0x0e56 + +#define USB_VENDOR_ID_DIPLING 0x0B9B +#define USB_DEVICE_ID_DIPLING_RCCONTROLLER 0x4012 #define USB_VENDOR_ID_DRACAL_RAPHNET 0x289b #define USB_DEVICE_ID_RAPHNET_2NES2SNES 0x0002 diff --git a/drivers/hid/hid-rcsim.c b/drivers/hid/hid-rcsim.c new file mode 100644 index 000000000000..0f214cb5816a --- /dev/null +++ b/drivers/hid/hid-rcsim.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for several HID compatible RC Simulator Controllers. + * Currently supported controllers are: + * + * - Phoenix RC (HID variant) (8ch) + * - Car VRC2.0 (2ch) + * - Real Flight G5/G6/G7 (6ch) + * - Aero Fly, FMS (8ch) + * - OrangeRX FrSky (6ch) + * + * Copyright (C) 2022 Marcus Folkesson <marcus.folkesson@xxxxxxxxx> + */ + +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/input.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/usb.h> + +#include "hid-ids.h" + +/* + * Some of these VID/PID are probably "borrowed", so keep them locally and + * do not populate hid-ids.h with those. + */ + +/* PHOENIXRC Controlloer (HID variant) */ +#define PHOENIXRC_VID (USB_VENDOR_ID_MULTIPLE_1781) +#define PHOENIXRC_PID (USB_DEVICE_ID_PHOENIXRC) +#define PHOENIXRC_DSIZE (8) + +/* VRC2 Controlloer */ +#define VRC2_VID (0x07c0) +#define VRC2_PID (0x1125) +#define VRC2_DSIZE (7) + +/* Realflight G4-&7 and Above Controller */ +#define REALFLIGHT_VID (USB_VENDOR_ID_MULTIPLE_1781) +#define REALFLIGHT_PID (USB_DEVICE_ID_REALFLIGHT) +#define REALFLIGHT_DSIZE (8) + +#define REALFLIGHT_BTN_A BIT(0) +#define REALFLIGHT_BTN_B BIT(1) + +/* XTR+G2+FMS Controller */ +#define XTRG2FMS_VID (USB_VENDOR_ID_DIPLING) +#define XTRG2FMS_PID (USB_DEVICE_ID_DIPLING_RCCONTROLLER) +#define XTRG2FMS_DSIZE (8) + +#define XTRG2FMS_X_HI GENMASK(3, 2) +#define XTRG2FMS_Y_HI GENMASK(1, 0) +#define XTRG2FMS_RX_HI GENMASK(7, 6) +#define XTRG2FMS_RY_HI GENMASK(5, 4) +#define XTRG2FMS_ALT1_HI GENMASK(3, 2) +#define XTRG2FMS_ALT2_HI GENMASK(1, 0) + +/* OrangeRX FrSky */ +#define ORANGERX_VID (0x0451) +#define ORANGERX_PID (0x16a5) +#define ORANGERX_DSIZE (8) + +enum rcsim_controller { + PHOENIXRC, + VRC2, + REALFLIGHT, + XTRG2FMS, + ORANGERX +}; + +struct rcsim_priv { + struct hid_device *hdev; + struct input_dev *input; + enum rcsim_controller controller; + u8 alt; +}; + +static int rcsim_open(struct input_dev *dev) +{ + struct rcsim_priv *priv = input_get_drvdata(dev); + + return hid_hw_open(priv->hdev); +} + +static void rcsim_close(struct input_dev *dev) +{ + struct rcsim_priv *priv = input_get_drvdata(dev); + + hid_hw_close(priv->hdev); +} + +static int rcsim_setup_input(struct rcsim_priv *priv) +{ + struct input_dev *input; + + input = devm_input_allocate_device(&priv->hdev->dev); + if (!input) + return -ENOMEM; + + input->id.bustype = priv->hdev->bus; + input->id.vendor = priv->hdev->vendor; + input->id.product = priv->hdev->product; + input->id.version = priv->hdev->bus; + input->phys = priv->hdev->phys; + input->uniq = priv->hdev->uniq; + input->open = rcsim_open; + input->close = rcsim_close; + + input_set_drvdata(input, priv); + + switch (priv->controller) { + case PHOENIXRC: + input_set_abs_params(input, ABS_X, 0, 255, 0, 0); + input_set_abs_params(input, ABS_Y, 0, 255, 0, 0); + input_set_abs_params(input, ABS_RX, 0, 255, 0, 0); + input_set_abs_params(input, ABS_RY, 0, 255, 0, 0); + input_set_abs_params(input, ABS_Z, 0, 255, 0, 0); + input_set_abs_params(input, ABS_RZ, 0, 255, 0, 0); + input_set_abs_params(input, ABS_RUDDER, 0, 255, 0, 0); + input_set_abs_params(input, ABS_THROTTLE, 0, 255, 0, 0); + input->name = "RC Simuator Controller PhoenixRC"; + break; + case VRC2: + input_set_abs_params(input, ABS_GAS, 0, 2048, 0, 0); + input_set_abs_params(input, ABS_WHEEL, 0, 2048, 0, 0); + input->name = "RC Simuator Controller VRC2.0"; + break; + case REALFLIGHT: + input_set_abs_params(input, ABS_X, 0, 1024, 0, 0); + input_set_abs_params(input, ABS_Y, 0, 1024, 0, 0); + input_set_abs_params(input, ABS_RX, 0, 1024, 0, 0); + input_set_abs_params(input, ABS_RY, 0, 1024, 0, 0); + input_set_capability(input, EV_KEY, BTN_A); + input_set_capability(input, EV_KEY, BTN_B); + input->name = "RC Simuator Controller Realflight"; + break; + case XTRG2FMS: + input_set_abs_params(input, ABS_X, 0, 1024, 0, 0); + input_set_abs_params(input, ABS_Y, 0, 1024, 0, 0); + input_set_abs_params(input, ABS_RX, 0, 1024, 0, 0); + input_set_abs_params(input, ABS_RY, 0, 1024, 0, 0); + input_set_abs_params(input, ABS_Z, 0, 1024, 0, 0); + input_set_abs_params(input, ABS_RZ, 0, 1024, 0, 0); + input_set_abs_params(input, ABS_RUDDER, 0, 1024, 0, 0); + input_set_abs_params(input, ABS_THROTTLE, 0, 1024, 0, 0); + input->name = "RC Simuator Controller AeroFly, FMS"; + priv->alt = 0; + break; + case ORANGERX: + input_set_abs_params(input, ABS_X, 0, 255, 0, 0); + input_set_abs_params(input, ABS_Y, 0, 255, 0, 0); + input_set_abs_params(input, ABS_RX, 0, 255, 0, 0); + input_set_abs_params(input, ABS_RY, 0, 255, 0, 0); + input_set_abs_params(input, ABS_RUDDER, 0, 255, 0, 0); + input_set_abs_params(input, ABS_THROTTLE, 0, 255, 0, 0); + input->name = "RC Simuator Controller OrangeRX FrSky"; + break; + }; + + priv->input = input; + return input_register_device(priv->input); +} + +static int rcsim_raw_event(struct hid_device *hdev, + struct hid_report *report, + u8 *raw_data, int size) +{ + struct rcsim_priv *priv = hid_get_drvdata(hdev); + u16 value; + + switch (priv->controller) { + case PHOENIXRC: + if (size != PHOENIXRC_DSIZE) + break; + + /* X, RX, Y and RY, RUDDER and THROTTLE are sent every time */ + input_report_abs(priv->input, ABS_X, raw_data[2]); + input_report_abs(priv->input, ABS_Y, raw_data[0]); + input_report_abs(priv->input, ABS_RX, raw_data[4]); + input_report_abs(priv->input, ABS_RY, raw_data[3]); + input_report_abs(priv->input, ABS_RUDDER, raw_data[5]); + input_report_abs(priv->input, ABS_THROTTLE, raw_data[6]); + + /* Z and RZ are sent every other time */ + if (priv->alt) + input_report_abs(priv->input, ABS_Z, raw_data[7]); + else + input_report_abs(priv->input, ABS_RZ, raw_data[7]); + + priv->alt ^= 1; + break; + case VRC2: + if (size != VRC2_DSIZE) + break; + value = (raw_data[1] << 8 | raw_data[0]) & GENMASK(10, 0); + input_report_abs(priv->input, ABS_GAS, value); + value = (raw_data[3] << 8 | raw_data[2]) & GENMASK(10, 0); + input_report_abs(priv->input, ABS_WHEEL, value); + break; + case REALFLIGHT: + if (size != REALFLIGHT_DSIZE) + break; + input_report_abs(priv->input, ABS_X, raw_data[2]); + input_report_abs(priv->input, ABS_Y, raw_data[1]); + input_report_abs(priv->input, ABS_RX, raw_data[5]); + input_report_abs(priv->input, ABS_RY, raw_data[3]); + input_report_abs(priv->input, ABS_MISC, raw_data[4]); + input_report_key(priv->input, BTN_A, + raw_data[7] & REALFLIGHT_BTN_A); + input_report_key(priv->input, BTN_B, + raw_data[7] & REALFLIGHT_BTN_B); + break; + case XTRG2FMS: + if (size != XTRG2FMS_DSIZE) + break; + + /* X, RX, Y and RY are sent every time */ + value = FIELD_GET(XTRG2FMS_X_HI, raw_data[3]); + value = (value << 8) | raw_data[1]; + input_report_abs(priv->input, ABS_X, value); + + value = FIELD_GET(XTRG2FMS_Y_HI, raw_data[3]); + value = (value << 8) | raw_data[2]; + input_report_abs(priv->input, ABS_Y, value); + + value = FIELD_GET(XTRG2FMS_RX_HI, raw_data[3]); + value = (value << 8) | raw_data[0]; + input_report_abs(priv->input, ABS_RX, value); + + value = FIELD_GET(XTRG2FMS_RY_HI, raw_data[3]); + value = (value << 8) | raw_data[4]; + input_report_abs(priv->input, ABS_RY, value); + + /* Z, RZ, RUDDER and THROTTLE are sent every other time */ + value = FIELD_GET(XTRG2FMS_ALT1_HI, raw_data[7]); + value = (value << 8) | raw_data[6]; + if (priv->alt) + input_report_abs(priv->input, ABS_Z, value); + else + input_report_abs(priv->input, ABS_RUDDER, value); + + value = FIELD_GET(XTRG2FMS_ALT2_HI, raw_data[7]); + value = (value << 8) | raw_data[5]; + if (priv->alt) + input_report_abs(priv->input, ABS_RZ, value); + else + input_report_abs(priv->input, ABS_THROTTLE, value); + + priv->alt ^= 1; + break; + case ORANGERX: + if (size != ORANGERX_DSIZE) + break; + input_report_abs(priv->input, ABS_X, raw_data[0]); + input_report_abs(priv->input, ABS_Y, raw_data[2]); + input_report_abs(priv->input, ABS_RX, raw_data[3]); + input_report_abs(priv->input, ABS_RY, raw_data[1]); + input_report_abs(priv->input, ABS_RUDDER, raw_data[5]); + input_report_abs(priv->input, ABS_THROTTLE, raw_data[6]); + break; + }; + + input_sync(priv->input); + return 0; +} + +static int rcsim_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct device *dev = &hdev->dev; + struct rcsim_priv *priv; + int ret; + + if (!hid_is_using_ll_driver(hdev, &usb_hid_driver)) + return -ENODEV; + + ret = hid_parse(hdev); + if (ret) + return ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->hdev = hdev; + priv->controller = id->driver_data; + hid_set_drvdata(hdev, priv); + + ret = rcsim_setup_input(priv); + if (ret) + return ret; + + return hid_hw_start(hdev, HID_CONNECT_HIDRAW); +} + +static const struct hid_device_id rcsim_devices[] = { + { HID_USB_DEVICE(PHOENIXRC_VID, PHOENIXRC_PID), .driver_data = PHOENIXRC }, + { HID_USB_DEVICE(VRC2_VID, VRC2_PID), .driver_data = VRC2 }, + { HID_USB_DEVICE(REALFLIGHT_VID, REALFLIGHT_PID), .driver_data = REALFLIGHT }, + { HID_USB_DEVICE(XTRG2FMS_VID, XTRG2FMS_PID), .driver_data = XTRG2FMS }, + { HID_USB_DEVICE(ORANGERX_VID, ORANGERX_PID), .driver_data = ORANGERX }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(hid, rcsim_devices); + +static struct hid_driver rcsim_driver = { + .name = "rcsim", + .id_table = rcsim_devices, + .probe = rcsim_probe, + .raw_event = rcsim_raw_event, +}; +module_hid_driver(rcsim_driver); + +MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@xxxxxxxxx>"); +MODULE_LICENSE("GPL"); -- 2.37.1