main: log missing CPU features
[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(0x200);
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(0x201);
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(0x202);
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
642void cdrInterrupt(void) {
643 int no_busy_error = 0;
644 int start_rotating = 0;
645 int error = 0;
646 unsigned int seekTime = 0;
647 u32 second_resp_time = 0;
648 u8 ParamC;
649 u8 set_loc[3];
650 u16 Cmd;
651 int i;
652
653 if (cdr.Stat) {
654 CDR_LOG_I("%u cdrom: cmd %02x with irqstat %x\n",
655 psxRegs.cycle, cdr.CmdInProgress, cdr.Stat);
656 return;
657 }
658 if (cdr.Irq1Pending) {
659 // hand out the "newest" sector, according to nocash
660 cdrUpdateTransferBuf(CDR_getBuffer());
661 CDR_LOG_I("cdrom: %x:%02x:%02x loaded on ack\n",
662 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
663 SetResultSize(1);
664 cdr.Result[0] = cdr.Irq1Pending;
665 cdr.Stat = (cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady;
666 cdr.Irq1Pending = 0;
667 setIrq(0x205);
668 return;
669 }
670
671 cdr.Ctrl &= ~0x80;
672
673 // default response
674 SetResultSize(1);
675 cdr.Result[0] = cdr.StatP;
676 cdr.Stat = Acknowledge;
677
678 Cmd = cdr.CmdInProgress;
679 cdr.CmdInProgress = 0;
680 ParamC = cdr.ParamC;
681
682 if (Cmd < 0x100) {
683 cdr.Cmd = 0;
684 cdr.ParamC = 0;
685 }
686
687 switch (Cmd) {
688 case CdlNop:
689 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
690 cdr.StatP &= ~STATUS_SHELLOPEN;
691 no_busy_error = 1;
692 break;
693
694 case CdlSetloc:
695 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
696
697 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
698 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))
699 {
700 CDR_LOG("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
701 error = ERROR_INVALIDARG;
702 goto set_error;
703 }
704 else
705 {
706 for (i = 0; i < 3; i++)
707 set_loc[i] = btoi(cdr.Param[i]);
708 memcpy(cdr.SetSector, set_loc, 3);
709 cdr.SetSector[3] = 0;
710 cdr.SetlocPending = 1;
711 }
712 break;
713
714 do_CdlPlay:
715 case CdlPlay:
716 StopCdda();
717 StopReading();
718
719 cdr.FastBackward = 0;
720 cdr.FastForward = 0;
721
722 // BIOS CD Player
723 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
724
725 if (ParamC != 0 && cdr.Param[0] != 0) {
726 int track = btoi( cdr.Param[0] );
727
728 if (track <= cdr.ResultTN[1])
729 cdr.CurTrack = track;
730
731 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
732
733 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
734 for (i = 0; i < 3; i++)
735 set_loc[i] = cdr.ResultTD[2 - i];
736 seekTime = cdrSeekTime(set_loc);
737 memcpy(cdr.SetSectorPlay, set_loc, 3);
738 }
739 }
740 else if (cdr.SetlocPending) {
741 seekTime = cdrSeekTime(cdr.SetSector);
742 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
743 }
744 else {
745 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
746 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
747 }
748 cdr.SetlocPending = 0;
749
750 /*
751 Rayman: detect track changes
752 - fixes logo freeze
753
754 Twisted Metal 2: skip PREGAP + starting accurate SubQ
755 - plays tracks without retry play
756
757 Wild 9: skip PREGAP + starting accurate SubQ
758 - plays tracks without retry play
759 */
760 Find_CurTrack(cdr.SetSectorPlay);
761 ReadTrack(cdr.SetSectorPlay);
762 cdr.TrackChanged = FALSE;
763 cdr.FirstSector = 1;
764
765 if (!Config.Cdda)
766 CDR_play(cdr.SetSectorPlay);
767
768 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
769
770 // BIOS player - set flag again
771 cdr.Play = TRUE;
772
773 CDRPLAYSEEKREAD_INT(cdReadTime + seekTime, 1);
774 start_rotating = 1;
775 break;
776
777 case CdlForward:
778 // TODO: error 80 if stopped
779 cdr.Stat = Complete;
780
781 // GameShark CD Player: Calls 2x + Play 2x
782 cdr.FastForward = 1;
783 cdr.FastBackward = 0;
784 break;
785
786 case CdlBackward:
787 cdr.Stat = Complete;
788
789 // GameShark CD Player: Calls 2x + Play 2x
790 cdr.FastBackward = 1;
791 cdr.FastForward = 0;
792 break;
793
794 case CdlStandby:
795 if (cdr.DriveState != DRIVESTATE_STOPPED) {
796 error = ERROR_INVALIDARG;
797 goto set_error;
798 }
799 second_resp_time = cdReadTime * 125 / 2;
800 start_rotating = 1;
801 break;
802
803 case CdlStandby + 0x100:
804 cdr.Stat = Complete;
805 break;
806
807 case CdlStop:
808 if (cdr.Play) {
809 // grab time for current track
810 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
811
812 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
813 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
814 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
815 }
816
817 StopCdda();
818 StopReading();
819 SetPlaySeekRead(cdr.StatP, 0);
820 cdr.StatP &= ~STATUS_ROTATING;
821
822 second_resp_time = 0x800;
823 if (cdr.DriveState == DRIVESTATE_STANDBY)
824 second_resp_time = cdReadTime * 30 / 2;
825
826 cdr.DriveState = DRIVESTATE_STOPPED;
827 break;
828
829 case CdlStop + 0x100:
830 cdr.Stat = Complete;
831 break;
832
833 case CdlPause:
834 StopCdda();
835 StopReading();
836 /*
837 Gundam Battle Assault 2: much slower (*)
838 - Fixes boot, gameplay
839
840 Hokuto no Ken 2: slower
841 - Fixes intro + subtitles
842
843 InuYasha - Feudal Fairy Tale: slower
844 - Fixes battles
845 */
846 /* Gameblabla - Tightening the timings (as taken from Duckstation).
847 * The timings from Duckstation are based upon hardware tests.
848 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
849 * seems to be timing sensitive as it can depend on the CPU's clock speed.
850 * */
851 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
852 {
853 second_resp_time = 7000;
854 }
855 else
856 {
857 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 2 : 1) * 1000000);
858 }
859 SetPlaySeekRead(cdr.StatP, 0);
860 cdr.Ctrl |= 0x80;
861 break;
862
863 case CdlPause + 0x100:
864 cdr.Stat = Complete;
865 break;
866
867 case CdlReset:
868 StopCdda();
869 StopReading();
870 SetPlaySeekRead(cdr.StatP, 0);
871 cdr.Muted = FALSE;
872 cdr.Mode = 0x20; /* This fixes This is Football 2, Pooh's Party lockups */
873 second_resp_time = 4100000;
874 no_busy_error = 1;
875 start_rotating = 1;
876 break;
877
878 case CdlReset + 0x100:
879 cdr.Stat = Complete;
880 break;
881
882 case CdlMute:
883 cdr.Muted = TRUE;
884 break;
885
886 case CdlDemute:
887 cdr.Muted = FALSE;
888 break;
889
890 case CdlSetfilter:
891 cdr.File = cdr.Param[0];
892 cdr.Channel = cdr.Param[1];
893 break;
894
895 case CdlSetmode:
896 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
897 cdr.Mode = cdr.Param[0];
898 no_busy_error = 1;
899 break;
900
901 case CdlGetparam:
902 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
903 SetResultSize(5);
904 cdr.Result[1] = cdr.Mode;
905 cdr.Result[2] = 0;
906 cdr.Result[3] = cdr.File;
907 cdr.Result[4] = cdr.Channel;
908 no_busy_error = 1;
909 break;
910
911 case CdlGetlocL:
912 SetResultSize(8);
913 memcpy(cdr.Result, cdr.Transfer, 8);
914 break;
915
916 case CdlGetlocP:
917 SetResultSize(8);
918 memcpy(&cdr.Result, &cdr.subq, 8);
919 break;
920
921 case CdlReadT: // SetSession?
922 // really long
923 second_resp_time = cdReadTime * 290 / 4;
924 start_rotating = 1;
925 break;
926
927 case CdlReadT + 0x100:
928 cdr.Stat = Complete;
929 break;
930
931 case CdlGetTN:
932 SetResultSize(3);
933 if (CDR_getTN(cdr.ResultTN) == -1) {
934 cdr.Stat = DiskError;
935 cdr.Result[0] |= STATUS_ERROR;
936 } else {
937 cdr.Stat = Acknowledge;
938 cdr.Result[1] = itob(cdr.ResultTN[0]);
939 cdr.Result[2] = itob(cdr.ResultTN[1]);
940 }
941 break;
942
943 case CdlGetTD:
944 cdr.Track = btoi(cdr.Param[0]);
945 SetResultSize(4);
946 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
947 cdr.Stat = DiskError;
948 cdr.Result[0] |= STATUS_ERROR;
949 } else {
950 cdr.Stat = Acknowledge;
951 cdr.Result[0] = cdr.StatP;
952 cdr.Result[1] = itob(cdr.ResultTD[2]);
953 cdr.Result[2] = itob(cdr.ResultTD[1]);
954 /* According to Nocash's documentation, the function doesn't care about ff.
955 * This can be seen also in Mednafen's implementation. */
956 //cdr.Result[3] = itob(cdr.ResultTD[0]);
957 }
958 break;
959
960 case CdlSeekL:
961 case CdlSeekP:
962 StopCdda();
963 StopReading();
964 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
965
966 seekTime = cdrSeekTime(cdr.SetSector);
967 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
968 /*
969 Crusaders of Might and Magic = 0.5x-4x
970 - fix cutscene speech start
971
972 Eggs of Steel = 2x-?
973 - fix new game
974
975 Medievil = ?-4x
976 - fix cutscene speech
977
978 Rockman X5 = 0.5-4x
979 - fix capcom logo
980 */
981 CDRPLAYSEEKREAD_INT(cdReadTime + seekTime, 1);
982 start_rotating = 1;
983 break;
984
985 case CdlTest:
986 switch (cdr.Param[0]) {
987 case 0x20: // System Controller ROM Version
988 SetResultSize(4);
989 memcpy(cdr.Result, Test20, 4);
990 break;
991 case 0x22:
992 SetResultSize(8);
993 memcpy(cdr.Result, Test22, 4);
994 break;
995 case 0x23: case 0x24:
996 SetResultSize(8);
997 memcpy(cdr.Result, Test23, 4);
998 break;
999 }
1000 no_busy_error = 1;
1001 break;
1002
1003 case CdlID:
1004 second_resp_time = 20480;
1005 break;
1006
1007 case CdlID + 0x100:
1008 SetResultSize(8);
1009 cdr.Result[0] = cdr.StatP;
1010 cdr.Result[1] = 0;
1011 cdr.Result[2] = 0;
1012 cdr.Result[3] = 0;
1013
1014 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1015 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1016 cdr.Result[1] = 0xc0;
1017 }
1018 else {
1019 if (stat.Type == 2)
1020 cdr.Result[1] |= 0x10;
1021 if (CdromId[0] == '\0')
1022 cdr.Result[1] |= 0x80;
1023 }
1024 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1025
1026 /* This adds the string "PCSX" in Playstation bios boot screen */
1027 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1028 cdr.Stat = Complete;
1029 break;
1030
1031 case CdlInit:
1032 StopCdda();
1033 StopReading();
1034 SetPlaySeekRead(cdr.StatP, 0);
1035 // yes, it really sets STATUS_SHELLOPEN
1036 cdr.StatP |= STATUS_SHELLOPEN;
1037 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1038 CDRLID_INT(20480);
1039 no_busy_error = 1;
1040 start_rotating = 1;
1041 break;
1042
1043 case CdlGetQ:
1044 no_busy_error = 1;
1045 break;
1046
1047 case CdlReadToc:
1048 second_resp_time = cdReadTime * 180 / 4;
1049 no_busy_error = 1;
1050 start_rotating = 1;
1051 break;
1052
1053 case CdlReadToc + 0x100:
1054 cdr.Stat = Complete;
1055 no_busy_error = 1;
1056 break;
1057
1058 case CdlReadN:
1059 case CdlReadS:
1060 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1061
1062 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1063 // Read* acts as play for cdda tracks in cdda mode
1064 goto do_CdlPlay;
1065
1066 StopCdda();
1067 if (cdr.SetlocPending) {
1068 seekTime = cdrSeekTime(cdr.SetSector);
1069 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1070 cdr.SetlocPending = 0;
1071 }
1072 cdr.Reading = 1;
1073 cdr.FirstSector = 1;
1074
1075 // Fighting Force 2 - update subq time immediately
1076 // - fixes new game
1077 ReadTrack(cdr.SetSectorPlay);
1078
1079 CDRPLAYSEEKREAD_INT(((cdr.Mode & 0x80) ? (cdReadTime) : cdReadTime * 2) + seekTime, 1);
1080
1081 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1082 start_rotating = 1;
1083 break;
1084 case CdlSync:
1085 default:
1086 CDR_LOG_I("Invalid command: %02x\n", Cmd);
1087 error = ERROR_INVALIDCMD;
1088 // FALLTHROUGH
1089
1090 set_error:
1091 SetResultSize(2);
1092 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1093 cdr.Result[1] = error;
1094 cdr.Stat = DiskError;
1095 break;
1096 }
1097
1098 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1099 cdr.DriveState = DRIVESTATE_STANDBY;
1100 cdr.StatP |= STATUS_ROTATING;
1101 }
1102
1103 if (!no_busy_error) {
1104 switch (cdr.DriveState) {
1105 case DRIVESTATE_LID_OPEN:
1106 case DRIVESTATE_RESCAN_CD:
1107 case DRIVESTATE_PREPARE_CD:
1108 SetResultSize(2);
1109 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1110 cdr.Result[1] = ERROR_NOTREADY;
1111 cdr.Stat = DiskError;
1112 break;
1113 }
1114 }
1115
1116 if (second_resp_time) {
1117 cdr.CmdInProgress = Cmd | 0x100;
1118 CDR_INT(second_resp_time);
1119 }
1120 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1121 cdr.CmdInProgress = cdr.Cmd;
1122 CDR_LOG_I("%u cdrom: cmd %02x came before %02x finished\n",
1123 psxRegs.cycle, cdr.Cmd, Cmd);
1124 }
1125
1126 setIrq(Cmd);
1127}
1128
1129#ifdef HAVE_ARMV7
1130 #define ssat32_to_16(v) \
1131 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1132#else
1133 #define ssat32_to_16(v) do { \
1134 if (v < -32768) v = -32768; \
1135 else if (v > 32767) v = 32767; \
1136 } while (0)
1137#endif
1138
1139static void cdrPrepCdda(s16 *buf, int samples)
1140{
1141#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1142 int i;
1143 for (i = 0; i < samples; i++) {
1144 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1145 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1146 }
1147#endif
1148}
1149
1150static void cdrAttenuate(s16 *buf, int samples, int stereo)
1151{
1152 int i, l, r;
1153 int ll = cdr.AttenuatorLeftToLeft;
1154 int lr = cdr.AttenuatorLeftToRight;
1155 int rl = cdr.AttenuatorRightToLeft;
1156 int rr = cdr.AttenuatorRightToRight;
1157
1158 if (lr == 0 && rl == 0 && 0x78 <= ll && ll <= 0x88 && 0x78 <= rr && rr <= 0x88)
1159 return;
1160
1161 if (!stereo && ll == 0x40 && lr == 0x40 && rl == 0x40 && rr == 0x40)
1162 return;
1163
1164 if (stereo) {
1165 for (i = 0; i < samples; i++) {
1166 l = buf[i * 2];
1167 r = buf[i * 2 + 1];
1168 l = (l * ll + r * rl) >> 7;
1169 r = (r * rr + l * lr) >> 7;
1170 ssat32_to_16(l);
1171 ssat32_to_16(r);
1172 buf[i * 2] = l;
1173 buf[i * 2 + 1] = r;
1174 }
1175 }
1176 else {
1177 for (i = 0; i < samples; i++) {
1178 l = buf[i];
1179 l = l * (ll + rl) >> 7;
1180 //r = r * (rr + lr) >> 7;
1181 ssat32_to_16(l);
1182 //ssat32_to_16(r);
1183 buf[i] = l;
1184 }
1185 }
1186}
1187
1188static void cdrReadInterruptSetResult(unsigned char result)
1189{
1190 if (cdr.Stat) {
1191 CDR_LOG_I("cdrom: %d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1192 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1193 cdr.CmdInProgress, cdr.Stat);
1194 cdr.Irq1Pending = result;
1195 return;
1196 }
1197 SetResultSize(1);
1198 cdr.Result[0] = result;
1199 cdr.Stat = (result & STATUS_ERROR) ? DiskError : DataReady;
1200 setIrq(0x203);
1201}
1202
1203static void cdrUpdateTransferBuf(const u8 *buf)
1204{
1205 if (!buf)
1206 return;
1207 memcpy(cdr.Transfer, buf, DATA_SIZE);
1208 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1209 CDR_LOG("cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1210 if (cdr.FifoOffset < 2048 + 12)
1211 CDR_LOG("cdrom: FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1212}
1213
1214static void cdrReadInterrupt(void)
1215{
1216 u8 *buf = NULL, *hdr;
1217
1218 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1219
1220 ReadTrack(cdr.SetSectorPlay);
1221 if (cdr.NoErr)
1222 buf = CDR_getBuffer();
1223 if (buf == NULL)
1224 cdr.NoErr = 0;
1225
1226 if (!cdr.NoErr) {
1227 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1228 memset(cdr.Transfer, 0, DATA_SIZE);
1229 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1230 return;
1231 }
1232
1233 if (!cdr.Irq1Pending)
1234 cdrUpdateTransferBuf(buf);
1235
1236 if ((!cdr.Muted) && (cdr.Mode & MODE_STRSND) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA
1237 hdr = buf + 4;
1238 // Firemen 2: Multi-XA files - briefings, cutscenes
1239 if( cdr.FirstSector == 1 && (cdr.Mode & MODE_SF)==0 ) {
1240 cdr.File = hdr[0];
1241 cdr.Channel = hdr[1];
1242 }
1243
1244 /* Gameblabla
1245 * Skips playing on channel 255.
1246 * Fixes missing audio in Blue's Clues : Blue's Big Musical. (Should also fix Taxi 2)
1247 * TODO : Check if this is the proper behaviour.
1248 * */
1249 if ((hdr[2] & 0x4) && hdr[0] == cdr.File && hdr[1] == cdr.Channel && cdr.Channel != 255) {
1250 int ret = xa_decode_sector(&cdr.Xa, buf + 4, cdr.FirstSector);
1251 if (!ret) {
1252 cdrAttenuate(cdr.Xa.pcm, cdr.Xa.nsamples, cdr.Xa.stereo);
1253 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, cdr.FirstSector);
1254 cdr.FirstSector = 0;
1255 }
1256 else cdr.FirstSector = -1;
1257 }
1258 }
1259
1260 /*
1261 Croc 2: $40 - only FORM1 (*)
1262 Judge Dredd: $C8 - only FORM1 (*)
1263 Sim Theme Park - no adpcm at all (zero)
1264 */
1265
1266 if (!(cdr.Mode & MODE_STRSND) || !(buf[4+2] & 0x4))
1267 cdrReadInterruptSetResult(cdr.StatP);
1268
1269 cdr.SetSectorPlay[2]++;
1270 if (cdr.SetSectorPlay[2] == 75) {
1271 cdr.SetSectorPlay[2] = 0;
1272 cdr.SetSectorPlay[1]++;
1273 if (cdr.SetSectorPlay[1] == 60) {
1274 cdr.SetSectorPlay[1] = 0;
1275 cdr.SetSectorPlay[0]++;
1276 }
1277 }
1278
1279 if (!cdr.Irq1Pending) {
1280 // update for CdlGetlocP
1281 ReadTrack(cdr.SetSectorPlay);
1282 }
1283
1284 CDRPLAYSEEKREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1285}
1286
1287/*
1288cdrRead0:
1289 bit 0,1 - mode
1290 bit 2 - unknown
1291 bit 3 - unknown
1292 bit 4 - unknown
1293 bit 5 - 1 result ready
1294 bit 6 - 1 dma ready
1295 bit 7 - 1 command being processed
1296*/
1297
1298unsigned char cdrRead0(void) {
1299 if (cdr.ResultReady)
1300 cdr.Ctrl |= 0x20;
1301 else
1302 cdr.Ctrl &= ~0x20;
1303
1304 cdr.Ctrl |= 0x40; // data fifo not empty
1305
1306 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1307 cdr.Ctrl |= 0x18;
1308
1309 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1310
1311 return psxHu8(0x1800) = cdr.Ctrl;
1312}
1313
1314void cdrWrite0(unsigned char rt) {
1315 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1316
1317 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1318}
1319
1320unsigned char cdrRead1(void) {
1321 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1322 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1323 else
1324 psxHu8(0x1801) = 0;
1325 cdr.ResultP++;
1326 if (cdr.ResultP == cdr.ResultC)
1327 cdr.ResultReady = 0;
1328
1329 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1330
1331 return psxHu8(0x1801);
1332}
1333
1334void cdrWrite1(unsigned char rt) {
1335 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1336 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1337
1338 switch (cdr.Ctrl & 3) {
1339 case 0:
1340 break;
1341 case 3:
1342 cdr.AttenuatorRightToRightT = rt;
1343 return;
1344 default:
1345 return;
1346 }
1347
1348#ifdef CDR_LOG_CMD_IRQ
1349 SysPrintf("%u cdrom: CD1 write: %x (%s)", psxRegs.cycle, rt, CmdName[rt]);
1350 if (cdr.ParamC) {
1351 int i;
1352 SysPrintf(" Param[%d] = {", cdr.ParamC);
1353 for (i = 0; i < cdr.ParamC; i++)
1354 SysPrintf(" %x,", cdr.Param[i]);
1355 SysPrintf("}\n");
1356 } else {
1357 SysPrintf("\n");
1358 }
1359#endif
1360
1361 cdr.ResultReady = 0;
1362 cdr.Ctrl |= 0x80;
1363
1364 if (!cdr.CmdInProgress) {
1365 cdr.CmdInProgress = rt;
1366 // should be something like 12k + controller delays
1367 CDR_INT(5000);
1368 }
1369 else {
1370 CDR_LOG_I("%u cdrom: cmd while busy: %02x, prev %02x, busy %02x\n",
1371 psxRegs.cycle, rt, cdr.Cmd, cdr.CmdInProgress);
1372 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1373 cdr.CmdInProgress = rt;
1374 }
1375
1376 cdr.Cmd = rt;
1377}
1378
1379unsigned char cdrRead2(void) {
1380 unsigned char ret = 0;
1381
1382 if (cdr.FifoOffset < cdr.FifoSize)
1383 ret = cdr.Transfer[cdr.FifoOffset++];
1384 else
1385 CDR_LOG_I("cdrom: read empty fifo (%d)\n", cdr.FifoSize);
1386
1387 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1388 return ret;
1389}
1390
1391void cdrWrite2(unsigned char rt) {
1392 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1393 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1394
1395 switch (cdr.Ctrl & 3) {
1396 case 0:
1397 if (cdr.ParamC < 8) // FIXME: size and wrapping
1398 cdr.Param[cdr.ParamC++] = rt;
1399 return;
1400 case 1:
1401 cdr.Reg2 = rt;
1402 setIrq(0x204);
1403 return;
1404 case 2:
1405 cdr.AttenuatorLeftToLeftT = rt;
1406 return;
1407 case 3:
1408 cdr.AttenuatorRightToLeftT = rt;
1409 return;
1410 }
1411}
1412
1413unsigned char cdrRead3(void) {
1414 if (cdr.Ctrl & 0x1)
1415 psxHu8(0x1803) = cdr.Stat | 0xE0;
1416 else
1417 psxHu8(0x1803) = cdr.Reg2 | 0xE0;
1418
1419 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1420 return psxHu8(0x1803);
1421}
1422
1423void cdrWrite3(unsigned char rt) {
1424 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1425 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1426
1427 switch (cdr.Ctrl & 3) {
1428 case 0:
1429 break; // transfer
1430 case 1:
1431 if (cdr.Stat & rt) {
1432#ifdef CDR_LOG_CMD_IRQ
1433 SysPrintf("%u cdrom: ack %02x (w %02x)\n",
1434 psxRegs.cycle, cdr.Stat & rt, rt);
1435#endif
1436 // note: Croc vs Discworld Noir
1437 if (!(psxRegs.interrupt & (1 << PSXINT_CDR)) &&
1438 (cdr.CmdInProgress || cdr.Irq1Pending))
1439 CDR_INT(850); // 711-993
1440 }
1441 cdr.Stat &= ~rt;
1442
1443 if (rt & 0x40)
1444 cdr.ParamC = 0;
1445 return;
1446 case 2:
1447 cdr.AttenuatorLeftToRightT = rt;
1448 return;
1449 case 3:
1450 if (rt & 0x20) {
1451 memcpy(&cdr.AttenuatorLeftToLeft, &cdr.AttenuatorLeftToLeftT, 4);
1452 CDR_LOG("CD-XA Volume: %02x %02x | %02x %02x\n",
1453 cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1454 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight);
1455 }
1456 return;
1457 }
1458
1459 // test: Viewpoint
1460 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1461 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1462 }
1463 else if (rt & 0x80) {
1464 switch (cdr.Mode & 0x30) {
1465 case MODE_SIZE_2328:
1466 case 0x00:
1467 cdr.FifoOffset = 12;
1468 cdr.FifoSize = 2048 + 12;
1469 break;
1470
1471 case MODE_SIZE_2340:
1472 default:
1473 cdr.FifoOffset = 0;
1474 cdr.FifoSize = 2340;
1475 break;
1476 }
1477 }
1478 else if (!(rt & 0xc0))
1479 cdr.FifoOffset = DATA_SIZE; // fifo empty
1480}
1481
1482void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1483 u32 cdsize;
1484 int size;
1485 u8 *ptr;
1486
1487 CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr);
1488
1489 switch (chcr & 0x71000000) {
1490 case 0x11000000:
1491 ptr = (u8 *)PSXM(madr);
1492 if (ptr == NULL) {
1493 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1494 break;
1495 }
1496
1497 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1498
1499 /*
1500 GS CDX: Enhancement CD crash
1501 - Setloc 0:0:0
1502 - CdlPlay
1503 - Spams DMA3 and gets buffer overrun
1504 */
1505 size = DATA_SIZE - cdr.FifoOffset;
1506 if (size > cdsize)
1507 size = cdsize;
1508 if (size > 0)
1509 {
1510 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1511 cdr.FifoOffset += size;
1512 psxCpu->Clear(madr, size / 4);
1513 }
1514 if (size < cdsize)
1515 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1516
1517 CDRDMA_INT((cdsize/4) * 24);
1518
1519 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1520 if (chcr & 0x100) {
1521 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1522 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1523 }
1524 else {
1525 // halted
1526 psxRegs.cycle += (cdsize/4) * 24 - 20;
1527 }
1528 return;
1529
1530 default:
1531 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1532 break;
1533 }
1534
1535 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1536 DMA_INTERRUPT(3);
1537}
1538
1539void cdrDmaInterrupt(void)
1540{
1541 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1542 {
1543 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1544 DMA_INTERRUPT(3);
1545 }
1546}
1547
1548static void getCdInfo(void)
1549{
1550 u8 tmp;
1551
1552 CDR_getTN(cdr.ResultTN);
1553 CDR_getTD(0, cdr.SetSectorEnd);
1554 tmp = cdr.SetSectorEnd[0];
1555 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1556 cdr.SetSectorEnd[2] = tmp;
1557}
1558
1559void cdrReset() {
1560 memset(&cdr, 0, sizeof(cdr));
1561 cdr.CurTrack = 1;
1562 cdr.File = 1;
1563 cdr.Channel = 1;
1564 cdr.Reg2 = 0x1f;
1565 cdr.Stat = NoIntr;
1566 cdr.DriveState = DRIVESTATE_STANDBY;
1567 cdr.StatP = STATUS_ROTATING;
1568 cdr.FifoOffset = DATA_SIZE; // fifo empty
1569
1570 // BIOS player - default values
1571 cdr.AttenuatorLeftToLeft = 0x80;
1572 cdr.AttenuatorLeftToRight = 0x00;
1573 cdr.AttenuatorRightToLeft = 0x00;
1574 cdr.AttenuatorRightToRight = 0x80;
1575
1576 getCdInfo();
1577}
1578
1579int cdrFreeze(void *f, int Mode) {
1580 u32 tmp;
1581 u8 tmpp[3];
1582
1583 if (Mode == 0 && !Config.Cdda)
1584 CDR_stop();
1585
1586 cdr.freeze_ver = 0x63647202;
1587 gzfreeze(&cdr, sizeof(cdr));
1588
1589 if (Mode == 1) {
1590 cdr.ParamP = cdr.ParamC;
1591 tmp = cdr.FifoOffset;
1592 }
1593
1594 gzfreeze(&tmp, sizeof(tmp));
1595
1596 if (Mode == 0) {
1597 getCdInfo();
1598
1599 cdr.FifoOffset = tmp;
1600 cdr.FifoSize = (cdr.Mode & 0x20) ? 2340 : 2048 + 12;
1601
1602 // read right sub data
1603 tmpp[0] = btoi(cdr.Prev[0]);
1604 tmpp[1] = btoi(cdr.Prev[1]);
1605 tmpp[2] = btoi(cdr.Prev[2]);
1606 cdr.Prev[0]++;
1607 ReadTrack(tmpp);
1608
1609 if (cdr.Play) {
1610 if (cdr.freeze_ver < 0x63647202)
1611 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1612
1613 Find_CurTrack(cdr.SetSectorPlay);
1614 if (!Config.Cdda)
1615 CDR_play(cdr.SetSectorPlay);
1616 if (psxRegs.interrupt & (1 << PSXINT_CDRPLAY_OLD))
1617 CDRPLAYSEEKREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime, 1);
1618 }
1619
1620 if ((cdr.freeze_ver & 0xffffff00) != 0x63647200) {
1621 // old versions did not latch Reg2, have to fixup..
1622 if (cdr.Reg2 == 0) {
1623 SysPrintf("cdrom: fixing up old savestate\n");
1624 cdr.Reg2 = 7;
1625 }
1626 // also did not save Attenuator..
1627 if ((cdr.AttenuatorLeftToLeft | cdr.AttenuatorLeftToRight
1628 | cdr.AttenuatorRightToLeft | cdr.AttenuatorRightToRight) == 0)
1629 {
1630 cdr.AttenuatorLeftToLeft = cdr.AttenuatorRightToRight = 0x80;
1631 }
1632 }
1633 }
1634
1635 return 0;
1636}
1637
1638void LidInterrupt(void) {
1639 getCdInfo();
1640 cdrLidSeekInterrupt();
1641}