Skip to content

Commit 67bae7b

Browse files
committed
Add basic SPU driver to psyqo
Based on a patch by Vatuu. Provides SPU initialization, DMA writes to SPU RAM, channel silencing via dummy ADPCM loop, ADPCM playback, and free channel detection. Signed-off-by: Nicolas 'Pixel' Noble <nicolas@nobis-crew.org>
1 parent b745534 commit 67bae7b

5 files changed

Lines changed: 127 additions & 34 deletions

File tree

src/mips/common/hardware/spu.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ struct SPUVoice {
5555
#define SPU_NOISE_EN_HIGH HW_U16(0x1f801d96)
5656
#define SPU_REVERB_EN_LOW HW_U16(0x1f801d98)
5757
#define SPU_REVERB_EN_HIGH HW_U16(0x1f801d9a)
58+
#define SPU_REVERB_ADDR HW_U16(0x1f801da2)
5859

5960
#define SPU_RAM_DTA HW_U16(0x1f801da6)
6061
#define SPU_CTRL HW_U16(0x1f801daa)

src/mips/psyqo/msf.hh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ SOFTWARE.
3232

3333
namespace psyqo {
3434

35-
static inline constexpr uint8_t btoi(uint8_t b) { return ((b / 16) * 10) + (b % 16); }
36-
static inline constexpr uint8_t itob(uint8_t i) { return ((i / 10) * 16) + (i % 10); }
35+
constexpr uint8_t btoi(uint8_t b) { return ((b / 16) * 10) + (b % 16); }
36+
constexpr uint8_t itob(uint8_t i) { return ((i / 10) * 16) + (i % 10); }
3737

3838
struct MSF {
3939
MSF() : m(0), s(0), f(0) {}

src/mips/psyqo/spu.hh

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,31 @@ SOFTWARE.
2626

2727
#pragma once
2828

29+
#include "fixed-point.hh"
30+
2931
namespace psyqo {
3032

3133
class SPU {
3234
public:
33-
static void reset();
34-
static void resetVoice(unsigned voice);
35+
static void initialize();
36+
static void silenceChannels(uint32_t channelMask);
37+
static void dmaWrite(uint32_t spuAddress, const void *ramAddress, uint16_t dataSize, uint8_t blockSize);
38+
39+
struct ChannelPlaybackConfig {
40+
FixedPoint<12, uint16_t> sampleRate;
41+
uint16_t volumeLeft, volumeRight;
42+
uint32_t adsr;
43+
};
44+
45+
static void playADPCM(uint8_t channelId, uint16_t spuRamAddress, const ChannelPlaybackConfig &config, bool hardCut);
46+
static uint32_t getNextFreeChannel();
47+
48+
static constexpr uint32_t NO_FREE_CHANNEL = 0xffffffff;
49+
static constexpr uint32_t BASE_SAMPLE_RATE = 44100;
3550

3651
private:
37-
static void waitIdle();
52+
template <typename T>
53+
static bool waitForStatus(T mask, T expected, const volatile T *value);
3854
};
3955

4056
} // namespace psyqo

src/mips/psyqo/src/kernel.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ void dmaIRQ() {
317317
} // namespace
318318

319319
void psyqo::Kernel::Internal::prepare(Application& application) {
320-
SPU::reset();
320+
SPU::initialize();
321321
Hardware::CPU::IMask.clear();
322322
Hardware::CPU::IReg.clear();
323323
for (unsigned i = 0; i < 7; i++) {

src/mips/psyqo/src/spu.cpp

Lines changed: 104 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,48 +26,124 @@ SOFTWARE.
2626

2727
#include "psyqo/spu.hh"
2828

29-
#include <EASTL/atomic.h>
30-
31-
#include "EASTL/internal/atomic/atomic_memory_order.h"
29+
#include "common/hardware/dma.h"
3230
#include "common/hardware/spu.h"
31+
#include "psyqo/kernel.hh"
32+
33+
constexpr uint16_t DUMMY_SAMPLE_POSITION = 0x1000;
34+
constexpr uint8_t DUMMY_SAMPLE_SIZE = 16;
35+
alignas(4) constexpr uint8_t DUMMY_SAMPLE[] = {0x00, 0b101, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
36+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
37+
38+
void psyqo::SPU::dmaWrite(const uint32_t spuAddress, const void *ramAddress, const uint16_t dataSize,
39+
const uint8_t blockSize) {
40+
Kernel::assert(blockSize % sizeof(uint32_t) == 0 && blockSize != 0 && blockSize <= 16, "Invalid DMA block size");
41+
SPU_CTRL &= ~(0b11 << 4);
42+
waitForStatus<uint16_t>(0b11 << 4, 0b00 << 4, &SPU_STATUS);
43+
SPU_CTRL |= 1 << 5;
44+
SPU_RAM_DTA = spuAddress / 8;
45+
waitForStatus<uint16_t>(1 << 5, 1 << 5, &SPU_STATUS);
46+
47+
DPCR |= 1 << 19;
48+
DPCR &= ~(0b111 << 16);
49+
DPCR |= 0b100 << 16;
50+
DMA_CTRL[DMA_SPU].MADR = reinterpret_cast<uint32_t>(ramAddress);
51+
DMA_CTRL[DMA_SPU].BCR = blockSize | ((dataSize / blockSize) << 16);
52+
DMA_CTRL[DMA_SPU].CHCR = 1 | 1 << 9 | 1 << 24;
3353

34-
void psyqo::SPU::resetVoice(unsigned voiceID) {
35-
SPU_VOICES[voiceID].volumeLeft = 0;
36-
SPU_VOICES[voiceID].volumeRight = 0;
37-
SPU_VOICES[voiceID].sampleRate = 0;
38-
SPU_VOICES[voiceID].sampleStartAddr = 0;
39-
SPU_VOICES[voiceID].ad = 0x000f;
40-
SPU_VOICES[voiceID].currentVolume = 0;
41-
SPU_VOICES[voiceID].sampleRepeatAddr = 0;
42-
SPU_VOICES[voiceID].sr = 0x0000;
54+
waitForStatus<uint32_t>(1 << 24, 0 << 24, &DMA_CTRL[DMA_SPU].CHCR);
4355
}
4456

45-
void psyqo::SPU::waitIdle() {
46-
do {
47-
for (unsigned c = 0; c < 256; c++) eastl::atomic_signal_fence(eastl::memory_order_relaxed);
48-
} while ((SPU_STATUS & 0x07ff) != 0);
57+
void psyqo::SPU::silenceChannels(const uint32_t channelMask) {
58+
SPU_KEY_OFF_LOW = channelMask & 0xffff;
59+
SPU_KEY_OFF_HIGH = (channelMask >> 16) & 0xffff;
60+
61+
for (uint8_t channel = 0; channel < 24; channel++) {
62+
if (!((channelMask >> channel) & 1)) {
63+
continue;
64+
}
65+
SPU_VOICES[channel].volumeLeft = 0;
66+
SPU_VOICES[channel].volumeRight = 0;
67+
SPU_VOICES[channel].sampleRate = 0;
68+
SPU_VOICES[channel].sampleStartAddr = DUMMY_SAMPLE_POSITION / 8;
69+
SPU_VOICES[channel].sampleRepeatAddr = DUMMY_SAMPLE_POSITION / 8;
70+
}
71+
72+
SPU_KEY_ON_LOW = channelMask & 0xffff;
73+
SPU_KEY_ON_HIGH = (channelMask >> 16) & 0xffff;
4974
}
5075

51-
void psyqo::SPU::reset() {
52-
SPU_VOL_MAIN_LEFT = 0x3800;
53-
SPU_VOL_MAIN_RIGHT = 0x3800;
76+
template <typename T>
77+
bool psyqo::SPU::waitForStatus(const T mask, const T expected, const volatile T *value) {
78+
for (int timeout = 10000; timeout >= 0; timeout--) {
79+
if ((*value & mask) == expected) {
80+
return true;
81+
}
82+
}
83+
return false;
84+
}
85+
86+
void psyqo::SPU::initialize() {
87+
SBUS_DEV4_CTRL = 1 | 0b1110 << 4 | 1 << 8 | 1 << 12 | 1 << 13 | 0b1001 << 16 | 0 << 24 | 1 << 29;
88+
DPCR |= 1 << 19;
89+
5490
SPU_CTRL = 0;
55-
SPU_KEY_ON_LOW = 0;
56-
SPU_KEY_ON_HIGH = 0;
57-
SPU_KEY_OFF_LOW = 0xffff;
58-
SPU_KEY_OFF_HIGH = 0xffff;
59-
SPU_RAM_DTC = 4;
60-
SPU_VOL_CD_LEFT = 0;
61-
SPU_VOL_CD_RIGHT = 0;
91+
92+
SPU_VOL_MAIN_LEFT = 0x7fff;
93+
SPU_VOL_MAIN_RIGHT = 0x7fff;
94+
SPU_REVERB_LEFT = 0;
95+
SPU_REVERB_RIGHT = 0;
96+
6297
SPU_PITCH_MOD_LOW = 0;
6398
SPU_PITCH_MOD_HIGH = 0;
6499
SPU_NOISE_EN_LOW = 0;
65100
SPU_NOISE_EN_HIGH = 0;
66101
SPU_REVERB_EN_LOW = 0;
67102
SPU_REVERB_EN_HIGH = 0;
103+
SPU_REVERB_ADDR = 0xfffe;
104+
SPU_VOL_CD_LEFT = 0;
105+
SPU_VOL_CD_RIGHT = 0;
68106
SPU_VOL_EXT_LEFT = 0;
69107
SPU_VOL_EXT_RIGHT = 0;
70-
SPU_CTRL = 0x8000;
108+
SPU_RAM_DTC = 4;
109+
110+
dmaWrite(DUMMY_SAMPLE_POSITION, &DUMMY_SAMPLE, DUMMY_SAMPLE_SIZE, 4);
111+
112+
SPU_CTRL = 1 << 15 | 1 << 14 | 1 << 6;
113+
114+
silenceChannels(0xffffffff);
115+
}
116+
117+
void psyqo::SPU::playADPCM(const uint8_t channelId, const uint16_t spuRamAddress, const ChannelPlaybackConfig &config,
118+
const bool hardCut) {
119+
Kernel::assert(channelId < 24, "Invalid SPU channel ID");
120+
if (hardCut) {
121+
if (channelId > 15) {
122+
SPU_KEY_OFF_HIGH |= 1 << (channelId - 16);
123+
} else {
124+
SPU_KEY_OFF_LOW |= 1 << (channelId);
125+
}
126+
}
127+
128+
SPU_VOICES[channelId].volumeLeft = config.volumeLeft;
129+
SPU_VOICES[channelId].volumeRight = config.volumeRight;
130+
SPU_VOICES[channelId].sampleRate = config.sampleRate.value;
131+
SPU_VOICES[channelId].sampleStartAddr = spuRamAddress / 8;
132+
SPU_VOICES[channelId].ad = config.adsr & 0xffff;
133+
SPU_VOICES[channelId].sr = (config.adsr >> 16) & 0xffff;
134+
135+
if (channelId > 15) {
136+
SPU_KEY_ON_HIGH |= 1 << (channelId - 16);
137+
} else {
138+
SPU_KEY_ON_LOW |= 1 << (channelId);
139+
}
140+
}
71141

72-
for (unsigned i = 0; i < 24; i++) resetVoice(i);
142+
uint32_t psyqo::SPU::getNextFreeChannel() {
143+
for (uint8_t channel = 0; channel < 24; channel++) {
144+
if (SPU_VOICES[channel].currentVolume == 0) {
145+
return channel;
146+
}
147+
}
148+
return NO_FREE_CHANNEL;
73149
}

0 commit comments

Comments
 (0)