host-aprom/docs/IO4.md
2024-08-15 19:43:38 +01:00

13 KiB

SEGA IO4 Protocol

IO4 is SEGA's latest (as of 2024) iteration of their JAMMA IO boards. As this board can be populated with either a USB port or a JVS 2 port, or both, multiple versions of it exist. This document focuses exclusively on the USB implementation.

The IO4 exposes itself as a USB HID device. It has a single pair of HID endpoints, with an 8ms interval for IN and 1ms interval for OUT. The complete descriptors are included at the end of this document.

The protocol described here is not explicitly unique to IO4; it is SEGA's UsbIO protocol.

USB Product Strings

UsbIO devices identify themselves using the USB product string. There are 8 fields are separated by ;:

Field Content
Board type Up to 14 characters
Board number Up to 8 characters, padded with spaces on the right
Mode Hexadecimal value up to FF
Firmware revision Hexadecimal value up to FF
Firmware checksum Hexadecimal value up to FFFF
Custom chip number Up to 5 characters, padded with spaces on the right
Config Hexadecimal value up to FF
Features report See below

The features report contains multiple entries, separated by _. Each entry is of the form NAME=value where value is a comma-separated list of one or more items. All numeric values are in hexadecimal unless otherwise specified.

Feature name Usage Arguments
GOUT General purpose output Number of digital outputs
PWMOUT PWM output Number of PWM outputs
ADIN ADC inputs Number of ADC inputs; bit depth (eg 14)
ROTIN Rotary input Number of rotary inputs
COININ Coin counter Number of coin counters
SWIN Switch input Number of players; Number of switches per player
UQ<n> Unique IO function n (1-9) Command ID; 0 to 3 additional arguments

IO4 -> Host (report ID 1)

Every 8ms, the Host initiates an interrupt transfer for HID. This is always responded to with report ID 1.

The structure of this report is described in the descriptor, and matches the below structure:

struct {
    uint8_t bReportId = 1;
    uint16_t wADC[8];
    uint16_t wRotary[4];
    uint16_t wCoin[2];
    uint16_t wButtons[2];
    struct {
        uint8_t bResetReason : 4;
        uint8_t bTimeoutSet : 1;
        uint8_t bSampleCountSet : 1;
        uint8_t Rsv : 2;
    } BoardStatus;
    struct {
        uint8_t Rsv : 2;
        uint8_t bTimeoutOcurred : 1;
        uint8_t Rsv : 5;
    } UsbStatus;
    uint8_t bUnique[29];
}

bUnique is always written as 29 null bytes.

It is critical that bTimeoutSet and bSampleCountSet are updated correctly, as amdaemon validates these bits.

When a timeout occurs, bTimeoutOcurred is set. While the first two bits of UsbStatus are used internally on the IO4 board, they are masked out before transmission.

bResetReason is a bit field:

  • 1: Power-on reset
  • 2: Pin reset
  • 4: Watchdog timer reset
  • 8: Voltage monitoring reset (either Vdet1 or Vdet2)

Host -> IO4 (report ID 16)

Packets to IO4 are always 64 bytes, the first of which is a command byte.

enum {
    IO4_Command_SetCommTimeout = 1,
    IO4_Command_SetSamplingCount = 2,
    IO4_Command_ClearBoardStatus = 3,
    IO4_Command_SetGeneralOutput = 4,
    IO4_Command_SetPwmOutput = 5,
    IO4_Command_SetUniqueOutput = 0x41,

    IO4_Command_84 = 0x84,
    IO4_Command_UpdateFirmware = 0x85,
    IO4_Command_88 = 0x88,
};

Set Communication Timeout [01]

struct {
    uint8_t bReportId = 16;
    uint8_t bCmd = 1;
    uint8_t bTimeout;

    uint8_t Rsv[61];
}

Sets the timeout value for the IO board. I'm not totally sure of the unit currently, but work on the assumption it's multiples of 200us.

Set bTimeout to 0 to disable the timeout.

When timeout occurs, the IO board performs a soft reset.

Set Switch Sampling Count [02]

struct {
    uint8_t bReportId = 16;
    uint8_t bCmd = 2;
    uint8_t bSamplingCount;

    uint8_t Rsv[61];
}

