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