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