diff --git a/CMakeLists.txt b/CMakeLists.txt index 668f75ec..39d1a273 100644 --- a/CMakeLists.txt +++ b/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) diff --git a/cherryusb.cmake b/cherryusb.cmake index c8aa8621..69573249 100644 --- a/cherryusb.cmake +++ b/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) diff --git a/class/gamepad/usb_gamepad.h b/class/gamepad/usb_gamepad.h new file mode 100644 index 00000000..28e2e646 --- /dev/null +++ b/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 */ \ No newline at end of file diff --git a/class/gamepad/usbd_gamepad.c b/class/gamepad/usbd_gamepad.c new file mode 100644 index 00000000..9967f8c6 --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/class/gamepad/usbd_gamepad.h b/class/gamepad/usbd_gamepad.h new file mode 100644 index 00000000..8f4f20a8 --- /dev/null +++ b/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 */ \ No newline at end of file diff --git a/demo/gamepad_template.c b/demo/gamepad_template.c new file mode 100644 index 00000000..2decbc7c --- /dev/null +++ b/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) { + } +} \ No newline at end of file