Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
cdda.c
Go to the documentation of this file.
1/*
2
3MIT License
4
5Copyright (c) 2026 PCSX-Redux authors
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25*/
26
27// CD-DA level through the ATV matrix, read back from the SPU CD capture
28// buffer. Track 2 of the test disc is a 450 Hz sine, identical on both
29// channels, with a peak of 24987.
30
31// Pre-fill for the capture region, small enough that an untouched buffer fails every level check.
32#define CDDA_MARKER 0x0101
33#define CDDA_SPU_DELAY (*(volatile uint32_t *)0xbf801014)
34
36 static uint16_t s_cddaCapture[0x400];
37
38 static int cddaSpuDma(uint32_t spuAddr, void *buf, uint32_t bytes, int isRead) {
39 const uint16_t tsa = spuAddr >> 3;
40 const uint16_t mode = isRead ? 0x30 : 0x20;
41 SPU_RAM_DTA = tsa;
42 for (int i = 0; i < 0xf01 && SPU_RAM_DTA != tsa; i++);
43 SPU_CTRL = (SPU_CTRL & ~0x30) | mode;
44 for (volatile int i = 0; i < 60; i++);
45 for (int i = 0; i < 0xf01 && (SPU_STATUS & 0x30) != mode; i++);
46 CDDA_SPU_DELAY = (CDDA_SPU_DELAY & 0xf0ffffff) | (isRead ? 0x22000000 : 0x20000000);
47 DMA_CTRL[DMA_SPU].MADR = (uint32_t)buf & 0x1fffffff;
48 DMA_CTRL[DMA_SPU].BCR = ((bytes >> 6) << 16) | 0x10;
49 DMA_CTRL[DMA_SPU].CHCR = isRead ? 0x01000200 : 0x01000201;
50 int ok = 0;
51 for (unsigned t = 0; t < 4000000u; t++) {
52 if ((DMA_CTRL[DMA_SPU].CHCR & 0x01000000) == 0) {
53 ok = 1;
54 break;
55 }
56 }
57 SPU_CTRL = SPU_CTRL & ~0x30;
58 for (volatile int i = 0; i < 60; i++);
59 return ok;
60 }
61
62 static int cddaSimpleCmd(uint8_t cmd) {
63 uint8_t response[16];
64 CDROM_REG0 = 0;
66 uint32_t timeout = 2000000;
67 int ok = waitCDRomIRQWithTimeout(&timeout);
70 return ok;
71 }
72
73 static void cddaWaitPauseComplete() {
75 uint32_t elapsed = 0;
76 while (elapsed < 5000000u) {
77 uint32_t budget = 200000u;
78 int ok = waitCDRomIRQWithTimeout(&budget);
79 elapsed = budget;
80 if (!ok) continue;
81 uint8_t cause = ackCDRomCause();
82 uint8_t response[16];
84 if (cause == 2) return;
85 }
86 }
87
88 // Resets the drive, pre-fills the SPU capture region, sets mute or demute and the ATV
89 // matrix, and turns on CD audio in the SPU. 0 on a failed step.
90 static int cddaAudioSetup(const uint8_t atv[4], int demute) {
91 if (!resetCDRom()) return 0;
92
93 DPCR |= 0x000b0000;
94 SPU_CTRL = 0;
95 for (volatile int i = 0; i < 20000; i++);
98 SPU_KEY_OFF_LOW = 0xffff;
99 SPU_KEY_OFF_HIGH = 0xffff;
100 SPU_RAM_DTC = 4;
101 SPU_CTRL = 0x8001;
102 for (volatile int i = 0; i < 20000; i++);
103 for (int i = 0; i < 0x400; i++) s_cddaCapture[i] = CDDA_MARKER;
104 if (!cddaSpuDma(0x000, s_cddaCapture, 0x800, 0)) return 0;
105
106 if (!cddaSimpleCmd(demute ? CDL_DEMUTE : CDL_MUTE)) return 0;
107
108 CDROM_REG0 = 2;
109 CDROM_REG2 = atv[0];
110 CDROM_REG3 = atv[1];
111 CDROM_REG0 = 3;
112 CDROM_REG1 = atv[2];
113 CDROM_REG2 = atv[3];
114 CDROM_REG0 = 3;
115 CDROM_REG3 = 0x20;
116 CDROM_REG0 = 0;
117
118 SPU_VOL_CD_LEFT = 0x7fff;
119 SPU_VOL_CD_RIGHT = 0x7fff;
120 SPU_CTRL = SPU_CTRL | 0x8001;
121 return 1;
122 }
123
124 // Peak and number of sign changes of the left side of the capture buffer.
125 static int cddaBufferPeak(unsigned *signChanges) {
126 int peak = 0;
127 unsigned changes = 0;
128 int prev = 0;
129 for (int i = 0; i < 0x200; i++) {
130 int v = (int16_t)s_cddaCapture[i];
131 int a = v < 0 ? -v : v;
132 if (a > peak) peak = a;
133 int sign = v >= 0 ? 1 : -1;
134 if (i && sign != prev) changes++;
135 prev = sign;
136 }
137 *signChanges = changes;
138 return peak;
139 }
140
141 // Reads the capture buffer back while audio plays. On a SCPH-9002 the capture sometimes
142 // reads all zeros for a while after the audio should have started, so an empty capture is
143 // retried a few times, 250 ms apart. -1 on a failed DMA.
144 static int cddaCapturePeak(unsigned *signChanges) {
145 int peak = 0;
146 for (unsigned attempt = 0; attempt < 8; attempt++) {
147 if (attempt) {
149 while (updateTime() < 250000u);
150 }
151 if (!cddaSpuDma(0x000, s_cddaCapture, 0x800, 1)) return -1;
152 peak = cddaBufferPeak(signChanges);
153 if (peak) {
154 if (attempt) ramsyscall_printf("capture was empty %u time(s) before audio\n", attempt);
155 break;
156 }
157 }
158 return peak;
159 }
160
161 // Plays track 2 with the given ATV, captures 512 samples per side while playing,
162 // and returns the left side's peak and number of sign changes. -1 on a failed step.
163 static int cddaPeak(const uint8_t atv[4], int demute, unsigned *signChanges) {
164 if (!cddaAudioSetup(atv, demute)) return -1;
165 if (!setMode(0)) return -1;
166
167 CDROM_REG0 = 0;
168 CDROM_REG2 = 0x02;
169 if (!cddaSimpleCmd(CDL_PLAY)) return -1;
170
172 while (updateTime() < 1500000u);
173
174 int peak = cddaCapturePeak(signChanges);
175
176 CDROM_REG0 = 0;
178 cddaWaitPauseComplete();
179
180 // Leave the head on the data track: a GetLocL with an audio sector as the last one
181 // read answers with an error, and the next test reads the location after a reset.
182 CDROM_REG0 = 0;
183 CDROM_REG2 = 0x00;
184 CDROM_REG2 = 0x02;
185 CDROM_REG2 = 0x16;
186 if (cddaSimpleCmd(CDL_SETLOC) && cddaSimpleCmd(CDL_SEEKL)) cddaWaitPauseComplete();
187
188 return peak;
189 }
190)
191
192CESTER_TEST(cddaLevelUnity, test_instances,
193 static const uint8_t atv[4] = {0x80, 0x00, 0x80, 0x00};
194 unsigned changes = 0;
195 int peak = cddaPeak(atv, 1, &changes);
196 ramsyscall_printf("CD-DA level at unity ATV: peak %i, %u sign changes\n", peak, changes);
200)
201
203 static const uint8_t atv[4] = {0x40, 0x00, 0x40, 0x00};
204 unsigned changes = 0;
205 int peak = cddaPeak(atv, 1, &changes);
206 ramsyscall_printf("CD-DA level at half ATV: peak %i, %u sign changes\n", peak, changes);
210)
211
212CESTER_TEST(cddaLevelClipped, test_instances,
213 static const uint8_t atv[4] = {0x80, 0x80, 0x80, 0x80};
214 unsigned changes = 0;
215 int peak = cddaPeak(atv, 1, &changes);
216 ramsyscall_printf("CD-DA level at double ATV: peak %i, %u sign changes\n", peak, changes);
220)
221
222CESTER_TEST(cddaMuted, test_instances,
223 static const uint8_t atv[4] = {0x80, 0x00, 0x80, 0x00};
224 unsigned changes = 0;
225 int peak = cddaPeak(atv, 0, &changes);
226 ramsyscall_printf("CD-DA level when muted: peak %i\n", peak);
228)
int peak
Definition cdda.c:195
CESTER_TEST(cddaLevelHalf, test_instances, static const uint8_t atv[4]={0x40, 0x00, 0x40, 0x00};unsigned changes=0;int peak=cddaPeak(atv, 1, &changes);ramsyscall_printf("CD-DA level at half ATV: peak %i, %u sign changes\n", peak, changes);cester_assert_int_ge(peak, 11300);cester_assert_int_le(peak, 11800);cester_assert_uint_ge(changes, 4);) CESTER_TEST(cddaLevelClipped
#define CDDA_MARKER
Definition cdda.c:32
cester_assert_uint_ge(changes, 4)
test_instances
Definition cdda.c:212
cester_assert_int_le(peak, 23400)
cester_assert_int_ge(peak, 22800)
#define CDDA_SPU_DELAY
Definition cdda.c:33
unsigned changes
Definition cdda.c:194
CESTER_BODY(static uint16_t s_cddaCapture;static int cddaSpuDma(uint32_t spuAddr, void *buf, uint32_t bytes, int isRead) { const uint16_t tsa=spuAddr > > 3;const uint16_t mode=isRead ? 0x30 :0x20;SPU_RAM_DTA=tsa;for(int i=0;i< 0xf01 &&SPU_RAM_DTA !=tsa;i++);SPU_CTRL=(SPU_CTRL &~0x30)|mode;for(volatile int i=0;i< 60;i++);for(int i=0;i< 0xf01 &&(SPU_STATUS &0x30) !=mode;i++);CDDA_SPU_DELAY=(CDDA_SPU_DELAY &0xf0ffffff)|(isRead ? 0x22000000 :0x20000000);DMA_CTRL[DMA_SPU].MADR=(uint32_t) buf &0x1fffffff;DMA_CTRL[DMA_SPU].BCR=((bytes > > 6)<< 16)|0x10;DMA_CTRL[DMA_SPU].CHCR=isRead ? 0x01000200 :0x01000201;int ok=0;for(unsigned t=0;t< 4000000u;t++) { if((DMA_CTRL[DMA_SPU].CHCR &0x01000000)==0) { ok=1;break;} } SPU_CTRL=SPU_CTRL &~0x30;for(volatile int i=0;i< 60;i++);return ok;} static int cddaSimpleCmd(uint8_t cmd) { uint8_t response[16];CDROM_REG0=0;CDROM_REG1=cmd;uint32_t timeout=2000000;int ok=waitCDRomIRQWithTimeout(&timeout);ackCDRomCause();readResponse(response);return ok;} static void cddaWaitPauseComplete() { initializeTime();uint32_t elapsed=0;while(elapsed< 5000000u) { uint32_t budget=200000u;int ok=waitCDRomIRQWithTimeout(&budget);elapsed=budget;if(!ok) continue;uint8_t cause=ackCDRomCause();uint8_t response[16];readResponse(response);if(cause==2) return;} } static int cddaAudioSetup(const uint8_t atv[4], int demute) { if(!resetCDRom()) return 0;DPCR|=0x000b0000;SPU_CTRL=0;for(volatile int i=0;i< 20000;i++);SPU_VOL_MAIN_LEFT=0;SPU_VOL_MAIN_RIGHT=0;SPU_KEY_OFF_LOW=0xffff;SPU_KEY_OFF_HIGH=0xffff;SPU_RAM_DTC=4;SPU_CTRL=0x8001;for(volatile int i=0;i< 20000;i++);for(int i=0;i< 0x400;i++) s_cddaCapture[i]=CDDA_MARKER;if(!cddaSpuDma(0x000, s_cddaCapture, 0x800, 0)) return 0;if(!cddaSimpleCmd(demute ? CDL_DEMUTE :CDL_MUTE)) return 0;CDROM_REG0=2;CDROM_REG2=atv[0];CDROM_REG3=atv[1];CDROM_REG0=3;CDROM_REG1=atv[2];CDROM_REG2=atv[3];CDROM_REG0=3;CDROM_REG3=0x20;CDROM_REG0=0;SPU_VOL_CD_LEFT=0x7fff;SPU_VOL_CD_RIGHT=0x7fff;SPU_CTRL=SPU_CTRL|0x8001;return 1;} static int cddaBufferPeak(unsigned *signChanges) { int peak=0;unsigned changes=0;int prev=0;for(int i=0;i< 0x200;i++) { int v=(int16_t) s_cddaCapture[i];int a=v< 0 ? -v :v;if(a > peak) peak=a;int sign=v >=0 ? 1 :-1;if(i &&sign !=prev) changes++;prev=sign;} *signChanges=changes;return peak;} static int cddaCapturePeak(unsigned *signChanges) { int peak=0;for(unsigned attempt=0;attempt< 8;attempt++) { if(attempt) { initializeTime();while(updateTime()< 250000u);} if(!cddaSpuDma(0x000, s_cddaCapture, 0x800, 1)) return -1;peak=cddaBufferPeak(signChanges);if(peak) { if(attempt) ramsyscall_printf("capture was empty %u time(s) before audio\n", attempt);break;} } return peak;} static int cddaPeak(const uint8_t atv[4], int demute, unsigned *signChanges) { if(!cddaAudioSetup(atv, demute)) return -1;if(!setMode(0)) return -1;CDROM_REG0=0;CDROM_REG2=0x02;if(!cddaSimpleCmd(CDL_PLAY)) return -1;initializeTime();while(updateTime()< 1500000u);int peak=cddaCapturePeak(signChanges);CDROM_REG0=0;CDROM_REG1=CDL_PAUSE;cddaWaitPauseComplete();CDROM_REG0=0;CDROM_REG2=0x00;CDROM_REG2=0x02;CDROM_REG2=0x16;if(cddaSimpleCmd(CDL_SETLOC) &&cddaSimpleCmd(CDL_SEEKL)) cddaWaitPauseComplete();return peak;}[0x400])
Definition cdda.c:35
initializeTime()
uint8_t response[16]
Definition cdlgetlocl.c:84
readResponse(response)
DMA_CTRL[DMA_CDROM] CHCR
Definition cdlreadn.c:185
ackCDRomCause()
int ok
Definition cdlstop.c:75
uint8_t cause
Definition cdltest.c:207
@ CDL_PLAY
Definition cdrom.h:45
@ CDL_MUTE
Definition cdrom.h:53
@ CDL_DEMUTE
Definition cdrom.h:54
@ CDL_SEEKL
Definition cdrom.h:63
@ CDL_PAUSE
Definition cdrom.h:51
@ CDL_SETLOC
Definition cdrom.h:44
#define CDROM_REG0
Definition cdrom.h:31
#define CDROM_REG3
Definition cdrom.h:34
#define CDROM_REG1
Definition cdrom.h:32
#define CDROM_REG2
Definition cdrom.h:33
#define SPU_VOL_CD_RIGHT
Definition spu.h:108
#define SPU_VOL_CD_LEFT
Definition spu.h:107
#define SPU_STATUS
Definition spu.h:106
#define SPU_VOL_MAIN_LEFT
Definition spu.h:84
#define SPU_KEY_OFF_LOW
Definition spu.h:90
#define SPU_KEY_OFF_HIGH
Definition spu.h:91
#define SPU_CTRL
Definition spu.h:104
#define SPU_VOL_MAIN_RIGHT
Definition spu.h:85
#define SPU_RAM_DTC
Definition spu.h:105
#define SPU_RAM_DTA
Definition spu.h:103
uint32_t t
Definition cop0.c:79
ramsyscall_printf("=== e01_kseg1_reads_no_fill ===\n")
#define DMA_CTRL
Definition dma.h:36
@ DMA_SPU
Definition dma.h:43
int i
Definition gte-regio.c:297
#define DPCR
Definition hwregs.h:49
uint16_t cmd[MON_CMD_WORDS]
Definition monitor.c:69
int a
Definition mktest.py:134
uint32_t timeout
Definition race.c:103
static const void * buf
Definition syscalls.h:61
return((size_t(*)(int, const void *, size_t)) 0xa0)(fd
static void uint32_t mode
Definition syscalls.h:231
void uint32_t(classId, spec)
cester_assert_int_eq(3, xaCommand(CDL_SETMODE, &mode, 1, NULL, NULL))