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