Annotation of hatari/src/psg.c, revision 1.1.1.8

1.1       root        1: /*
1.1.1.4   root        2:   Hatari - psg.c
1.1       root        3: 
1.1.1.4   root        4:   This file is distributed under the GNU Public License, version 2 or at
                      5:   your option any later version. Read the file gpl.txt for details.
1.1.1.3   root        6: 
1.1.1.4   root        7:   Programmable Sound Generator (YM-2149) - PSG
1.1.1.3   root        8: 
1.1.1.4   root        9:   Also used for the printer (centronics) port emulation (PSG Port B, Register 15)
1.1       root       10: */
1.1.1.7   root       11: 
                     12: 
                     13: /* 2007/04/14  [NP]    First approximation to get cycle accurate accesses to ff8800/02 */
                     14: /*                     by cumulating wait state of 1 cycle and rounding the final      */
                     15: /*                     result to 4.                                                    */
                     16: /* 2007/04/29  [NP]    Functions PSG_Void_WriteByte and PSG_Void_ReadByte to handle    */
                     17: /*                     accesses to $ff8801/03. These adresses have no effect, but they */
                     18: /*                     must give a 1 cycle wait state (e.g. move.l d0,ff8800).         */
                     19: /* 2007/09/29  [NP]    Replace printf by calls to HATARI_TRACE.                        */
                     20: /* 2007/10/23  [NP]    In PSG_Void_WriteByte, add a wait state only if no wait state   */
                     21: /*                     were added so far (hack, but gives good result).                */
                     22: /* 2007/11/18  [NP]    In PSG_DataRegister_WriteByte, set unused bit to 0, in case     */
                     23: /*                     the data reg is read later (fix Mindbomb Demo / BBC).           */
                     24: 
                     25: 
                     26: /* Emulating wait states when accessing $ff8800/01/02/03 with different 'move' variants        */
                     27: /* is a complex task. So far, adding 1 cycle wait state to each access and rounding the        */
                     28: /* final number to 4 gives some good results, but this is certainly not the way it's   */
                     29: /* working for real in the ST.                                                         */
                     30: /* The following examples show some verified wait states for different accesses :      */
                     31: /*     lea     $ffff8800,a1                                                            */
                     32: /*     lea     $ffff8802,a2                                                            */
                     33: /*     lea     $ffff8801,a3                                                            */
                     34: /*                                                                                     */
                     35: /*     movep.w d1,(a1)         ; 20 16+4       (ventura loader)                        */
                     36: /*     movep.l d1,(a1)         ; 28 24+4       (ventura loader, ulm loader)            */
                     37: /*                                                                                     */
                     38: /*     movep.l d6,0(a5)        ; 28 24+4       (SNY I, TCB)                            */
                     39: /*     movep.w d5,0(a5)        ; 20 16+4       (SNY I, TCB)                            */
                     40: /*                                                                                     */
                     41: /*     move.b d1,(a1)          ; 12 8+4                                                */
                     42: /*     move.b d1,(a2)          ; 12 8+4                                                */
                     43: /*     move.b d1,(a3)          ; 12 8+4        (crickey ulm hidden)                    */
                     44: /*                                                                                     */
                     45: /*     move.w d1,(a1)          ; 12 8+4                                                */
                     46: /*     move.w d1,(a2)          ; 12 8+4                                                */
                     47: /*     move.l d1,(a1)          ; 16 12+4       (ulm loader)                            */
                     48: /*                                                                                     */
                     49: /*     movem.l d1,(a1)         ; 20 16+4                                               */
                     50: /*     movem.l d1-d2,(a1)      ; 28 24+4                                               */
                     51: /*     movem.l d1-d3,(a1)      ; 40 32+4+4                                             */
                     52: /*     movem.l d1-d4,(a1)      ; 48 40+4+4                                             */
                     53: /*     movem.l d1-d5,(a1)      ; 60 48+4+4+4                                           */
                     54: /*     movem.l d1-d6,(a1)      ; 68 56+4+4+4                                           */
                     55: /*     movem.l d1-d7,(a1)      ; 80 64+4+4+4+4                                         */
                     56: /*     movem.l d0-d7,(a1)      ; 88 72+4+4+4+4                                         */
                     57: /*                                                                                     */
                     58: /* This gives the following "model" :                                                  */
                     59: /*     - each access to $ff8800 or $ff8802 add 1 cycle wait state                      */
                     60: /*     - access to $ff8801 or $ff8803 give 0 wait state, except if this is the only    */
                     61: /*       register accessed (move.b for example).                                       */
                     62: 
                     63: 
                     64: 
1.1.1.8 ! root       65: const char PSG_rcsid[] = "Hatari $Id: psg.c,v 1.19 2008/03/26 22:15:27 thothy Exp $";
1.1       root       66: 
                     67: #include "main.h"
