diff options
Diffstat (limited to 'libpsn00b/psxspu/common.c')
| -rw-r--r-- | libpsn00b/psxspu/common.c | 154 |
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; +} |
