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