From: VegetablCat <vegetablecat@foxmail.com>
To: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
vegetablecat <vegetablecat@foxmail.com>
Subject: [PATCH] Input: xpad - add support for Beitong KP series controllers
Date: Sun, 26 Jul 2026 16:18:34 +0800 [thread overview]
Message-ID: <tencent_3FA5706BA66E2CCCFA362E90F805A8FF370A@qq.com> (raw)
From: vegetablecat <vegetablecat@foxmail.com>
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.
Based on earlier work by Zixing Liu <zixing.liu@canonical.com>.
Tested on 20bc:5126 (KP20A) and 20bc:515b (KP40D).
Signed-off-by: vegetablecat <vegetablecat@foxmail.com>
---
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
next reply other threads:[~2026-07-26 8:19 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-26 8:18 VegetablCat [this message]
2026-07-26 8:26 ` [PATCH] Input: xpad - add support for Beitong KP series controllers sashiko-bot
-- strict thread matches above, loose matches on Subject: below --
2026-07-26 8:01 VegetablCat
2026-07-26 8:10 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=tencent_3FA5706BA66E2CCCFA362E90F805A8FF370A@qq.com \
--to=vegetablecat@foxmail.com \
--cc=dmitry.torokhov@gmail.com \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox