Переглянути джерело

feat(class/gamepad): add gamepad device

Signed-off-by: sakumisu <1203593632@qq.com>
sakumisu 1 тиждень тому
батько
коміт
7a2089f032

+ 2 - 0
CMakeLists.txt

@@ -10,6 +10,7 @@ if(BL_SDK_BASE)
     set(CONFIG_CHERRYUSB_DEVICE_MSC 1)
     set(CONFIG_CHERRYUSB_DEVICE_AUDIO 1)
     set(CONFIG_CHERRYUSB_DEVICE_VIDEO 1)
+    set(CONFIG_CHERRYUSB_DEVICE_GAMEPAD 1)
 
     set(CONFIG_CHERRYUSB_HOST_CDC_ACM 1)
     set(CONFIG_CHERRYUSB_HOST_CDC_ECM 1)
@@ -182,6 +183,7 @@ elseif(HPM_SDK_BASE)
     set(CONFIG_CHERRYUSB_DEVICE_MSC 1)
     set(CONFIG_CHERRYUSB_DEVICE_AUDIO 1)
     set(CONFIG_CHERRYUSB_DEVICE_VIDEO 1)
+    set(CONFIG_CHERRYUSB_DEVICE_GAMEPAD 1)
 
     set(CONFIG_CHERRYUSB_HOST_CDC_ACM 1)
     set(CONFIG_CHERRYUSB_HOST_CDC_ECM 1)

+ 4 - 0
cherryusb.cmake

@@ -51,6 +51,7 @@ list(
     ${CMAKE_CURRENT_LIST_DIR}/class/vendor/net
     ${CMAKE_CURRENT_LIST_DIR}/class/vendor/wifi
     ${CMAKE_CURRENT_LIST_DIR}/class/aoa
+    ${CMAKE_CURRENT_LIST_DIR}/class/gamepad
 )
 
 if(CONFIG_CHERRYUSB_DEVICE)
@@ -85,6 +86,9 @@ if(CONFIG_CHERRYUSB_DEVICE)
     if(CONFIG_CHERRYUSB_DEVICE_ADB)
         list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/adb/usbd_adb.c)
     endif()
+    if(CONFIG_CHERRYUSB_DEVICE_GAMEPAD)
+        list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/class/gamepad/usbd_gamepad.c)
+    endif()
 
     if(CONFIG_CHERRYUSB_DEVICE_FSDEV_ST)
         list(APPEND cherryusb_srcs ${CMAKE_CURRENT_LIST_DIR}/port/fsdev/usb_dc_fsdev.c)

+ 224 - 0
class/gamepad/usb_gamepad.h

@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2026, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef USB_GAMEPAD_H
+#define USB_GAMEPAD_H
+
+#include "usb_hid.h"
+
+/*
+ *  GAMEPAD BUTTON LAYOUT
+ *
+ *            ____________________________              __
+ *           / [__L2__]          [__R2__] \               |
+ *          / [__ L1 __]        [__ R1 __] \              | Triggers
+ *       __/________________________________\__         __|
+ *      /                                  _   \          |
+ *     /      /\           __             (B4)  \         |
+ *    /       ||      __  |A1|  __     _       _ \        | Main Pad
+ *   |    <===DP===> |S1|      |S2|  (B3) -|- (B2)|       |
+ *    \       ||      ¯¯        ¯¯       _       /        |
+ *    /\      \/   /   \        /   \   (B1)   /\       __|
+ *   /  \________ | LS  | ____ | RS  | _______/  \        |
+ *  |         /  \ \___/ /    \ \___/ /  \         |      | Sticks
+ *  |        /    \_____/      \_____/    \        |    __|
+ *  |       /       L3            R3       \       |
+ *   \_____/                                \_____/
+ *
+ *     |________|______|    |______|___________|
+ *       D-Pad    Left       Right    Face
+ *               Stick      Stick    Buttons
+ *
+ *  Extended: A2=Touchpad/Capture  A3=Mute  L4/R4=Paddles
+ */
+
+// W3C Gamepad API standard button order
+// Bit position = W3C button index (trivial conversion: 1 << index)
+//
+//             Gamepad                   XInput    Switch    PS3/4/5    DInput
+//              ------                   ------    ------    -------    ------
+
+// Face buttons (right cluster)
+#define USB_GAMEPAD_BUTTON_B1 (1 << 0) // A         B         Cross      2
+#define USB_GAMEPAD_BUTTON_B2 (1 << 1) // B         A         Circle     3
+#define USB_GAMEPAD_BUTTON_B3 (1 << 2) // X         Y         Square     1
+#define USB_GAMEPAD_BUTTON_B4 (1 << 3) // Y         X         Triangle   4
+
+// Shoulder buttons
+#define USB_GAMEPAD_BUTTON_L1 (1 << 4) // LB        L         L1         5
+#define USB_GAMEPAD_BUTTON_R1 (1 << 5) // RB        R         R1         6
+#define USB_GAMEPAD_BUTTON_L2 (1 << 6) // LT        ZL        L2         7
+#define USB_GAMEPAD_BUTTON_R2 (1 << 7) // RT        ZR        R2         8
+
+// Center cluster
+#define USB_GAMEPAD_BUTTON_S1 (1 << 8) // Back      -         Select     9
+#define USB_GAMEPAD_BUTTON_S2 (1 << 9) // Start     +         Start      10
+
+// Stick clicks
+#define USB_GAMEPAD_BUTTON_L3 (1 << 10) // LS        LS        L3         11
+#define USB_GAMEPAD_BUTTON_R3 (1 << 11) // RS        RS        R3         12
+
+// D-pad
+#define USB_GAMEPAD_BUTTON_DU (1 << 12) // D-Up      D-Up      D-Up       Hat
+#define USB_GAMEPAD_BUTTON_DD (1 << 13) // D-Down    D-Down    D-Down     Hat
+#define USB_GAMEPAD_BUTTON_DL (1 << 14) // D-Left    D-Left    D-Left     Hat
+#define USB_GAMEPAD_BUTTON_DR (1 << 15) // D-Right   D-Right   D-Right    Hat
+
+// Auxiliary
+#define USB_GAMEPAD_BUTTON_A1 (1 << 16) // Guide     Home      PS         13
+#define USB_GAMEPAD_BUTTON_A2 (1 << 17) // -         Capture   Touchpad   14
+#define USB_GAMEPAD_BUTTON_A3 (1 << 18) // -         -         Mute       -
+#define USB_GAMEPAD_BUTTON_A4 (1 << 19) // -         -         -          -
+
+// Paddles (extended)
+#define USB_GAMEPAD_BUTTON_L4 (1 << 20) // P1        -         -          -
+#define USB_GAMEPAD_BUTTON_R4 (1 << 21) // P2        -         -          -
+
+#define XINPUT_VID        0x045E // Microsoft
+#define XINPUT_PID        0x028E // Xbox 360 Controller
+#define XINPUT_BCD_DEVICE 0x0114 // v1.14
+
+/* XInput (Xbox 360) USB */
+
+// XInput Interface Class/Subclass/Protocol
+#define XINPUT_INTERFACE_CLASS    0xFF
+#define XINPUT_INTERFACE_SUBCLASS 0x5D
+#define XINPUT_INTERFACE_PROTOCOL 0x01
+
+#define XINPUT_BUTTON_MASK_UP    (1U << 0)
+#define XINPUT_BUTTON_MASK_DOWN  (1U << 1)
+#define XINPUT_BUTTON_MASK_LEFT  (1U << 2)
+#define XINPUT_BUTTON_MASK_RIGHT (1U << 3)
+#define XINPUT_BUTTON_MASK_START (1U << 4)
+#define XINPUT_BUTTON_MASK_BACK  (1U << 5)
+#define XINPUT_BUTTON_MASK_L3    (1U << 6)
+#define XINPUT_BUTTON_MASK_R3    (1U << 7)
+#define XINPUT_BUTTON_MASK_LB    (1U << 8)
+#define XINPUT_BUTTON_MASK_RB    (1U << 9)
+#define XINPUT_BUTTON_MASK_GUIDE (1U << 10)
+//#define XINPUT_BUTTON_MASK_UNUSED        (1U << 11)
+#define XINPUT_BUTTON_MASK_A (1U << 12)
+#define XINPUT_BUTTON_MASK_B (1U << 13)
+#define XINPUT_BUTTON_MASK_X (1U << 14)
+#define XINPUT_BUTTON_MASK_Y (1U << 15)
+
+// LED patterns for report_id 0x01
+#define XINPUT_LED_OFF          0x00
+#define XINPUT_LED_BLINK        0x01
+#define XINPUT_LED_FLASH_1      0x02
+#define XINPUT_LED_FLASH_2      0x03
+#define XINPUT_LED_FLASH_3      0x04
+#define XINPUT_LED_FLASH_4      0x05
+#define XINPUT_LED_ON_1         0x06
+#define XINPUT_LED_ON_2         0x07
+#define XINPUT_LED_ON_3         0x08
+#define XINPUT_LED_ON_4         0x09
+#define XINPUT_LED_ROTATE       0x0A
+#define XINPUT_LED_BLINK_SLOW   0x0B
+#define XINPUT_LED_BLINK_SLOW_1 0x0C
+#define XINPUT_LED_BLINK_SLOW_2 0x0D
+
+struct xinput_in_report {
+    uint8_t report_id;   /* Always 0x00 */
+    uint8_t report_size; /* Always 0x14 (20) */
+    uint16_t buttons;    /* DPAD, Start, Back, L3, R3, LB, RB, Guide, A, B, X, Y */
+    uint8_t lt;          /* Left trigger (0-255) */
+    uint8_t rt;          /* Right trigger (0-255) */
+    int16_t lx;          /* Left stick X (-32768 to 32767) */
+    int16_t ly;          /* Left stick Y (-32768 to 32767) */
+    int16_t rx;          /* Right stick X (-32768 to 32767) */
+    int16_t ry;          /* Right stick Y (-32768 to 32767) */
+    uint8_t reserved[6]; /* Reserved/padding */
+} __PACKED;
+
+struct xinput_out_report {
+    uint8_t report_id;   // 0x00 = rumble, 0x01 = LED
+    uint8_t report_size; // 0x08
+    uint8_t led;         // LED pattern (0x00 for rumble)
+    uint8_t rumble_l;    // Left motor (large, 0-255)
+    uint8_t rumble_r;    // Right motor (small, 0-255)
+    uint8_t reserved[3]; // Padding
+} __PACKED;
+
+// clang-format off
+#define XINPUT_DESCRIPTOR_LEN (9 + 16 + 7 + 7)
+
+#define XINPUT_DESCRIPTOR_INIT(bInterfaceNumber, out_ep, in_ep)                                                                     \
+    USB_INTERFACE_DESCRIPTOR_INIT(bInterfaceNumber, 0x00, 0x02, 0xff, 0x5d, 0x01, 0x00), /* XInput proprietary descriptor (0x21) */ \
+    16, 0x21, 0x00, 0x01, 0x01, 0x24, in_ep, 0x14, 0x03, 0x00, 0x03, 0x13, out_ep, 0x00, 0x03, 0x00,                               \
+    USB_ENDPOINT_DESCRIPTOR_INIT(in_ep, 0x03, 32, 0x01),                                                                        \
+    USB_ENDPOINT_DESCRIPTOR_INIT(out_ep, 0x03, 32, 0x08)
+// clang-format on
+
+#define SWITCH_VID        0x0F0D // 0x057E Nintendo Pro Controller
+#define SWITCH_PID        0x0092 // 0x2009
+#define SWITCH_BCD_DEVICE 0x0100 // v1.00
+
+// Button masks (16-bit)
+#define SWITCH_MASK_Y       (1U << 0)
+#define SWITCH_MASK_B       (1U << 1)
+#define SWITCH_MASK_A       (1U << 2)
+#define SWITCH_MASK_X       (1U << 3)
+#define SWITCH_MASK_L       (1U << 4)
+#define SWITCH_MASK_R       (1U << 5)
+#define SWITCH_MASK_ZL      (1U << 6)
+#define SWITCH_MASK_ZR      (1U << 7)
+#define SWITCH_MASK_MINUS   (1U << 8)
+#define SWITCH_MASK_PLUS    (1U << 9)
+#define SWITCH_MASK_L3      (1U << 10)
+#define SWITCH_MASK_R3      (1U << 11)
+#define SWITCH_MASK_HOME    (1U << 12)
+#define SWITCH_MASK_CAPTURE (1U << 13)
+
+// D-pad / Hat switch values
+#define SWITCH_HAT_UP         0x00
+#define SWITCH_HAT_UP_RIGHT   0x01
+#define SWITCH_HAT_RIGHT      0x02
+#define SWITCH_HAT_DOWN_RIGHT 0x03
+#define SWITCH_HAT_DOWN       0x04
+#define SWITCH_HAT_DOWN_LEFT  0x05
+#define SWITCH_HAT_LEFT       0x06
+#define SWITCH_HAT_UP_LEFT    0x07
+#define SWITCH_HAT_CENTER     0x08
+
+// Analog stick range
+#define SWITCH_JOYSTICK_MIN 0x00
+#define SWITCH_JOYSTICK_MID 0x80
+#define SWITCH_JOYSTICK_MAX 0xFF
+
+struct switch_in_report {
+    uint16_t buttons; // 16 button bits
+    uint8_t hat;      // D-pad (hat switch, 0-8)
+    uint8_t lx;       // Left stick X (0-255, 128 = center)
+    uint8_t ly;       // Left stick Y (0-255, 128 = center)
+    uint8_t rx;       // Right stick X (0-255, 128 = center)
+    uint8_t ry;       // Right stick Y (0-255, 128 = center)
+    uint8_t vendor;   // Vendor-specific byte
+} __PACKED;
+
+struct switch_out_report {
+    uint8_t data[8]; // Vendor-specific rumble data
+} __PACKED;
+
+#define HID_SWITCH_REPORT_DESC_SIZE 86
+
+// clang-format off
+#define SWITCH_DESCRIPTOR_LEN HID_CUSTOM_INOUT_DESCRIPTOR_LEN
+
+#define SWITCH_DESCRIPTOR_INIT(bInterfaceNumber, out_ep, in_ep) \
+    HID_CUSTOM_INOUT_DESCRIPTOR_INIT(bInterfaceNumber, 0x00, HID_SWITCH_REPORT_DESC_SIZE, out_ep, in_ep, 64, 0x01)
+// clang-format on
+
+struct usb_gamepad_report {
+    uint32_t buttons;
+    uint8_t lt;
+    uint8_t rt;
+    uint8_t lx;
+    uint8_t ly;
+    uint8_t rx;
+    uint8_t ry;
+};
+
+#endif /* USB_GAMEPAD_H */