Set the number of samples required in the debouncer for an input pin to be counted. The easiest way to understand this value is to study the IO4's debouncing functionality below.

#define NUM_INPUT 32

uint8_t counter[NUM_INPUT] = { 0 };
uint8_t samplingCount[NUM_INPUT] = { 0 };
uint8_t debounced[NUM_INPUT / 8] = { 0 };

void Debouncer_Tick(uint8_t* sampledData) {
    uint8_t bitMask = 0;
    uint8_t bitsDiffer = 0;
    for (int i = 0; i < NUM_INPUT; i++) {
        if (i % 7 == 0) {
            bitsDiffer = sampledData[i / 8] ^ debounced[i / 8];
            bitMask = 0x01;
        }

        if (!(bitsDiffer & bitMask)) {
            // If the input doesn't differ, reset our counter
            counter[i] = 0;
        } else {
            if (counter[i] < samplingCount[i]) {
                counter[i]++;
            } else {
                // Toggle the bit in the output once we've seen it differ for enough counts
                counter[i] = 0;
                debounced[i / 8] ^= bitMask;
            }
        }

        bitMask <<= 1;
    }
}

Clear Board Status [03]

struct {
    uint8_t bReportId = 16;
    uint8_t bCmd = 3;

    uint8_t Rsv[62];
}

Unsets the timeout and sampling count values.

bSystemStatus and bTimeoutOcurred are reset to 0.

Set General Output [04]

struct {
    uint8_t bReportId = 16;
    uint8_t bCmd = 4;

    uint8_t bData[3];

    uint8_t Rsv[59];
}

Write 20 digital output values. 4 bits of padding are included.

Set PWM Output [05]

struct {
    uint8_t bReportId = 16;
    uint8_t bCmd = 5;

    uint8_t Rsv[62];
}

Write PWM duty cycles. As IO4 has no PWM outputs this packet is empty.

Set Unique Output [41]

struct {
    uint8_t bReportId = 16;
    uint8_t bCmd = 0x41;

    uint8_t bUnique[62];
}

Write 62 bytes of "unique" data. IO4 uses the first 8 bytes of these as follows:

Byte Usage
0 Enable mask (bit 7=PWM1, 2=PWM6, 0~1 unused)
1 Brightness scaler (1~256, with 0=256)
2 PWM1 brightness (pin CN3.55)
3 PWM2 brightness (pin CN3.56)
4 PWM3 brightness (pin CN9.5 )
5 PWM4 brightness (pin CN9.6 )
6 PWM5 brightness (pin CN9.9 )
7 PWM6 brightness (pin CN9.10)

Unknown 84 [84]

Update Firmware [85]

Unknown 88 [88]

Hardware Pinout

This is all wrong lol

  • [0], P60: 1P START

  • [1], P61: 1P RIGHT

  • [2], P62: 1P LEFT

  • [3], P63: 1P UP

  • [4], P64: 1P DOWN

  • [5], P65: 1P PUSH1

  • [6], P66 [NC] Service

  • [7], P67 [NC]

  • [8], P70 [NC]

  • [9], P71 [NC] Test

  • [10], P72: 1P PUSH2

  • [11], P73: 1P PUSH3

  • [12], P74: 1P PUSH4

  • [13], P75: 1P PUSH5

  • [14], P76: 1P PUSH6

  • [15], P77: 1P PUSH7

  • [16], PC0: 2P START

  • [17], PC1: 2P RIGHT

  • [18], PC2: 2P LEFT

  • [19], PC3: 2P UP

  • [20], PC4: 2P DOWN

  • [21], PC5: 2P PUSH1

  • [22], PC6 [NC]

  • [23], PC7 [NC]

  • [24], PE0 [NC]

  • [25], PE1 [NC]

  • [26], PE2: 2P PUSH2

  • [27], PE3: 2P PUSH3

  • [28], PE4: 2P PUSH4

  • [29], PE5: 2P PUSH5

  • [30], PE6: 2P PUSH6

  • [31], PE7: 2P PUSH7

  • P02: ? (GetPort0ButtonsB)

  • P03: ? (GetPort0ButtonsB)

  • PD?: ? (GetPortDButtons)

