cdrom: should use the last Setloc loc and SeekL
[pcsx_rearmed.git] / libpcsxcore / cdrom.c
CommitLineData
ef79bbde
P
1/***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
22bbabf6 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
ef79bbde
P
18 ***************************************************************************/
19
22bbabf6 20/*
ef79bbde
P
21* Handles all CD-ROM registers and functions.
22*/
23
24#include "cdrom.h"
25#include "ppf.h"
9f8b032d 26#include "psxdma.h"
8f2bb0cb 27#include "arm_features.h"
ef79bbde 28
8b380e97 29/* logging */
30#if 0
31#define CDR_LOG SysPrintf
32#else
33#define CDR_LOG(...)
34#endif
35#if 0
36#define CDR_LOG_I SysPrintf
37#else
38#define CDR_LOG_I(...)
39#endif
40#if 0
41#define CDR_LOG_IO SysPrintf
42#else
43#define CDR_LOG_IO(...)
44#endif
45//#define CDR_LOG_CMD_IRQ
46
af93c8be 47static struct {
48 unsigned char OCUP;
49 unsigned char Reg1Mode;
50 unsigned char Reg2;
51 unsigned char CmdProcess;
52 unsigned char Ctrl;
53 unsigned char Stat;
54
55 unsigned char StatP;
56
57 unsigned char Transfer[DATA_SIZE];
58 struct {
59 unsigned char Track;
60 unsigned char Index;
61 unsigned char Relative[3];
62 unsigned char Absolute[3];
63 } subq;
64 unsigned char TrackChanged;
65 boolean m_locationChanged;
66 unsigned char pad1[2];
67 unsigned int freeze_ver;
68
69 unsigned char Prev[4];
70 unsigned char Param[8];
71 unsigned char Result[16];
72
73 unsigned char ParamC;
74 unsigned char ParamP;
75 unsigned char ResultC;
76 unsigned char ResultP;
77 unsigned char ResultReady;
78 unsigned char Cmd;
79 unsigned char Readed;
80 unsigned char SetlocPending;
81 u32 Reading;
82
83 unsigned char ResultTN[6];
84 unsigned char ResultTD[4];
85 unsigned char SetSectorPlay[4];
86 unsigned char SetSectorEnd[4];
87 unsigned char SetSector[4];
88 unsigned char Track;
89 boolean Play, Muted;
90 int CurTrack;
91 int Mode, File, Channel;
92 int Reset;
93 int NoErr;
94 int FirstSector;
95
96 xa_decode_t Xa;
97
98 int Init;
99
100 u16 Irq;
101 u8 IrqRepeated;
102 u32 eCycle;
103
104 u8 Seeked;
105
106 u8 DriveState;
107 u8 FastForward;
108 u8 FastBackward;
109 u8 pad;
110
111 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
112 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
113 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
114 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
115} cdr;
8e1040b6 116static unsigned char *pTransfer;
f4627eb3 117static s16 read_buf[CD_FRAMESIZE_RAW/2];
ef79bbde
P
118
119/* CD-ROM magic numbers */
1f035e27 120#define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
ef79bbde
P
121#define CdlNop 1
122#define CdlSetloc 2
123#define CdlPlay 3
124#define CdlForward 4
125#define CdlBackward 5
126#define CdlReadN 6
127#define CdlStandby 7
128#define CdlStop 8
129#define CdlPause 9
b0bd140d 130#define CdlReset 10
ef79bbde
P
131#define CdlMute 11
132#define CdlDemute 12
133#define CdlSetfilter 13
134#define CdlSetmode 14
dcd72441 135#define CdlGetparam 15
ef79bbde
P
136#define CdlGetlocL 16
137#define CdlGetlocP 17
138#define CdlReadT 18
139#define CdlGetTN 19
140#define CdlGetTD 20
141#define CdlSeekL 21
142#define CdlSeekP 22
143#define CdlSetclock 23
144#define CdlGetclock 24
145#define CdlTest 25
146#define CdlID 26
147#define CdlReadS 27
b0bd140d 148#define CdlInit 28
e4268a49 149#define CdlGetQ 29
ef79bbde
P
150#define CdlReadToc 30
151
af93c8be 152#ifdef CDR_LOG_CMD_IRQ
153static const char * const CmdName[0x100] = {
ef79bbde
P
154 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
155 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
b0bd140d 156 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
dcd72441 157 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
ef79bbde
P
158 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
159 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
160 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
b0bd140d 161 "CdlInit", NULL, "CDlReadToc", NULL
ef79bbde 162};
af93c8be 163#endif
ef79bbde
P
164
165unsigned char Test04[] = { 0 };
166unsigned char Test05[] = { 0 };
167unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
168unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
169unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
170
9f8b032d 171// cdr.Stat:
172#define NoIntr 0
173#define DataReady 1
174#define Complete 2
175#define Acknowledge 3
176#define DataEnd 4
177#define DiskError 5
178
179/* Modes flags */
180#define MODE_SPEED (1<<7) // 0x80
181#define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
182#define MODE_SIZE_2340 (1<<5) // 0x20
183#define MODE_SIZE_2328 (1<<4) // 0x10
cd0c9f5b 184#define MODE_SIZE_2048 (0<<4) // 0x00
9f8b032d 185#define MODE_SF (1<<3) // 0x08 channel on/off
186#define MODE_REPORT (1<<2) // 0x04
187#define MODE_AUTOPAUSE (1<<1) // 0x02
188#define MODE_CDDA (1<<0) // 0x01
189
190/* Status flags */
191#define STATUS_PLAY (1<<7) // 0x80
192#define STATUS_SEEK (1<<6) // 0x40
193#define STATUS_READ (1<<5) // 0x20
194#define STATUS_SHELLOPEN (1<<4) // 0x10
195#define STATUS_UNKNOWN3 (1<<3) // 0x08
196#define STATUS_UNKNOWN2 (1<<2) // 0x04
197#define STATUS_ROTATING (1<<1) // 0x02
198#define STATUS_ERROR (1<<0) // 0x01
199
e4268a49 200/* Errors */
003cfc63 201#define ERROR_NOTREADY (1<<7) // 0x80
e4268a49 202#define ERROR_INVALIDCMD (1<<6) // 0x40
203#define ERROR_INVALIDARG (1<<5) // 0x20
9f8b032d 204
ef79bbde
P
205// 1x = 75 sectors per second
206// PSXCLK = 1 sec in the ps
207// so (PSXCLK / 75) = cdr read time (linuzappz)
208#define cdReadTime (PSXCLK / 75)
209
80f91a20 210enum drive_state {
211 DRIVESTATE_STANDBY = 0,
212 DRIVESTATE_LID_OPEN,
213 DRIVESTATE_RESCAN_CD,
214 DRIVESTATE_PREPARE_CD,
e4268a49 215 DRIVESTATE_STOPPED,
80f91a20 216};
217
39df67df 218// for cdr.Seeked
219enum seeked_state {
220 SEEK_PENDING = 0,
221 SEEK_DONE = 1,
39df67df 222};
223
ef79bbde 224static struct CdrStat stat;
ef79bbde 225
fffad32e 226static unsigned int msf2sec(const u8 *msf) {
9f8b032d 227 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
228}
229
fffad32e 230// for that weird psemu API..
231static unsigned int fsm2sec(const u8 *msf) {
232 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
233}
234
305c8c93 235static void sec2msf(unsigned int s, u8 *msf) {
9f8b032d 236 msf[0] = s / 75 / 60;
237 s = s - msf[0] * 75 * 60;
238 msf[1] = s / 75;
239 s = s - msf[1] * 75;
240 msf[2] = s;
241}
242
39df67df 243// cdrInterrupt
ef79bbde 244#define CDR_INT(eCycle) { \
d28b54b1 245 psxRegs.interrupt |= (1 << PSXINT_CDR); \
246 psxRegs.intCycle[PSXINT_CDR].cycle = eCycle; \
247 psxRegs.intCycle[PSXINT_CDR].sCycle = psxRegs.cycle; \
248 new_dyna_set_event(PSXINT_CDR, eCycle); \
ae602c19 249}
ef79bbde 250
39df67df 251// cdrReadInterrupt
ef79bbde 252#define CDREAD_INT(eCycle) { \
d28b54b1 253 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
254 psxRegs.intCycle[PSXINT_CDREAD].cycle = eCycle; \
255 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
256 new_dyna_set_event(PSXINT_CDREAD, eCycle); \
ae602c19 257}
ef79bbde 258
39df67df 259// cdrLidSeekInterrupt
9f8b032d 260#define CDRLID_INT(eCycle) { \
261 psxRegs.interrupt |= (1 << PSXINT_CDRLID); \
262 psxRegs.intCycle[PSXINT_CDRLID].cycle = eCycle; \
263 psxRegs.intCycle[PSXINT_CDRLID].sCycle = psxRegs.cycle; \
264 new_dyna_set_event(PSXINT_CDRLID, eCycle); \
265}
266
39df67df 267// cdrPlayInterrupt
268#define CDRMISC_INT(eCycle) { \
7f457614 269 psxRegs.interrupt |= (1 << PSXINT_CDRPLAY); \
270 psxRegs.intCycle[PSXINT_CDRPLAY].cycle = eCycle; \
271 psxRegs.intCycle[PSXINT_CDRPLAY].sCycle = psxRegs.cycle; \
272 new_dyna_set_event(PSXINT_CDRPLAY, eCycle); \
273}
274
ef79bbde
P
275#define StopReading() { \
276 if (cdr.Reading) { \
277 cdr.Reading = 0; \
d28b54b1 278 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
ef79bbde 279 } \
9ffe8d5c 280 cdr.StatP &= ~(STATUS_READ|STATUS_SEEK);\
ef79bbde
P
281}
282
283#define StopCdda() { \
284 if (cdr.Play) { \
285 if (!Config.Cdda) CDR_stop(); \
9f8b032d 286 cdr.StatP &= ~STATUS_PLAY; \
ef79bbde 287 cdr.Play = FALSE; \
9f8b032d 288 cdr.FastForward = 0; \
289 cdr.FastBackward = 0; \
290 /*SPU_registerCallback( SPUirq );*/ \
ef79bbde
P
291 } \
292}
293
294#define SetResultSize(size) { \
94c118c3 295 cdr.ResultP = 0; \
ef79bbde
P
296 cdr.ResultC = size; \
297 cdr.ResultReady = 1; \
298}
299
af93c8be 300static void setIrq(int log_cmd)
94c118c3 301{
302 if (cdr.Stat & cdr.Reg2)
303 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
af93c8be 304
305#ifdef CDR_LOG_CMD_IRQ
306 {
307 int i;
308 SysPrintf("CDR IRQ=%d cmd %02x stat %02x: ",
309 !!(cdr.Stat & cdr.Reg2), log_cmd, cdr.Stat);
310 for (i = 0; i < cdr.ResultC; i++)
311 SysPrintf("%02x ", cdr.Result[i]);
312 SysPrintf("\n");
313 }
314#endif
94c118c3 315}
9f8b032d 316
80f91a20 317// timing used in this function was taken from tests on real hardware
318// (yes it's slow, but you probably don't want to modify it)
9f8b032d 319void cdrLidSeekInterrupt()
320{
80f91a20 321 switch (cdr.DriveState) {
322 default:
323 case DRIVESTATE_STANDBY:
9f8b032d 324 cdr.StatP &= ~STATUS_SEEK;
325
80f91a20 326 if (CDR_getStatus(&stat) == -1)
327 return;
9f8b032d 328
80f91a20 329 if (stat.Status & STATUS_SHELLOPEN)
9f8b032d 330 {
80f91a20 331 StopCdda();
332 cdr.DriveState = DRIVESTATE_LID_OPEN;
333 CDRLID_INT(0x800);
334 }
335 break;
9f8b032d 336
80f91a20 337 case DRIVESTATE_LID_OPEN:
338 if (CDR_getStatus(&stat) == -1)
339 stat.Status &= ~STATUS_SHELLOPEN;
9f8b032d 340
80f91a20 341 // 02, 12, 10
342 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
343 StopReading();
344 cdr.StatP |= STATUS_SHELLOPEN;
9f8b032d 345
80f91a20 346 // could generate error irq here, but real hardware
347 // only sometimes does that
348 // (not done when lots of commands are sent?)
9f8b032d 349
80f91a20 350 CDRLID_INT(cdReadTime * 30);
351 break;
9f8b032d 352 }
80f91a20 353 else if (cdr.StatP & STATUS_ROTATING) {
354 cdr.StatP &= ~STATUS_ROTATING;
355 }
356 else if (!(stat.Status & STATUS_SHELLOPEN)) {
357 // closed now
358 CheckCdrom();
9f8b032d 359
80f91a20 360 // cdr.StatP STATUS_SHELLOPEN is "sticky"
361 // and is only cleared by CdlNop
9f8b032d 362
80f91a20 363 cdr.DriveState = DRIVESTATE_RESCAN_CD;
364 CDRLID_INT(cdReadTime * 105);
365 break;
366 }
9f8b032d 367
80f91a20 368 // recheck for close
369 CDRLID_INT(cdReadTime * 3);
370 break;
9f8b032d 371
80f91a20 372 case DRIVESTATE_RESCAN_CD:
373 cdr.StatP |= STATUS_ROTATING;
374 cdr.DriveState = DRIVESTATE_PREPARE_CD;
9f8b032d 375
80f91a20 376 // this is very long on real hardware, over 6 seconds
377 // make it a bit faster here...
378 CDRLID_INT(cdReadTime * 150);
379 break;
9f8b032d 380
80f91a20 381 case DRIVESTATE_PREPARE_CD:
382 cdr.StatP |= STATUS_SEEK;
9f8b032d 383
80f91a20 384 cdr.DriveState = DRIVESTATE_STANDBY;
385 CDRLID_INT(cdReadTime * 26);
386 break;
9f8b032d 387 }
388}
389
fffad32e 390static void Find_CurTrack(const u8 *time)
391{
392 int current, sect;
9f8b032d 393
fffad32e 394 current = msf2sec(time);
9f8b032d 395
fffad32e 396 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
397 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
398 sect = fsm2sec(cdr.ResultTD);
399 if (sect - current >= 150)
9f8b032d 400 break;
9f8b032d 401 }
402}
403
73b29eeb 404static void generate_subq(const u8 *time)
405{
406 unsigned char start[3], next[3];
407 unsigned int this_s, start_s, next_s, pregap;
408 int relative_s;
409
410 CDR_getTD(cdr.CurTrack, start);
411 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
412 pregap = 150;
413 CDR_getTD(cdr.CurTrack + 1, next);
414 }
415 else {
416 // last track - cd size
417 pregap = 0;
418 next[0] = cdr.SetSectorEnd[2];
419 next[1] = cdr.SetSectorEnd[1];
420 next[2] = cdr.SetSectorEnd[0];
421 }
422
423 this_s = msf2sec(time);
424 start_s = fsm2sec(start);
425 next_s = fsm2sec(next);
426
427 cdr.TrackChanged = FALSE;
428
429 if (next_s - this_s < pregap) {
430 cdr.TrackChanged = TRUE;
431 cdr.CurTrack++;
432 start_s = next_s;
433 }
434
435 cdr.subq.Index = 1;
436
437 relative_s = this_s - start_s;
438 if (relative_s < 0) {
439 cdr.subq.Index = 0;
440 relative_s = -relative_s;
441 }
442 sec2msf(relative_s, cdr.subq.Relative);
443
444 cdr.subq.Track = itob(cdr.CurTrack);
445 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
446 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
447 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
448 cdr.subq.Absolute[0] = itob(time[0]);
449 cdr.subq.Absolute[1] = itob(time[1]);
450 cdr.subq.Absolute[2] = itob(time[2]);
451}
452
453static void ReadTrack(const u8 *time) {
fffad32e 454 unsigned char tmp[3];
455 struct SubQ *subq;
456 u16 crc;
ef79bbde 457
fffad32e 458 tmp[0] = itob(time[0]);
459 tmp[1] = itob(time[1]);
460 tmp[2] = itob(time[2]);
ef79bbde 461
fffad32e 462 if (memcmp(cdr.Prev, tmp, 3) == 0)
463 return;
39df67df 464
fffad32e 465 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
9f8b032d 466
c9c7a925 467 cdr.NoErr = CDR_readTrack(tmp);
fffad32e 468 memcpy(cdr.Prev, tmp, 3);
9f8b032d 469
fffad32e 470 if (CheckSBI(time))
471 return;
9f8b032d 472
fffad32e 473 subq = (struct SubQ *)CDR_getBufferSub();
73b29eeb 474 if (subq != NULL && cdr.CurTrack == 1) {
fffad32e 475 crc = calcCrc((u8 *)subq + 12, 10);
73b29eeb 476 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
fffad32e 477 cdr.subq.Track = subq->TrackNumber;
478 cdr.subq.Index = subq->IndexNumber;
479 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
480 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
fffad32e 481 }
92ca1ba6 482 else {
73b29eeb 483 CDR_LOG_I("subq bad crc @%02x:%02x:%02x\n",
92ca1ba6 484 tmp[0], tmp[1], tmp[2]);
485 }
9f8b032d 486 }
fffad32e 487 else {
73b29eeb 488 generate_subq(time);
9f8b032d 489 }
9f8b032d 490
fffad32e 491 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
492 cdr.subq.Track, cdr.subq.Index,
493 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
494 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
495}
9f8b032d 496
e4268a49 497static void AddIrqQueue(unsigned short irq, unsigned long ecycle) {
498 if (cdr.Irq != 0) {
499 if (irq == cdr.Irq || irq + 0x100 == cdr.Irq) {
500 cdr.IrqRepeated = 1;
501 CDR_INT(ecycle);
502 return;
503 }
504
fffad32e 505 CDR_LOG_I("cdr: override cmd %02x -> %02x\n", cdr.Irq, irq);
e4268a49 506 }
9f8b032d 507
fffad32e 508 cdr.Irq = irq;
509 cdr.eCycle = ecycle;
9f8b032d 510
fffad32e 511 CDR_INT(ecycle);
ef79bbde
P
512}
513
7f457614 514static void cdrPlayInterrupt_Autopause()
515{
bf4df3ba 516 u32 abs_lev_max = 0;
517 boolean abs_lev_chselect;
518 u32 i;
f4627eb3 519
fffad32e 520 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
7f457614 521 CDR_LOG( "CDDA STOP\n" );
7f457614 522
523 // Magic the Gathering
524 // - looping territory cdda
525
526 // ...?
527 //cdr.ResultReady = 1;
528 //cdr.Stat = DataReady;
529 cdr.Stat = DataEnd;
af93c8be 530 setIrq(0x200);
7f457614 531
532 StopCdda();
533 }
c9c7a925 534 else if (((cdr.Mode & MODE_REPORT) || cdr.FastForward || cdr.FastBackward)) {
fffad32e 535 cdr.Result[0] = cdr.StatP;
536 cdr.Result[1] = cdr.subq.Track;
537 cdr.Result[2] = cdr.subq.Index;
bf4df3ba 538
539 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
540
541 /* 8 is a hack. For accuracy, it should be 588. */
542 for (i = 0; i < 8; i++)
543 {
544 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
545 }
546 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
547 abs_lev_max |= abs_lev_chselect << 15;
9b470ab7 548
fffad32e 549 if (cdr.subq.Absolute[2] & 0x10) {
550 cdr.Result[3] = cdr.subq.Relative[0];
551 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
552 cdr.Result[5] = cdr.subq.Relative[2];
7f457614 553 }
fffad32e 554 else {
555 cdr.Result[3] = cdr.subq.Absolute[0];
556 cdr.Result[4] = cdr.subq.Absolute[1];
557 cdr.Result[5] = cdr.subq.Absolute[2];
558 }
559
bf4df3ba 560 cdr.Result[6] = abs_lev_max >> 0;
561 cdr.Result[7] = abs_lev_max >> 8;
7f457614 562
563 // Rayman: Logo freeze (resultready + dataready)
564 cdr.ResultReady = 1;
565 cdr.Stat = DataReady;
566
567 SetResultSize(8);
af93c8be 568 setIrq(0x201);
7f457614 569 }
570}
571
39df67df 572// also handles seek
7f457614 573void cdrPlayInterrupt()
574{
892bd7d4 575 if (cdr.Seeked == SEEK_PENDING) {
e7e33ef2 576 if (cdr.Stat) {
8b380e97 577 CDR_LOG_I("cdrom: seek stat hack\n");
e7e33ef2 578 CDRMISC_INT(0x1000);
579 return;
580 }
39df67df 581 SetResultSize(1);
582 cdr.StatP |= STATUS_ROTATING;
583 cdr.StatP &= ~STATUS_SEEK;
584 cdr.Result[0] = cdr.StatP;
892bd7d4 585 cdr.Seeked = SEEK_DONE;
a34a2445 586 if (cdr.Irq == 0) {
39df67df 587 cdr.Stat = Complete;
af93c8be 588 setIrq(0x202);
39df67df 589 }
590
55b8460a 591 if (cdr.SetlocPending) {
592 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
593 cdr.SetlocPending = 0;
dc0ee8d4 594 cdr.m_locationChanged = TRUE;
55b8460a 595 }
fffad32e 596 Find_CurTrack(cdr.SetSectorPlay);
73b29eeb 597 ReadTrack(cdr.SetSectorPlay);
fffad32e 598 cdr.TrackChanged = FALSE;
39df67df 599 }
600
601 if (!cdr.Play) return;
7f457614 602
7f457614 603 CDR_LOG( "CDDA - %d:%d:%d\n",
604 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2] );
8b380e97 605
fffad32e 606 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
607 StopCdda();
608 cdr.TrackChanged = TRUE;
609 }
ffd2bcfa 610 else {
611 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
612 }
7f457614 613
892bd7d4 614 if (!cdr.Irq && !cdr.Stat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
7f457614 615 cdrPlayInterrupt_Autopause();
616
ffd2bcfa 617 if (CDR_readCDDA && !cdr.Muted && !Config.Cdda) {
ecd502e1 618 cdrAttenuate(read_buf, CD_FRAMESIZE_RAW / 4, 1);
f4627eb3 619 if (SPU_playCDDAchannel)
ecd502e1 620 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW);
f4627eb3 621 }
fffad32e 622
7f457614 623 cdr.SetSectorPlay[2]++;
624 if (cdr.SetSectorPlay[2] == 75) {
625 cdr.SetSectorPlay[2] = 0;
626 cdr.SetSectorPlay[1]++;
627 if (cdr.SetSectorPlay[1] == 60) {
628 cdr.SetSectorPlay[1] = 0;
629 cdr.SetSectorPlay[0]++;
630 }
631 }
632
dc0ee8d4 633 if (cdr.m_locationChanged)
634 {
635 CDRMISC_INT(cdReadTime * 30);
636 cdr.m_locationChanged = FALSE;
637 }
638 else
639 {
640 CDRMISC_INT(cdReadTime);
641 }
fffad32e 642
643 // update for CdlGetlocP/autopause
73b29eeb 644 generate_subq(cdr.SetSectorPlay);
7f457614 645}
646
ef79bbde 647void cdrInterrupt() {
e4268a49 648 u16 Irq = cdr.Irq;
649 int no_busy_error = 0;
650 int start_rotating = 0;
651 int error = 0;
652 int delay;
fbf19ce6 653 unsigned int seekTime = 0;
2da09dae 654 u8 set_loc[3];
655 int i;
ef79bbde 656
9f8b032d 657 // Reschedule IRQ
ef79bbde 658 if (cdr.Stat) {
8b380e97 659 CDR_LOG_I("cdrom: stat hack: %02x %x\n", cdr.Irq, cdr.Stat);
e7e33ef2 660 CDR_INT(0x1000);
ef79bbde
P
661 return;
662 }
663
ef79bbde
P
664 cdr.Ctrl &= ~0x80;
665
e4268a49 666 // default response
667 SetResultSize(1);
668 cdr.Result[0] = cdr.StatP;
669 cdr.Stat = Acknowledge;
670
671 if (cdr.IrqRepeated) {
672 cdr.IrqRepeated = 0;
673 if (cdr.eCycle > psxRegs.cycle) {
674 CDR_INT(cdr.eCycle);
675 goto finish;
676 }
677 }
678
679 cdr.Irq = 0;
680
ef79bbde 681 switch (Irq) {
ef79bbde 682 case CdlNop:
80f91a20 683 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
684 cdr.StatP &= ~STATUS_SHELLOPEN;
e4268a49 685 no_busy_error = 1;
ef79bbde
P
686 break;
687
688 case CdlSetloc:
2da09dae 689 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
690
691 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
692 if (((cdr.Param[0] & 0x0F) > 0x09) || (cdr.Param[0] > 0x99) || ((cdr.Param[1] & 0x0F) > 0x09) || (cdr.Param[1] >= 0x60) || ((cdr.Param[2] & 0x0F) > 0x09) || (cdr.Param[2] >= 0x75))
693 {
694 CDR_LOG("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
695 error = ERROR_INVALIDARG;
696 goto set_error;
697 }
698 else
699 {
700 for (i = 0; i < 3; i++)
701 {
702 set_loc[i] = btoi(cdr.Param[i]);
703 }
704
705 i = msf2sec(cdr.SetSectorPlay);
706 i = abs(i - msf2sec(set_loc));
707 if (i > 16)
708 cdr.Seeked = SEEK_PENDING;
709
710 memcpy(cdr.SetSector, set_loc, 3);
711 cdr.SetSector[3] = 0;
712 cdr.SetlocPending = 1;
713 }
ef79bbde
P
714 break;
715
55b8460a 716 do_CdlPlay:
ef79bbde 717 case CdlPlay:
030bd76a 718 StopCdda();
39df67df 719 if (cdr.Seeked == SEEK_PENDING) {
720 // XXX: wrong, should seek instead..
39df67df 721 cdr.Seeked = SEEK_DONE;
9f8b032d 722 }
c9c7a925 723
724 cdr.FastBackward = 0;
725 cdr.FastForward = 0;
726
55b8460a 727 if (cdr.SetlocPending) {
728 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
729 cdr.SetlocPending = 0;
dc0ee8d4 730 cdr.m_locationChanged = TRUE;
55b8460a 731 }
9f8b032d 732
2d5ca6c0 733 // BIOS CD Player
734 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
735
736 if (cdr.ParamC == 0 || cdr.Param[0] == 0) {
737 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
738 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
739 }
740 else
741 {
742 int track = btoi( cdr.Param[0] );
743
744 if (track <= cdr.ResultTN[1])
745 cdr.CurTrack = track;
746
747 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
748
749 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
750 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
751 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
752 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
753 }
754 }
755
9f8b032d 756 /*
757 Rayman: detect track changes
758 - fixes logo freeze
759
760 Twisted Metal 2: skip PREGAP + starting accurate SubQ
761 - plays tracks without retry play
762
763 Wild 9: skip PREGAP + starting accurate SubQ
764 - plays tracks without retry play
765 */
fffad32e 766 Find_CurTrack(cdr.SetSectorPlay);
73b29eeb 767 ReadTrack(cdr.SetSectorPlay);
fffad32e 768 cdr.TrackChanged = FALSE;
9f8b032d 769
48aa3a5a 770 StopReading();
2d5ca6c0 771 if (!Config.Cdda)
772 CDR_play(cdr.SetSectorPlay);
9f8b032d 773
774 // Vib Ribbon: gameplay checks flag
775 cdr.StatP &= ~STATUS_SEEK;
ef79bbde 776 cdr.Result[0] = cdr.StatP;
9f8b032d 777
778 cdr.StatP |= STATUS_PLAY;
9f8b032d 779
780 // BIOS player - set flag again
781 cdr.Play = TRUE;
782
39df67df 783 CDRMISC_INT( cdReadTime );
e4268a49 784 start_rotating = 1;
ef79bbde
P
785 break;
786
9f8b032d 787 case CdlForward:
e4268a49 788 // TODO: error 80 if stopped
9f8b032d 789 cdr.Stat = Complete;
790
9f8b032d 791 // GameShark CD Player: Calls 2x + Play 2x
c9c7a925 792 cdr.FastForward = 1;
9f8b032d 793 cdr.FastBackward = 0;
ef79bbde
P
794 break;
795
9f8b032d 796 case CdlBackward:
9f8b032d 797 cdr.Stat = Complete;
798
9f8b032d 799 // GameShark CD Player: Calls 2x + Play 2x
c9c7a925 800 cdr.FastBackward = 1;
9f8b032d 801 cdr.FastForward = 0;
ef79bbde
P
802 break;
803
9f8b032d 804 case CdlStandby:
e4268a49 805 if (cdr.DriveState != DRIVESTATE_STOPPED) {
806 error = ERROR_INVALIDARG;
807 goto set_error;
808 }
809 AddIrqQueue(CdlStandby + 0x100, cdReadTime * 125 / 2);
810 start_rotating = 1;
811 break;
812
813 case CdlStandby + 0x100:
ef79bbde
P
814 cdr.Stat = Complete;
815 break;
816
817 case CdlStop:
030bd76a 818 if (cdr.Play) {
819 // grab time for current track
820 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
821
822 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
823 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
824 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
825 }
826
827 StopCdda();
828 StopReading();
829
e4268a49 830 delay = 0x800;
831 if (cdr.DriveState == DRIVESTATE_STANDBY)
832 delay = cdReadTime * 30 / 2;
833
834 cdr.DriveState = DRIVESTATE_STOPPED;
835 AddIrqQueue(CdlStop + 0x100, delay);
836 break;
837
838 case CdlStop + 0x100:
9f8b032d 839 cdr.StatP &= ~STATUS_ROTATING;
ef79bbde
P
840 cdr.Result[0] = cdr.StatP;
841 cdr.Stat = Complete;
ef79bbde
P
842 break;
843
844 case CdlPause:
12228917 845 /*
846 Gundam Battle Assault 2: much slower (*)
847 - Fixes boot, gameplay
848
849 Hokuto no Ken 2: slower
850 - Fixes intro + subtitles
851
852 InuYasha - Feudal Fairy Tale: slower
853 - Fixes battles
854 */
4e70ea5a 855 /* Gameblabla - Tightening the timings (as taken from Duckstation).
856 * The timings from Duckstation are based upon hardware tests.
857 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
858 * seems to be timing sensitive as it can depend on the CPU's clock speed.
859 * */
6c192edd 860 if (cdr.DriveState == DRIVESTATE_STANDBY)
e3d555e0 861 {
4e70ea5a 862 delay = 7000;
e3d555e0 863 }
864 else
865 {
4e70ea5a 866 delay = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * (1000000));
e3d555e0 867 CDRMISC_INT((cdr.Mode & MODE_SPEED) ? cdReadTime / 2 : cdReadTime);
868 }
869 AddIrqQueue(CdlPause + 0x100, delay);
ef79bbde
P
870 cdr.Ctrl |= 0x80;
871 break;
872
e4268a49 873 case CdlPause + 0x100:
9f8b032d 874 cdr.StatP &= ~STATUS_READ;
ef79bbde 875 cdr.Result[0] = cdr.StatP;
9f8b032d 876 cdr.Stat = Complete;
ef79bbde
P
877 break;
878
b0bd140d 879 case CdlReset:
880 cdr.Muted = FALSE;
881 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
882 AddIrqQueue(CdlReset + 0x100, 4100000);
e4268a49 883 no_busy_error = 1;
884 start_rotating = 1;
885 break;
886
b0bd140d 887 case CdlReset + 0x100:
9f8b032d 888 cdr.Stat = Complete;
ef79bbde
P
889 break;
890
9f8b032d 891 case CdlMute:
030bd76a 892 cdr.Muted = TRUE;
ef79bbde
P
893 break;
894
9f8b032d 895 case CdlDemute:
030bd76a 896 cdr.Muted = FALSE;
ef79bbde
P
897 break;
898
9f8b032d 899 case CdlSetfilter:
030bd76a 900 cdr.File = cdr.Param[0];
901 cdr.Channel = cdr.Param[1];
9f8b032d 902 break;
ef79bbde
P
903
904 case CdlSetmode:
e4268a49 905 no_busy_error = 1;
9f8b032d 906 break;
ef79bbde 907
dcd72441 908 case CdlGetparam:
909 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
910 SetResultSize(5);
9f8b032d 911 cdr.Result[1] = cdr.Mode;
dcd72441 912 cdr.Result[2] = 0;
913 cdr.Result[3] = cdr.File;
914 cdr.Result[4] = cdr.Channel;
e4268a49 915 no_busy_error = 1;
9f8b032d 916 break;
ef79bbde 917
9f8b032d 918 case CdlGetlocL:
ef79bbde 919 SetResultSize(8);
e4268a49 920 memcpy(cdr.Result, cdr.Transfer, 8);
9f8b032d 921 break;
ef79bbde
P
922
923 case CdlGetlocP:
fffad32e 924 SetResultSize(8);
925 memcpy(&cdr.Result, &cdr.subq, 8);
e4268a49 926 break;
892bd7d4 927
e4268a49 928 case CdlReadT: // SetSession?
929 // really long
930 AddIrqQueue(CdlReadT + 0x100, cdReadTime * 290 / 4);
931 start_rotating = 1;
932 break;
933
934 case CdlReadT + 0x100:
935 cdr.Stat = Complete;
ef79bbde
P
936 break;
937
9f8b032d 938 case CdlGetTN:
ef79bbde 939 SetResultSize(3);
9f8b032d 940 if (CDR_getTN(cdr.ResultTN) == -1) {
ef79bbde 941 cdr.Stat = DiskError;
9f8b032d 942 cdr.Result[0] |= STATUS_ERROR;
943 } else {
944 cdr.Stat = Acknowledge;
945 cdr.Result[1] = itob(cdr.ResultTN[0]);
946 cdr.Result[2] = itob(cdr.ResultTN[1]);
947 }
948 break;
ef79bbde 949
9f8b032d 950 case CdlGetTD:
ef79bbde
P
951 cdr.Track = btoi(cdr.Param[0]);
952 SetResultSize(4);
ef79bbde
P
953 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
954 cdr.Stat = DiskError;
9f8b032d 955 cdr.Result[0] |= STATUS_ERROR;
ef79bbde
P
956 } else {
957 cdr.Stat = Acknowledge;
958 cdr.Result[0] = cdr.StatP;
959 cdr.Result[1] = itob(cdr.ResultTD[2]);
960 cdr.Result[2] = itob(cdr.ResultTD[1]);
cfeb7cab 961 /* According to Nocash's documentation, the function doesn't care about ff.
962 * This can be seen also in Mednafen's implementation. */
963 //cdr.Result[3] = itob(cdr.ResultTD[0]);
ef79bbde
P
964 }
965 break;
966
9f8b032d 967 case CdlSeekL:
39df67df 968 case CdlSeekP:
030bd76a 969 StopCdda();
970 StopReading();
9f8b032d 971 cdr.StatP |= STATUS_SEEK;
9f8b032d 972
973 /*
974 Crusaders of Might and Magic = 0.5x-4x
975 - fix cutscene speech start
976
977 Eggs of Steel = 2x-?
978 - fix new game
979
980 Medievil = ?-4x
981 - fix cutscene speech
982
983 Rockman X5 = 0.5-4x
984 - fix capcom logo
985 */
39df67df 986 CDRMISC_INT(cdr.Seeked == SEEK_DONE ? 0x800 : cdReadTime * 4);
892bd7d4 987 cdr.Seeked = SEEK_PENDING;
92d79a62 988 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
e4268a49 989 start_rotating = 1;
ef79bbde
P
990 break;
991
992 case CdlTest:
9f8b032d 993 switch (cdr.Param[0]) {
994 case 0x20: // System Controller ROM Version
ef79bbde
P
995 SetResultSize(4);
996 memcpy(cdr.Result, Test20, 4);
997 break;
998 case 0x22:
999 SetResultSize(8);
1000 memcpy(cdr.Result, Test22, 4);
1001 break;
1002 case 0x23: case 0x24:
1003 SetResultSize(8);
1004 memcpy(cdr.Result, Test23, 4);
1005 break;
9f8b032d 1006 }
e4268a49 1007 no_busy_error = 1;
ef79bbde
P
1008 break;
1009
9f8b032d 1010 case CdlID:
e4268a49 1011 AddIrqQueue(CdlID + 0x100, 20480);
ef79bbde
P
1012 break;
1013
e4268a49 1014 case CdlID + 0x100:
ef79bbde 1015 SetResultSize(8);
89ec56af 1016 cdr.Result[0] = cdr.StatP;
1017 cdr.Result[1] = 0;
1018 cdr.Result[2] = 0;
1019 cdr.Result[3] = 0;
9f8b032d 1020
d8432250 1021 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1022 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1023 cdr.Result[1] = 0xc0;
9f8b032d 1024 }
1025 else {
89ec56af 1026 if (stat.Type == 2)
1027 cdr.Result[1] |= 0x10;
1028 if (CdromId[0] == '\0')
9f8b032d 1029 cdr.Result[1] |= 0x80;
9f8b032d 1030 }
89ec56af 1031 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
9f8b032d 1032
ef79bbde
P
1033 strncpy((char *)&cdr.Result[4], "PCSX", 4);
1034 cdr.Stat = Complete;
1035 break;
1036
b0bd140d 1037 case CdlInit:
e4268a49 1038 // yes, it really sets STATUS_SHELLOPEN
1039 cdr.StatP |= STATUS_SHELLOPEN;
1040 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1041 CDRLID_INT(20480);
1042 no_busy_error = 1;
1043 start_rotating = 1;
9f8b032d 1044 break;
1045
e4268a49 1046 case CdlGetQ:
6360a61b 1047 no_busy_error = 1;
9f8b032d 1048 break;
1049
1050 case CdlReadToc:
e4268a49 1051 AddIrqQueue(CdlReadToc + 0x100, cdReadTime * 180 / 4);
1052 no_busy_error = 1;
1053 start_rotating = 1;
ef79bbde
P
1054 break;
1055
e4268a49 1056 case CdlReadToc + 0x100:
9f8b032d 1057 cdr.Stat = Complete;
e4268a49 1058 no_busy_error = 1;
ef79bbde
P
1059 break;
1060
94c118c3 1061 case CdlReadN:
1062 case CdlReadS:
55b8460a 1063 if (cdr.SetlocPending) {
fbf19ce6 1064 seekTime = abs(msf2sec(cdr.SetSectorPlay) - msf2sec(cdr.SetSector)) * (cdReadTime / 200);
c9c7a925 1065 /*
1066 * Gameblabla :
1067 * It was originally set to 1000000 for Driver, however it is not high enough for Worms Pinball
1068 * and was unreliable for that game.
1069 * I also tested it against Mednafen and Driver's titlescreen music starts 25 frames later, not immediatly.
1070 *
1071 * Obviously, this isn't perfect but right now, it should be a bit better.
1072 * Games to test this against if you change that setting :
1073 * - Driver (titlescreen music delay and retry mission)
1074 * - Worms Pinball (Will either not boot or crash in the memory card screen)
1075 * - Viewpoint (short pauses if the delay in the ingame music is too long)
1076 *
1077 * It seems that 3386880 * 5 is too much for Driver's titlescreen and it starts skipping.
1078 * However, 1000000 is not enough for Worms Pinball to reliably boot.
1079 */
1080 if(seekTime > 3386880 * 2) seekTime = 3386880 * 2;
55b8460a 1081 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1082 cdr.SetlocPending = 0;
dc0ee8d4 1083 cdr.m_locationChanged = TRUE;
55b8460a 1084 }
1085 Find_CurTrack(cdr.SetSectorPlay);
1086
1087 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1088 // Read* acts as play for cdda tracks in cdda mode
1089 goto do_CdlPlay;
1090
030bd76a 1091 cdr.Reading = 1;
1092 cdr.FirstSector = 1;
ef79bbde 1093
9f8b032d 1094 // Fighting Force 2 - update subq time immediately
1095 // - fixes new game
55b8460a 1096 ReadTrack(cdr.SetSectorPlay);
9f8b032d 1097
1098
1099 // Crusaders of Might and Magic - update getlocl now
1100 // - fixes cutscene speech
1101 {
1102 u8 *buf = CDR_getBuffer();
a48ae3a7 1103 if (buf != NULL)
1104 memcpy(cdr.Transfer, buf, 8);
9f8b032d 1105 }
39df67df 1106
9f8b032d 1107 /*
1108 Duke Nukem: Land of the Babes - seek then delay read for one frame
1109 - fixes cutscenes
914722b2 1110 C-12 - Final Resistance - doesn't like seek
9f8b032d 1111 */
c9c7a925 1112
1113 /*
1114 By nicolasnoble from PCSX Redux :
1115 "It LOOKS like this logic is wrong, therefore disabling it with `&& false` for now.
1116 For "PoPoLoCrois Monogatari II", the game logic will soft lock and will never issue GetLocP to detect
1117 the end of its XA streams, as it seems to assume ReadS will not return a status byte with the SEEK
1118 flag set. I think the reasonning is that since it's invalid to call GetLocP while seeking, the game
1119 tries to protect itself against errors by preventing from issuing a GetLocP while it knows the
1120 last status was "seek". But this makes the logic just softlock as it'll never get a notification
1121 about the fact the drive is done seeking and the read actually started.
1122
1123 In other words, this state machine here is probably wrong in assuming the response to ReadS/ReadN is
1124 done right away. It's rather when it's done seeking, and the read has actually started. This probably
1125 requires a bit more work to make sure seek delays are processed properly.
1126 Checked with a few games, this seems to work fine."
1127
1128 Gameblabla additional notes :
1129 This still needs the "+ seekTime" that PCSX Redux doesn't have for the Driver "retry" mission error.
1130 */
1131 cdr.StatP |= STATUS_READ;
1132 cdr.StatP &= ~STATUS_SEEK;
9f8b032d 1133
c9c7a925 1134 CDREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime);
ef79bbde 1135
9f8b032d 1136 cdr.Result[0] = cdr.StatP;
e4268a49 1137 start_rotating = 1;
ef79bbde 1138 break;
1f035e27 1139 case CdlSync:
ef79bbde 1140 default:
e4268a49 1141 CDR_LOG_I("Invalid command: %02x\n", Irq);
1142 error = ERROR_INVALIDCMD;
1143 // FALLTHROUGH
1144
1145 set_error:
1146 SetResultSize(2);
1147 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1148 cdr.Result[1] = error;
1149 cdr.Stat = DiskError;
ef79bbde
P
1150 break;
1151 }
1152
e4268a49 1153 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1154 cdr.DriveState = DRIVESTATE_STANDBY;
1155 cdr.StatP |= STATUS_ROTATING;
1156 }
1157
1158 if (!no_busy_error) {
80f91a20 1159 switch (cdr.DriveState) {
1160 case DRIVESTATE_LID_OPEN:
1161 case DRIVESTATE_RESCAN_CD:
1162 case DRIVESTATE_PREPARE_CD:
1163 SetResultSize(2);
1164 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
003cfc63 1165 cdr.Result[1] = ERROR_NOTREADY;
80f91a20 1166 cdr.Stat = DiskError;
1167 break;
1168 }
1169 }
9f8b032d 1170
e4268a49 1171finish:
af93c8be 1172 setIrq(Irq);
e4268a49 1173 cdr.ParamC = 0;
ef79bbde
P
1174}
1175
8f2bb0cb 1176#ifdef HAVE_ARMV7
53598a71 1177 #define ssat32_to_16(v) \
1178 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1179#else
1180 #define ssat32_to_16(v) do { \
1181 if (v < -32768) v = -32768; \
1182 else if (v > 32767) v = 32767; \
1183 } while (0)
1184#endif
1185
1186void cdrAttenuate(s16 *buf, int samples, int stereo)
1187{
1188 int i, l, r;
1189 int ll = cdr.AttenuatorLeftToLeft;
1190 int lr = cdr.AttenuatorLeftToRight;
1191 int rl = cdr.AttenuatorRightToLeft;
1192 int rr = cdr.AttenuatorRightToRight;
1193
1194 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1195 return;
1196
1197 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1198 return;
1199
1200 if (stereo) {
1201 for (i = 0; i < samples; i++) {
1202 l = buf[i * 2];
1203 r = buf[i * 2 + 1];
1204 l = (l * ll + r * rl) >> 7;
1205 r = (r * rr + l * lr) >> 7;
1206 ssat32_to_16(l);
1207 ssat32_to_16(r);
1208 buf[i * 2] = l;
1209 buf[i * 2 + 1] = r;
1210 }
1211 }
1212 else {
1213 for (i = 0; i < samples; i++) {
1214 l = buf[i];
1215 l = l * (ll + rl) >> 7;
1216 //r = r * (rr + lr) >> 7;
1217 ssat32_to_16(l);
1218 //ssat32_to_16(r);
1219 buf[i] = l;
1220 }
1221 }
1222}
1223
ef79bbde
P
1224void cdrReadInterrupt() {
1225 u8 *buf;
1226
1227 if (!cdr.Reading)
1228 return;
1229
9f8b032d 1230 if (cdr.Irq || cdr.Stat) {
8b380e97 1231 CDR_LOG_I("cdrom: read stat hack %02x %x\n", cdr.Irq, cdr.Stat);
e7e33ef2 1232 CDREAD_INT(0x1000);
ef79bbde
P
1233 return;
1234 }
1235
9f8b032d 1236 cdr.OCUP = 1;
ef79bbde 1237 SetResultSize(1);
9f8b032d 1238 cdr.StatP |= STATUS_READ|STATUS_ROTATING;
1239 cdr.StatP &= ~STATUS_SEEK;
1240 cdr.Result[0] = cdr.StatP;
39df67df 1241 cdr.Seeked = SEEK_DONE;
ef79bbde 1242
55b8460a 1243 ReadTrack(cdr.SetSectorPlay);
ef79bbde
P
1244
1245 buf = CDR_getBuffer();
1246 if (buf == NULL)
c9c7a925 1247 cdr.NoErr = 0;
ef79bbde 1248
c9c7a925 1249 if (!cdr.NoErr) {
8b380e97 1250 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
ef79bbde
P
1251 memset(cdr.Transfer, 0, DATA_SIZE);
1252 cdr.Stat = DiskError;
9f8b032d 1253 cdr.Result[0] |= STATUS_ERROR;
ef79bbde
P
1254 CDREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime);
1255 return;
1256 }
1257
1258 memcpy(cdr.Transfer, buf, DATA_SIZE);
1259 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1260
ef79bbde 1261
8b380e97 1262 CDR_LOG("cdrReadInterrupt() Log: cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
ef79bbde 1263
9f8b032d 1264 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1265 // Firemen 2: Multi-XA files - briefings, cutscenes
1266 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1267 cdr.File = cdr.Transfer[4 + 0];
1268 cdr.Channel = cdr.Transfer[4 + 1];
1269 }
1270
e73e384c 1271 /* Gameblabla
1272 * Skips playing on channel 255.
1273 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1274 * TODO : Check if this is the proper behaviour.
1275 * */
cd0c9f5b 1276 if((cdr.Transfer[4 + 2] & 0x4) &&
9f8b032d 1277 (cdr.Transfer[4 + 1] == cdr.Channel) &&
e73e384c 1278 (cdr.Transfer[4 + 0] == cdr.File) && cdr.Channel != 255) {
ef79bbde 1279 int ret = xa_decode_sector(&cdr.Xa, cdr.Transfer+4, cdr.FirstSector);
ef79bbde 1280 if (!ret) {
53598a71 1281 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
fcaa8d22 1282 /*
1283 * Gameblabla -
1284 * This is a hack for Megaman X4, Castlevania etc...
1285 * that regressed from the new m_locationChanged and CDROM timings changes.
1286 * It is mostly noticeable in Castevania however and the stuttering can be very jarring.
1287 *
1288 * According to PCSX redux authors, we shouldn't cause a location change if
1289 * the sector difference is too small.
1290 * I attempted to go with that approach but came empty handed.
1291 * So for now, let's just set cdr.m_locationChanged to false when playing back any ADPCM samples.
1292 * This does not regress Crash Team Racing's intro at least.
1293 */
1294 cdr.m_locationChanged = FALSE;
ef79bbde
P
1295 SPU_playADPCMchannel(&cdr.Xa);
1296 cdr.FirstSector = 0;
1297 }
1298 else cdr.FirstSector = -1;
1299 }
1300 }
1301
55b8460a 1302 cdr.SetSectorPlay[2]++;
1303 if (cdr.SetSectorPlay[2] == 75) {
1304 cdr.SetSectorPlay[2] = 0;
1305 cdr.SetSectorPlay[1]++;
1306 if (cdr.SetSectorPlay[1] == 60) {
1307 cdr.SetSectorPlay[1] = 0;
1308 cdr.SetSectorPlay[0]++;
9f8b032d 1309 }
1310 }
1311
1312 cdr.Readed = 0;
1313
dc0ee8d4 1314 uint32_t delay = (cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime;
1315 if (cdr.m_locationChanged) {
1316 CDREAD_INT(delay * 30);
1317 cdr.m_locationChanged = FALSE;
1318 } else {
1319 CDREAD_INT(delay);
1320 }
9f8b032d 1321
1322 /*
1323 Croc 2: $40 - only FORM1 (*)
1324 Judge Dredd: $C8 - only FORM1 (*)
1325 Sim Theme Park - no adpcm at all (zero)
1326 */
1327
a34a2445 1328 if (!(cdr.Mode & MODE_STRSND) || !(cdr.Transfer[4+2] & 0x4)) {
9f8b032d 1329 cdr.Stat = DataReady;
af93c8be 1330 setIrq(0x203);
ef79bbde 1331 }
9f8b032d 1332
fffad32e 1333 // update for CdlGetlocP
55b8460a 1334 ReadTrack(cdr.SetSectorPlay);
ef79bbde
P
1335}
1336
1337/*
1338cdrRead0:
94c118c3 1339 bit 0,1 - mode
ef79bbde
P
1340 bit 2 - unknown
1341 bit 3 - unknown
1342 bit 4 - unknown
1343 bit 5 - 1 result ready
1344 bit 6 - 1 dma ready
1345 bit 7 - 1 command being processed
1346*/
1347
1348unsigned char cdrRead0(void) {
1349 if (cdr.ResultReady)
1350 cdr.Ctrl |= 0x20;
1351 else
1352 cdr.Ctrl &= ~0x20;
1353
1354 if (cdr.OCUP)
1355 cdr.Ctrl |= 0x40;
dda25fa4 1356// else
1357// cdr.Ctrl &= ~0x40;
ef79bbde
P
1358
1359 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1360 cdr.Ctrl |= 0x18;
1361
af93c8be 1362 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
ef79bbde
P
1363
1364 return psxHu8(0x1800) = cdr.Ctrl;
1365}
1366
ef79bbde 1367void cdrWrite0(unsigned char rt) {
af93c8be 1368 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
8b380e97 1369
50b13be9 1370 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
ef79bbde
P
1371}
1372
1373unsigned char cdrRead1(void) {
94c118c3 1374 if ((cdr.ResultP & 0xf) < cdr.ResultC)
9f8b032d 1375 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
94c118c3 1376 else
ef79bbde 1377 psxHu8(0x1801) = 0;
94c118c3 1378 cdr.ResultP++;
1379 if (cdr.ResultP == cdr.ResultC)
1380 cdr.ResultReady = 0;
1381
af93c8be 1382 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
8b380e97 1383
ef79bbde
P
1384 return psxHu8(0x1801);
1385}
1386
1387void cdrWrite1(unsigned char rt) {
af93c8be 1388 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1389 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
9f8b032d 1390
94c118c3 1391 switch (cdr.Ctrl & 3) {
1392 case 0:
1393 break;
1394 case 3:
53598a71 1395 cdr.AttenuatorRightToRightT = rt;
94c118c3 1396 return;
1397 default:
1398 return;
9f8b032d 1399 }
1400
9f8b032d 1401 cdr.Cmd = rt;
ef79bbde
P
1402 cdr.OCUP = 0;
1403
8b380e97 1404#ifdef CDR_LOG_CMD_IRQ
89ec56af 1405 SysPrintf("CD1 write: %x (%s)", rt, CmdName[rt]);
ef79bbde 1406 if (cdr.ParamC) {
af93c8be 1407 int i;
ef79bbde
P
1408 SysPrintf(" Param[%d] = {", cdr.ParamC);
1409 for (i = 0; i < cdr.ParamC; i++)
1410 SysPrintf(" %x,", cdr.Param[i]);
1411 SysPrintf("}\n");
1412 } else {
1413 SysPrintf("\n");
1414 }
1415#endif
1416
2daaaae3 1417 cdr.ResultReady = 0;
94c118c3 1418 cdr.Ctrl |= 0x80;
1419 // cdr.Stat = NoIntr;
1420 AddIrqQueue(cdr.Cmd, 0x800);
2daaaae3 1421
9f8b032d 1422 switch (cdr.Cmd) {
ef79bbde 1423
94c118c3 1424 case CdlReadN:
030bd76a 1425 case CdlReadS:
94c118c3 1426 case CdlPause:
9f8b032d 1427 StopCdda();
1428 StopReading();
9f8b032d 1429 break;
1430
94c118c3 1431 case CdlInit:
b0bd140d 1432 case CdlReset:
39df67df 1433 cdr.Seeked = SEEK_DONE;
9f8b032d 1434 StopCdda();
1435 StopReading();
94c118c3 1436 break;
9f8b032d 1437
ef79bbde 1438 case CdlSetmode:
9f8b032d 1439 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
8b380e97 1440
ef79bbde 1441 cdr.Mode = cdr.Param[0];
9f8b032d 1442
1443 // Squaresoft on PlayStation 1998 Collector's CD Vol. 1
1444 // - fixes choppy movie sound
1445 if( cdr.Play && (cdr.Mode & MODE_CDDA) == 0 )
1446 StopCdda();
ef79bbde 1447 break;
9f8b032d 1448 }
ef79bbde
P
1449}
1450
1451unsigned char cdrRead2(void) {
1452 unsigned char ret;
1453
1454 if (cdr.Readed == 0) {
1455 ret = 0;
1456 } else {
8e1040b6 1457 ret = *pTransfer++;
ef79bbde
P
1458 }
1459
af93c8be 1460 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
ef79bbde
P
1461 return ret;
1462}
1463
1464void cdrWrite2(unsigned char rt) {
af93c8be 1465 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1466 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
9f8b032d 1467
94c118c3 1468 switch (cdr.Ctrl & 3) {
1469 case 0:
1470 if (cdr.ParamC < 8) // FIXME: size and wrapping
1471 cdr.Param[cdr.ParamC++] = rt;
1472 return;
1473 case 1:
1474 cdr.Reg2 = rt;
af93c8be 1475 setIrq(0x204);
94c118c3 1476 return;
1477 case 2:
53598a71 1478 cdr.AttenuatorLeftToLeftT = rt;
94c118c3 1479 return;
1480 case 3:
53598a71 1481 cdr.AttenuatorRightToLeftT = rt;
94c118c3 1482 return;
ef79bbde
P
1483 }
1484}
1485
1486unsigned char cdrRead3(void) {
94c118c3 1487 if (cdr.Ctrl & 0x1)
1488 psxHu8(0x1803) = cdr.Stat | 0xE0;
1489 else
1490 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
8b380e97 1491
af93c8be 1492 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
ef79bbde
P
1493 return psxHu8(0x1803);
1494}
1495
1496void cdrWrite3(unsigned char rt) {
af93c8be 1497 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1498 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
056d6759 1499
94c118c3 1500 switch (cdr.Ctrl & 3) {
1501 case 0:
a34a2445 1502 break; // transfer
94c118c3 1503 case 1:
a34a2445 1504 cdr.Stat &= ~rt;
1505
1506 if (rt & 0x40)
1507 cdr.ParamC = 0;
1508 return;
94c118c3 1509 case 2:
53598a71 1510 cdr.AttenuatorLeftToRightT = rt;
94c118c3 1511 return;
1512 case 3:
53598a71 1513 if (rt & 0x20) {
1514 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1515 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n",
1516 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1517 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1518 }
94c118c3 1519 return;
9f8b032d 1520 }
056d6759 1521
94c118c3 1522 if ((rt & 0x80) && cdr.Readed == 0) {
ef79bbde 1523 cdr.Readed = 1;
8e1040b6 1524 pTransfer = cdr.Transfer;
ef79bbde
P
1525
1526 switch (cdr.Mode & 0x30) {
9f8b032d 1527 case MODE_SIZE_2328:
ef79bbde 1528 case 0x00:
8e1040b6 1529 pTransfer += 12;
ef79bbde 1530 break;
9f8b032d 1531
1532 case MODE_SIZE_2340:
8e1040b6 1533 pTransfer += 0;
9f8b032d 1534 break;
1535
ef79bbde
P
1536 default:
1537 break;
1538 }
1539 }
1540}
1541
1542void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1543 u32 cdsize;
c7071c43 1544 int size;
ef79bbde
P
1545 u8 *ptr;
1546
ef79bbde 1547 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
ef79bbde
P
1548
1549 switch (chcr) {
1550 case 0x11000000:
1551 case 0x11400100:
1552 if (cdr.Readed == 0) {
ef79bbde 1553 CDR_LOG("psxDma3() Log: *** DMA 3 *** NOT READY\n");
ef79bbde
P
1554 break;
1555 }
1556
1557 cdsize = (bcr & 0xffff) * 4;
1558
9f8b032d 1559 // Ape Escape: bcr = 0001 / 0000
1560 // - fix boot
1561 if( cdsize == 0 )
1562 {
cd0c9f5b 1563 switch (cdr.Mode & (MODE_SIZE_2340|MODE_SIZE_2328)) {
9f8b032d 1564 case MODE_SIZE_2340: cdsize = 2340; break;
cd0c9f5b 1565 case MODE_SIZE_2328: cdsize = 2328; break;
1566 default:
1567 case MODE_SIZE_2048: cdsize = 2048; break;
9f8b032d 1568 }
1569 }
1570
1571
ef79bbde
P
1572 ptr = (u8 *)PSXM(madr);
1573 if (ptr == NULL) {
ef79bbde 1574 CDR_LOG("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
ef79bbde
P
1575 break;
1576 }
9f8b032d 1577
1578 /*
1579 GS CDX: Enhancement CD crash
1580 - Setloc 0:0:0
1581 - CdlPlay
1582 - Spams DMA3 and gets buffer overrun
1583 */
8e1040b6 1584 size = CD_FRAMESIZE_RAW - (pTransfer - cdr.Transfer);
c7071c43 1585 if (size > cdsize)
1586 size = cdsize;
1587 if (size > 0)
9f8b032d 1588 {
8e1040b6 1589 memcpy(ptr, pTransfer, size);
9f8b032d 1590 }
1591
ef79bbde 1592 psxCpu->Clear(madr, cdsize / 4);
8e1040b6 1593 pTransfer += cdsize;
9f8b032d 1594
9f8b032d 1595 if( chcr == 0x11400100 ) {
58ebb94c 1596 HW_DMA3_MADR = SWAPu32(madr + cdsize);
9f8b032d 1597 CDRDMA_INT( (cdsize/4) / 4 );
1598 }
1599 else if( chcr == 0x11000000 ) {
fc4803bd 1600 // CDRDMA_INT( (cdsize/4) * 1 );
1601 // halted
1602 psxRegs.cycle += (cdsize/4) * 24/2;
1603 CDRDMA_INT(16);
9f8b032d 1604 }
1605 return;
1606
ef79bbde 1607 default:
ef79bbde 1608 CDR_LOG("psxDma3() Log: Unknown cddma %x\n", chcr);
ef79bbde
P
1609 break;
1610 }
1611
1612 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1613 DMA_INTERRUPT(3);
1614}
1615
9f8b032d 1616void cdrDmaInterrupt()
1617{
ad418c19 1618 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1619 {
1620 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1621 DMA_INTERRUPT(3);
1622 }
9f8b032d 1623}
1624
fffad32e 1625static void getCdInfo(void)
1626{
1627 u8 tmp;
1628
1629 CDR_getTN(cdr.ResultTN);
1630 CDR_getTD(0, cdr.SetSectorEnd);
1631 tmp = cdr.SetSectorEnd[0];
1632 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1633 cdr.SetSectorEnd[2] = tmp;
1634}
1635
ef79bbde
P
1636void cdrReset() {
1637 memset(&cdr, 0, sizeof(cdr));
1638 cdr.CurTrack = 1;
1639 cdr.File = 1;
1640 cdr.Channel = 1;
a2d3ccb8 1641 cdr.Reg2 = 0x1f;
1642 cdr.Stat = NoIntr;
80f91a20 1643 cdr.DriveState = DRIVESTATE_STANDBY;
22bbabf6 1644 cdr.StatP = STATUS_ROTATING;
8e1040b6 1645 pTransfer = cdr.Transfer;
c9c7a925 1646
056d6759 1647 // BIOS player - default values
53598a71 1648 cdr.AttenuatorLeftToLeft = 0x80;
1649 cdr.AttenuatorLeftToRight = 0x00;
1650 cdr.AttenuatorRightToLeft = 0x00;
1651 cdr.AttenuatorRightToRight = 0x80;
fffad32e 1652
1653 getCdInfo();
ef79bbde
P
1654}
1655
496d88d4 1656int cdrFreeze(void *f, int Mode) {
8e1040b6 1657 u32 tmp;
fffad32e 1658 u8 tmpp[3];
ef79bbde 1659
fffad32e 1660 if (Mode == 0 && !Config.Cdda)
1661 CDR_stop();
9f8b032d 1662
55b8460a 1663 cdr.freeze_ver = 0x63647202;
9f8b032d 1664 gzfreeze(&cdr, sizeof(cdr));
1665
94c118c3 1666 if (Mode == 1) {
1667 cdr.ParamP = cdr.ParamC;
8e1040b6 1668 tmp = pTransfer - cdr.Transfer;
94c118c3 1669 }
ef79bbde
P
1670
1671 gzfreeze(&tmp, sizeof(tmp));
1672
42f3c512 1673 if (Mode == 0) {
fffad32e 1674 getCdInfo();
1675
8e1040b6 1676 pTransfer = cdr.Transfer + tmp;
ef79bbde 1677
fffad32e 1678 // read right sub data
f84d4864
DS
1679 tmpp[0] = btoi(cdr.Prev[0]);
1680 tmpp[1] = btoi(cdr.Prev[1]);
1681 tmpp[2] = btoi(cdr.Prev[2]);
fffad32e 1682 cdr.Prev[0]++;
73b29eeb 1683 ReadTrack(tmpp);
fffad32e 1684
1685 if (cdr.Play) {
55b8460a 1686 if (cdr.freeze_ver < 0x63647202)
1687 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1688
fffad32e 1689 Find_CurTrack(cdr.SetSectorPlay);
1690 if (!Config.Cdda)
1691 CDR_play(cdr.SetSectorPlay);
1692 }
c69642c8 1693
1694 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1695 // old versions did not latch Reg2, have to fixup..
1696 if (cdr.Reg2 == 0) {
1697 SysPrintf("cdrom: fixing up old savestate\n");
1698 cdr.Reg2 = 7;
1699 }
0f2dee0f 1700 // also did not save Attenuator..
1701 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1702 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1703 {
1704 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1705 }
c69642c8 1706 }
42f3c512 1707 }
1708
ef79bbde
P
1709 return 0;
1710}
9f8b032d 1711
1712void LidInterrupt() {
fffad32e 1713 getCdInfo();
fffad32e 1714 StopCdda();
80f91a20 1715 cdrLidSeekInterrupt();
9f8b032d 1716}