USB: Add support for Type 2 DenshaCon

Add support for TCPP20009 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/tcpp20009/
This commit is contained in:
Joe Stringer 2024-08-04 20:48:01 -07:00 committed by lightningterror
parent a8ce6a9f0e
commit edcd1d0f9f
7 changed files with 475 additions and 0 deletions

View File

@ -389,6 +389,7 @@ set(pcsx2USBSources
USB/usb-pad/usb-pad-sdl-ff.cpp
USB/usb-pad/usb-pad.cpp
USB/usb-pad/usb-seamic.cpp
USB/usb-pad/usb-train.cpp
USB/usb-pad/usb-trance-vibrator.cpp
USB/usb-pad/usb-turntable.cpp
USB/usb-printer/usb-printer.cpp
@ -425,6 +426,7 @@ set(pcsx2USBHeaders
USB/usb-pad/usb-realplay.h
USB/usb-pad/usb-pad-sdl-ff.h
USB/usb-pad/usb-pad.h
USB/usb-pad/usb-train.h
USB/usb-pad/usb-trance-vibrator.h
USB/usb-printer/usb-printer.h
)

View File

@ -11,6 +11,7 @@
#include "usb-mic/usb-mic.h"
#include "usb-msd/usb-msd.h"
#include "usb-pad/usb-pad.h"
#include "usb-pad/usb-train.h"
#include "usb-pad/usb-trance-vibrator.h"
#include "usb-pad/usb-turntable.h"
#include "usb-printer/usb-printer.h"
@ -83,6 +84,7 @@ void RegisterDevice::Register()
inst.Add(DEVTYPE_GUNCON2, new usb_lightgun::GunCon2Device());
inst.Add(DEVTYPE_GAMETRAK, new usb_pad::GametrakDevice());
inst.Add(DEVTYPE_REALPLAY, new usb_pad::RealPlayDevice());
inst.Add(DEVTYPE_TRAIN, new usb_pad::TrainDevice());
}
void RegisterDevice::Unregister()

View File

@ -41,6 +41,7 @@ enum DeviceType : s32
DEVTYPE_DJ,
DEVTYPE_GAMETRAK,
DEVTYPE_REALPLAY,
DEVTYPE_TRAIN,
};
class DeviceProxy

View File

