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";
}
std::span<const char*> TrainDevice::SubTypes() const
{
static const char* subtypes[] = {
TRANSLATE_NOOP("USB", "Type 2"),
TRANSLATE_NOOP("USB", "Shinkansen"),
};
return subtypes;
}
enum TrainControlID
{
CID_TC_POWER,
@ -46,6 +55,11 @@ namespace usb_pad
};
std::span<const InputBindingInfo> TrainDevice::Bindings(u32 subtype) const
{
switch (subtype)
{
case TRAIN_TYPE2:
case TRAIN_SHINKANSEN:
{
static constexpr const InputBindingInfo bindings[] = {
{"Power", TRANSLATE_NOOP("USB", "Power"), ICON_PF_LEFT_ANALOG_DOWN, InputBindingInfo::Type::Axis, CID_TC_POWER, GenericInputBinding::LeftStickDown},
@ -64,6 +78,11 @@ namespace usb_pad
return bindings;
}
default:
break;
}
return {};
}
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_)
, type(type_)
{
Reset();
}
@ -273,8 +293,68 @@ namespace usb_pad
return notches[std::size(notches) - 1].second;
}
static u8 dct02_power(u8 value)
{
// (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.
#define dct01_buttons(buttons) (buttons)
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)
{
@ -289,6 +369,10 @@ namespace usb_pad
s->UpdateHatSwitch();
switch (s->type)
{
case TRAIN_TYPE2:
{
TrainConData_Type2 out = {};
out.control = 0x1;
out.brake = dct01_brake(s->data.brake);
@ -297,17 +381,49 @@ namespace usb_pad
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
{
TrainDeviceState* s = new TrainDeviceState(port);
TrainDeviceState* s = new TrainDeviceState(port, static_cast<TrainDeviceTypes>(subtype));
s->desc.full = &s->desc_dev;
s->desc.str = dct01_desc_strings;
switch (subtype)
{
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)
goto fail;

View File

@ -9,6 +9,12 @@
namespace usb_pad
{
enum TrainDeviceTypes
{
TRAIN_TYPE2, // TCPP20009 or similar
TRAIN_SHINKANSEN, // TCPP20011
TRAIN_COUNT,
};
class TrainDevice final : public DeviceProxy
{
@ -16,6 +22,7 @@ namespace usb_pad
USBDevice* CreateDevice(SettingsInterface& si, u32 port, u32 subtype) const override;
const char* Name() const override;
const char* TypeName() const override;
std::span<const char*> SubTypes() const override;
float GetBindingValue(const USBDevice* dev, u32 bind_index) const override;
void SetBindingValue(USBDevice* dev, u32 bind_index, float value) const override;
std::span<const InputBindingInfo> Bindings(u32 subtype) const override;
@ -33,11 +40,22 @@ namespace usb_pad
u8 buttons;
};
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)
struct TrainDeviceState
{
TrainDeviceState(u32 port_);
TrainDeviceState(u32 port_, TrainDeviceTypes type_);
~TrainDeviceState();
void Reset();
@ -48,6 +66,7 @@ namespace usb_pad
USBDescDevice desc_dev{};
u32 port = 0;
TrainDeviceTypes type = TRAIN_TYPE2;
struct
{
@ -126,4 +145,16 @@ namespace usb_pad
// dct01_dev_descriptor
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