+ 218 - 0
class/gamepad/usbd_gamepad.c

@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2026, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbd_core.h"
+#include "usbd_hid.h"
+#include "usbd_gamepad.h"
+
+extern int hid_class_interface_request_handler(uint8_t busid, struct usb_setup_packet *setup, uint8_t **data, uint32_t *len);
+
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t gamepad_report_buffer[64];
+
+static int xinput_vendor_class_request_handler(uint8_t busid, struct usb_setup_packet *setup, uint8_t **data, uint32_t *len)
+{
+    struct xinput_in_report xinput_report;
+
+    memset(&xinput_report, 0, sizeof(xinput_report));
+    xinput_report.report_size = 20;
+
+    memcpy(*data, &xinput_report, sizeof(xinput_report));
+    *len = sizeof(xinput_report);
+    return 0;
+}
+
+int usbd_gamepad_xinput_send_report(uint8_t ep, struct usb_gamepad_report *report)
+{
+    struct xinput_in_report *xinput_report;
+
+    xinput_report = (struct xinput_in_report *)gamepad_report_buffer;
+    memset(xinput_report, 0, sizeof(xinput_report));
+    xinput_report->report_size = 20;
+
+    if (report->buttons & USB_GAMEPAD_BUTTON_DU)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_UP;
+    if (report->buttons & USB_GAMEPAD_BUTTON_DD)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_DOWN;
+    if (report->buttons & USB_GAMEPAD_BUTTON_DL)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_LEFT;
+    if (report->buttons & USB_GAMEPAD_BUTTON_DR)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_RIGHT;
+    if (report->buttons & USB_GAMEPAD_BUTTON_S2)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_START;
+    if (report->buttons & USB_GAMEPAD_BUTTON_S1)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_BACK;
+    if (report->buttons & USB_GAMEPAD_BUTTON_L3)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_L3;
+    if (report->buttons & USB_GAMEPAD_BUTTON_R3)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_R3;
+    if (report->buttons & USB_GAMEPAD_BUTTON_L1)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_LB;
+    if (report->buttons & USB_GAMEPAD_BUTTON_R1)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_RB;
+    if (report->buttons & USB_GAMEPAD_BUTTON_A1)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_GUIDE;
+    if (report->buttons & USB_GAMEPAD_BUTTON_B1)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_A;
+    if (report->buttons & USB_GAMEPAD_BUTTON_B2)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_B;
+    if (report->buttons & USB_GAMEPAD_BUTTON_B3)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_X;
+    if (report->buttons & USB_GAMEPAD_BUTTON_B4)
+        xinput_report->buttons |= XINPUT_BUTTON_MASK_Y;
+
+    // Analog triggers (0-255), fall back to digital if analog is 0 but button pressed
+    xinput_report->lt = report->lt;
+    xinput_report->rt = report->rt;
+    if (xinput_report->lt == 0 && (report->buttons & USB_GAMEPAD_BUTTON_L2))
+        xinput_report->lt = 0xFF;
+    if (xinput_report->rt == 0 && (report->buttons & USB_GAMEPAD_BUTTON_R2))
+        xinput_report->rt = 0xFF;
+
+    return usbd_ep_start_write(0, ep, gamepad_report_buffer, sizeof(struct xinput_in_report));
+}
+
+// Convert gamepad dpad mask to switch hat value
+static uint8_t convert_dpad_to_switch_hat(uint32_t buttons)
+{
+    // Joypad uses active-high (1 = pressed)
+    uint8_t up = (buttons & USB_GAMEPAD_BUTTON_DU) ? 1 : 0;
+    uint8_t down = (buttons & USB_GAMEPAD_BUTTON_DD) ? 1 : 0;
+    uint8_t left = (buttons & USB_GAMEPAD_BUTTON_DL) ? 1 : 0;
+    uint8_t right = (buttons & USB_GAMEPAD_BUTTON_DR) ? 1 : 0;
+
+    if (up && right)
+        return SWITCH_HAT_UP_RIGHT;
+    if (up && left)
+        return SWITCH_HAT_UP_LEFT;
+    if (down && right)
+        return SWITCH_HAT_DOWN_RIGHT;
+    if (down && left)
+        return SWITCH_HAT_DOWN_LEFT;
+    if (up)
+        return SWITCH_HAT_UP;
+    if (down)
+        return SWITCH_HAT_DOWN;
+    if (left)
+        return SWITCH_HAT_LEFT;
+    if (right)
+        return SWITCH_HAT_RIGHT;
+
+    return SWITCH_HAT_CENTER;
+}
+
+int usbd_gamepad_switch_send_report(uint8_t ep, struct usb_gamepad_report *report)
+{
+    struct switch_in_report *switch_report;
+
+    switch_report = (struct switch_in_report *)gamepad_report_buffer;
+    memset(switch_report, 0, sizeof(switch_report));
+
+    if (report->buttons & USB_GAMEPAD_BUTTON_S1)
+        switch_report->buttons |= SWITCH_MASK_MINUS;
+    if (report->buttons & USB_GAMEPAD_BUTTON_S2)
+        switch_report->buttons |= SWITCH_MASK_PLUS;
+    if (report->buttons & USB_GAMEPAD_BUTTON_L1)
+        switch_report->buttons |= SWITCH_MASK_L;
+    if (report->buttons & USB_GAMEPAD_BUTTON_R1)
+        switch_report->buttons |= SWITCH_MASK_R;
+    if (report->buttons & USB_GAMEPAD_BUTTON_L2)
+        switch_report->buttons |= SWITCH_MASK_ZL;
+    if (report->buttons & USB_GAMEPAD_BUTTON_R2)
+        switch_report->buttons |= SWITCH_MASK_ZR;
+    if (report->buttons & USB_GAMEPAD_BUTTON_L3)
+        switch_report->buttons |= SWITCH_MASK_L3;
+    if (report->buttons & USB_GAMEPAD_BUTTON_R3)
+        switch_report->buttons |= SWITCH_MASK_R3;
+    if (report->buttons & USB_GAMEPAD_BUTTON_A1)
+        switch_report->buttons |= SWITCH_MASK_HOME;
+    if (report->buttons & USB_GAMEPAD_BUTTON_A2)
+        switch_report->buttons |= SWITCH_MASK_CAPTURE;
+    if (report->buttons & USB_GAMEPAD_BUTTON_B1)
+        switch_report->buttons |= SWITCH_MASK_B;
+    if (report->buttons & USB_GAMEPAD_BUTTON_B2)
+        switch_report->buttons |= SWITCH_MASK_A;
+    if (report->buttons & USB_GAMEPAD_BUTTON_B3)
+        switch_report->buttons |= SWITCH_MASK_Y;
+    if (report->buttons & USB_GAMEPAD_BUTTON_B4)
+        switch_report->buttons |= SWITCH_MASK_X;
+
+    switch_report->hat = convert_dpad_to_switch_hat(report->buttons);
+
+    // Analog sticks (HID convention: 0=up, 255=down - no inversion needed)
+    switch_report->lx = report->lx;
+    switch_report->ly = report->ly;
+    switch_report->rx = report->rx;
+    switch_report->ry = report->ry;
+
+    switch_report->vendor = 0;
+
+    return usbd_ep_start_write(0, ep, gamepad_report_buffer, sizeof(struct switch_in_report));
+}
+
+struct usbd_interface *usbd_gamepad_xinput_init_intf(struct usbd_interface *intf)
+{
+    intf->class_interface_handler = NULL;
+    intf->class_endpoint_handler = NULL;
+    intf->vendor_handler = xinput_vendor_class_request_handler;
+    intf->notify_handler = NULL;
+
+    return intf;
+}
+
+static const uint8_t hid_switch_report_desc[HID_SWITCH_REPORT_DESC_SIZE] = {
+    0x05, 0x01,       // Usage Page (Generic Desktop Ctrls)
+    0x09, 0x05,       // Usage (Game Pad)
+    0xA1, 0x01,       // Collection (Application)
+    0x15, 0x00,       //   Logical Minimum (0)
+    0x25, 0x01,       //   Logical Maximum (1)
+    0x35, 0x00,       //   Physical Minimum (0)
+    0x45, 0x01,       //   Physical Maximum (1)
+    0x75, 0x01,       //   Report Size (1)
+    0x95, 0x10,       //   Report Count (16)
+    0x05, 0x09,       //   Usage Page (Button)
+    0x19, 0x01,       //   Usage Minimum (Button 1)
+    0x29, 0x10,       //   Usage Maximum (Button 16)
+    0x81, 0x02,       //   Input (Data,Var,Abs)
+    0x05, 0x01,       //   Usage Page (Generic Desktop Ctrls)
+    0x25, 0x07,       //   Logical Maximum (7)
+    0x46, 0x3B, 0x01, //   Physical Maximum (315)
+    0x75, 0x04,       //   Report Size (4)
+    0x95, 0x01,       //   Report Count (1)
+    0x65, 0x14,       //   Unit (Eng Rot:Angular Pos)
+    0x09, 0x39,       //   Usage (Hat switch)
+    0x81, 0x42,       //   Input (Data,Var,Abs,Null)
+    0x65, 0x00,       //   Unit (None)
+    0x95, 0x01,       //   Report Count (1)
+    0x81, 0x01,       //   Input (Const) - 4-bit padding
+    0x26, 0xFF, 0x00, //   Logical Maximum (255)
+    0x46, 0xFF, 0x00, //   Physical Maximum (255)
+    0x09, 0x30,       //   Usage (X) - Left Stick X
+    0x09, 0x31,       //   Usage (Y) - Left Stick Y
+    0x09, 0x32,       //   Usage (Z) - Right Stick X
+    0x09, 0x35,       //   Usage (Rz) - Right Stick Y
+    0x75, 0x08,       //   Report Size (8)
+    0x95, 0x04,       //   Report Count (4)
+    0x81, 0x02,       //   Input (Data,Var,Abs)
+    0x06, 0x00, 0xFF, //   Usage Page (Vendor Defined)
+    0x09, 0x20,       //   Usage (0x20)
+    0x95, 0x01,       //   Report Count (1)
+    0x81, 0x02,       //   Input (Data,Var,Abs) - Vendor byte
+    0x0A, 0x21, 0x26, //   Usage (0x2621)
+    0x95, 0x08,       //   Report Count (8)
+    0x91, 0x02,       //   Output (Data,Var,Abs) - Rumble
+    0xC0,             // End Collection
+};
+
+struct usbd_interface *usbd_gamepad_switch_init_intf(struct usbd_interface *intf)
+{
+    intf->class_interface_handler = hid_class_interface_request_handler;
+    intf->class_endpoint_handler = NULL;
+    intf->vendor_handler = NULL;
+    intf->notify_handler = NULL;
+
+    intf->hid_report_descriptor = hid_switch_report_desc;
+    intf->hid_report_descriptor_len = HID_SWITCH_REPORT_DESC_SIZE;
+    return intf;
+}