@ -0,0 +1,333 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "usb-train.h"
#include "common/Console.h"
#include "Host.h"
#include "IconsPromptFont.h"
#include "Input/InputManager.h"
#include "StateWrapper.h"
#include "USB/deviceproxy.h"
#include "USB/USB.h"
#include "USB/qemu-usb/USBinternal.h"
#include "USB/qemu-usb/desc.h"
namespace usb_pad
{
const char* TrainDevice::Name() const
{
return TRANSLATE_NOOP("USB", "Train Controller");
}
const char* TrainDevice::TypeName() const
{
return "TrainController";
}
enum TrainControlID
{
CID_TC_POWER,
CID_TC_BRAKE,
CID_TC_UP,
CID_TC_RIGHT,
CID_TC_DOWN,
CID_TC_LEFT,
// TCPP20009 sends the buttons in this order in the relevant byte
CID_TC_B,
CID_TC_A,
CID_TC_C,
CID_TC_D,
CID_TC_SELECT,
CID_TC_START,
BUTTONS_OFFSET = CID_TC_B,
};
std::span<const InputBindingInfo> TrainDevice::Bindings(u32 subtype) const
{
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},
{"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;
}
static void train_handle_reset(USBDevice* dev)
{
TrainDeviceState* s = USB_CONTAINER_OF(dev, TrainDeviceState, dev);
s->Reset();
}
static void train_handle_control(USBDevice* dev, USBPacket* p, int request, int value,
int index, int length, uint8_t* data)
{
if (usb_desc_handle_control(dev, p, request, value, index, length, data) < 0)
p->status = USB_RET_STALL;
}
static void train_handle_destroy(USBDevice* dev) noexcept
{
TrainDeviceState* s = USB_CONTAINER_OF(dev, TrainDeviceState, dev);
delete s;
}
bool TrainDevice::Freeze(USBDevice* dev, StateWrapper& sw) const
{
TrainDeviceState* s = USB_CONTAINER_OF(dev, TrainDeviceState, dev);
if (!sw.DoMarker("TrainController"))
return false;
sw.Do(&s->data.power);
sw.Do(&s->data.brake);
return true;
}
static constexpr u32 button_mask(u32 bind_index)
{
return (1u << (bind_index - TrainControlID::BUTTONS_OFFSET));
}
static constexpr u8 button_at(u8 value, u32 index)
{
return value & button_mask(index);
}
float TrainDevice::GetBindingValue(const USBDevice* dev, u32 bind_index) const
{
const TrainDeviceState* s = USB_CONTAINER_OF(dev, const TrainDeviceState, dev);
switch (bind_index)
{
case CID_TC_POWER:
return (static_cast<float>(s->data.power) / 255.0f);
case CID_TC_BRAKE:
return (static_cast<float>(s->data.brake) / 255.0f);
case CID_TC_UP:
return static_cast<float>(s->data.hat_up);
case CID_TC_DOWN:
return static_cast<float>(s->data.hat_down);
case CID_TC_LEFT:
return static_cast<float>(s->data.hat_left);
case CID_TC_RIGHT:
return static_cast<float>(s->data.hat_right);
case CID_TC_A:
case CID_TC_B:
case CID_TC_C:
case CID_TC_D:
case CID_TC_SELECT:
case CID_TC_START:
{
return (button_at(s->data.buttons, bind_index) != 0u) ? 1.0f : 0.0f;
}
default:
return 0.0f;
}
}
void TrainDevice::SetBindingValue(USBDevice* dev, u32 bind_index, float value) const
{
TrainDeviceState* s = USB_CONTAINER_OF(dev, TrainDeviceState, dev);
switch (bind_index)
{
case CID_TC_POWER:
s->data.power = static_cast<u32>(std::clamp<long>(std::lroundf(value * 255.0f), 0, 255));
break;
case CID_TC_BRAKE:
s->data.brake = static_cast<u32>(std::clamp<long>(std::lroundf(value * 255.0f), 0, 255));
break;
case CID_TC_UP:
s->data.hat_up = static_cast<u8>(std::clamp<long>(std::lroundf(value * 255.0f), 0, 255));
s->UpdateHatSwitch();
break;
case CID_TC_DOWN:
s->data.hat_down = static_cast<u8>(std::clamp<long>(std::lroundf(value * 255.0f), 0, 255));
s->UpdateHatSwitch();
break;
case CID_TC_LEFT:
s->data.hat_left = static_cast<u8>(std::clamp<long>(std::lroundf(value * 255.0f), 0, 255));
s->UpdateHatSwitch();
break;
case CID_TC_RIGHT:
s->data.hat_right = static_cast<u8>(std::clamp<long>(std::lroundf(value * 255.0f), 0, 255));
s->UpdateHatSwitch();
break;
case CID_TC_A:
case CID_TC_B:
case CID_TC_C:
case CID_TC_D:
case CID_TC_SELECT:
case CID_TC_START:
{
const u32 mask = button_mask(bind_index);
if (value >= 0.5f)
s->data.buttons |= mask;
else
s->data.buttons &= ~mask;
}
break;
default:
break;
}
}
TrainDeviceState::TrainDeviceState(u32 port_)
: port(port_)
{
Reset();
}
TrainDeviceState::~TrainDeviceState() = default;
void TrainDeviceState::Reset()
{
data.power = 0x00;
data.brake = 0x00;
}
void TrainDeviceState::UpdateHatSwitch() noexcept
{
if (data.hat_up && data.hat_right)
data.hatswitch = 1;
else if (data.hat_right && data.hat_down)
data.hatswitch = 3;
else if (data.hat_down && data.hat_left)
data.hatswitch = 5;
else if (data.hat_left && data.hat_up)
data.hatswitch = 7;
else if (data.hat_up)
data.hatswitch = 0;
else if (data.hat_right)
data.hatswitch = 2;
else if (data.hat_down)
data.hatswitch = 4;
else if (data.hat_left)
data.hatswitch = 6;
else
data.hatswitch = 8;
}
static u8 dct01_power(u8 value)
{
// (N) 0x81 0x6D 0x54 0x3F 0x21 0x00 (P5)
static std::pair<u8, u8> const notches[] = {
// { control_in, emulated_out },
{0xF8, 0x00},
{0xC8, 0x21},
{0x98, 0x3F},
{0x58, 0x54},
{0x28, 0x6D},
{0x00, 0x81},
};
for (const auto& x : notches)
{
if (value >= x.first)
return x.second;
}
return notches[std::size(notches) - 1].second;
}
static u8 dct01_brake(u8 value)
{
// (NB) 0x79 0x8A 0x94 0x9A 0xA2 0xA8 0xAF 0xB2 0xB5 0xB9 (EB)
static std::pair<u8, u8> const notches[] = {
// { control_in, emulated_out },
{0xF8, 0xB9},
{0xE6, 0xB5},
{0xCA, 0xB2},
{0xAE, 0xAF},
{0x92, 0xA8},
{0x76, 0xA2},
{0x5A, 0x9A},
{0x3E, 0x94},
{0x22, 0x8A},
{0x00, 0x79},
};
for (const auto& x : notches)
{
if (value >= x.first)
return x.second;
}
return notches[std::size(notches) - 1].second;
}
// TrainControlID buttons are laid out in Type 2 ordering, no need to remap.
#define dct01_buttons(buttons) (buttons)
static void train_handle_data(USBDevice* dev, USBPacket* p)
{
TrainDeviceState* s = USB_CONTAINER_OF(dev, TrainDeviceState, dev);
if (p->pid != USB_TOKEN_IN || p->ep->nr != 1)
{
Console.Error("Unhandled TrainController request pid=%d ep=%u", p->pid, p->ep->nr);
p->status = USB_RET_STALL;
return;
}
s->UpdateHatSwitch();
TrainConData_Type2 out = {};
out.control = 0x1;
out.brake = dct01_brake(s->data.brake);
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));
}
USBDevice* TrainDevice::CreateDevice(SettingsInterface& si, u32 port, u32 subtype) const
{
TrainDeviceState* s = new TrainDeviceState(port);
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)
goto fail;
if (usb_desc_parse_config(taito_denshacon_config_descriptor, sizeof(taito_denshacon_config_descriptor), s->desc_dev) < 0)
goto fail;
s->dev.speed = USB_SPEED_FULL;
s->dev.klass.handle_attach = usb_desc_attach;
s->dev.klass.handle_reset = train_handle_reset;
s->dev.klass.handle_control = train_handle_control;
s->dev.klass.handle_data = train_handle_data;
s->dev.klass.unrealize = train_handle_destroy;
s->dev.klass.usb_desc = &s->desc;
s->dev.klass.product_desc = s->desc.str[2];
usb_desc_init(&s->dev);
usb_ep_init(&s->dev);
train_handle_reset(&s->dev);
return &s->dev;
fail:
train_handle_destroy(&s->dev);
return nullptr;
}
} // namespace usb_pad

