* [PATCH] Input: xpad - add support for Beitong KP series controllers
@ 2026-07-26 17:39 VegetablCat
2026-07-27 4:59 ` Dmitry Torokhov
2026-08-03 9:53 ` Shengyu Qu
0 siblings, 2 replies; 5+ messages in thread
From: VegetablCat @ 2026-07-26 17:39 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, vegetablecat
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. The 25ms timeout for the
usb_control_msg_recv() call is consistent with the existing
usage in xpad_start_input() for the legacy Xbox 360 vendor
capability request.
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_REACHABLE(CONFIG_HID) to handle the case where xpad is
built-in (y) and HID is a loadable module (m).
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..8cd4001 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_REACHABLE(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_REACHABLE(CONFIG_HID)
+ error = hid_register_driver(&xpad_receiver_driver);
+ if (error)
+ return error;
+#endif
+
+ error = usb_register(&xpad_driver);
+ if (error) {
+#if IS_REACHABLE(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_REACHABLE(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
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] Input: xpad - add support for Beitong KP series controllers
2026-07-26 17:39 [PATCH] Input: xpad - add support for Beitong KP series controllers VegetablCat
@ 2026-07-27 4:59 ` Dmitry Torokhov
2026-08-03 9:53 ` Shengyu Qu
1 sibling, 0 replies; 5+ messages in thread
From: Dmitry Torokhov @ 2026-07-27 4:59 UTC (permalink / raw)
To: VegetablCat; +Cc: linux-input, linux-kernel
On Mon, Jul 27, 2026 at 01:39:41AM +0800, VegetablCat wrote:
> From: vegetablecat <vegetablecat@foxmail.com>
^^^^ Huh?
Thanks.
--
Dmitry
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] Input: xpad - add support for Beitong KP series controllers
2026-07-26 17:39 [PATCH] Input: xpad - add support for Beitong KP series controllers VegetablCat
2026-07-27 4:59 ` Dmitry Torokhov
@ 2026-08-03 9:53 ` Shengyu Qu
1 sibling, 0 replies; 5+ messages in thread
From: Shengyu Qu @ 2026-08-03 9:53 UTC (permalink / raw)
To: VegetablCat, Dmitry Torokhov; +Cc: wiagn233, linux-input, linux-kernel
[-- Attachment #1.1: Type: text/plain, Size: 7228 bytes --]
在 2026/7/27 01:39, VegetablCat 写道:
> @@ -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 },
? Where does the ID come from? It's not same as the existing ID below.
Have you confirmed that those IDs are all consistent with real devices?
Or those just from some random AI slop.... Add ID only for tested
device, don't just add IDs without testing. Besides, it's already tested
that KP50B/C doesn't need such quirk and still works well.
Also I would say don't use "Controller" in device name since those are
dongles, also controller itself uses different VID from dongles
(confirmed on KP50B/C).
> + { 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_REACHABLE(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_REACHABLE(CONFIG_HID)
> + error = hid_register_driver(&xpad_receiver_driver);
> + if (error)
> + return error;
> +#endif
> +
> + error = usb_register(&xpad_driver);
> + if (error) {
> +#if IS_REACHABLE(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_REACHABLE(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");
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH] Input: xpad - add support for Beitong KP series controllers
@ 2026-07-26 8:18 VegetablCat
0 siblings, 0 replies; 5+ messages in thread
From: VegetablCat @ 2026-07-26 8:18 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, vegetablecat
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
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH] Input: xpad - add support for Beitong KP series controllers
@ 2026-07-26 8:01 VegetablCat
0 siblings, 0 replies; 5+ messages in thread
From: VegetablCat @ 2026-07-26 8:01 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, vegetablecat
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 a minimal HID receiver driver for the KP40D
receiver dongle (0x20dd:0x515c) that binds to its HID interface
to prevent hid-generic from interfering. This requires replacing
module_usb_driver() with manual module_init/module_exit to
register both the USB and HID drivers.
Tested on 20bc:5126 (KP20A) and 20bc:515b (KP40D).
Signed-off-by: vegetablecat <vegetablecat@foxmail.com>
---
drivers/input/joystick/xpad.c | 111 +++++++++++++++++++++++++++++++++-
1 file changed, 110 insertions(+), 1 deletion(-)
diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index feb8f36..d33ca18 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,71 @@ static struct usb_driver xpad_driver = {
.id_table = xpad_table,
};
-module_usb_driver(xpad_driver);
+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,
+};
+
+static int __init xpad_init(void)
+{
+ int error;
+
+ error = hid_register_driver(&xpad_receiver_driver);
+ if (error)
+ return error;
+
+ error = usb_register(&xpad_driver);
+ if (error) {
+ hid_unregister_driver(&xpad_receiver_driver);
+ return error;
+ }
+
+ return 0;
+}
+
+static void __exit xpad_exit(void)
+{
+ usb_deregister(&xpad_driver);
+ hid_unregister_driver(&xpad_receiver_driver);
+}
+
+module_init(xpad_init);
+module_exit(xpad_exit);
MODULE_AUTHOR("Marko Friedemann <mfr@bmx-chemnitz.de>");
MODULE_DESCRIPTION("Xbox pad driver");
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-03 9:53 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-26 17:39 [PATCH] Input: xpad - add support for Beitong KP series controllers VegetablCat
2026-07-27 4:59 ` Dmitry Torokhov
2026-08-03 9:53 ` Shengyu Qu
-- strict thread matches above, loose matches on Subject: below --
2026-07-26 8:18 VegetablCat
2026-07-26 8:01 VegetablCat
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®