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