add maple example
This commit is contained in:
parent
498052f711
commit
ff6126b76a
403
maple.c
Normal file
403
maple.c
Normal file
@ -0,0 +1,403 @@
|
||||
#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 that happens to have a hard dependency on SH4 DMA (unlike the
|
||||
TA which the a more relaxed restriction "must use 32-byte transfers").
|
||||
|
||||
The structure of the transmit buffer given to the Maple DMA unit is always:
|
||||
|
||||
[ host instruction ]
|
||||
[ recieve data address ]
|
||||
[ protocol data ]
|
||||
[ command code ]
|
||||
[ destination ap ]
|
||||
[ source ap ]
|
||||
[ data size ]
|
||||
[ (variable-length data) ]
|
||||
[ host instruction ]
|
||||
[ recieve 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 "recieve 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 `recieve_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 recieve 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 recieve 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
|
||||
//
|
||||
|
||||
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;
|
||||
|
||||
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(" ");
|
||||
}
|
16
serial.c
16
serial.c
@ -1,8 +1,18 @@
|
||||
#include <stdint.h>
|
||||
|
||||
volatile uint8_t * SCFTDR2 = (volatile uint8_t *)0xffe8000c;
|
||||
volatile uint16_t * SCFSR2 = (volatile uint16_t *)0xffe80010;
|
||||
volatile uint16_t * SCFCR2 = (volatile uint16_t *)0xffe80018;
|
||||
/*
|
||||
This example does not configure the SCIF, and presumes it is already
|
||||
configured for UART transmission.
|
||||
|
||||
This is *not* the default SCIF state as initialized by the Dreamcast boot
|
||||
rom. However, the serial loader does not attempt to un-initialize the SCIF
|
||||
prior to jumping to system memory.
|
||||
*/
|
||||
|
||||
// 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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user