Used for coins:

  • P00: ? (GetPort0ButtonsA)
  • P01: ? (GetPort0ButtonsA)
  • PE0: ? (inverted, GetPortEButtons)
  • PE1: ? (inverted, GetPortEButtons)
uint8_t GetPortEButtons(void) {
    uint8_t buttons = dat_portE >> 1 & 0xfd  // PE1
    buttons |= 0xfc
    buttons |= (dat_portE & 1) << 1;  // PE0

    return ~buttons;
}

USB Descriptors

USB Strings

Index Use String
1 Manufacturer SEGA INTERACTIVE
2 Product I/O CONTROL BD;BDNUMBER;MD;RV;SUM ;CPNUM;CF;GOUT=14_ADIN=8,E_ROTIN=4_COININ=2_SWIN=2,E_UQ1=41,6;

Device Descriptor

12 01 00 02 00 00 00 40 a3 0c 21 00 00 01 01 02 00 01

Field Value
bLength 18
bDescriptorType Device
bcdUSB 2.00
bDeviceClass Use class information in the Interface Descriptors
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x0CA3
idProduct 0x0021
bcdDevice 2.00
iManufacturer 1
iProduct 2
iSerialNumber 0
bNumConfigurations 1

Configuration Descriptor

09 02 29 00 01 01 00 c0 32

Field Value
bLength 9
bDescriptorType Configuration
wTotalLength 41
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes Self powered
bMaxPower 100mA

09 04 00 00 02 03 00 00 00

Field Value
bLength 9
bDescriptorType Interface
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass HID
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0

09 21 11 01 00 01 22 71 00

Field Value
bLength 9
bDescriptorType HID
bcdHID 1.11
bCountryCode 0
bNumDescriptors 1
bDescriptorType[0] HID
wDescriptorLength[0] 113

07 05 81 03 40 00 08

Field Value
bLength 7
bDescriptorType Endpoint
bEndpointAddress IN/D2H
bmAttributes Interrupt
wMaxPacketSize 64
bInterval 8ms

07 05 01 03 40 00 01

Field Value
bLength 7
bDescriptorType Endpoint
bEndpointAddress OUT/H2D
bmAttributes Interrupt
wMaxPacketSize 64
bInterval 1ms

HID Descriptor

05 01 09 04 a1 01 85 01 09 01 a1 00 09 30 09 31 09 30 09 31 09 30 09 31 09 30 09 31 09 33 09 34 09 33 09 34 09 36 09 36 15 00 27 ff ff 00 00 35 00 47 ff ff 00 00 95 0e 75 10 81 02 c0 05 02 05 09 19 01 29 30 15 00 25 01 45 01 75 01 95 30 81 02 09 00 75 08 95 1d 81 01 06 a0 ff 09 00 85 10 a1 01 09 00 15 00 26 ff 00 75 08 95 3f 91 02 c0 c0

Usage Page (Generic Desktop Ctrls)
Usage (Joystick)
Collection (Application)
  Report ID (1)

  Usage (Pointer)
  Collection (Physical)
    Usage (X)
    Usage (Y)
    Usage (X)
    Usage (Y)
    Usage (X)
    Usage (Y)
    Usage (X)
    Usage (Y)
    Usage (Rx)
    Usage (Ry)
    Usage (Rx)
    Usage (Ry)
    Usage (Slider)
    Usage (Slider)
    Logical Minimum (0)
    Logical Maximum (65534)
    Physical Minimum (0)
    Physical Maximum (65534)
    Report Count (14)
    Report Size (16)
    Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
  End Collection

  Usage Page (Sim Ctrls)
  Usage Page (Button)
  Usage Minimum (0x01)
  Usage Maximum (0x30)
  Logical Minimum (0)
  Logical Maximum (1)
  Physical Maximum (1)
  Report Size (1)
  Report Count (48)
  Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)

  Usage (0x00)
  Report Size (8)
  Report Count (29)
  Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
  Usage Page (Vendor Defined 0xFFA0)
  Usage (0x00)


  Report ID (16)

  Collection (Application)
    Usage (0x00)
    Logical Minimum (0)
    Logical Maximum (255)
    Report Size (8)
    Report Count (63)
    Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
  End Collection
End Collection