aboutsummaryrefslogtreecommitdiff
path: root/libpsn00b/psxspu/common.c
diff options
context:
space:
mode:
Diffstat (limited to 'libpsn00b/psxspu/common.c')
-rw-r--r--libpsn00b/psxspu/common.c154
1 files changed, 154 insertions, 0 deletions
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;
+}