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