+ 22 - 0
class/gamepad/usbd_gamepad.h

@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2026, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef USBD_GAMEPAD_H
+#define USBD_GAMEPAD_H
+
+#include "usb_gamepad.h"
+
+#define USBD_GAMEPAD_MODE_XINPUT  0
+#define USBD_GAMEPAD_MODE_SWITCH  1
+#define USBD_GAMEPAD_MODE_XBOXONE 2
+#define USBD_GAMEPAD_MODE_PS4     3
+
+struct usbd_interface *usbd_gamepad_xinput_init_intf(struct usbd_interface *intf);
+struct usbd_interface *usbd_gamepad_switch_init_intf(struct usbd_interface *intf);
+
+int usbd_gamepad_xinput_send_report(uint8_t ep, struct usb_gamepad_report *report);
+int usbd_gamepad_switch_send_report(uint8_t ep, struct usb_gamepad_report *report);
+
+#endif /* USBD_GAMEPAD_H */

+ 250 - 0
demo/gamepad_template.c

@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2026, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbd_core.h"
+#include "usbd_gamepad.h"
+
+#define GAMEPAD_IN_EP  0x81
+#define GAMEPAD_OUT_EP 0x02
+
+#define USBD_MAX_POWER 500
+
+static const uint8_t xinput_device_descriptor[] = {
+    USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, XINPUT_VID, XINPUT_PID, XINPUT_BCD_DEVICE, 0x01)
+};
+
+static const uint8_t switch_device_descriptor[] = {
+    USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, SWITCH_VID, SWITCH_PID, SWITCH_BCD_DEVICE, 0x01)
+};
+
+static const uint8_t xinput_config_descriptor[] = {
+    USB_CONFIG_DESCRIPTOR_INIT((9 + XINPUT_DESCRIPTOR_LEN), 0x01, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
+    XINPUT_DESCRIPTOR_INIT(0x00, GAMEPAD_OUT_EP, GAMEPAD_IN_EP)
+};
+
+static const uint8_t switch_config_descriptor[] = {
+    USB_CONFIG_DESCRIPTOR_INIT((9 + SWITCH_DESCRIPTOR_LEN), 0x01, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
+    SWITCH_DESCRIPTOR_INIT(0x00, GAMEPAD_OUT_EP, GAMEPAD_IN_EP)
+};
+
+static const char *xinput_string_descriptors[] = {
+    (const char[]){ 0x09, 0x04 }, /* Langid */
+    "Microsoft",                  /* Manufacturer */
+    "XInput STANDARD GAMEPAD",    /* Product */
+    "1.0",                        /* Serial Number */
+};
+
+static const char *switch_string_descriptors[] = {
+    (const char[]){ 0x09, 0x04 }, /* Langid */
+    "HORI",                       /* Manufacturer */
+    "Switch Pro Controller",      /* Product */
+    "1.0",                        /* Serial Number */
+};
+
+uint8_t gamepad_mode = USBD_GAMEPAD_MODE_XINPUT;
+bool gamepad_init_flag = false;
+
+static const uint8_t *device_descriptor_callback(uint8_t speed)
+{
+    switch (gamepad_mode) {
+        case USBD_GAMEPAD_MODE_XINPUT:
+            return xinput_device_descriptor;
+        case USBD_GAMEPAD_MODE_SWITCH:
+            return switch_device_descriptor;
+        case USBD_GAMEPAD_MODE_XBOXONE:
+            break;
+        case USBD_GAMEPAD_MODE_PS4:
+            break;
+
+        default:
+            break;
+    }
+    return NULL;
+}
+
+static const uint8_t *config_descriptor_callback(uint8_t speed)
+{
+    switch (gamepad_mode) {
+        case USBD_GAMEPAD_MODE_XINPUT:
+            return xinput_config_descriptor;
+        case USBD_GAMEPAD_MODE_SWITCH:
+            return switch_config_descriptor;
+        case USBD_GAMEPAD_MODE_XBOXONE:
+            break;
+        case USBD_GAMEPAD_MODE_PS4:
+            break;
+
+        default:
+            break;
+    }
+    return NULL;
+}
+
+static const uint8_t *device_quality_descriptor_callback(uint8_t speed)
+{
+    return NULL;
+}
+
+static const char *string_descriptor_callback(uint8_t speed, uint8_t index)
+{
+    if (index > 3) {
+        return NULL;
+    }
+
+    switch (gamepad_mode) {
+        case USBD_GAMEPAD_MODE_XINPUT:
+            return xinput_string_descriptors[index];
+        case USBD_GAMEPAD_MODE_SWITCH:
+            return switch_string_descriptors[index];
+        case USBD_GAMEPAD_MODE_XBOXONE:
+            break;
+        case USBD_GAMEPAD_MODE_PS4:
+            break;
+
+        default:
+            break;
+    }
+    return NULL;
+}
+
+const struct usb_descriptor gamepad_descriptor = {
+    .device_descriptor_callback = device_descriptor_callback,
+    .config_descriptor_callback = config_descriptor_callback,
+    .device_quality_descriptor_callback = device_quality_descriptor_callback,
+    .string_descriptor_callback = string_descriptor_callback
+};
+
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t gamepad_read_buffer[64];
+struct usb_gamepad_report gamepad_report;
+
+#define GAMEPAD_STATE_IDLE 0
+#define GAMEPAD_STATE_BUSY 1
+
+volatile uint8_t gamepad_state = GAMEPAD_STATE_IDLE;
+
+static void usbd_event_handler(uint8_t busid, uint8_t event)
+{
+    switch (event) {
+        case USBD_EVENT_RESET:
+            break;
+        case USBD_EVENT_CONNECTED:
+            break;
+        case USBD_EVENT_DISCONNECTED:
+            break;
+        case USBD_EVENT_RESUME:
+            break;
+        case USBD_EVENT_SUSPEND:
+            break;
+        case USBD_EVENT_CONFIGURED:
+            usbd_ep_start_read(busid, GAMEPAD_OUT_EP, gamepad_read_buffer, usbd_get_ep_mps(busid, GAMEPAD_OUT_EP));
+            break;
+        case USBD_EVENT_SET_REMOTE_WAKEUP:
+            break;
+        case USBD_EVENT_CLR_REMOTE_WAKEUP:
+            break;
+
+        default:
+            break;
+    }
+}
+
+static void usbd_gamepad_int_in_callback(uint8_t busid, uint8_t ep, uint32_t nbytes)
+{
+    gamepad_state = GAMEPAD_STATE_IDLE;
+}
+
+void usbd_gamepad_int_out_callback(uint8_t busid, uint8_t ep, uint32_t nbytes)
+{
+    usbd_ep_start_read(busid, GAMEPAD_OUT_EP, gamepad_read_buffer, usbd_get_ep_mps(busid, GAMEPAD_OUT_EP));
+}
+
+/*!< endpoint call back */
+static struct usbd_endpoint gamepad_in_ep = {
+    .ep_cb = usbd_gamepad_int_in_callback,
+    .ep_addr = GAMEPAD_IN_EP
+};
+
+static struct usbd_endpoint gamepad_out_ep = {
+    .ep_cb = usbd_gamepad_int_out_callback,
+    .ep_addr = GAMEPAD_OUT_EP
+};
+
+static struct usbd_interface intf0;
+
+void gamepad_init(uint8_t busid, uintptr_t reg_base)
+{
+    if (gamepad_init_flag) {
+        return;
+    }
+
+    gamepad_init_flag = true;
+
+    usbd_desc_register(busid, &gamepad_descriptor);
+
+    switch (gamepad_mode) {
+        case USBD_GAMEPAD_MODE_XINPUT:
+            usbd_add_interface(busid, usbd_gamepad_xinput_init_intf(&intf0));
+            break;
+        case USBD_GAMEPAD_MODE_SWITCH:
+            usbd_add_interface(busid, usbd_gamepad_switch_init_intf(&intf0));
+            break;
+        case USBD_GAMEPAD_MODE_XBOXONE:
+            break;
+        case USBD_GAMEPAD_MODE_PS4:
+            break;
+
+        default:
+            break;
+    }
+
+    usbd_add_endpoint(busid, &gamepad_in_ep);
+    usbd_add_endpoint(busid, &gamepad_out_ep);
+    usbd_initialize(busid, reg_base, usbd_event_handler);
+}
+
+void gamepad_change_mode(uint8_t mode, uintptr_t reg_base)
+{
+    gamepad_mode = mode;
+
+    if (gamepad_init_flag) {
+        usbd_deinitialize(0);
+    }
+    gamepad_init_flag = false;
+    gamepad_init(0, reg_base);
+}
+
+void gamepad_test(uint8_t busid)
+{
+    static uint32_t test_counter = 0;
+
+    if (usb_device_is_configured(busid) == false) {
+        return;
+    }
+
+    gamepad_state = GAMEPAD_STATE_BUSY;
+    memset(&gamepad_report, 0, sizeof(gamepad_report));
+
+    gamepad_report.buttons = (1 << (test_counter % 18));
+
+    switch (gamepad_mode) {
+        case USBD_GAMEPAD_MODE_XINPUT:
+            usbd_gamepad_xinput_send_report(GAMEPAD_IN_EP, &gamepad_report);
+            break;
+        case USBD_GAMEPAD_MODE_SWITCH:
+            usbd_gamepad_switch_send_report(GAMEPAD_IN_EP, &gamepad_report);
+            break;
+        case USBD_GAMEPAD_MODE_XBOXONE:
+            break;
+        case USBD_GAMEPAD_MODE_PS4:
+            break;
+
+        default:
+            break;
+    }
+
+    test_counter++;
+    while (gamepad_state == GAMEPAD_STATE_BUSY) {
+    }
+}