1.1.1.3   root       68: #include "configuration.h"
1.1.1.4   root       69: #include "ioMem.h"
1.1.1.5   root       70: #include "joy.h"
1.1.1.7   root       71: #include "log.h"
1.1.1.4   root       72: #include "m68000.h"
1.1       root       73: #include "memorySnapShot.h"
                     74: #include "sound.h"
1.1.1.4   root       75: #include "printer.h"            /* because Printer I/O goes through PSG Register 15 */
1.1.1.3   root       76: #include "psg.h"
1.1.1.7   root       77: #if ENABLE_DSP_EMU
                     78: #include "falcon/dsp.h"
                     79: #endif
                     80: #include "video.h"
                     81: #include "trace.h"
1.1       root       82: 
1.1.1.4   root       83: Uint8 PSGRegisterSelect;        /* 0xff8800 (read/write) */
                     84: Uint8 PSGRegisters[16];         /* Register in PSG, see PSG_REG_xxxx */
                     85: 
                     86: static BOOL bLastWriteToIOB;    /* boolean flag: did the last write to the PSG go to IOB? */
1.1       root       87: 
                     88: 
                     89: /*-----------------------------------------------------------------------*/
1.1.1.7   root       90: /**
                     91:  * Reset variables used in PSG
                     92:  */
1.1       root       93: void PSG_Reset(void)
                     94: {
1.1.1.4   root       95:        PSGRegisterSelect = 0;
                     96:        memset(PSGRegisters, 0, sizeof(PSGRegisters));
                     97:        bLastWriteToIOB = FALSE;
1.1       root       98: }
                     99: 
1.1.1.2   root      100: 
                    101: /*-----------------------------------------------------------------------*/
1.1.1.7   root      102: /**
                    103:  * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type)
                    104:  */
1.1       root      105: void PSG_MemorySnapShot_Capture(BOOL bSave)
                    106: {
1.1.1.4   root      107:        /* Save/Restore details */
                    108:        MemorySnapShot_Store(&PSGRegisterSelect, sizeof(PSGRegisterSelect));
                    109:        MemorySnapShot_Store(PSGRegisters, sizeof(PSGRegisters));
                    110:        MemorySnapShot_Store(&bLastWriteToIOB, sizeof(bLastWriteToIOB));
1.1       root      111: }
                    112: 
1.1.1.2   root      113: 
                    114: /*-----------------------------------------------------------------------*/
1.1.1.7   root      115: /**
1.1.1.8 ! root      116:  * Write byte to 0xff8800, this is used as a selector for when we read/write
1.1.1.7   root      117:  * to address 0xff8802
                    118:  */
1.1.1.4   root      119: void PSG_SelectRegister_WriteByte(void)
1.1       root      120: {
1.1.1.7   root      121: //     M68000_WaitState(4);
                    122:        M68000_WaitState(1);                            /* [NP] FIXME not 100% accurate, but gives good results */
1.1.1.4   root      123: 
1.1.1.8 ! root      124:        /* Store register to select (value in bits 0-3). Use IoAccessCurrentAddress
        !           125:         * to be able to handle the PSG mirror registers, too. */
        !           126:        PSGRegisterSelect = IoMem[IoAccessCurrentAddress] & 0x0f;
1.1.1.7   root      127: 
                    128:        if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_REG ) )
                    129:          {
                    130:            int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
                    131:            int nLineCycles = nFrameCycles % nCyclesPerLine;
                    132:            HATARI_TRACE_PRINT ( "write ym sel reg=%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
                    133:                PSGRegisterSelect, nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
                    134:          }
1.1       root      135: }
                    136: 
1.1.1.2   root      137: 
                    138: /*-----------------------------------------------------------------------*/
1.1.1.7   root      139: /**
                    140:  * Read byte from 0xff8800, returns PSG data
                    141:  */
1.1.1.4   root      142: void PSG_SelectRegister_ReadByte(void)
1.1       root      143: {
1.1.1.6   root      144:        M68000_WaitState(4);
                    145: 
1.1.1.5   root      146:        if (PSGRegisterSelect == 14)
                    147:        {
                    148:                /* Second parallel port joystick uses centronics strobe bit as fire button: */
                    149:                if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED)
                    150:                {
                    151:                        if (Joy_GetStickData(JOYID_PARPORT2) & 0x80)
                    152:                                PSGRegisters[14] &= ~32;
                    153:                        else
                    154:                                PSGRegisters[14] |= 32;
                    155:                }
                    156:        }
                    157:        else if (PSGRegisterSelect == 15)
                    158:        {
                    159:                /* PSG register 15 is parallel port data register - used by parallel port joysticks: */
                    160:                if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT1].nJoystickMode != JOYSTICK_DISABLED)
                    161:                {
                    162:                        PSGRegisters[15] &= 0x0f;
                    163:                        PSGRegisters[15] |= ~Joy_GetStickData(JOYID_PARPORT1) << 4;
                    164:                }
                    165:                if (ConfigureParams.Joysticks.Joy[JOYID_PARPORT2].nJoystickMode != JOYSTICK_DISABLED)
                    166:                {
                    167:                        PSGRegisters[15] &= 0xf0;
                    168:                        PSGRegisters[15] |= ~Joy_GetStickData(JOYID_PARPORT2) & 0x0f;
                    169:                }
                    170:        }
                    171: 
