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