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