USB: Add support for Shinkansen DenshaCon

Add support for TCPP20011 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/tcpp20011
This commit is contained in:
Joe Stringer 2024-08-18 10:45:12 -07:00 committed by lightningterror
parent edcd1d0f9f
commit 904d6b9c78
2 changed files with 178 additions and 31 deletions

View File

@ -25,6 +25,15 @@ namespace usb_pad
return "TrainController"; return "TrainController";
} }
std::span<const char*> TrainDevice::SubTypes() const
{
static const char* subtypes[] = {
TRANSLATE_NOOP("USB", "Type 2"),
TRANSLATE_NOOP("USB", "Shinkansen"),
};
return subtypes;
}
enum TrainControlID enum TrainControlID
{ {
CID_TC_POWER, CID_TC_POWER,
@ -47,22 +56,32 @@ namespace usb_pad
std::span<const InputBindingInfo> TrainDevice::Bindings(u32 subtype) const std::span<const InputBindingInfo> TrainDevice::Bindings(u32 subtype) const
{ {
static constexpr const InputBindingInfo bindings[] = { switch (subtype)
{"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}, case TRAIN_TYPE2:
{"Up", TRANSLATE_NOOP("USB", "D-Pad Up"), ICON_PF_DPAD_UP, InputBindingInfo::Type::Button, CID_TC_UP, GenericInputBinding::DPadUp}, case TRAIN_SHINKANSEN:
{"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}, static constexpr const InputBindingInfo bindings[] = {
{"Right", TRANSLATE_NOOP("USB", "D-Pad Right"), ICON_PF_DPAD_RIGHT, InputBindingInfo::Type::Button, CID_TC_RIGHT, GenericInputBinding::DPadRight}, {"Power", TRANSLATE_NOOP("USB", "Power"), ICON_PF_LEFT_ANALOG_DOWN, InputBindingInfo::Type::Axis, CID_TC_POWER, GenericInputBinding::LeftStickDown},
{"A", TRANSLATE_NOOP("USB", "A Button"), ICON_PF_KEY_A, InputBindingInfo::Type::Button, CID_TC_A, GenericInputBinding::Square}, {"Brake", TRANSLATE_NOOP("USB", "Brake"), ICON_PF_LEFT_ANALOG_UP, InputBindingInfo::Type::Axis, CID_TC_BRAKE, GenericInputBinding::LeftStickUp},
{"B", TRANSLATE_NOOP("USB", "B Button"), ICON_PF_KEY_B, InputBindingInfo::Type::Button, CID_TC_B, GenericInputBinding::Cross}, {"Up", TRANSLATE_NOOP("USB", "D-Pad Up"), ICON_PF_DPAD_UP, InputBindingInfo::Type::Button, CID_TC_UP, GenericInputBinding::DPadUp},
{"C", TRANSLATE_NOOP("USB", "C Button"), ICON_PF_KEY_C, InputBindingInfo::Type::Button, CID_TC_C, GenericInputBinding::Circle}, {"Down", TRANSLATE_NOOP("USB", "D-Pad Down"), ICON_PF_DPAD_DOWN, InputBindingInfo::Type::Button, CID_TC_DOWN, GenericInputBinding::DPadDown},
{"D", TRANSLATE_NOOP("USB", "D Button"), ICON_PF_KEY_D, InputBindingInfo::Type::Button, CID_TC_D, GenericInputBinding::Triangle}, {"Left", TRANSLATE_NOOP("USB", "D-Pad Left"), ICON_PF_DPAD_LEFT, InputBindingInfo::Type::Button, CID_TC_LEFT, GenericInputBinding::DPadLeft},
{"Select", TRANSLATE_NOOP("USB", "Select"), ICON_PF_SELECT_SHARE, InputBindingInfo::Type::Button, CID_TC_SELECT, GenericInputBinding::Select}, {"Right", TRANSLATE_NOOP("USB", "D-Pad Right"), ICON_PF_DPAD_RIGHT, InputBindingInfo::Type::Button, CID_TC_RIGHT, GenericInputBinding::DPadRight},
{"Start", TRANSLATE_NOOP("USB", "Start"), ICON_PF_START, InputBindingInfo::Type::Button, CID_TC_START, GenericInputBinding::Start}, {"A", TRANSLATE_NOOP("USB", "A Button"), ICON_PF_KEY_A, InputBindingInfo::Type::Button, CID_TC_A, GenericInputBinding::Square},
}; {"B", TRANSLATE_NOOP("USB", "B Button"), ICON_PF_KEY_B, InputBindingInfo::Type::Button, CID_TC_B, GenericInputBinding::Cross},
{"C", TRANSLATE_NOOP("USB", "C Button"), ICON_PF_KEY_C, InputBindingInfo::Type::Button, CID_TC_C, GenericInputBinding::Circle},
{"D", TRANSLATE_NOOP("USB", "D Button"), ICON_PF_KEY_D, InputBindingInfo::Type::Button, CID_TC_D, GenericInputBinding::Triangle},
{"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; return bindings;
}
default:
break;
}
return {};
} }
static void train_handle_reset(USBDevice* dev) static void train_handle_reset(USBDevice* dev)
@ -191,8 +210,9 @@ namespace usb_pad
} }
} }
TrainDeviceState::TrainDeviceState(u32 port_) TrainDeviceState::TrainDeviceState(u32 port_, TrainDeviceTypes type_)
: port(port_) : port(port_)
, type(type_)
{ {
Reset(); Reset();
} }
@ -273,8 +293,68 @@ namespace usb_pad
return notches[std::size(notches) - 1].second; return notches[std::size(notches) - 1].second;
} }
// TrainControlID buttons are laid out in Type 2 ordering, no need to remap. static u8 dct02_power(u8 value)
#define dct01_buttons(buttons) (buttons) {
// (N) 0x12 0x24 0x36 0x48 0x5A 0x6C 0x7E 0x90 0xA2 0xB4 0xC6 0xD7 0xE9 0xFB (P13)
static std::pair<u8, u8> const notches[] = {
// { control_in, emulated_out },
{0xF7, 0xFB},
{0xE4, 0xE9},
{0xD1, 0xD7},
{0xBE, 0xC6},
{0xAB, 0xB4},
{0x98, 0xA2},
{0x85, 0x90},
{0x72, 0x7E},
{0x5F, 0x6C},
{0x4C, 0x5A},
{0x39, 0x48},
{0x26, 0x36},
{0x13, 0x24},
{0x00, 0x12},
};
for (const auto& x : notches)
{
if (value >= x.first)
return x.second;
}
return notches[std::size(notches) - 1].second;
}
static u8 dct02_brake(u8 value)
{
// (NB) 0x1C 0x38 0x54 0x70 0x8B 0xA7 0xC3 0xDF 0xFB (EB)
static std::pair<u8, u8> const notches[] = {
// { control_in, emulated_out },
{0xF8, 0xFB},
{0xCA, 0xDF},
{0xAE, 0xC3},
{0x92, 0xA7},
{0x76, 0x8B},
{0x5A, 0x70},
{0x3E, 0x54},
{0x22, 0x38},
{0x00, 0x1C},
};
for (const auto& x : notches)
{
if (value >= x.first)
return x.second;
}
return notches[std::size(notches) - 1].second;
}
#define get_ab(buttons) (button_at(buttons, CID_TC_A) | button_at(buttons, CID_TC_B))
#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))
// TrainControlID buttons are laid out in Type 2 ordering, no need to remap.
constexpr u8 dct01_buttons(u8 buttons) { return buttons; }
constexpr u8 dct02_buttons(u8 buttons)
{
return ((get_ab(buttons) << 2) | (swap_cd(buttons) >> 2) | get_ss(buttons));
}
static void train_handle_data(USBDevice* dev, USBPacket* p) static void train_handle_data(USBDevice* dev, USBPacket* p)
{ {
@ -289,25 +369,61 @@ namespace usb_pad
s->UpdateHatSwitch(); s->UpdateHatSwitch();
TrainConData_Type2 out = {}; switch (s->type)
out.control = 0x1; {
out.brake = dct01_brake(s->data.brake); case TRAIN_TYPE2:
out.power = dct01_power(s->data.power); {
out.horn = 0xFF; // Button C doubles as horn. TrainConData_Type2 out = {};
out.hat = s->data.hatswitch; out.control = 0x1;
out.buttons = dct01_buttons(s->data.buttons); out.brake = dct01_brake(s->data.brake);
usb_packet_copy(p, &out, sizeof(out)); out.power = dct01_power(s->data.power);
out.horn = 0xFF; // Button C doubles as horn.
out.hat = s->data.hatswitch;
out.buttons = dct01_buttons(s->data.buttons);
usb_packet_copy(p, &out, sizeof(out));
break;
}
case TRAIN_SHINKANSEN:
{
TrainConData_Shinkansen out = {};
out.brake = dct02_brake(s->data.brake);
out.power = dct02_power(s->data.power);
out.horn = 0xFF; // Button C doubles as horn, skip.
out.hat = s->data.hatswitch;
out.buttons = dct02_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;
return;
}
} }
USBDevice* TrainDevice::CreateDevice(SettingsInterface& si, u32 port, u32 subtype) const USBDevice* TrainDevice::CreateDevice(SettingsInterface& si, u32 port, u32 subtype) const
{ {
TrainDeviceState* s = new TrainDeviceState(port); TrainDeviceState* s = new TrainDeviceState(port, static_cast<TrainDeviceTypes>(subtype));
s->desc.full = &s->desc_dev; s->desc.full = &s->desc_dev;
s->desc.str = dct01_desc_strings;
if (usb_desc_parse_dev(dct01_dev_descriptor, sizeof(dct01_dev_descriptor), s->desc, s->desc_dev) < 0) switch (subtype)
goto fail; {
case TRAIN_TYPE2:
s->desc.str = dct01_desc_strings;
if (usb_desc_parse_dev(dct01_dev_descriptor, sizeof(dct01_dev_descriptor), s->desc, s->desc_dev) < 0)
goto fail;
break;
case TRAIN_SHINKANSEN:
s->desc.str = dct02_desc_strings;
if (usb_desc_parse_dev(dct02_dev_descriptor, sizeof(dct02_dev_descriptor), s->desc, s->desc_dev) < 0)
goto fail;
break;
default:
goto fail;
}
if (usb_desc_parse_config(taito_denshacon_config_descriptor, sizeof(taito_denshacon_config_descriptor), s->desc_dev) < 0) if (usb_desc_parse_config(taito_denshacon_config_descriptor, sizeof(taito_denshacon_config_descriptor), s->desc_dev) < 0)
goto fail; goto fail;

View File

@ -9,6 +9,12 @@
namespace usb_pad namespace usb_pad
{ {
enum TrainDeviceTypes
{
TRAIN_TYPE2, // TCPP20009 or similar
TRAIN_SHINKANSEN, // TCPP20011
TRAIN_COUNT,
};
class TrainDevice final : public DeviceProxy class TrainDevice final : public DeviceProxy
{ {
@ -16,6 +22,7 @@ namespace usb_pad
USBDevice* CreateDevice(SettingsInterface& si, u32 port, u32 subtype) const override; USBDevice* CreateDevice(SettingsInterface& si, u32 port, u32 subtype) const override;
const char* Name() const override; const char* Name() const override;
const char* TypeName() const override; const char* TypeName() const override;
std::span<const char*> SubTypes() const override;
float GetBindingValue(const USBDevice* dev, u32 bind_index) const override; float GetBindingValue(const USBDevice* dev, u32 bind_index) const override;
void SetBindingValue(USBDevice* dev, u32 bind_index, float value) const override; void SetBindingValue(USBDevice* dev, u32 bind_index, float value) const override;
std::span<const InputBindingInfo> Bindings(u32 subtype) const override; std::span<const InputBindingInfo> Bindings(u32 subtype) const override;
@ -33,11 +40,22 @@ namespace usb_pad
u8 buttons; u8 buttons;
}; };
static_assert(sizeof(TrainConData_Type2) == 6); static_assert(sizeof(TrainConData_Type2) == 6);
struct TrainConData_Shinkansen
{
u8 brake;
u8 power;
u8 horn;
u8 hat;
u8 buttons;
u8 pad;
};
static_assert(sizeof(TrainConData_Shinkansen) == 6);
#pragma pack(pop) #pragma pack(pop)
struct TrainDeviceState struct TrainDeviceState
{ {
TrainDeviceState(u32 port_); TrainDeviceState(u32 port_, TrainDeviceTypes type_);
~TrainDeviceState(); ~TrainDeviceState();
void Reset(); void Reset();
@ -48,6 +66,7 @@ namespace usb_pad
USBDescDevice desc_dev{}; USBDescDevice desc_dev{};
u32 port = 0; u32 port = 0;
TrainDeviceTypes type = TRAIN_TYPE2;
struct struct
{ {
@ -126,4 +145,16 @@ namespace usb_pad
// dct01_dev_descriptor // dct01_dev_descriptor
DEFINE_DCT_DEV_DESCRIPTOR(dct01, 0x04, 0x0004); DEFINE_DCT_DEV_DESCRIPTOR(dct01, 0x04, 0x0004);
// ---- Shinkansen controller ----
static const USBDescStrings dct02_desc_strings = {
"",
"TAITO",
"TAITO_DENSYA_CON_T02",
"TCPP20011",
};
// dct02_dev_descriptor
DEFINE_DCT_DEV_DESCRIPTOR(dct02, 0x05, 0x0005);
} // namespace usb_pad } // namespace usb_pad