View File

@ -0,0 +1,129 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "USB/deviceproxy.h"
#include "USB/qemu-usb/qusb.h"
#include "USB/qemu-usb/desc.h"
namespace usb_pad
{
class TrainDevice final : public DeviceProxy
{
public:
USBDevice* CreateDevice(SettingsInterface& si, u32 port, u32 subtype) const override;
const char* Name() const override;
const char* TypeName() 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;
bool Freeze(USBDevice* dev, StateWrapper& sw) const override;
};
#pragma pack(push, 1)
struct TrainConData_Type2
{
u8 control;
u8 brake;
u8 power;
u8 horn;
u8 hat;
u8 buttons;
};
static_assert(sizeof(TrainConData_Type2) == 6);
#pragma pack(pop)
struct TrainDeviceState
{
TrainDeviceState(u32 port_);
~TrainDeviceState();
void Reset();
void UpdateHatSwitch() noexcept;
USBDevice dev{};
USBDesc desc{};
USBDescDevice desc_dev{};
u32 port = 0;
struct
{
// intermediate state, resolved at query time
bool hat_left : 1;
bool hat_right : 1;
bool hat_up : 1;
bool hat_down : 1;
u8 power; // 255 is fully applied
u8 brake; // 255 is fully applied
u8 hatswitch; // direction
u8 buttons; // active high
} data = {};
};
// Taito Densha Controllers as described at:
// https://marcriera.github.io/ddgo-controller-docs/controllers/usb/
#define DEFINE_DCT_DEV_DESCRIPTOR(prefix, subclass, product) \
static const uint8_t prefix##_dev_descriptor[] = { \
/* bLength */ USB_DEVICE_DESC_SIZE, \
/* bDescriptorType */ USB_DEVICE_DESCRIPTOR_TYPE, \
/* bcdUSB */ WBVAL(0x0110), /* USB 1.1 */ \
/* bDeviceClass */ 0xFF, \
/* bDeviceSubClass */ subclass, \
/* bDeviceProtocol */ 0x00, \
/* bMaxPacketSize0 */ 0x08, \
/* idVendor */ WBVAL(0x0ae4), \
/* idProduct */ WBVAL(product), \
/* bcdDevice */ WBVAL(0x0102), /* 1.02 */ \
/* iManufacturer */ 0x01, \
/* iProduct */ 0x02, \
/* iSerialNumber */ 0x03, \
/* bNumConfigurations */ 0x01, \
}
// These settings are common across multiple models.
static const uint8_t taito_denshacon_config_descriptor[] = {
USB_CONFIGURATION_DESC_SIZE, // bLength
USB_CONFIGURATION_DESCRIPTOR_TYPE, // bDescriptorType
WBVAL(25), // wTotalLength
0x01, // bNumInterfaces
0x01, // bConfigurationValue
0x00, // iConfiguration (String Index)
0xA0, // bmAttributes
0xFA, // bMaxPower 500mA
USB_INTERFACE_DESC_SIZE, // bLength
USB_INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType
0x00, // bInterfaceNumber
0x00, // bAlternateSetting
0x01, // bNumEndpoints
USB_CLASS_HID, // bInterfaceClass
0x00, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0x00, // iInterface (String Index)
USB_ENDPOINT_DESC_SIZE, // bLength
USB_ENDPOINT_DESCRIPTOR_TYPE, // bDescriptorType
USB_ENDPOINT_IN(1), // bEndpointAddress (IN/D2H)
USB_ENDPOINT_TYPE_INTERRUPT, // bmAttributes (Interrupt)
WBVAL(8), // wMaxPacketSize
0x14, // bInterval 20 (unit depends on device speed)
// 25 bytes (43 total with dev descriptor)
};
// ---- Two handle controller "Type 2" ----
static const USBDescStrings dct01_desc_strings = {
"",
"TAITO",
"TAITO_DENSYA_CON_T01",
"TCPP20009",
};
// dct01_dev_descriptor
DEFINE_DCT_DEV_DESCRIPTOR(dct01, 0x04, 0x0004);
} // namespace usb_pad

