406 lines
13 KiB
C
406 lines
13 KiB
C
#include <stdint.h>
|
|
|
|
//
|
|
// SH4 SCIF definitions
|
|
//
|
|
|
|
// SH4 on-chip peripheral module control
|
|
volatile uint8_t * SCFTDR2 = (volatile uint8_t *)(0xffe80000 + 0x0c);
|
|
volatile uint16_t * SCFSR2 = (volatile uint16_t *)(0xffe80000 + 0x10);
|
|
volatile uint16_t * SCFCR2 = (volatile uint16_t *)(0xffe80000 + 0x18);
|
|
|
|
#define SCFSR2__TDFE (1 << 5)
|
|
#define SCFSR2__TEND (1 << 6)
|
|
|
|
#define SCFCR2__TTRG(n) (((n) & 0b11) << 4)
|
|
|
|
static inline void character(const char c)
|
|
{
|
|
// wait for transmit fifo to become partially empty
|
|
while ((*SCFSR2 & SCFSR2__TDFE) == 0);
|
|
|
|
// unset TDFE bit
|
|
*SCFSR2 &= ~SCFSR2__TDFE;
|
|
|
|
*SCFTDR2 = c;
|
|
}
|
|
|
|
static inline void string(const char * s)
|
|
{
|
|
while (*s != 0) {
|
|
character(*s++);
|
|
}
|
|
}
|
|
|
|
void print_base16(uint32_t n, int len)
|
|
{
|
|
char buf[len];
|
|
char * bufi = &buf[len - 1];
|
|
|
|
while (bufi >= buf) {
|
|
uint32_t nib = n & 0xf;
|
|
n = n >> 4;
|
|
if (nib > 9) {
|
|
nib += (97 - 10);
|
|
} else {
|
|
nib += (48 - 0);
|
|
}
|
|
|
|
*bufi = nib;
|
|
bufi -= 1;
|
|
}
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
character(buf[i]);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Maple protocol definitions
|
|
//
|
|
|
|
/*
|
|
The Maple bus controller is a functional unit inside Holly. The only way to
|
|
interact with it is via "Maple DMA"--a special DMA unit only used for
|
|
interactions with the Maple controller.
|
|
|
|
The "Maple DMA" unit is really more of a "FIFO", similar to the TA, except the
|
|
Maple controller has a hard dependency on SH4 DMA (unlike the TA which also
|
|
allows non-DMA transfers).
|
|
|
|
The structure of the transmit buffer given to the Maple DMA unit is always:
|
|
|
|
[ host instruction ]
|
|
[ receive data address ]
|
|
[ protocol data ]
|
|
[ command code ]
|
|
[ destination ap ]
|
|
[ source ap ]
|
|
[ data size ]
|
|
[ (variable-length data) ]
|
|
[ host instruction ]
|
|
[ receive data address ]
|
|
[ protocol data ]
|
|
...
|
|
|
|
Each "host instruction" identifies the Maple port/destination that the
|
|
following "Maple protocol data" is sent to. There can be multiple host
|
|
instructions in a single Maple DMA buffer.
|
|
|
|
The "receive data address" is the System Memory address where you want the
|
|
reply to your Maple command to be stored.
|
|
|
|
See DCDBSysArc990907E.pdf page 276.
|
|
|
|
See also MAPLE82E.pdf page 20 -- this shows the Maple bus from the
|
|
perspective of (e.g) a Dreamcast Controller.
|
|
*/
|
|
|
|
typedef struct maple_protocol_header {
|
|
uint8_t command_code;
|
|
uint8_t destination_ap;
|
|
uint8_t source_ap;
|
|
uint8_t data_size;
|
|
} maple_protocol_header;
|
|
|
|
typedef struct maple_host_command {
|
|
uint32_t host_instruction; // interpreted by the Maple DMA controller
|
|
uint32_t receive_data_address; // interpreted by the Maple DMA controller
|
|
|
|
maple_protocol_header protocol_header;
|
|
|
|
// variable-length "parameter" data follows
|
|
} maple_host_command;
|
|
|
|
// Maple Host Instruction : DCDBSysArc990907E.pdf page 269
|
|
#define MAPLE__HOST_INSTRUCTION__END_FLAG (1 << 31)
|
|
#define MAPLE__HOST_INSTRUCTION__PORT_SELECT__A (0 << 16)
|
|
#define MAPLE__HOST_INSTRUCTION__PORT_SELECT__B (1 << 16)
|
|
#define MAPLE__HOST_INSTRUCTION__PORT_SELECT__C (2 << 16)
|
|
#define MAPLE__HOST_INSTRUCTION__PORT_SELECT__D (3 << 16)
|
|
#define MAPLE__HOST_INSTRUCTION__PATTERN__NORMAL (0b000 << 8)
|
|
#define MAPLE__HOST_INSTRUCTION__TRANSFER_LENGTH(n) (((n) & 0xff) << 0)
|
|
|
|
// (supported) command codes are defined per-peripheral; there is also general
|
|
// information about command codes in MAPLE82E.pdf page 72.
|
|
|
|
// "AP" is defined in MAPLE82E.pdf page 21
|
|
|
|
#define MAPLE__AP__PORT_SELECT__A (0b00 << 6)
|
|
#define MAPLE__AP__PORT_SELECT__B (0b01 << 6)
|
|
#define MAPLE__AP__PORT_SELECT__C (0b10 << 6)
|
|
#define MAPLE__AP__PORT_SELECT__D (0b11 << 6)
|
|
|
|
// "device" roughly means "the thing directly connected to the Dreamcast"
|
|
//
|
|
// "expansion device" roughly means "the thing with some indirect connection to
|
|
// the Dreamcast (e.g: a VMU)"
|
|
//
|
|
// "port" is used to indicate that the AP is the Dreamcast itself (via that
|
|
// port)
|
|
#define MAPLE__AP__DE__DEVICE (1 << 5)
|
|
#define MAPLE__AP__DE__EXPANSION_DEVICE (0 << 5)
|
|
#define MAPLE__AP__DE__PORT (0 << 5)
|
|
|
|
// Ft0_090e.pdf describes the commands supported by Dreamcast Controllers
|
|
// specifically.
|
|
//
|
|
// The simplest command supported by a Dreamcast Controller is the "Device
|
|
// Request" command. The reply to a "Device Request" command is "Device Status".
|
|
#define MAPLE__COMMAND_CODE__DEVICE_REQUEST (0x01)
|
|
#define MAPLE__COMMAND_CODE__DEVICE_STATUS (0x05)
|
|
|
|
// A Maple DMA response is simply the Maple protocol data sent by the target/AP in
|
|
// response to the command
|
|
|
|
typedef struct maple_host_reply {
|
|
maple_protocol_header protocol_header;
|
|
|
|
// variable-length "parameter" data follows
|
|
} maple_host_reply;
|
|
|
|
// the Device Request command has no fields
|
|
|
|
// the Device Status reply has these fields:
|
|
|
|
typedef struct maple_device_id {
|
|
uint32_t ft;
|
|
uint32_t fd[3];
|
|
} maple_device_id;
|
|
|
|
typedef struct maple_device_status {
|
|
maple_device_id device_id;
|
|
uint8_t destination_code;
|
|
uint8_t connection_direction;
|
|
uint8_t product_name[30];
|
|
uint8_t license[60];
|
|
uint16_t low_consumption_standby_current;
|
|
uint16_t maximum_current_consumption;
|
|
} maple_device_status;
|
|
|
|
/*
|
|
maple_device_status is concatenated after maple_host_reply, as in:
|
|
|
|
struct {
|
|
maple_host_reply host_reply;
|
|
maple_device_status device_status;
|
|
};
|
|
|
|
This ^ is the structure that will be stored at `receive_data_address`.
|
|
*/
|
|
|
|
//
|
|
// Maple interface definitions
|
|
//
|
|
|
|
// system bus interface
|
|
volatile uint32_t * ISTNRM = (volatile uint32_t *)(0xa05f6800 + 0x100);
|
|
|
|
#define ISTNRM__END_OF_DMA_MAPLE_DMA (1 << 12)
|
|
|
|
// Maple interface
|
|
volatile uint32_t * MDSTAR = (volatile uint32_t *)(0xa05f6c00 + 0x04);
|
|
volatile uint32_t * MDTSEL = (volatile uint32_t *)(0xa05f6c00 + 0x10);
|
|
volatile uint32_t * MDEN = (volatile uint32_t *)(0xa05f6c00 + 0x14);
|
|
volatile uint32_t * MDST = (volatile uint32_t *)(0xa05f6c00 + 0x18);
|
|
volatile uint32_t * MSYS = (volatile uint32_t *)(0xa05f6c00 + 0x80);
|
|
volatile uint32_t * MDAPRO = (volatile uint32_t *)(0xa05f6c00 + 0x8c);
|
|
|
|
#define MDEN__DMA_ENABLE__ABORT (0 << 0)
|
|
#define MDEN__DMA_ENABLE__ENABLE (1 << 0)
|
|
#define MDST__START_STATUS (1 << 0)
|
|
|
|
#define MSYS__TIME_OUT_COUNTER(n) (((n) & 0xffff) << 16)
|
|
#define MSYS__SENDING_RATE___2M (0 << 8)
|
|
|
|
#define MDAPRO__SECURITY_CODE (0x6155 << 16)
|
|
#define MDAPRO__TOP_ADDRESS(n) (((n) & 0x7f) << 8)
|
|
#define MDAPRO__BOTTOM_ADDRESS(n) (((n) & 0x7f) << 0)
|
|
|
|
#define MDTSEL__TRIGGER_SELECT__SOFTWARE_INITIATION (0 << 0)
|
|
|
|
#define MDSTAR__TABLE_ADDRESS(n) (((n) & 0xfffffe0) << 0)
|
|
|
|
/*
|
|
See DCDBSysArc990907E.pdf page 274.
|
|
|
|
Maple DMA uses SH4 DMA channel 0 in "DDT" mode. This means that Holly's Maple
|
|
DMA functional unit is externally controlling the SH4's DMA unit.
|
|
|
|
The Dreamcast boot rom leaves channel 0 already configured for Maple DMA,
|
|
though if you felt rebellious and wished to used channel 0 for something else,
|
|
you would need to reconfigure channel 0 for Maple/DDT afterwards.
|
|
|
|
Note that this `maple_dma_start` function does not configure SH4 DMA channel
|
|
0, and presumes it is already in the correct state.
|
|
*/
|
|
void maple_dma_start(void * command_buf)
|
|
{
|
|
// if SH4 cache were enabled, it would be necessary to use the `ocbwb` sh4
|
|
// instruction to write back the cache to system memory, prior to continuing.
|
|
|
|
// if SH4 cache were enabled, it would be necessary to use the `ocbi` sh4
|
|
// instruction to invalidate any possibly-cached areas of the receive buffer,
|
|
// as these are imminently going to be rewritten by the DMA unit independently
|
|
// of cache access.
|
|
|
|
// disable Maple DMA and abort any possibly-in-progress transfers
|
|
*MDEN = MDEN__DMA_ENABLE__ABORT;
|
|
while ((*MDST & MDST__START_STATUS) != 0);
|
|
|
|
// clear Maple DMA end status
|
|
*ISTNRM = ISTNRM__END_OF_DMA_MAPLE_DMA;
|
|
|
|
// 20nsec * 0xc350 = 1ms
|
|
uint32_t one_msec = 0xc350;
|
|
// set the Maple bus controller timeout and transfer rate
|
|
*MSYS = MSYS__TIME_OUT_COUNTER(one_msec)
|
|
| MSYS__SENDING_RATE___2M;
|
|
|
|
// MDAPRO controls which (system memory) addresses are considered valid for
|
|
// Maple DMA. An attempt to use an address outside of this range will cause an
|
|
// error interrupt in from the Maple DMA unit.
|
|
//
|
|
// 0x40 through 0x7F allows for transfers from 0x0c000000 to 0x0fffffff
|
|
// System Memory is 0x0c000000 through 0x0cffffff (16MB)
|
|
//
|
|
// It is probably possible to do strange things such as use texture memory as
|
|
// a Maple DMA receive buffer, but TOP_ADDRESS would need to be lowered
|
|
// accordingly.
|
|
*MDAPRO = MDAPRO__SECURITY_CODE
|
|
| MDAPRO__TOP_ADDRESS(0x40)
|
|
| MDAPRO__BOTTOM_ADDRESS(0x7f);
|
|
|
|
// SOFTWARE_INITIATION allows a Maple DMA transfer to be initiated by a write
|
|
// to the MDST register.
|
|
*MDTSEL = MDTSEL__TRIGGER_SELECT__SOFTWARE_INITIATION;
|
|
|
|
// the Maple DMA start address must be 32-byte aligned
|
|
*MDSTAR = MDSTAR__TABLE_ADDRESS((uint32_t)command_buf);
|
|
|
|
// re-enable Maple DMA
|
|
*MDEN = MDEN__DMA_ENABLE__ENABLE;
|
|
|
|
// start Maple DMA (completes asynchronously)
|
|
*MDST = MDST__START_STATUS;
|
|
}
|
|
|
|
void maple_dma_wait_complete()
|
|
{
|
|
// wait for Maple DMA completion
|
|
while ((*ISTNRM & ISTNRM__END_OF_DMA_MAPLE_DMA) == 0);
|
|
// clear Maple DMA end status
|
|
*ISTNRM = ISTNRM__END_OF_DMA_MAPLE_DMA;
|
|
}
|
|
|
|
//
|
|
// Maple command buffer construction
|
|
//
|
|
|
|
#define MAPLE__RECEIVE_DATA_ADDRESS__MASK 0x1fffffff
|
|
|
|
void maple_device_request(uint8_t * send_buf, uint8_t * recv_buf)
|
|
{
|
|
maple_host_command * host_command = (maple_host_command *)send_buf;
|
|
|
|
host_command->host_instruction = MAPLE__HOST_INSTRUCTION__END_FLAG
|
|
| MAPLE__HOST_INSTRUCTION__PORT_SELECT__A
|
|
| MAPLE__HOST_INSTRUCTION__PATTERN__NORMAL
|
|
| MAPLE__HOST_INSTRUCTION__TRANSFER_LENGTH(0);
|
|
|
|
host_command->receive_data_address = ((uint32_t)recv_buf) & MAPLE__RECEIVE_DATA_ADDRESS__MASK;
|
|
|
|
host_command->protocol_header.command_code = MAPLE__COMMAND_CODE__DEVICE_REQUEST;
|
|
|
|
host_command->protocol_header.destination_ap = MAPLE__AP__PORT_SELECT__A
|
|
| MAPLE__AP__DE__DEVICE;
|
|
|
|
host_command->protocol_header.source_ap = MAPLE__AP__PORT_SELECT__A
|
|
| MAPLE__AP__DE__PORT;
|
|
|
|
host_command->protocol_header.data_size = 0 / 4;
|
|
}
|
|
|
|
void main()
|
|
{
|
|
// set the transmit trigger to `1 byte`--this changes the behavior of TDFE
|
|
*SCFCR2 = SCFCR2__TTRG(0b11);
|
|
|
|
// Maple DMA buffers must be 32-byte aligned
|
|
uint8_t send_buf[1024] __attribute__((aligned(32)));
|
|
uint8_t recv_buf[1024] __attribute__((aligned(32)));
|
|
|
|
// fill send_buf with a "device request" command
|
|
// recv_buf is reply destination address
|
|
maple_device_request(send_buf, recv_buf);
|
|
|
|
maple_dma_start(send_buf);
|
|
|
|
maple_dma_wait_complete();
|
|
|
|
// decode the reply in recv_buf
|
|
|
|
maple_host_reply * host_reply = (maple_host_reply *)recv_buf;
|
|
|
|
if (host_reply->protocol_header.command_code != MAPLE__COMMAND_CODE__DEVICE_STATUS) {
|
|
string("maple port A: invalid response or disconnected\n");
|
|
return;
|
|
}
|
|
|
|
string("host_reply:\n");
|
|
string(" protocol_header:\n");
|
|
string(" command_code : ");
|
|
print_base16(host_reply->protocol_header.command_code, 2); character('\n');
|
|
string(" destination_ap : ");
|
|
print_base16(host_reply->protocol_header.destination_ap, 2); character('\n');
|
|
string(" source_ap : ");
|
|
print_base16(host_reply->protocol_header.source_ap, 2); character('\n');
|
|
string(" data_size : ");
|
|
print_base16(host_reply->protocol_header.data_size, 2); character('\n');
|
|
|
|
maple_device_status * device_status = (maple_device_status *)(recv_buf + (sizeof (maple_host_reply)));
|
|
|
|
// the Maple bus protocol is big endian, but the SH4 is running in little endian mode
|
|
#define bswap32 __builtin_bswap32
|
|
#define bswap16 __builtin_bswap16
|
|
|
|
string(" device_status:\n");
|
|
string(" device_id:\n");
|
|
string(" ft : ");
|
|
print_base16(bswap32(device_status->device_id.ft), 8); character('\n');
|
|
string(" fd[0]: ");
|
|
print_base16(bswap32(device_status->device_id.fd[0]), 8); character('\n');
|
|
string(" fd[1]: ");
|
|
print_base16(bswap32(device_status->device_id.fd[1]), 8); character('\n');
|
|
string(" fd[2]: ");
|
|
print_base16(bswap32(device_status->device_id.fd[2]), 8); character('\n');
|
|
string(" destination_code: ");
|
|
print_base16(bswap32(device_status->destination_code), 2); character('\n');
|
|
string(" connection_direction: ");
|
|
print_base16(bswap32(device_status->connection_direction), 2); character('\n');
|
|
string(" product_name: \"");
|
|
for (int i = 0; i < 30; i++)
|
|
character(device_status->product_name[i]);
|
|
string("\"\n");
|
|
string(" license: \"");
|
|
for (int i = 0; i < 60; i++)
|
|
character(device_status->license[i]);
|
|
string("\"\n");
|
|
string(" low_consumption_standby_current: ");
|
|
print_base16(bswap16(device_status->low_consumption_standby_current), 4); character('\n');
|
|
string(" maximum_current_consumption: ");
|
|
print_base16(bswap16(device_status->maximum_current_consumption), 4); character('\n');
|
|
|
|
// hack: "flush" the above characters before the serial loader resets the
|
|
// serial interface
|
|
//
|
|
// Other methods to detect "transmit fifo is empty" appear to be
|
|
// non-functional (including polling SCFDR2 or SCFSR2)
|
|
//
|
|
// It would also be appropriate to use an SH7091 timer here, to delay a
|
|
// certain number of milliseconds, though that would require defining more
|
|
// registers.
|
|
string(" ");
|
|
}
|