mirror of https://github.com/PCSX2/pcsx2.git
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:
parent
904d6b9c78
commit
ed3405b5a9
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue