EverDrive-64 X7
The EverDrive-64 X7 is a flash cart made by Krikzz.
Serial Communication
The EverDrive-64 X7 supports serial over USB communication to a host PC. This can be used to load ROMs and allows running ROMs to send an receive data from the host PC. Krikzz provides a reference implementation here.
Serial Registers
The EverDrive-64 X7 provides various registers starting at the address 0x1F800000
in the cartridge address space. The following registers are used for USB communcation.
Name | Description | Address relative to 0x1F800000 | Size (bytes) |
---|---|---|---|
REG_USB_CFG | Reads USB status and sends read and write commands | 0x0004 | 4 |
REG_USB_DATA | The temporary data buffer used to read and write to USB | 0x0400 | 512 |
REG_KEY | Set to 0xAA55 on initialization | 0x8004 | 4 |
REG_SYS_CFG | Set to 0 on initialization | 0x8000 | 4 |
The REG_USB_CFG
register contains various bits as described below
Name | Description | Bits (15-0) | Hex Mask |
---|---|---|---|
USB_LE_CFG | Set to a 1 when reading and writing data | 15 | 0x8000 |
USB_LE_CTR | Set to a 1 when reading and writing data | 14 | 0x4000 |
USB_STA_BSY | Behavior not known | 13 | 0x2000 |
USB_STA_PWR | Set to when 1 when data can be read or written | 12 | 0x1000 |
USB_STA_TXE | Set to 0 when data can be written | 11 | 0x0800 |
USB_CFG_RD/USB_STA_RXF | Writing a 1 switches the controller to read mode. Writing a 0 switches the controller to write mode.
Reads as a 0 when data is available to be read. |
10 | 0x0400 |
USB_CFG_ACT/USB_STA_ACT | Writing a 1 starts reading or writing to REG_USB_DATA. The direction of data is specified by the value of USB_CFG_RD. The amount of data written is specified by baddr.
When reading this bit, a 1 indicates the USB port is busy reading or writing data. |
9 | 0x0200 |
baddr | These 5 bits give the address into REG_USB_DAT where USB data is read from and written to. Data is read starting at baddr offset until the end of the REG_USB_DAT buffer. This means the amount of data read is 512 - baddr . Because of this, when copying data to and from REG_USB_DAT, you don't always start copying at the beginning of the buffer.
|
8..0 | 0x01FF |
Initializing EverDrive
Below is the code used to initialize the EverDrive to be used for USB IO from krikzz's reference code with comments
/*************************************************************************
* Peripheral Interface (PI) Registers
*/
#define PI_BASE_REG 0x04600000
/* PI dom1 latency (R/W): [7:0] domain 1 device latency */
#define PI_BSD_DOM1_LAT_REG (PI_BASE_REG+0x14)
/* PI dom1 pulse width (R/W): [7:0] domain 1 device R/W strobe pulse width */
#define PI_BSD_DOM1_PWD_REG (PI_BASE_REG+0x18)
#define IO_WRITE(addr,data) (*(vu32 *)PHYS_TO_K1(addr)=(u32)(data))
void bi_init() {
// sets the PI manager latency
IO_WRITE(PI_BSD_DOM1_LAT_REG, 0x04);
// sets the PI manager pulse
IO_WRITE(PI_BSD_DOM1_PWD_REG, 0x0C);
// the specific value of 0xAA55 needs to be written here to enable USB IO
bi_reg_wr(REG_KEY, 0xAA55);
bi_reg_wr(REG_SYS_CFG, 0);
// this flushes serial buffer of any pending data
bi_usb_init();
// sets the save type. Not needed for USB IO
bi_set_save_type(SAVE_OFF);
}
The example code sets latency and pulse for the values expected for USB communication. Different devices such as flash, sram, or cartridge would expect different values. You should change these to the appropriate values for the device before doing a DMA to that device. If you are using libultra there is a function designed to configure the PI manager for you before starting a DMA using osEPiStartDma
. The first parameter of this function accepts a pointer to a OSPiHandle
which contains the latency and pulse values that should be used by the PI manager.
Sending Data to the Host
Below is the example code for sending data to the host PC in the function bi_usb_wr
. Keep in mind that src
should be aligned on 8 byte boundaries since it is used in a DMA transfer. For the same reason the length should be a multiple of 2.
#define USB_LE_CFG 0x8000
#define USB_LE_CTR 0x4000
#define USB_CFG_ACT 0x0200
#define USB_CFG_RD 0x0400
#define USB_CFG_WR 0x0000
#define USB_STA_ACT 0x0200
#define USB_STA_RXF 0x0400
#define USB_STA_TXE 0x0800
#define USB_STA_PWR 0x1000
#define USB_STA_BSY 0x2000
#define USB_CMD_RD_NOP (USB_LE_CFG | USB_LE_CTR | USB_CFG_RD)
#define USB_CMD_RD (USB_LE_CFG | USB_LE_CTR | USB_CFG_RD | USB_CFG_ACT)
#define USB_CMD_WR_NOP (USB_LE_CFG | USB_LE_CTR | USB_CFG_WR)
#define USB_CMD_WR (USB_LE_CFG | USB_LE_CTR | USB_CFG_WR | USB_CFG_ACT)
#define REG_BASE 0x1F800000
#define KSEG1 0xA0000000
#define REG_ADDR(reg) (KSEG1 | REG_BASE | (reg))
u8 bi_usb_busy() {
u32 tout = 0;
// wait for the USB_STA_ACT bit to clear in REG_USB_CFG
// indicating the USB port is no longer busy
while ((bi_reg_rd(REG_USB_CFG) & USB_STA_ACT) != 0) {
// check to see if the operation has timed out
if (tout++ != 8192)continue;
// Turn off USB serial communication
bi_reg_wr(REG_USB_CFG, USB_CMD_RD_NOP);
return BI_ERR_USB_TOUT;
}
return 0;
}
u8 bi_usb_wr(void *src, u32 len) {
u8 resp = 0;
u16 blen, baddr;
// switch USB to write mode but don't start the transfer yet
bi_reg_wr(REG_USB_CFG, USB_CMD_WR_NOP);
while (len) {
// data can only be sent in chunks of 512 bytes
blen = 512;
if (blen > len)blen = len;
// calculate the offset into REG_USB_DAT from the length of data
baddr = 512 - blen;
// DMA the data from ram into the temporary buffer on the cartridge
// Note that sending less than 512 bytes will result it baddr being
// a positive value making the start of the DMA copy be somewhere
// in the middle of REG_USB_DAT instead of always starting at the
// beginning
sysPI_wr(src, REG_ADDR(REG_USB_DAT + baddr), blen);
src += 512;
// Start sending data from REG_USB_DAT over the usb starting at
// baddr and ending at the end of the 512 byte buffer.
bi_reg_wr(REG_USB_CFG, USB_CMD_WR | baddr);
// Wait for the data to finish sending over USB
resp = bi_usb_busy();
if (resp)break;
len -= blen;
}
return resp;
}
Reading Data from the Host
Below is an example of reading data from the host PC. Since dst
is used in a DMA transfer it must be aligned to 8 bytes. For the same reason, len should be multiple of 2. The EverDrive-64 X7 also has the limitation that at least 16 bytes must be read. For this reason you should pad data sent to the EverDrive to a multiple of 16 bytes. You also must not read more data than what as been send from the host PC otherwise the read operation will time out while waiting for data that never comes. To solve this you can either prefix data sent to the EverDrive with a header indicating the amount of data being sent or you read data 16 bytes at a time calling bi_usb_can_wr
each time to check if there is more data.
// returns 1 if there is data waiting to be read
u8 bi_usb_can_wr() {
// check if USB_STA_PWR is 1 and USB_STA_TXE is 0 in REG_USB_CFG
u32 status = bi_reg_rd(REG_USB_CFG) & (USB_STA_PWR | USB_STA_TXE);
if (status == USB_STA_PWR)return 1;
return 0;
}
u8 bi_usb_rd(void *dst, u32 len) {
u8 resp = 0;
u16 blen, baddr;
while (len) {
// data cannot be read in chunks larger than 512 bytes
blen = 512;
if (blen > len)blen = len;
baddr = 512 - blen;
// tell the EverDrive to write data received from USB to
// to REG_USB_DAT starting at address baddr. It will continue
// to write data until the end of the 512 byte buffer. If
// there is not enough data to fill the buffer it will wait
// until that data is received
bi_reg_wr(REG_USB_CFG, USB_CMD_RD | baddr);
// wait for the serial data to be copied into REG_USB_DAT
// if there is not enough data to write to the end of the
// buffer this operation will time out
resp = bi_usb_busy();
if (resp)break;
// copy data from REG_USB_DAT into ram
sysPI_rd(dst, REG_ADDR(REG_USB_DAT + baddr), blen);
dst += blen;
len -= blen;
}
return resp;
}
Common Code
Code used for both reading and writing
void sysPI_rd(void *ram, unsigned long pi_address, unsigned long len) {
pi_address &= 0x1FFFFFFF;
// invalidates the cache so the new values in ram after reading
// wont mismatch what is in the cache
// this is part of libdragon, for the libultra equivalent use osInvalDCache
data_cache_hit_writeback_invalidate(ram, len);
// the rest of the code is for a DMA transfer from the cartridge into RAM.
// use osEPiStartDma for libultra
disable_interrupts();
while (dma_busy());
IO_WRITE(PI_STATUS_REG, 3);
PI_regs->ram_address = ram;
PI_regs->pi_address = pi_address; //(pi_address | 0x10000000) & 0x1FFFFFFF;
PI_regs->write_length = len - 1;
while (dma_busy());
enable_interrupts();
}
void sysPI_wr(void *ram, unsigned long pi_address, unsigned long len) {
pi_address &= 0x1FFFFFFF;
// Writes the cache back to ram so the DMA gets the correct value
// this is part of libdragon, for the libultra equivalent use osWritebackDCache
data_cache_hit_writeback(ram, len);
// the rest of the code is for a DMA transfer from the RAM into the cartridge.
// use osEPiStartDma for libultra
disable_interrupts();
while (dma_busy());
IO_WRITE(PI_STATUS_REG, 3);
PI_regs->ram_address = ram;
PI_regs->pi_address = pi_address; //(pi_address | 0x10000000) & 0x1FFFFFFF;
PI_regs->read_length = len - 1;
while (dma_busy());
enable_interrupts();
}
// Writes a given value to the given register on the EverDrive
void bi_reg_wr(u16 reg, u32 val) {
sysPI_wr(&val, REG_ADDR(reg), 4);
}
// Reads the given register value
u32 bi_reg_rd(u16 reg) {
u32 val;
sysPI_rd(&val, REG_ADDR(reg), 4);
return val;
}