Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 32blit-pico/input/usb_hid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct GamepadMapping {
#define NO 0xFF

static const GamepadMapping gamepad_mappings[]{
{0x057E2009, 3, 2, 1, 0, 17, 16, 19, 18, 8, 12, 11}, // Switch Pro Controller
{0x15320705, 0, 1, 3, 4, NO, NO, NO, NO, 16, 15, 13}, // Razer Raiju Mobile
{0x20D6A711, 2, 1, 3, 0, NO, NO, NO, NO, 8, 12, 10}, // PowerA wired Switch pro controller
{0x2DC89018, 0, 1, 3, 4, NO, NO, NO, NO, 10, 11, NO}, // 8BitDo Zero 2
Expand Down
148 changes: 146 additions & 2 deletions 32blit-pico/usb/host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@
static int hid_report_id = -1;
static uint16_t buttons_offset = 0, num_buttons = 0;
static uint16_t hat_offset = 0xFFFF, stick_offset = 0;
static uint8_t axis_size = 8;

enum class SwitchProInit : uint8_t {
Mounted = 0,
Handshake,
ReadCalibration,
};

static SwitchProInit switch_pro_init_state = SwitchProInit::Mounted;
static uint8_t switch_pro_seq = 0;
static uint16_t switch_pro_calibration_x[2], switch_pro_calibration_y[2];

uint32_t hid_gamepad_id = 0;
bool hid_keyboard_detected = false;
Expand All @@ -20,6 +31,106 @@ uint8_t hid_hat = 8;
uint32_t hid_buttons = 0;
uint8_t hid_keys[6]{};

static bool is_switch_pro_controller() {
return hid_gamepad_id == 0x057E2009;
}

static void switch_pro_read_flash(uint8_t dev_addr, uint8_t instance, uint32_t addr, uint8_t len) {
uint8_t buf[16];
buf[0] = 1; // rumble + command
buf[1] = switch_pro_seq;
// rumble data
buf[2] = 0; buf[3] = 1; buf[4] = buf[5] = 0x40;
buf[6] = 0; buf[7] = 1; buf[8] = buf[9] = 0x40;

buf[10] = 0x10; // read flash
buf[11] = addr & 0xFF;
buf[12] = (addr >> 8) & 0xFF;
buf[13] = (addr >> 16) & 0xFF;
buf[14] = (addr >> 24) & 0xFF;
buf[15] = len;

#if TUSB_VERSION_MINOR >= 16
tuh_hid_send_report(dev_addr, instance, 0, buf, 16);
#endif

switch_pro_seq++;
if(switch_pro_seq > 0xF)
switch_pro_seq = 0;
}

static void switch_pro_mount(uint8_t dev_addr, uint8_t instance) {
uint8_t data = 2; // handshake
#if TUSB_VERSION_MINOR >= 16
tuh_hid_send_report(dev_addr, instance, 0x80, &data, 1);
#endif

switch_pro_init_state = SwitchProInit::Mounted;

// report descriptor is inaccurate
hid_report_id = 0x30;
buttons_offset = 2 * 8;
num_buttons = 24;
stick_offset = 5 * 8;
axis_size = 12;
hat_offset = 0xFFFF; // no hat, only buttons
}

static void switch_pro_parse_left_stick_calibration(const uint8_t *data) {
uint16_t max_x = data[0] | (data[1] & 0xF) << 8;
uint16_t max_y = data[1] >> 4 | data[2] << 4;
uint16_t center_x = data[3] | (data[4] & 0xF) << 8;
uint16_t center_y = data[4] >> 4 | data[5] << 4;
uint16_t min_x = data[6] | (data[7] & 0xF) << 8;
uint16_t min_y = data[7] >> 4 | data[8] << 4;

switch_pro_calibration_x[0] = center_x - min_x;
switch_pro_calibration_x[1] = center_x + max_x - switch_pro_calibration_x[0];
switch_pro_calibration_y[0] = center_y - min_y;
switch_pro_calibration_y[1] = center_y + max_y - switch_pro_calibration_y[0];
}

static void switch_pro_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) {

if(switch_pro_init_state == SwitchProInit::Handshake) {
// first report after sending usb enable
// request left stick calibration data
switch_pro_read_flash(dev_addr, instance, 0x8010, 11);
switch_pro_init_state = SwitchProInit::ReadCalibration;
}

if(report[0] == 0x81 && report[1] == 2) { // handshake
uint8_t data = 4; // disable bluetooth / enable usb
#if TUSB_VERSION_MINOR >= 16
tuh_hid_send_report(dev_addr, instance, 0x80, &data, 1);
#endif
switch_pro_init_state = SwitchProInit::Handshake;
} else if(report[0] == 0x21) {
// response to cmd
auto cmd = report[14];

if(cmd == 0x10) { // flash read
uint32_t addr = report[15] | report[16] << 8 | report[17] << 16 | report[18] << 24;
// uint8_t len = report[19];
auto read_data = report + 20;

if(addr == 0x603D) {
// factory left stick calibration
switch_pro_parse_left_stick_calibration(read_data);
} else if(addr == 0x8010) {
// user left stick calibration
if(read_data[0] == 0xB2 && read_data[1] == 0xA1) {
// magic present, use it
switch_pro_parse_left_stick_calibration(read_data + 2);
} else {
// request factory calibration
switch_pro_read_flash(dev_addr, instance, 0x603D, 9);
}
}
}
}
}

void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) {
uint16_t vid = 0, pid = 0;
tuh_vid_pid_get(dev_addr, &vid, &pid);
Expand All @@ -36,6 +147,9 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_re
return;
}