1.1.1.4   root      172:        /* Read data last selected by register */
1.1.1.8 ! root      173:        IoMem[IoAccessCurrentAddress] = PSGRegisters[PSGRegisterSelect];
1.1       root      174: }
                    175: 
1.1.1.2   root      176: 
                    177: /*-----------------------------------------------------------------------*/
1.1.1.7   root      178: /**
                    179:  * Write byte to 0xff8802, stores according to PSG select register (write 0xff8800)
                    180:  */
1.1.1.4   root      181: void PSG_DataRegister_WriteByte(void)
                    182: {
1.1.1.7   root      183: //     M68000_WaitState(4);
                    184:        M68000_WaitState(1);                            /* [NP] FIXME not 100% accurate, but gives good results */
1.1.1.6   root      185: 
1.1.1.8 ! root      186:        /* Create samples up until this point with current values */
        !           187:        Sound_Update();
        !           188: 
        !           189:        /* Copy value to PSGRegisters[]. Use IoAccessCurrentAddress to be able
        !           190:         * to handle the PSG mirror registers, too. */
        !           191:        PSGRegisters[PSGRegisterSelect] = IoMem[IoAccessCurrentAddress];
1.1.1.4   root      192: 
1.1.1.7   root      193:        /* [NP] Clear unused bits for some regs */
                    194:        if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_COARSE ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_COARSE )
                    195:                || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_COARSE ) || ( PSGRegisterSelect == PSG_REG_ENV_SHAPE ) )
                    196:          PSGRegisters[PSGRegisterSelect] &= 0x0f;      /* only keep bits 0 - 3 */
                    197: 
                    198:        else if ( ( PSGRegisterSelect == PSG_REG_CHANNEL_A_AMP ) || ( PSGRegisterSelect == PSG_REG_CHANNEL_B_AMP )
                    199:                || ( PSGRegisterSelect == PSG_REG_CHANNEL_C_AMP ) )
                    200:          PSGRegisters[PSGRegisterSelect] &= 0x1f;      /* only keep bits 0 - 4 */
                    201: 
                    202: 
                    203:        if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_DATA ) )
                    204:          {
                    205:            int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
                    206:            int nLineCycles = nFrameCycles % nCyclesPerLine;
                    207:            HATARI_TRACE_PRINT ( "write ym data reg=%x val=%x video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
1.1.1.8 ! root      208:                PSGRegisterSelect, PSGRegisters[PSGRegisterSelect], nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
1.1.1.7   root      209:          }
                    210: 
                    211: 
1.1.1.4   root      212:        switch (PSGRegisterSelect)
                    213:        {
                    214: 
                    215:         /* Check registers 8,9 and 10 which are 'amplitude' for each channel and
                    216:          * store if wrote to (to check for sample playback) */
                    217:         case PSG_REG_CHANNEL_A_AMP:
                    218:                bWriteChannelAAmp = TRUE;
                    219:                break;
                    220:         case PSG_REG_CHANNEL_B_AMP:
                    221:                bWriteChannelBAmp = TRUE;
                    222:                break;
                    223:         case PSG_REG_CHANNEL_C_AMP:
                    224:                bWriteChannelCAmp = TRUE;
                    225:                break;
                    226: 
                    227:         case PSG_REG_ENV_SHAPE:            /* Whenever 'write' to register 13, cause envelope to reset */
                    228:                bEnvelopeFreqFlag = TRUE;
                    229:                bWriteEnvelopeFreq = TRUE;
                    230:                break;
                    231: 
                    232:         /*
                    233:          * FIXME: This is only a prelimary dirty hack!
                    234:          * Port B (Printer port) - writing here needs to be dispatched to the printer
                    235:          * STROBE (Port A bit5) does a short LOW and back to HIGH when the char is valid
                    236:          * To print you need to write the character byte to IOB and you need to toggle STROBE
                    237:          * (like EmuTOS does)....therefor we print when STROBE gets low and last write access to
                    238:          * the PSG was to IOB
                    239:          */
                    240:         case PSG_REG_IO_PORTA:
                    241:                /* Printer dispatching only when printing is activated */
                    242:                if (ConfigureParams.Printer.bEnablePrinting)
                    243:                {
1.1.1.7   root      244:                        /* Bit 5 - Centronics strobe? If STROBE is low and the last write did go to IOB then
                    245:                         * there is data in PORTB to print/transfer to the emulated Centronics port
                    246:                         */
                    247:                        if ((((PSGRegisters[PSG_REG_IO_PORTA]&(1<<5))==0) && bLastWriteToIOB))
1.1.1.4   root      248:                        {
                    249:                                /* Seems like we want to print something... */
                    250:                                Printer_TransferByteTo((unsigned char) PSGRegisters[PSG_REG_IO_PORTB]);
                    251:                        }
                    252:                }
1.1.1.7   root      253:                /* Bit 3 - Centronics as input */
                    254:                if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<3))
                    255:                {
                    256:                        /* FIXME: might be needed if we want to emulate sound sampling hardware */
                    257:                }
                    258:                
                    259:                /* handle Falcon specific bits in PORTA of the PSG */
                    260:                if (ConfigureParams.System.nMachineType == MACHINE_FALCON)
                    261:                {
                    262:                        /* Bit 4 - DSP reset? */
                    263:                        if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<4))
                    264:                        {
                    265:                                Log_Printf(LOG_DEBUG, "Calling DSP_Reset?\n");
                    266: #if ENABLE_DSP_EMU
                    267:                                if (ConfigureParams.System.nDSPType == DSP_TYPE_EMU) {
                    268:                                        DSP_Reset();
                    269:                                }
                    270: #endif
                    271:                        }
                    272:                        /* Bit 6 - Internal Speaker control */
                    273:                        if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<6))
                    274:                        {
                    275:                                /*Log_Printf(LOG_DEBUG, "Falcon: Internal Speaker state\n");*/
                    276:                                /* FIXME: add code to handle? (if we want to emulate the speaker at all? */
                    277:                        }
                    278:                        /* Bit 7 - Reset IDE? */
                    279:                        if(PSGRegisters[PSG_REG_IO_PORTA]&(1<<7))
                    280:                        {
                    281:                                Log_Printf(LOG_DEBUG, "Falcon: Reset IDE subsystem\n");
                    282:                                /* FIXME: add code to handle IDE reset */
                    283:                        }
                    284:                }
