[PATCH v2] Input: xpad - add support for Beitong KP series controllers
From: VegetablCat <hidden>
Date: 2026-07-26 08:21:57
Also in:
lkml
Subsystem:
input (keyboard, mouse, joystick, touchscreen) drivers, the rest · Maintainers:
Dmitry Torokhov, Linus Torvalds
From: vegetablecat <redacted> Beitong KP series controllers (0x20bc:*) enumerate as standard Xbox 360 devices but require a Microsoft OS Extended Compat ID descriptor read (XUSB10) to lock into XInput mode. Without this request the controller re-enumerates as a Nintendo Switch device (057e:2069) after approximately 1.9 seconds and stops reporting valid input. Add FLAG_READ_XUSB10 and device table entries for KP20A/D, KP40A/B/D/F, KP50B/C/D/E, and KP70A. Devices with this flag trigger a usb_control_msg_recv() during probe to read the XUSB10 descriptor, which is the sole condition required to keep the controllers in XInput mode. The flag follows the same pattern as FLAG_DELAY_INIT used for the ByoWave Proteus Controller. Additionally, add an optional HID receiver driver for the KP40D receiver dongle (0x20dd:0x515c) that binds to its HID interface to prevent hid-generic from interfering. The receiver dongle is a separate USB device with its own vendor ID (0x20dd), distinct from the controller (0x20bc). The HID code is guarded with IS_ENABLED(CONFIG_HID) to avoid build failures when HID is disabled or built as a module. Tested on 20bc:5126 (KP20A) and 20bc:515b (KP40D). Signed-off-by: vegetablecat <redacted> --- drivers/input/joystick/xpad.c | 119 +++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-)
diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index feb8f36..13b6168 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c@@ -28,6 +28,7 @@ */ #include <linux/bits.h> +#include <linux/hid.h> #include <linux/kernel.h> #include <linux/input.h> #include <linux/rcupdate.h>
@@ -72,6 +73,7 @@ #define PKT_XBE2_FW_5_11 4 #define FLAG_DELAY_INIT BIT(0) +#define FLAG_READ_XUSB10 BIT(1) static bool dpad_to_buttons; module_param(dpad_to_buttons, bool, S_IRUGO);
@@ -332,6 +334,32 @@ static const struct xpad_device { { 0x1bad, 0xfd00, "Razer Onza TE", 0, XTYPE_XBOX360 }, { 0x1bad, 0xfd01, "Razer Onza", 0, XTYPE_XBOX360 }, { 0x1ee9, 0x1590, "ZOTAC Gaming Zone", 0, XTYPE_XBOX360 }, + { 0x20bc, 0x5125, "BETOP BTP-KP20A/BTP-KP40A Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5126, "BETOP BTP-KP20A Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5127, "BETOP BTP-KP20A/BTP-KP40A Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5128, "BETOP BTP-KP20A Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x512f, "BETOP BTP-KP70A Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5130, "BETOP BTP-KP70A Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5133, "BETOP BTP-KP50B Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5145, "BETOP BTP-KP40A/BTP-KP40B Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5146, "BETOP BTP-KP40A/BTP-KP40B Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5149, "BETOP BTP-KP50C Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5150, "BETOP BTP-KP50D Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5151, "BETOP BTP-KP50D Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5152, "BETOP BTP-KP50E Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5153, "BETOP BTP-KP50E Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5154, "BETOP BTP-KP40D Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5155, "BETOP BTP-KP40D Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5158, "BETOP BTP-KP20D Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5159, "BETOP BTP-KP20D Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x515b, "BETOP BTP-KP40D Controller (White)", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x515c, "BETOP BTP-KP40D Controller (White)", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x515d, "BETOP BTP-KP40F Controller (White)", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x515e, "BETOP BTP-KP40F Controller (White)", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x515f, "BETOP BTP-KP70A Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5160, "BETOP BTP-KP70A Controller", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x5169, "BETOP BTP-KP40F Controller (Black)", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, + { 0x20bc, 0x516a, "BETOP BTP-KP40F Controller (Black)", 0, XTYPE_XBOX360, FLAG_READ_XUSB10 }, { 0x20bc, 0x5134, "BETOP BTP-KP50B Xinput Dongle", 0, XTYPE_XBOX360 }, { 0x20bc, 0x514a, "BETOP BTP-KP50C Xinput Dongle", 0, XTYPE_XBOX360 }, { 0x20d6, 0x2001, "BDA Xbox Series X Wired Controller", 0, XTYPE_XBOXONE },
@@ -778,6 +806,7 @@ struct usb_xpad { time64_t mode_btn_down_ts; bool delay_init; /* init packets should be delayed */ bool delayed_init_done; + bool read_xusb10; /* read XUSB10 descriptor during probe */ }; static int xpad_init_input(struct usb_xpad *xpad);
@@ -2044,6 +2073,7 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id struct usb_device *udev = interface_to_usbdev(intf); struct usb_xpad *xpad; struct usb_endpoint_descriptor *ep_irq_in, *ep_irq_out; + u8 ms_feature_descriptor[40]; int i, error; if (intf->cur_altsetting->desc.bNumEndpoints != 2)
@@ -2082,6 +2112,8 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id xpad->name = xpad_device[i].name; if (xpad_device[i].flags & FLAG_DELAY_INIT) xpad->delay_init = true; + if (xpad_device[i].flags & FLAG_READ_XUSB10) + xpad->read_xusb10 = true; xpad->packet_type = PKT_XB; INIT_WORK(&xpad->work, xpad_presence_work);
@@ -2148,6 +2180,19 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id xpad->irq_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; usb_set_intfdata(intf, xpad); + if (xpad->read_xusb10) { + error = usb_control_msg_recv(udev, 0, 0xee, + USB_TYPE_VENDOR | USB_DIR_IN | + USB_RECIP_DEVICE, + 0x0000, 0x0004, + ms_feature_descriptor, + sizeof(ms_feature_descriptor), 25, + GFP_KERNEL); + if (error) + dev_warn(&intf->dev, + "unable to read Microsoft feature descriptor: %d\n", + error); + } /* Packet type detection */ if (le16_to_cpu(udev->descriptor.idVendor) == 0x045e) { /* Microsoft controllers */
@@ -2312,7 +2357,79 @@ static struct usb_driver xpad_driver = { .id_table = xpad_table, }; -module_usb_driver(xpad_driver); +#if IS_ENABLED(CONFIG_HID) +static int xpad_receiver_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int error; + + error = hid_parse(hdev); + if (error) + return error; + + error = hid_hw_start(hdev, 0); + if (error) + return error; + + error = hid_hw_open(hdev); + if (error) { + hid_hw_stop(hdev); + return error; + } + + return 0; +} + +static void xpad_receiver_remove(struct hid_device *hdev) +{ + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static const struct hid_device_id xpad_receiver_devices[] = { + { HID_USB_DEVICE(0x20dd, 0x515c) }, + { } +}; +MODULE_DEVICE_TABLE(hid, xpad_receiver_devices); + +static struct hid_driver xpad_receiver_driver = { + .name = "xpad-kp40d-receiver", + .id_table = xpad_receiver_devices, + .probe = xpad_receiver_probe, + .remove = xpad_receiver_remove, +}; +#endif + +static int __init xpad_init(void) +{ + int error; + +#if IS_ENABLED(CONFIG_HID) + error = hid_register_driver(&xpad_receiver_driver); + if (error) + return error; +#endif + + error = usb_register(&xpad_driver); + if (error) { +#if IS_ENABLED(CONFIG_HID) + hid_unregister_driver(&xpad_receiver_driver); +#endif + return error; + } + + return 0; +} + +static void __exit xpad_exit(void) +{ + usb_deregister(&xpad_driver); +#if IS_ENABLED(CONFIG_HID) + hid_unregister_driver(&xpad_receiver_driver); +#endif +} + +module_init(xpad_init); +module_exit(xpad_exit); MODULE_AUTHOR("Marko Friedemann <mfr@bmx-chemnitz.de>"); MODULE_DESCRIPTION("Xbox pad driver");
--
2.55.0