hat_offset = 0xFFFF;
axis_size = 8;

// basic and probably wrong report descriptor parsing
auto desc_end = desc_report + desc_len;
auto p = desc_report;
Expand Down Expand Up @@ -104,6 +218,10 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_re

hid_gamepad_id = (vid << 16) | pid;

// switch pro controller
if(is_switch_pro_controller())
switch_pro_mount(dev_addr, instance);

if(!tuh_hid_receive_report(dev_addr, instance)) {
printf("Cound not request report!\n");
}
Expand All @@ -130,6 +248,10 @@ void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t cons
return;
}

// switch pro controller setup
if(is_switch_pro_controller())
switch_pro_report(dev_addr, instance, report, len);

// check report id if we have one
if(hid_report_id == -1 || report[0] == hid_report_id) {
// I hope these are reasonably aligned
Expand All @@ -138,8 +260,30 @@ void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t cons
else
hid_hat = 8;

hid_joystick[0] = report_data[stick_offset / 8];
hid_joystick[1] = report_data[stick_offset / 8 + 1];
if(axis_size == 8) {
hid_joystick[0] = report_data[stick_offset / 8];
hid_joystick[1] = report_data[stick_offset / 8 + 1];
} else if(axis_size == 12) {
uint16_t x = report_data[stick_offset / 8] | (report_data[stick_offset / 8 + 1] & 0xF) << 8;
uint16_t y = report_data[stick_offset / 8 + 1] >> 4 | (report_data[stick_offset / 8 + 2]) << 4;

// take the high bits
hid_joystick[0] = x >> 4;
hid_joystick[1] = y >> 4;

// apply switch pro calibration
if(is_switch_pro_controller()) {
int calib_x = (x - switch_pro_calibration_x[0]) * 255 / switch_pro_calibration_x[1];
int calib_y = (y - switch_pro_calibration_y[0]) * 255 / switch_pro_calibration_y[1];

// clamp
calib_x = calib_x < 0 ? 0 : (calib_x > 255 ? 255 : calib_x);
calib_y = calib_y < 0 ? 0 : (calib_y > 255 ? 255 : calib_y);

hid_joystick[0] = calib_x;
hid_joystick[1] = 0xFF - calib_y;
}
}

// get up to 32 buttons
hid_buttons = 0;
Expand Down