aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorspicyjpeg <thatspicyjpeg@gmail.com>2022-07-31 18:04:59 +0200
committerspicyjpeg <thatspicyjpeg@gmail.com>2022-07-31 18:04:59 +0200
commit9560a1427aec1681c5d0c2bc30190ce4b1ad8557 (patch)
tree3aad9d0f632687b86b9639c714d49a1a9f1c2397
parent073a859acf16ccbc0f49364e38126bf2bf03aa3d (diff)
downloadpsn00bsdk-9560a1427aec1681c5d0c2bc30190ce4b1ad8557.tar.gz
Rewrite libpsxspu in C and update sound examples
-rw-r--r--examples/sound/spustream/main.c48
-rw-r--r--examples/sound/vagsample/main.c10
-rw-r--r--libpsn00b/include/hwregs_a.h11
-rw-r--r--libpsn00b/include/hwregs_c.h143
-rw-r--r--libpsn00b/include/psxspu.h55
-rw-r--r--libpsn00b/psxgpu/common.c3
-rw-r--r--libpsn00b/psxgpu/image.c14
-rw-r--r--libpsn00b/psxpress/mdec.c8
-rw-r--r--libpsn00b/psxspu/common.c154
-rw-r--r--libpsn00b/psxspu/readme.txt21
-rw-r--r--libpsn00b/psxspu/spuinit.s124
-rw-r--r--libpsn00b/psxspu/spukeyon.s17
-rw-r--r--libpsn00b/psxspu/spureverbon.s16
-rw-r--r--libpsn00b/psxspu/spusetkey.s26
-rw-r--r--libpsn00b/psxspu/spusetreverb.s25
-rw-r--r--libpsn00b/psxspu/spusetreverbaddr.s25
-rw-r--r--libpsn00b/psxspu/spusetvoiceraw.s60
-rw-r--r--libpsn00b/psxspu/transfer.s108
18 files changed, 318 insertions, 550 deletions
diff --git a/examples/sound/spustream/main.c b/examples/sound/spustream/main.c
index 6b9db93..2ad122c 100644
--- a/examples/sound/spustream/main.c
+++ b/examples/sound/spustream/main.c
@@ -123,8 +123,8 @@ typedef struct {
} DB;
typedef struct {
- DB db[2];
- uint32_t db_active;
+ DB db[2];
+ int db_active;
} CONTEXT;
void init_context(CONTEXT *ctx) {
@@ -170,23 +170,13 @@ void display(CONTEXT *ctx) {
/* Stream interrupt handlers */
-// This is a silent looping sample used to keep unused SPU channels busy,
-// preventing them from accidentally triggering the SPU RAM interrupt and
-// throwing off the timing (all channels are always reading sample data, even
-// when "stopped"). It is 64 bytes as that is the minimum size for SPU DMA
-// transfers, however only the first 16 bytes are kept. The rest is going to be
-// overwritten by chunks.
-// https://problemkaputt.de/psx-spx.htm#spuinterrupt
-const uint8_t SPU_DUMMY_BLOCK[] = {
- 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
-};
-
// The first 4 KB of SPU RAM are reserved for capture buffers, so we have to
-// place stream buffers after those. Sony's SPU library additionally places a
-// dummy sample at 0x1000; we are going to do the same with the block above.
+// place stream buffers after those. A dummy sample is additionally placed by
+// default by the SPU library at 0x1000; it is going to be used here to keep
+// unused SPU channels busy, preventing them from accidentally triggering the
+// SPU RAM interrupt and throwing off the timing (all channels are always
+// reading sample data, even when "stopped").
+// https://problemkaputt.de/psx-spx.htm#spuinterrupt
#define DUMMY_BLOCK_ADDR 0x1000
#define BUFFER_START_ADDR 0x1010
#define CHUNK_SIZE (BUFFER_SIZE * NUM_CHANNELS)
@@ -207,7 +197,7 @@ static volatile StreamContext str_ctx;
// read from the CD and uploaded to SPU RAM. Due to DMA limitations it can't be
// allocated on the stack (especially not in the interrupt callbacks' stack,
// whose size is very limited).
-static uint8_t sector_buffer[2048];
+static uint32_t sector_buffer[512];
void spu_irq_handler(void) {
// Acknowledge the interrupt to ensure it can be triggered again. The only
@@ -231,7 +221,7 @@ void spu_irq_handler(void) {
str_ctx.spu_addr = BUFFER_START_ADDR + CHUNK_SIZE * str_ctx.db_active;
SPU_IRQ_ADDR = SPU_RAM_ADDR(str_ctx.spu_addr);
- for (uint32_t i = 0; i < NUM_CHANNELS; i++)
+ for (int i = 0; i < NUM_CHANNELS; i++)
SPU_CH_LOOP_ADDR(i) = SPU_RAM_ADDR(str_ctx.spu_addr + BUFFER_SIZE * i);
// Start loading the next chunk. cd_event_handler() will be called
@@ -241,7 +231,7 @@ void spu_irq_handler(void) {
CdControlF(CdlReadN, &pos);
}
-void cd_event_handler(int32_t event, uint8_t *payload) {
+void cd_event_handler(int event, uint8_t *payload) {
// Ignore all events other than a sector being ready.
// TODO: read errors should be handled properly
if (event != CdlDataReady)
@@ -255,7 +245,7 @@ void cd_event_handler(int32_t event, uint8_t *payload) {
// other buffer, as we're overriding loop addresses) at the end.
// NOTE: this isn't actually necessary here as the stream converter script
// already sets these flags in the file.
- /*for (uint32_t i = 0; i < NUM_CHANNELS; i++) {
+ /*for (int i = 0; i < NUM_CHANNELS; i++) {
if (
str_ctx.spu_pos >= (BUFFER_SIZE * i - 2048) &&
str_ctx.spu_pos < (BUFFER_SIZE * i)
@@ -268,7 +258,7 @@ void cd_event_handler(int32_t event, uint8_t *payload) {
// just treat the chunk as a single blob of data and copy it as-is; we only
// have to trim the padding at the end (if any) to avoid overwriting other
// data in SPU RAM.
- uint32_t length = CHUNK_SIZE - str_ctx.spu_pos;
+ size_t length = CHUNK_SIZE - str_ctx.spu_pos;
if (length > 2048)
length = 2048;
@@ -288,15 +278,9 @@ void cd_event_handler(int32_t event, uint8_t *payload) {
/* Stream helpers */
void init_spu_channels(void) {
- // Upload the dummy block to the SPU and play it on all channels, locking
- // them up and stopping them from messing with the SPU interrupt.
- // TODO: is this really necessary? (needs testing on real hardware)
- SpuSetTransferStartAddr(DUMMY_BLOCK_ADDR);
- SpuWrite(SPU_DUMMY_BLOCK, 64);
-
SPU_KEY_OFF = 0x00ffffff;
- for (uint32_t i = 0; i < 24; i++)
+ for (int i = 0; i < 24; i++)
SPU_CH_ADDR(i) = SPU_RAM_ADDR(DUMMY_BLOCK_ADDR);
SPU_KEY_ON = 0x00ffffff;
@@ -330,7 +314,7 @@ void init_stream(CdlFILE *file) {
void start_stream(void) {
SPU_KEY_OFF = CHANNEL_MASK;
- for (uint32_t i = 0; i < NUM_CHANNELS; i++) {
+ for (int i = 0; i < NUM_CHANNELS; i++) {
SPU_CH_ADDR(i) = SPU_RAM_ADDR(BUFFER_START_ADDR + BUFFER_SIZE * i);
SPU_CH_FREQ(i) = SAMPLE_RATE;
SPU_CH_ADSR(i) = 0x1fee80ff; // or 0x9fc080ff, 0xdff18087
@@ -429,7 +413,7 @@ int main(int argc, const char* argv[]) {
// Only set the sample rate registers if necessary.
if (pad->btn != 0xffff) {
- for (uint32_t i = 0; i < NUM_CHANNELS; i++)
+ for (int i = 0; i < NUM_CHANNELS; i++)
SPU_CH_FREQ(i) = sample_rate;
}
diff --git a/examples/sound/vagsample/main.c b/examples/sound/vagsample/main.c
index 1ec3b8a..c79e68e 100644
--- a/examples/sound/vagsample/main.c
+++ b/examples/sound/vagsample/main.c
@@ -32,7 +32,7 @@
*/
#include <stdio.h>
-#include <sys/types.h>
+#include <stdint.h>
#include <psxetc.h>
#include <psxgte.h>
#include <psxgpu.h>
@@ -104,8 +104,8 @@ void init(void)
SpuSetTransferStartAddr(addr_temp);
// Upload first sound clip and wait for transfer to finish
- SpuWrite(((unsigned char*)proyt)+48, proyt_size-48);
- SpuWait();
+ SpuWrite((const uint32_t *) &proyt[48], proyt_size-48);
+ SpuIsTransferCompleted(SPU_TRANSFER_WAIT);
// Obtain the address of the sound and advance address for the next one
// Samples are addressed in 8-byte units, so it'll have to be divided by 8
@@ -116,8 +116,8 @@ void init(void)
// Upload second sound clip
SpuSetTransferStartAddr(addr_temp);
- SpuWrite(((unsigned char*)tdfx)+48, tdfx_size-48);
- SpuWait();
+ SpuWrite((const uint32_t *) &tdfx[48], tdfx_size-48);
+ SpuIsTransferCompleted(SPU_TRANSFER_WAIT);
// Obtain the address of the second sound clip
tdfx_addr = addr_temp/8;
diff --git a/libpsn00b/include/hwregs_a.h b/libpsn00b/include/hwregs_a.h
index d8f6c72..c78b41a 100644
--- a/libpsn00b/include/hwregs_a.h
+++ b/libpsn00b/include/hwregs_a.h
@@ -1,10 +1,11 @@
-# Hardware register definitions for GNU assembler (as)
-#
-# Part of the PSn00bSDK Project by Lameguy64
-# 2019 Meido-Tek Productions
+# PSn00bSDK hardware registers definitions
+# (C) 2019-2022 Lameguy64, spicyjpeg - MPL licensed
+## Constants
-.set IOBASE, 0xbf80 # IO segment base (KSEG1)
+.set IOBASE, 0x1f80
+.set F_CPU, 33868800
+.set F_GPU, 53222400
## GPU
diff --git a/libpsn00b/include/hwregs_c.h b/libpsn00b/include/hwregs_c.h
index a9f4ee3..7b80590 100644
--- a/libpsn00b/include/hwregs_c.h
+++ b/libpsn00b/include/hwregs_c.h
@@ -14,116 +14,117 @@
/* Constants */
+#define IOBASE 0x1f800000
#define F_CPU 33868800UL
#define F_GPU 53222400UL
/* GPU */
-#define GPU_GP0 _MMIO32(0xbf801810)
-#define GPU_GP1 _MMIO32(0xbf801814)
+#define GPU_GP0 _MMIO32(IOBASE | 0x1810)
+#define GPU_GP1 _MMIO32(IOBASE | 0x1814)
/* CD drive */
-#define CD_STAT _MMIO8(0xbf801800)
-#define CD_CMD _MMIO8(0xbf801801)
-#define CD_DATA _MMIO8(0xbf801802)
-#define CD_IRQ _MMIO8(0xbf801803)
+#define CD_STAT _MMIO8(IOBASE | 0x1800)
+#define CD_CMD _MMIO8(IOBASE | 0x1801)
+#define CD_DATA _MMIO8(IOBASE | 0x1802)
+#define CD_IRQ _MMIO8(IOBASE | 0x1803)
-#define CD_REG(N) _MMIO8(0xbf801800 + (N))
+#define CD_REG(N) _MMIO8(IOBASE | 0x1800 + (N))
/* SPU */
-#define SPU_MASTER_VOL_L _MMIO16(0xbf801d80)
-#define SPU_MASTER_VOL_R _MMIO16(0xbf801d82)
-#define SPU_REVERB_VOL_L _MMIO16(0xbf801d84)
-#define SPU_REVERB_VOL_R _MMIO16(0xbf801d86)
-#define SPU_KEY_ON _MMIO32(0xbf801d88)
-#define SPU_KEY_OFF _MMIO32(0xbf801d8c)
-#define SPU_FM_MODE _MMIO32(0xbf801d90)
-#define SPU_NOISE_MODE _MMIO32(0xbf801d94)
-#define SPU_REVERB_ON _MMIO32(0xbf801d98)
-#define SPU_CHAN_STATUS _MMIO32(0xbf801d9c)
-
-#define SPU_REVERB_ADDR _MMIO16(0xbf801da2)
-#define SPU_IRQ_ADDR _MMIO16(0xbf801da4)
-#define SPU_ADDR _MMIO16(0xbf801da6)
-#define SPU_DATA _MMIO16(0xbf801da8)
-
-#define SPU_CTRL _MMIO16(0xbf801daa)
-#define SPU_DMA_CTRL _MMIO16(0xbf801dac)
-#define SPU_STAT _MMIO16(0xbf801dae)
-
-#define SPU_CD_VOL_L _MMIO16(0xbf801db0)
-#define SPU_CD_VOL_R _MMIO16(0xbf801db2)
-#define SPU_EXT_VOL_L _MMIO16(0xbf801db4)
-#define SPU_EXT_VOL_R _MMIO16(0xbf801db6)
-#define SPU_CURRENT_VOL_L _MMIO16(0xbf801db8)
-#define SPU_CURRENT_VOL_R _MMIO16(0xbf801dba)
+#define SPU_MASTER_VOL_L _MMIO16(IOBASE | 0x1d80)
+#define SPU_MASTER_VOL_R _MMIO16(IOBASE | 0x1d82)
+#define SPU_REVERB_VOL_L _MMIO16(IOBASE | 0x1d84)
+#define SPU_REVERB_VOL_R _MMIO16(IOBASE | 0x1d86)
+#define SPU_KEY_ON _MMIO32(IOBASE | 0x1d88)
+#define SPU_KEY_OFF _MMIO32(IOBASE | 0x1d8c)
+#define SPU_FM_MODE _MMIO32(IOBASE | 0x1d90)
+#define SPU_NOISE_MODE _MMIO32(IOBASE | 0x1d94)
+#define SPU_REVERB_ON _MMIO32(IOBASE | 0x1d98)
+#define SPU_CHAN_STATUS _MMIO32(IOBASE | 0x1d9c)
+
+#define SPU_REVERB_ADDR _MMIO16(IOBASE | 0x1da2)
+#define SPU_IRQ_ADDR _MMIO16(IOBASE | 0x1da4)
+#define SPU_ADDR _MMIO16(IOBASE | 0x1da6)
+#define SPU_DATA _MMIO16(IOBASE | 0x1da8)
+
+#define SPU_CTRL _MMIO16(IOBASE | 0x1daa)
+#define SPU_DMA_CTRL _MMIO16(IOBASE | 0x1dac)
+#define SPU_STAT _MMIO16(IOBASE | 0x1dae)
+
+#define SPU_CD_VOL_L _MMIO16(IOBASE | 0x1db0)
+#define SPU_CD_VOL_R _MMIO16(IOBASE | 0x1db2)
+#define SPU_EXT_VOL_L _MMIO16(IOBASE | 0x1db4)
+#define SPU_EXT_VOL_R _MMIO16(IOBASE | 0x1db6)
+#define SPU_CURRENT_VOL_L _MMIO16(IOBASE | 0x1db8)
+#define SPU_CURRENT_VOL_R _MMIO16(IOBASE | 0x1dba)
// These are not named SPU_VOICE_* to avoid name clashes with SPU attribute
// flags defined in psxspu.h.
-#define SPU_CH_VOL_L(N) _MMIO16(0xbf801c00 + 16 * (N))
-#define SPU_CH_VOL_R(N) _MMIO16(0xbf801c02 + 16 * (N))
-#define SPU_CH_FREQ(N) _MMIO16(0xbf801c04 + 16 * (N))
-#define SPU_CH_ADDR(N) _MMIO16(0xbf801c06 + 16 * (N))
-#define SPU_CH_ADSR(N) _MMIO32(0xbf801c08 + 16 * (N))
-#define SPU_CH_LOOP_ADDR(N) _MMIO16(0xbf801c0e + 16 * (N))
+#define SPU_CH_VOL_L(N) _MMIO16(IOBASE | 0x1c00 + 16 * (N))
+#define SPU_CH_VOL_R(N) _MMIO16(IOBASE | 0x1c02 + 16 * (N))
+#define SPU_CH_FREQ(N) _MMIO16(IOBASE | 0x1c04 + 16 * (N))
+#define SPU_CH_ADDR(N) _MMIO16(IOBASE | 0x1c06 + 16 * (N))
+#define SPU_CH_ADSR(N) _MMIO32(IOBASE | 0x1c08 + 16 * (N))
+#define SPU_CH_LOOP_ADDR(N) _MMIO16(IOBASE | 0x1c0e + 16 * (N))
/* MDEC */
-#define MDEC0 _MMIO32(0xbf801820)
-#define MDEC1 _MMIO32(0xbf801824)
+#define MDEC0 _MMIO32(IOBASE | 0x1820)
+#define MDEC1 _MMIO32(IOBASE | 0x1824)
/* SPI controller port */
// IMPORTANT: even though JOY_TXRX is a 32-bit register, it should only be
// accessed as 8-bit. Reading it as 16 or 32-bit works fine on real hardware,
// but leads to problems in some emulators.
-#define JOY_TXRX _MMIO8(0xbf801040)
-#define JOY_STAT _MMIO16(0xbf801044)
-#define JOY_MODE _MMIO16(0xbf801048)
-#define JOY_CTRL _MMIO16(0xbf80104a)
-#define JOY_BAUD _MMIO16(0xbf80104e)
+#define JOY_TXRX _MMIO8 (IOBASE | 0x1040)
+#define JOY_STAT _MMIO16(IOBASE | 0x1044)
+#define JOY_MODE _MMIO16(IOBASE | 0x1048)
+#define JOY_CTRL _MMIO16(IOBASE | 0x104a)
+#define JOY_BAUD _MMIO16(IOBASE | 0x104e)
/* Serial port */
-#define SIO_TXRX _MMIO8(0xbf801050)
-#define SIO_STAT _MMIO16(0xbf801054)
-#define SIO_MODE _MMIO16(0xbf801058)
-#define SIO_CTRL _MMIO16(0xbf80105a)
-#define SIO_BAUD _MMIO16(0xbf80105e)
+#define SIO_TXRX _MMIO8 (IOBASE | 0x1050)
+#define SIO_STAT _MMIO16(IOBASE | 0x1054)
+#define SIO_MODE _MMIO16(IOBASE | 0x1058)
+#define SIO_CTRL _MMIO16(IOBASE | 0x105a)
+#define SIO_BAUD _MMIO16(IOBASE | 0x105e)
/* IRQ controller */
-#define IRQ_STAT _MMIO32(0xbf801070)
-#define IRQ_MASK _MMIO32(0xbf801074)
+#define IRQ_STAT _MMIO32(IOBASE | 0x1070)
+#define IRQ_MASK _MMIO32(IOBASE | 0x1074)
/* DMA */
-#define DMA_DPCR _MMIO32(0xbf8010f0)
-#define DMA_DICR _MMIO32(0xbf8010f4)
+#define DMA_DPCR _MMIO32(IOBASE | 0x10f0)
+#define DMA_DICR _MMIO32(IOBASE | 0x10f4)
-#define DMA_MADR(N) _MMIO32(0xbf801080 + 16 * (N))
-#define DMA_BCR(N) _MMIO32(0xbf801084 + 16 * (N))
-#define DMA_CHCR(N) _MMIO32(0xbf801088 + 16 * (N))
+#define DMA_MADR(N) _MMIO32(IOBASE | 0x1080 + 16 * (N))
+#define DMA_BCR(N) _MMIO32(IOBASE | 0x1084 + 16 * (N))
+#define DMA_CHCR(N) _MMIO32(IOBASE | 0x1088 + 16 * (N))
/* Timers */
-#define TIMER_VALUE(N) _MMIO32(0xbf801100 + 16 * (N))
-#define TIMER_CTRL(N) _MMIO32(0xbf801104 + 16 * (N))
-#define TIMER_RELOAD(N) _MMIO32(0xbf801108 + 16 * (N))
+#define TIMER_VALUE(N) _MMIO32(IOBASE | 0x1100 + 16 * (N))
+#define TIMER_CTRL(N) _MMIO32(IOBASE | 0x1104 + 16 * (N))
+#define TIMER_RELOAD(N) _MMIO32(IOBASE | 0x1108 + 16 * (N))
/* Memory control */
-#define EXP1_ADDR _MMIO32(0xbf801000)
-#define EXP2_ADDR _MMIO32(0xbf801004)
-#define EXP1_DELAY_SIZE _MMIO32(0xbf801008)
-#define EXP3_DELAY_SIZE _MMIO32(0xbf80100c)
-#define BIOS_DELAY_SIZE _MMIO32(0xbf801010)
-#define SPU_DELAY_SIZE _MMIO32(0xbf801014)
-#define CD_DELAY_SIZE _MMIO32(0xbf801018)
-#define EXP2_DELAY_SIZE _MMIO32(0xbf80101c)
-#define COM_DELAY_CFG _MMIO32(0xbf801020)
-#define RAM_SIZE_CFG _MMIO32(0xbf801060)
+#define EXP1_ADDR _MMIO32(IOBASE | 0x1000)
+#define EXP2_ADDR _MMIO32(IOBASE | 0x1004)
+#define EXP1_DELAY_SIZE _MMIO32(IOBASE | 0x1008)
+#define EXP3_DELAY_SIZE _MMIO32(IOBASE | 0x100c)
+#define BIOS_DELAY_SIZE _MMIO32(IOBASE | 0x1010)
+#define SPU_DELAY_SIZE _MMIO32(IOBASE | 0x1014)
+#define CD_DELAY_SIZE _MMIO32(IOBASE | 0x1018)
+#define EXP2_DELAY_SIZE _MMIO32(IOBASE | 0x101c)
+#define COM_DELAY_CFG _MMIO32(IOBASE | 0x1020)
+#define RAM_SIZE_CFG _MMIO32(IOBASE | 0x1060)
#endif
diff --git a/libpsn00b/include/psxspu.h b/libpsn00b/include/psxspu.h
index 36bbe70..e4c667a 100644
--- a/libpsn00b/include/psxspu.h
+++ b/libpsn00b/include/psxspu.h
@@ -7,6 +7,8 @@
#define __PSXSPU_H
#include <stdint.h>
+#include <stddef.h>
+#include <hwregs_c.h>
/* Definitions */
@@ -33,10 +35,15 @@ typedef enum _SPU_AttrMask {
} SPU_AttrMask;
typedef enum _SPU_TransferMode {
- SPU_TRANSFER_BY_DMA = 0,
- SPU_TRANSFER_BY_IO = 1
+ SPU_TRANSFER_BY_DMA = 0,
+ SPU_TRANSFER_BY_IO = 1
} SPU_TransferMode;
+typedef enum _SPU_WaitMode {
+ SPU_TRANSFER_PEEK = 0,
+ SPU_TRANSFER_WAIT = 1
+} SPU_WaitMode;
+
/* Structure definitions */
typedef struct _SpuVolume {
@@ -66,6 +73,36 @@ typedef struct _SpuCommonAttr {
SpuExtAttr cd, ext;
} SpuCommonAttr;
+/* "Useless" macros for official SDK compatibility */
+
+#define SpuSetCommonMasterVolume(left, right) \
+ (SPU_MASTER_VOL_L = (left), SPU_MASTER_VOL_R = (right))
+#define SpuSetCommonCDVolume(left, right) \
+ (SPU_CD_VOL_L = (left), SPU_CD_VOL_R = (right))
+#define SpuSetCommonCDReverb(enable) \
+ ((enable) ? (SPU_CTRL |= 0x0004) : (SPU_CTRL &= 0xfffb))
+#define SpuSetCommonExtVolume(left, right) \
+ (SPU_EXT_VOL_L = (left), SPU_EXT_VOL_R = (right))
+#define SpuSetCommonExtReverb(enable) \
+ ((enable) ? (SPU_CTRL |= 0x0002) : (SPU_CTRL &= 0xfffd))
+
+#define SpuSetReverbAddr(addr) \
+ (SPU_REVERB_ADDR = ((addr) + 7) / 8)
+#define SpuSetIRQAddr(addr) \
+ (SPU_IRQ_ADDR = ((addr) + 7) / 8)
+
+#define SpuSetVoiceVolume(ch, left, right) \
+ (SPU_CH_VOL_L(ch) = (left), SPU_CH_VOL_R(ch) = (right))
+#define SpuSetVoicePitch(ch, pitch) \
+ (SPU_CH_FREQ(ch) = (pitch))
+#define SpuSetVoiceStartAddr(ch, addr) \
+ (SPU_CH_ADDR(ch) = ((addr) + 7) / 8)
+#define SpuSetVoiceADSR(ch, ar, dr, sr, rr, sl) \
+ (SPU_CH_ADSR(ch) = ((sl)) | ((dr) << 4) | ((ar) << 8) | ((rr) << 16) | ((sr) << 22) | (1 << 30))
+
+#define SpuSetKey(enable, voice_bit) \
+ ((enable) ? (SPU_KEY_ON = (voice_bit)) : (SPU_KEY_OFF = (voice_bit)))
+
/* Public API */
#ifdef __cplusplus
@@ -74,15 +111,11 @@ extern "C" {
void SpuInit(void);
-void SpuReverbOn(int voice);
-void SpuSetReverbAddr(int addr);
-void SpuSetReverbVolume(int left, int right);
-void SpuSetKey(int on_off, uint32_t voice_bit);
-
-int SpuSetTransferMode(int mode);
-int SpuSetTransferStartAddr(int addr);
-int SpuWrite(const uint8_t *addr, int size);
-void SpuWait(void);
+void SpuRead(const uint32_t *data, size_t size);
+void SpuWrite(const uint32_t *data, size_t size);
+SPU_TransferMode SpuSetTransferMode(SPU_TransferMode mode);
+uint32_t SpuSetTransferStartAddr(uint32_t addr);
+int SpuIsTransferCompleted(int mode);
#ifdef __cplusplus
}
diff --git a/libpsn00b/psxgpu/common.c b/libpsn00b/psxgpu/common.c
index bf115a1..a65e0c9 100644
--- a/libpsn00b/psxgpu/common.c
+++ b/libpsn00b/psxgpu/common.c
@@ -117,7 +117,6 @@ static void _vsync_halt(void) {
printf("psxgpu: VSync() timeout\n");
ChangeClearPAD(0);
ChangeClearRCnt(3, 0);
- return;
}
int VSync(int mode) {
@@ -152,7 +151,7 @@ int DrawSync(int mode) {
if (mode)
return (DMA_BCR(2) >> 16);
- // Wait for the queue to become empty, to make sure no .
+ // Wait for the queue to become empty.
// TODO: add a timeout
while (_queue_length)
__asm__ volatile("");
diff --git a/libpsn00b/psxgpu/image.c b/libpsn00b/psxgpu/image.c
index b3e5678..da51e7d 100644
--- a/libpsn00b/psxgpu/image.c
+++ b/libpsn00b/psxgpu/image.c
@@ -10,7 +10,7 @@
#define DMA_CHUNK_LENGTH 8
-/* Common internal load/store function */
+/* VRAM transfer API */
static void _load_store_image(
uint32_t command,
@@ -23,8 +23,10 @@ static void _load_store_image(
printf("psxgpu: can't transfer an odd number of pixels\n");
length /= 2;
- if ((length >= DMA_CHUNK_LENGTH) && (length % DMA_CHUNK_LENGTH))
- printf("psxgpu: transfer data length (%d) is not a multiple of %d\n", length, DMA_CHUNK_LENGTH);
+ if ((length >= DMA_CHUNK_LENGTH) && (length % DMA_CHUNK_LENGTH)) {
+ printf("psxgpu: transfer data length (%d) is not a multiple of %d, rounding\n", length, DMA_CHUNK_LENGTH);
+ length += DMA_CHUNK_LENGTH - 1;
+ }
DrawSync(0);
GPU_GP1 = 0x04000000; // Disable DMA request
@@ -45,17 +47,15 @@ static void _load_store_image(
else
DMA_BCR(2) = DMA_CHUNK_LENGTH | ((length / DMA_CHUNK_LENGTH) << 16);
- DMA_CHCR(2) = 0x01000200 | !(mode & 1);
+ DMA_CHCR(2) = 0x01000200 | ((mode & 1) ^ 1);
}
-/* Public VRAM API */
-
void LoadImage(const RECT *rect, const uint32_t *data) {
_load_store_image(0xa0000000, 2, rect, (uint32_t *) data);
}
void StoreImage(const RECT *rect, uint32_t *data) {
- _load_store_image(0xc0000000, 3, rect, (uint32_t *) data);
+ _load_store_image(0xc0000000, 3, rect, data);
}
/* .TIM image parsers */
diff --git a/libpsn00b/psxpress/mdec.c b/libpsn00b/psxpress/mdec.c
index b8d16b5..9c82d6b 100644
--- a/libpsn00b/psxpress/mdec.c
+++ b/libpsn00b/psxpress/mdec.c
@@ -126,9 +126,11 @@ void DecDCTin(const uint32_t *data, int mode) {
// data length as an argument rather than parsing it from the first 4 bytes of
// the stream.
void DecDCTinRaw(const uint32_t *data, size_t length) {
- // NOTE: if length >= DMA_CHUNK_LENGTH then it also has to be a multiple of
- // DMA_CHUNK_LENGTH, otherwise the DMA channel will get stuck waiting for
- // more data indefinitely.
+ if ((length >= DMA_CHUNK_LENGTH) && (length % DMA_CHUNK_LENGTH)) {
+ printf("psxmdec: transfer data length (%d) is not a multiple of %d, rounding\n", length, DMA_CHUNK_LENGTH);
+ length += DMA_CHUNK_LENGTH - 1;
+ }
+
DMA_MADR(0) = (uint32_t) data;
if (length < DMA_CHUNK_LENGTH)
DMA_BCR(0) = 0x00010000 | length;
diff --git a/libpsn00b/psxspu/common.c b/libpsn00b/psxspu/common.c
new file mode 100644
index 0000000..306cfd2
--- /dev/null
+++ b/libpsn00b/psxspu/common.c
@@ -0,0 +1,154 @@
+/*
+ * PSn00bSDK SPU library (common functions)
+ * (C) 2022 spicyjpeg - MPL licensed
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <psxspu.h>
+#include <hwregs_c.h>
+
+#define WRITABLE_AREA_ADDR 0x200
+#define DMA_CHUNK_LENGTH 16
+#define STATUS_TIMEOUT 0x100000
+
+/* Internal globals */
+
+static SPU_TransferMode _transfer_mode = SPU_TRANSFER_BY_DMA;
+static uint16_t _transfer_addr = WRITABLE_AREA_ADDR;
+
+/* SPU initialization */
+
+static void _wait_status(uint16_t mask, uint16_t value) {
+ for (int i = STATUS_TIMEOUT; i; i--) {
+ if ((SPU_STAT & mask) == value)
+ return;
+ }
+
+ printf("psxspu: status register timeout (0x%04x)\n", SPU_STAT);
+}
+
+void SpuInit(void) {
+ SPU_CTRL = 0x0000; // SPU disabled
+ _wait_status(0x001f, 0x0000);
+
+ SPU_MASTER_VOL_L = 0;
+ SPU_MASTER_VOL_R = 0;
+ SPU_REVERB_VOL_L = 0;
+ SPU_REVERB_VOL_R = 0;
+ SPU_KEY_OFF = 0x00ffffff;
+ SPU_FM_MODE = 0;
+ SPU_NOISE_MODE = 0;
+ SPU_REVERB_ON = 0;
+ SPU_REVERB_ADDR = 0xfffe;
+ SPU_CD_VOL_L = 0;
+ SPU_CD_VOL_R = 0;
+ SPU_EXT_VOL_L = 0;
+ SPU_EXT_VOL_R = 0;
+
+ for (int i = 0; i < 24; i++) {
+ SPU_CH_VOL_L(i) = 0;
+ SPU_CH_VOL_R(i) = 0;
+ SPU_CH_FREQ(i) = 0;
+ SPU_CH_ADDR(i) = 0;
+ }
+
+ DMA_DPCR |= 0x000b0000; // Enable DMA4
+ DMA_CHCR(4) = 0x00000201; // Stop DMA4
+
+ SPU_CTRL = 0xc011; // Enable SPU, DAC, CD audio, set manual transfer mode
+ _wait_status(0x001f, 0x0011);
+
+ // Upload a dummy ADPCM block to the first 16 bytes of SPU RAM. This may be
+ // freely used or overwritten.
+ SPU_ADDR = WRITABLE_AREA_ADDR;
+ _wait_status(0x0400, 0x0000);
+
+ SPU_DATA = 0x0500;
+ for (int i = 7; i; i--)
+ SPU_DATA = 0x0000;
+
+ // Sony's implementation leaves everything muted, however it makes sense to
+ // turn up at least the master and CD audio volume by default.
+ SPU_MASTER_VOL_L = 0x3fff;
+ SPU_MASTER_VOL_R = 0x3fff;
+ SPU_CD_VOL_L = 0x3fff;
+ SPU_CD_VOL_R = 0x3fff;
+}
+
+/* SPU RAM transfer API */
+
+static void _load_store_data(uint32_t *data, size_t length, int mode) {
+ if (length % 4)
+ printf("psxspu: can't transfer a number of bytes that isn't multiple of 4\n");
+
+ length /= 4;
+ if ((length >= DMA_CHUNK_LENGTH) && (length % DMA_CHUNK_LENGTH)) {
+ printf("psxspu: transfer data length (%d) is not a multiple of %d, rounding\n", length, DMA_CHUNK_LENGTH);
+ length += DMA_CHUNK_LENGTH - 1;
+ }
+
+ SPU_CTRL &= 0xffcf; // Disable DMA request
+ _wait_status(0x0030, 0x0000);
+
+ // Enable DMA request for writing (2) or reading (3)
+ SPU_ADDR = _transfer_addr;
+ SPU_CTRL |= mode << 4;
+ _wait_status(0x0400, 0x0000);
+
+ DMA_MADR(4) = (uint32_t) data;
+ if (length < DMA_CHUNK_LENGTH)
+ DMA_BCR(4) = 0x00010000 | length;
+ else
+ DMA_BCR(4) = DMA_CHUNK_LENGTH | ((length / DMA_CHUNK_LENGTH) << 16);
+
+ DMA_CHCR(4) = 0x01000200 | ((mode & 1) ^ 1);
+}
+
+void SpuRead(const uint32_t *data, size_t size) {
+ _load_store_data(data, size, 3);
+}
+
+void SpuWrite(const uint32_t *data, size_t size) {
+ if (_transfer_addr < WRITABLE_AREA_ADDR)
+ return;
+
+ // I/O transfer mode is not that useful, but whatever.
+ if (_transfer_mode) {
+ SPU_ADDR = _transfer_addr;
+ SPU_CTRL = (SPU_CTRL & 0xffcf) | 0x0010; // Manual transfer mode
+ _wait_status(0x0400, 0x0000);
+
+ for (int i = size; i; i -= 4) {
+ uint32_t value = *(data++);
+
+ SPU_DATA = (uint16_t) value;
+ SPU_DATA = (uint16_t) (value >> 16);
+ }
+
+ return;
+ }
+
+ _load_store_data(data, size, 2);
+}
+
+SPU_TransferMode SpuSetTransferMode(SPU_TransferMode mode) {
+ _transfer_mode = mode;
+ return mode;
+}
+
+uint32_t SpuSetTransferStartAddr(uint32_t addr) {
+ if (addr > 0x7ffff)
+ return 0;
+
+ _transfer_addr = (addr + 7) / 8;
+ return addr;
+}
+
+int SpuIsTransferCompleted(int mode) {
+ if (!mode)
+ return ((SPU_STAT >> 10) & 1) ^ 1;
+
+ _wait_status(0x0400, 0x0000);
+ return 1;
+}
diff --git a/libpsn00b/psxspu/readme.txt b/libpsn00b/psxspu/readme.txt
index 3ed90d0..07bbf97 100644
--- a/libpsn00b/psxspu/readme.txt
+++ b/libpsn00b/psxspu/readme.txt
@@ -3,29 +3,24 @@ PSX SPU Library, part of PSn00bSDK
Licensed under Mozilla Public License
- Open source implementation of the SPU library written mostly in MIPS
-assembly. Currently only supports SPU init, uploading sample data using DMA
-transfer and basic sample playback but is currently lacking a bunch of
-important functions.
-
- Very work in progress currently.
-
+Open source implementation of the SPU library written entirely in C. Currently
+only supports SPU initialization, reading/writing SPU RAM using DMA and basic
+sample playback. Most of the official API is not going to be implemented as the
+vast majority of it is just inefficient wrappers around accessing SPU registers
+directly, which can be done already using the macros defined in hwregs_c.h.
Library developer(s):
- Lameguy64
-
-
+ Lameguy64 (initial implementation in assembly)
+ spicyjpeg
+
Library header(s):
psxspu.h
-
Todo list:
* SPU RAM allocation routines yet to be implemented (heap must only be
stored in main RAM and not SPU RAM like in the official SDK).
- * SpuKeyOn() is actually not part of the official library.
-
* SPU reverb configuration functions yet to be implemented.
diff --git a/libpsn00b/psxspu/spuinit.s b/libpsn00b/psxspu/spuinit.s
deleted file mode 100644
index 6966213..0000000
--- a/libpsn00b/psxspu/spuinit.s
+++ /dev/null
@@ -1,124 +0,0 @@
-.set noreorder
-.set noat
-
-.include "hwregs_a.h"
-
-.section .text
-
-
-.global SpuInit
-.type SpuInit, @function
-SpuInit:
-
- addiu $sp, -4
- sw $ra, 0($sp)
-
- lui $v1, IOBASE
-
- # Stop and mute everything
-
- sh $0 , SPU_CTRL($v1) # Clear control settings
- jal SpuCtrlSync
- move $a0, $0
-
- sh $0 , SPU_MASTER_VOL_L($v1) # Clear master volume
- sh $0 , SPU_MASTER_VOL_R($v1)
-
- sh $0 , SPU_REVERB_VOL_L($v1) # Clear reverb volume
- sh $0 , SPU_REVERB_VOL_R($v1)
-
- sh $0 , SPU_CD_VOL_L($v1) # Clear CD volume
- sh $0 , SPU_CD_VOL_R($v1)
-
- sh $0 , SPU_EXT_VOL_L($v1) # Clear external audio volume
- sh $0 , SPU_EXT_VOL_R($v1)
-
- sw $0 , SPU_FM_MODE($v1) # Turn off FM modes
- sw $0 , SPU_NOISE_MODE($v1) # Turn off noise modes
- sw $0 , SPU_REVERB_ON($v1) # Turn off reverb modes
-
- li $v0, 0xfffe
- sh $v0, SPU_REVERB_ADDR($v1)
-
- lui $v0, 0x0200;
- ori $v0, 0x3fff;
-
- # Clear all voices
-
- addiu $a1, $sp, -20
- sw $0 , 0($a1)
- sw $0 , 4($a1)
- sw $0 , 8($a1)
- sw $0 , 12($a1)
-
- li $a2, 23
-
-.Lclear_voices:
- jal SpuSetVoiceRaw
- move $a0, $a2
- addiu $a2, -1
- bgez $a2, .Lclear_voices
- nop
-
- addiu $v0, $0, -1 # Set all keys to off
- sw $v0, SPU_KEY_OFF($v1)
-
- li $v0, 0x4 # Set SPU data transfer control
- sh $v0, SPU_DMA_CTRL($v1) # (usually always 0x4)
-
- lw $v0, DMA_DPCR($v1) # Enable DMA channel 4 (SPU DMA)
- lui $at, 0xb
- or $v0, $at
- sw $v0, DMA_DPCR($v1)
-
- li $v0, 0xC001 # Enable SPU
- sh $v0, SPU_CTRL($v1)
- jal SpuCtrlSync
- move $a0, $v0
-
- li $v0, 0x3fff # Activate master volume
- sh $v0, SPU_MASTER_VOL_L($v1)
- sh $v0, SPU_MASTER_VOL_R($v1)
-
- sh $v0, SPU_CD_VOL_L($v1) # Activate CD volume
- sh $v0, SPU_CD_VOL_R($v1)
-
- lw $ra, 0($sp)
- addiu $sp, 4
- jr $ra
- nop
-
-
-# Waits until bits 0-5 of SPUSTAT are equal to SPUCNT
-#
-# Destroys v0, v1, a0
-#
-.global SpuCtrlSync
-.type SpuCtrlSync, @function
-SpuCtrlSync:
- lui $v1, IOBASE
- andi $a0, 0x3f
-.Lctrl_wait:
- lhu $v0, SPU_STAT($v1) # Get SPUSTAT value
- nop
- andi $v0, 0x3f
- bne $v0, $a0, .Lctrl_wait # Wait until SPUCNT and SPUSTAT are equal
- nop
- jr $ra
- nop
-
-
-# Waits until SPU has finished transfers
-#
-.global SpuWait
-.type SpuWait, @function
-SpuWait:
- lui $v0, IOBASE
- lhu $v0, SPU_STAT($v0)
- nop
- andi $v0, 0x400
- bnez $v0, SpuWait
- nop
- jr $ra
- nop
- \ No newline at end of file
diff --git a/libpsn00b/psxspu/spukeyon.s b/libpsn00b/psxspu/spukeyon.s
deleted file mode 100644
index 33592c2..0000000
--- a/libpsn00b/psxspu/spukeyon.s
+++ /dev/null
@@ -1,17 +0,0 @@
-.set noreorder
-
-.include "hwregs_a.h"
-
-.section .data
-
-
-.global SpuKeyOn
-.type SpuKeyOn, @function
-SpuKeyOn:
- lui $v1, IOBASE
- li $v0, 1
- sll $v0, $a0
- sw $v0, SPU_KEY_ON($v1)
- sw $v0, SPU_KEY_ON($v1)
- jr $ra
- nop \ No newline at end of file
diff --git a/libpsn00b/psxspu/spureverbon.s b/libpsn00b/psxspu/spureverbon.s
deleted file mode 100644
index 635fac3..0000000
--- a/libpsn00b/psxspu/spureverbon.s
+++ /dev/null
@@ -1,16 +0,0 @@
-.set noreorder
-
-.include "hwregs_a.h"
-
-.section .data
-
-
-.global SpuReverbOn
-.type SpuReverbOn, @function
-SpuReverbOn:
- lui $v1, IOBASE
- li $v0, 1
- sll $v0, $a0
- sw $v0, SPU_REVERB_ON($v1)
- jr $ra
- nop \ No newline at end of file
diff --git a/libpsn00b/psxspu/spusetkey.s b/libpsn00b/psxspu/spusetkey.s
deleted file mode 100644
index 4ad0cff..0000000
--- a/libpsn00b/psxspu/spusetkey.s
+++ /dev/null
@@ -1,26 +0,0 @@
-.set noreorder
-
-.include "hwregs_a.h"
-
-.section .data
-
-
-.global SpuSetKey
-.type SpuSetKey, @function
-SpuSetKey:
- # a0 - 0: key off, 1: key on
- # a1 - Voice bit mask
-
- lui $a2, IOBASE
-
- beqz $a0, .Lkey_off
- nop
-
- jr $ra
- sw $a1, SPU_KEY_ON($a2)
-
-.Lkey_off:
-
- jr $ra
- sw $a1, SPU_KEY_OFF($a2)
- \ No newline at end of file
diff --git a/libpsn00b/psxspu/spusetreverb.s b/libpsn00b/psxspu/spusetreverb.s
deleted file mode 100644
index 8257812..0000000
--- a/libpsn00b/psxspu/spusetreverb.s
+++ /dev/null
@@ -1,25 +0,0 @@
-.set noreorder
-
-.include "hwregs_a.h"
-
-.section .data
-
-
-.global SpuSetReverb
-.type SpuSetReverb, @function
-SpuSetReverb:
- addiu $sp, -4
- sw $ra, 0($sp)
-
- lui $v1, IOBASE
- lhu $v0, SPU_CTRL($v1)
- nop
- ori $v0, 0x80 # Enable reverb
- sh $v0, SPU_CTRL($v1)
- jal SpuCtrlSync
- move $a0, $v0
-
- lw $ra, 0($sp)
- addiu $sp, 4
- jr $ra
- nop \ No newline at end of file
diff --git a/libpsn00b/psxspu/spusetreverbaddr.s b/libpsn00b/psxspu/spusetreverbaddr.s
deleted file mode 100644
index 089a91a..0000000
--- a/libpsn00b/psxspu/spusetreverbaddr.s
+++ /dev/null
@@ -1,25 +0,0 @@
-.set noreorder
-
-.include "hwregs_a.h"
-
-
-.section .text
-
-.global SpuSetReverbAddr
-.type SpuSetReverbAddr, @function
-SpuSetReverbAddr:
- lui $a3, IOBASE
- srl $a0, 3
- sh $a0, SPU_REVERB_ADDR($a3)
- jr $ra
- nop
-
-
-.global SpuSetReverbVolume
-.type SpuSetReverbVolume, @function
-SpuSetReverbVolume:
- lui $a3, IOBASE
- sh $a0, SPU_REVERB_VOL_L($a3)
- sh $a1, SPU_REVERB_VOL_R($a3)
- jr $ra
- nop \ No newline at end of file
diff --git a/libpsn00b/psxspu/spusetvoiceraw.s b/libpsn00b/psxspu/spusetvoiceraw.s
deleted file mode 100644
index 43450f6..0000000
--- a/libpsn00b/psxspu/spusetvoiceraw.s
+++ /dev/null
@@ -1,60 +0,0 @@
-.set noreorder
-
-.include "hwregs_a.h"
-
-
-.set PARAM_L, 0
-.set PARAM_R, 2
-.set PARAM_FREQ, 4
-.set PARAM_ADDR, 6
-.set PARAM_LOOP, 8
-.set PARAM_RES, 10
-.set PARAM_ADSR, 12
-
-
-.section .text
-
-.global SpuSetVoiceRaw
-.type SpuSetVoiceRaw, @function
-SpuSetVoiceRaw:
-
- # a0 - Voice number
- # a1 - Address to parameters
-
- sll $a0, 4
- addiu $a0, SPU_VOICE_BASE
-
- lui $v1, IOBASE
- or $a0, $v1
-
- lhu $v0, PARAM_L($a1)
- nop
- sh $v0, SPU_VOICE_VOL_L($a0)
-
- lhu $v0, PARAM_R($a1)
- nop
- sh $v0, SPU_VOICE_VOL_R($a0)
-
- lhu $v0, PARAM_FREQ($a1)
- nop
- sh $v0, SPU_VOICE_FREQ($a0)
-
- lhu $v0, PARAM_ADDR($a1)
- nop
- sh $v0, SPU_VOICE_ADDR($a0)
-
- lhu $v0, PARAM_LOOP($a1)
- nop
- sh $v0, SPU_VOICE_LOOP($a0)
-
-
- lw $v0, PARAM_ADSR($a1)
- nop
- sh $v0, SPU_VOICE_ADSR_L($a0)
- srl $v0, 16
- sh $v0, SPU_VOICE_ADSR_H($a0)
-
-
- jr $ra
- nop
- \ No newline at end of file
diff --git a/libpsn00b/psxspu/transfer.s b/libpsn00b/psxspu/transfer.s
deleted file mode 100644
index adcdb33..0000000
--- a/libpsn00b/psxspu/transfer.s
+++ /dev/null
@@ -1,108 +0,0 @@
-.set noreorder
-
-.include "hwregs_a.h"
-
-
-.section .text
-
-.global SpuSetTransferMode
-.type SpuSetTransferMode, @function
-SpuSetTransferMode:
- la $v0, _spu_transfer_mode
- sb $a0, 0($v0)
- jr $ra
- move $v0, $a0
-
-
-.global SpuSetTransferStartAddr
-.type SpuSetTransferStartAddr, @function
-SpuSetTransferStartAddr:
- li $v0, 0x1000 # Check if value is valid
- blt $a0, $v0, .Lbad_value
- nop
- lui $v0, 8 # 0x7ffff = (8<<16)-1
- addiu $v0, -1
- bgt $a0, $v0, .Lbad_value
- nop
-
- la $v1, _spu_transfer_addr
- srl $v0, $a0, 3 # Set transfer destination address
- sh $v0, 0($v1)
-
- jr $ra
- move $v0, $a0
-
-.Lbad_value:
- jr $ra
- move $v0, $0
-
-
-.global SpuWrite
-.type SpuWrite, @function
-SpuWrite:
- addiu $sp, -8
- sw $ra, 0($sp)
- sw $a0, 4($sp)
-
- lui $a3, IOBASE
-
- lhu $v0, SPU_CTRL($a3) # Set transfer mode to Stop
- nop
- andi $v0, 0xffcf
- sh $v0, SPU_CTRL($a3)
- jal SpuCtrlSync
- move $a0, $v0
-
- la $v1, _spu_transfer_addr # Set SPU write address
- lhu $v1, 0($v1)
- nop
- sh $v1, SPU_ADDR($a3)
-
- lhu $v0, SPU_CTRL($a3) # Set transfer mode to DMA write
- nop
- ori $v0, 0x20
- sh $v0, SPU_CTRL($a3)
- #jal SpuCtrlSync # Locks up on most emulators (bit 5 in
- #move $a0, $v0 # SPUSTAT likely not updating, seems to
- # be okay to not wait for it on real HW)
-
- lw $a0, 4($sp)
-
-.Ldma_wait: # Wait for SPU to be ready for DMA
- lhu $v0, SPU_STAT($a3)
- nop
- andi $v0, 0x400 # Bit 8 in SPUSTAT never changes to 1 on
- bnez $v0, .Ldma_wait # emulators so use bit 10 instead
- nop
-
- sw $a0, DMA4_MADR($a3) # Set DMA source address
-
- li $v0, 0x10 # 16 words per block (64 bytes)
- addiu $a1, 63 # Add by 63 to ensure all bytes get sent
- srl $a1, 6 # Equivalent to divide by 64
- andi $a1, 0xffff
- sll $a1, 16
- or $v0, $a1
- sw $v0, DMA4_BCR($a3)
-
- lui $v0, 0x0100 # Commence transfer
- ori $v0, 0x0201
- sw $v0, DMA4_CHCR($a3)
-
- lw $ra, 0($sp)
- addiu $sp, 8
- jr $ra
- nop
-
-
-.section .data
-
-.global _spu_transfer_mode
-.type _spu_transfer_mode, @object
-_spu_transfer_mode:
- .word 0x0
-
-.global _spu_transfer_addr
-.type _spu_transfer_addr, @object
-_spu_transfer_addr:
- .word 0x200 \ No newline at end of file