1.1.1.4   root      285:                break;
                    286:        }
                    287: 
                    288:        /* Remember if we wrote to IO Port B */
                    289:        bLastWriteToIOB = (PSGRegisterSelect == PSG_REG_IO_PORTB);
1.1       root      290: }
                    291: 
1.1.1.2   root      292: 
                    293: /*-----------------------------------------------------------------------*/
1.1.1.7   root      294: /**
                    295:  * Read byte from 0xff8802, returns 0xff
                    296:  */
1.1.1.4   root      297: void PSG_DataRegister_ReadByte(void)
1.1       root      298: {
1.1.1.6   root      299:        M68000_WaitState(4);
1.1.1.4   root      300: 
1.1.1.8 ! root      301:        IoMem[IoAccessCurrentAddress] = 0xff;
1.1       root      302: }
1.1.1.7   root      303: 
                    304: 
                    305: 
                    306: /*-----------------------------------------------------------------------*/
                    307: /**
                    308:  * Write byte to 0xff8801/03. Do nothing, but add some wait states if needed.
                    309:  */
                    310: void PSG_Void_WriteByte(void)
                    311: {
                    312:        /* [NP] FIXME If no wait states were added so far, it's possible we're accessing */
                    313:        /* 8801/8803 through a .B instruction, so we need to add a wait state */
                    314:        /* Else, the wait states will be added when writing to 8800/8802 */
                    315:        /* This works so far, but this model is certainly not 100% accurate */
                    316:        if ( nWaitStateCycles == 0 )
                    317:          M68000_WaitState(1);
                    318: 
                    319:        if ( HATARI_TRACE_LEVEL ( HATARI_TRACE_PSG_WRITE_DATA ) )
                    320:          {
                    321:            int nFrameCycles = Cycles_GetCounter(CYCLES_COUNTER_VIDEO);;
                    322:            int nLineCycles = nFrameCycles % nCyclesPerLine;
                    323:            HATARI_TRACE_PRINT ( "write ym 8801/03 video_cyc=%d %d@%d pc=%x instr_cycle %d\n" ,
                    324:                nFrameCycles, nLineCycles, nHBL, M68000_GetPC(), CurrentInstrCycles );
                    325:          }
                    326: }
                    327: 
                    328: 
                    329: 
                    330: /*-----------------------------------------------------------------------*/
                    331: /**
                    332:  * Read byte from 0xff8801/03. Do nothing, but add some wait states.
                    333:  */
                    334: void PSG_Void_ReadByte(void)
                    335: {
                    336:        M68000_WaitState(1);
                    337: }
                    338: 

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.