USB: Add support for Ryojouhen DenshaCon

Add support for TCPP20014 controllers, datasheets courtesy of Marc Riera. Tested with a One Handle MasCon for Nintendo Switch as the controller device.

Link: https://marcriera.github.io/ddgo-controller-docs/controllers/usb/tcpp20014/
This commit is contained in:
Joe Stringer 2024-08-18 14:41:59 -07:00 committed by lightningterror
parent 904d6b9c78
commit ed3405b5a9
2 changed files with 109 additions and 0 deletions

View File

@ -30,6 +30,7 @@ namespace usb_pad
static const char* subtypes[] = {
TRANSLATE_NOOP("USB", "Type 2"),
TRANSLATE_NOOP("USB", "Shinkansen"),
TRANSLATE_NOOP("USB", "Ryojōhen"),
};
return subtypes;
}
@ -51,6 +52,11 @@ namespace usb_pad
CID_TC_SELECT,
CID_TC_START,
// Ryojouhen controller has 7 buttons, map L/R onto existing indexes.
CID_TC_CAMERA,
CID_TC_L = CID_TC_C,
CID_TC_R = CID_TC_D,
BUTTONS_OFFSET = CID_TC_B,
};
@ -78,6 +84,26 @@ namespace usb_pad
return bindings;
}
case TRAIN_RYOJOUHEN:
{
static constexpr const InputBindingInfo bindings[] = {
{"Power", TRANSLATE_NOOP("USB", "Power"), ICON_PF_LEFT_ANALOG_DOWN, InputBindingInfo::Type::Axis, CID_TC_POWER, GenericInputBinding::LeftStickDown},
{"Brake", TRANSLATE_NOOP("USB", "Brake"), ICON_PF_LEFT_ANALOG_UP, InputBindingInfo::Type::Axis, CID_TC_BRAKE, GenericInputBinding::LeftStickUp},
{"Up", TRANSLATE_NOOP("USB", "D-Pad Up"), ICON_PF_DPAD_UP, InputBindingInfo::Type::Button, CID_TC_UP, GenericInputBinding::DPadUp},
{"Down", TRANSLATE_NOOP("USB", "D-Pad Down"), ICON_PF_DPAD_DOWN, InputBindingInfo::Type::Button, CID_TC_DOWN, GenericInputBinding::DPadDown},
{"Left", TRANSLATE_NOOP("USB", "D-Pad Left"), ICON_PF_DPAD_LEFT, InputBindingInfo::Type::Button, CID_TC_LEFT, GenericInputBinding::DPadLeft},
{"Right", TRANSLATE_NOOP("USB", "D-Pad Right"), ICON_PF_DPAD_RIGHT, InputBindingInfo::Type::Button, CID_TC_RIGHT, GenericInputBinding::DPadRight},
{"Announce", TRANSLATE_NOOP("USB", "Announce"), ICON_PF_KEY_B, InputBindingInfo::Type::Button, CID_TC_A, GenericInputBinding::Circle},
{"Horn", TRANSLATE_NOOP("USB", "Horn"), ICON_PF_KEY_A, InputBindingInfo::Type::Button, CID_TC_B, GenericInputBinding::Cross},
{"LeftDoor", TRANSLATE_NOOP("USB", "Left Door"), ICON_PF_KEY_L, InputBindingInfo::Type::Button, CID_TC_L, GenericInputBinding::Square},
{"RightDoor", TRANSLATE_NOOP("USB", "Right Door"), ICON_PF_KEY_R, InputBindingInfo::Type::Button, CID_TC_R, GenericInputBinding::Triangle},
{"Camera", TRANSLATE_NOOP("USB", "Camera Button"), ICON_PF_CAMERA, InputBindingInfo::Type::Button, CID_TC_CAMERA, GenericInputBinding::R1},
{"Select", TRANSLATE_NOOP("USB", "Select"), ICON_PF_SELECT_SHARE, InputBindingInfo::Type::Button, CID_TC_SELECT, GenericInputBinding::Select},
{"Start", TRANSLATE_NOOP("USB", "Start"), ICON_PF_START, InputBindingInfo::Type::Button, CID_TC_START, GenericInputBinding::Start},
};
return bindings;
}
default:
break;
}
@ -151,6 +177,7 @@ namespace usb_pad
case CID_TC_D:
case CID_TC_SELECT:
case CID_TC_START:
case CID_TC_CAMERA:
{
return (button_at(s->data.buttons, bind_index) != 0u) ? 1.0f : 0.0f;
}
@ -196,6 +223,7 @@ namespace usb_pad
case CID_TC_D:
case CID_TC_SELECT:
case CID_TC_START:
case CID_TC_CAMERA:
{
const u32 mask = button_mask(bind_index);
if (value >= 0.5f)
@ -345,7 +373,44 @@ namespace usb_pad
return notches[std::size(notches) - 1].second;
}
static u8 dct03_power(u8 value)
{
// (N) 0x00 0x3C 0x78 0xB4 0xF0 (P4)
static std::pair<u8, u8> const notches[] = {
// { control_in, emulated_out },
{0xC0, 0xF0},
{0x90, 0xB4},
{0x50, 0x78},
{0x30, 0x3C},
{0x00, 0x00},
};
for (const auto& x : notches)
{
if (value >= x.first)
return x.second;
}
return notches[std::size(notches) - 1].second;
}
static u8 dct03_brake(u8 value)
{
// Depending on the game, this device presents in either Non Self-Lapping or Self-Lapping mode.
// NSL Release Maintain Increase Emergency
// 0x23-0x64 0x65-0x89 0x8A-0xD6 0xD7
// SL Released B1 B2 B3 B4 B5 B6 EB
// 0x23-0x2A 0x2B-0x3C 0x3D-0x4E 0x4F-0x63 0x64-0x8A 0x8B-0xB0 0xB1-0xD6 0xD7
if (0x18 >= value)
return 0x23;
if (value >= 0xF8)
return 0xD7;
// We've trimmed 0x20 (0x8 for EB, 0x18 for release) leaving us with ~0xE0.
// 0xD7-0x23=0xB3 represents about 80% of the number space of 0xE0, so compress the remaining input values into that range.
u8 offset = 0x9 + value / 85;
return value / 5 * 4 + offset;
}
#define get_ab(buttons) (button_at(buttons, CID_TC_A) | button_at(buttons, CID_TC_B))
#define get_cd(buttons) (button_at(buttons, CID_TC_C) | button_at(buttons, CID_TC_D))
#define swap_cd(buttons) ((button_at(buttons, CID_TC_C) << 1) | (button_at(buttons, CID_TC_D) >> 1))
#define get_ss(buttons) (button_at(buttons, CID_TC_START) | button_at(buttons, CID_TC_SELECT))
@ -355,6 +420,10 @@ namespace usb_pad
{
return ((get_ab(buttons) << 2) | (swap_cd(buttons) >> 2) | get_ss(buttons));
}
constexpr u8 dct03_buttons(u8 buttons)
{
return (get_ab(buttons) | (button_at(buttons, CID_TC_CAMERA) >> 4) | ((get_cd(buttons) | get_ss(buttons)) << 1));
}
static void train_handle_data(USBDevice* dev, USBPacket* p)
{
@ -394,6 +463,17 @@ namespace usb_pad
usb_packet_copy(p, &out, sizeof(out));
break;
}
case TRAIN_RYOJOUHEN:
{
TrainConData_Ryojouhen out = {};
out.brake = dct03_brake(s->data.brake);
out.power = dct03_power(s->data.power);
out.horn = 0xFF; // Dedicated horn button, skip.
out.hat = s->data.hatswitch & 0x0F;
out.buttons = dct03_buttons(s->data.buttons);
usb_packet_copy(p, &out, sizeof(out));
break;
}
default:
Console.Error("Unhandled TrainController USB_TOKEN_IN pid=%d ep=%u type=%u", p->pid, p->ep->nr, s->type);
p->status = USB_RET_IOERROR;
@ -419,6 +499,11 @@ namespace usb_pad
if (usb_desc_parse_dev(dct02_dev_descriptor, sizeof(dct02_dev_descriptor), s->desc, s->desc_dev) < 0)
goto fail;
break;
case TRAIN_RYOJOUHEN:
s->desc.str = dct03_desc_strings;
if (usb_desc_parse_dev(dct03_dev_descriptor, sizeof(dct03_dev_descriptor), s->desc, s->desc_dev) < 0)
goto fail;
break;
default:
goto fail;

View File

@ -13,6 +13,7 @@ namespace usb_pad
{
TRAIN_TYPE2, // TCPP20009 or similar
TRAIN_SHINKANSEN, // TCPP20011
TRAIN_RYOJOUHEN, // TCPP20014
TRAIN_COUNT,
};
@ -51,6 +52,17 @@ namespace usb_pad
u8 pad;
};
static_assert(sizeof(TrainConData_Shinkansen) == 6);
struct TrainConData_Ryojouhen
{
u8 brake;
u8 power;
u8 horn;
u8 hat;
u8 buttons;
u8 pad[3];
};
static_assert(sizeof(TrainConData_Ryojouhen) == 8);
#pragma pack(pop)
struct TrainDeviceState
@ -157,4 +169,16 @@ namespace usb_pad
// dct02_dev_descriptor
DEFINE_DCT_DEV_DESCRIPTOR(dct02, 0x05, 0x0005);
// ---- Ryojouhen controller ----
static const USBDescStrings dct03_desc_strings = {
"",
"TAITO",
"TAITO_DENSYA_CON_T03",
"TCPP20014",
};
// dct03_dev_descriptor
DEFINE_DCT_DEV_DESCRIPTOR(dct03, 0xFF, 0x0007);
} // namespace usb_pad