View File

@ -402,6 +402,7 @@
<ClCompile Include="USB\usb-pad\usb-pad-sdl-ff.cpp" />
<ClCompile Include="USB\usb-pad\usb-pad.cpp" />
<ClCompile Include="USB\usb-pad\usb-seamic.cpp" />
<ClCompile Include="USB\usb-pad\usb-train.cpp" />
<ClCompile Include="USB\usb-pad\usb-trance-vibrator.cpp" />
<ClCompile Include="USB\usb-pad\usb-turntable.cpp" />
<ClCompile Include="USB\usb-printer\usb-printer.cpp" />
@ -853,6 +854,7 @@
<ClInclude Include="USB\usb-pad\usb-realplay.h" />
<ClInclude Include="USB\usb-pad\usb-pad-sdl-ff.h" />
<ClInclude Include="USB\usb-pad\usb-pad.h" />
<ClInclude Include="USB\usb-pad\usb-train.h" />
<ClInclude Include="USB\usb-pad\usb-trance-vibrator.h" />
<ClInclude Include="USB\usb-pad\usb-turntable.h" />
<ClInclude Include="USB\usb-printer\usb-printer.h" />

View File

@ -1199,6 +1199,9 @@
<ClCompile Include="USB\usb-pad\usb-trance-vibrator.cpp">
<Filter>System\Ps2\USB\usb-pad</Filter>
</ClCompile>
<ClCompile Include="USB\usb-pad\usb-train.cpp">
<Filter>System\Ps2\USB\usb-pad</Filter>
</ClCompile>
<ClCompile Include="USB\usb-pad\usb-turntable.cpp">
<Filter>System\Ps2\USB\usb-pad</Filter>
</ClCompile>
@ -2145,6 +2148,9 @@
<ClInclude Include="USB\usb-pad\usb-pad.h">
<Filter>System\Ps2\USB\usb-pad</Filter>
</ClInclude>
<ClInclude Include="USB\usb-pad\usb-train.h">
<Filter>System\Ps2\USB\usb-pad</Filter>
</ClInclude>
<ClInclude Include="USB\usb-pad\usb-trance-vibrator.h">
<Filter>System\Ps2\USB\usb-pad</Filter>
</ClInclude>