/* CRU module for 9901 I/O controller. */ #include "v9t9_common.h" #include "v9t9.h" #include "roms.h" #include "cru.h" #include "9900.h" #include "keyboard.h" #include "emulate.h" #include "sound.h" #include "vdp.h" #include "timer.h" #define _L LOG_CRU | LOG_INFO hw9901state hw9901; AudioGate audiogate; /***************************************************/ void reset9901() { memset(crukeyboardmap, 0, sizeof(crukeyboardmap)); memset((void *)&hw9901, 0, sizeof(hw9901)); } /* handle9901() is called when something about the interrupt state changes (processor interrupt mask, pending interrupt mask, etc). If an interrupt is needed, this triggers the INTPIN_INTREQ pin on the 9900, and sets the ST_INTERRUPT flag in stateflag. */ static void handle9901(void) { // any of these interrupts enabled? [optimization] if (hw9901.currentints & hw9901.int9901) { // There are 16 levels, and intlevel is 16 bits. // When it goes from 0x8000 to 0, we will see there // are no interrupts that can be passed. hw9901.intlevel = M_INT_EXT; while (hw9901.intlevel && ((hw9901.currentints & hw9901.int9901) & hw9901.intlevel) == 0) hw9901.intlevel = (hw9901.intlevel << 1) & (M_INT_EXT | M_INT_VDP | M_INT_CLOCK); if (hw9901.intlevel) { logger(_L | L_1, "Triggering interrupt... %04X/%04X/%04X\n", hw9901.intlevel, hw9901.currentints, hw9901.int9901); intpins9900 |= INTPIN_INTREQ; stateflag |= ST_INTERRUPT; } } } /* Trigger an interrupt, via hardware. */ void trigger9901int(u16 mask) { if (hw9901.int9901 & mask) { hw9901.currentints |= mask; // see if it applies handle9901(); } } /* Reset an interrupt, via hardware. */ void reset9901int(u16 mask) { if (hw9901.currentints & mask) { hw9901.currentints &= ~mask; // take care of pending interrupts handle9901(); } } u16 read9901int(void) { return hw9901.intlevel; } /***************************************************/ static int clock9901tag; // if TM_HZ < 46875, we must pretend // to have skipped this many clicks // for every call to clock_9901_event(). static u32 clockdelta; static void clock_9901_event(int tag) { logger(_L | L_2, "**** clock_9901_event %d/%d ****\n", hw9901.timer, clockdelta); // '<' prevents timer interrupts // when clockinvl (thus clockdelta) is zero if (hw9901.timer < clockdelta) { trigger9901int(M_INT_CLOCK); // if resolution is too low, at least // keep the wavelength the same if (hw9901.clockinvl < clockdelta) hw9901.timer += clockdelta + hw9901.clockinvl; else hw9901.timer += hw9901.clockinvl; } hw9901.timer -= clockdelta; } /* * Called from emulate() to mark time. */ void handle9901tick(void) { if (!hw9901.clockmode && hw9901.clockinvl && hw9901.timer-- == 0) { logger(_L | L_2, "**** handle9901tick ****"); trigger9901int(M_INT_CLOCK); hw9901.timer = hw9901.clockinvl; } } void setclockmode9901(u8 onoff) { // SBO 0 turns on clock mode, SBZ 0 turns on I/O mode if (!onoff) { // set new speed hw9901.clockinvl = hw9901.latchedclockinvl; hw9901.timer = hw9901.clockinvl; // TM_ResetEvent(clock9901tag); if (hw9901.clockinvl) { logger(_L | 0, "*** Setting clock to %5.2f Hz\n\n", 3000000.0 / 64 / hw9901.clockinvl); // #warning main clock hertz // clockdelta = 3000000 / 64 / TM_HZ; // TM_SetEvent(clock9901tag, TM_HZ * 100 / 100, 0, // TM_FUNC|TM_REPEAT, clock_9901_event); } else { // clockdelta = 0; } hw9901.clockmode = 0; } else { // setting clock mode freezes read register // (not to mention allowing access to the read register!) // but the decrementer continues if (hw9901.clockinvl) hw9901.latchedtimer = hw9901.timer % hw9901.clockinvl; else hw9901.latchedtimer = 0; hw9901.clockmode = 1; } } /******************************************************/ static u32 crur9901_0(u32 addr, u32 data, u32 num) { return 1; } /* Read INT_EXT status or lowest bit of timer. */ static u32 crur9901_1(u32 addr, u32 data, u32 num) { if (hw9901.clockmode) return (hw9901.latchedtimer & 1); else if (hw9901.int9901 & M_INT_EXT) { logger(_L | L_1, "crur9901_1: currentints=%04X\n", hw9901.currentints); return !(hw9901.currentints & M_INT_EXT); } else return 0; } /* Read INT_VDP status or 2nd bit of timer. Don't return INT_VDP status if INT1 is still waiting. */ static u32 crur9901_2(u32 addr, u32 data, u32 num) { if (hw9901.clockmode) return (hw9901.latchedtimer >> 1) & 1; else if (hw9901.int9901 & M_INT_VDP) { logger(_L | L_1, "crur9901_2: currentints=%04X\n", hw9901.currentints); //return !(hw9901.currentints&M_INT_EXT) && !!(hw9901.currentints & M_INT_VDP); return !(hw9901.currentints & M_INT_VDP); } else return 0; } static u32 crur9901_LS(u32 addr, u32 data, u32 num) { return (hw9901.clockmode ? 0 : 1); } /* Note: the keyboard module is only allowed to set the crukeyboardmap[], not take over the I/O for it. */ static u32 crur9901_KS(u32 addr, u32 data, u32 num) { int mask; u32 bit = addr / 2; mask = 1 << (bit - 3); if (hw9901.clockmode) return (hw9901.latchedtimer >> (bit - 1)) & 0x1; else if (hw9901.int9901 & (1 << bit)) return !(hw9901.currentints & ~(~0 << bit)) && !!(hw9901.currentints & (1 << bit)); else { u32 alphamask = ((bit - 3) == 4) ? ((AlphaLock || !caps) ? 0 : 0x10) : 0; logger(_L | L_2, "crukeyboardcol=%X, mask=%X, addr=%2X\n", crukeyboardcol, mask, addr); return !(((crukeyboardmap[crukeyboardcol] & mask) | alphamask)); } } static u32 cruralpha(u32 addr, u32 data, u32 num) { return caps & 1; } static u32 crurCSIn(u32 addr, u32 data, u32 num) { return cassette_read(); } /**********************************************/ static u32 cruw9901_0(u32 addr, u32 data, u32 num) { setclockmode9901(data); return 0; } /* Change an interrupt enable, or change bit in clock interval */ static u32 cruw9901_S(u32 addr, u32 data, u32 num) { u32 bit = addr / 2; if (hw9901.clockmode) { hw9901.latchedclockinvl = (hw9901.latchedclockinvl & ~(1 << bit)) | (data << bit); logger(_L | L_2, "cruw9901_S: hw9901.latchedclockinvl=%04X\n", hw9901.latchedclockinvl); } else { u32 mask = (~((~0) << num)) << bit; /* if (addr == 0x1a) { cruwAudioGate(addr, data, num); return; } else if (addr == 0x1c) { cruwCS2Motor(addr, data, num); return; }*/ logger(_L | L_2, "Altering 9901 bit... addr=%04X, data=%04X,mask=%04X\n", addr, data, mask); // First, writing a 0 will disable the interrupt, // and writing a 1 will enable it, or acknowledge it. hw9901.int9901 = (hw9901.int9901 & ~mask) | (data << bit); logger(_L | L_2, "before reset: int9901 = %04X, currentints = %04X\n", hw9901.int9901, hw9901.currentints); // This will acknowledge the interrupt, and possibly // trigger a lower-level pending interrupt. reset9901int(data << bit); logger(_L | L_2, "after reset: int9901 = %04X, currentints = %04X\n", hw9901.int9901, hw9901.currentints); // if (bit == 3) // if (data) debugger_enable(); else debugger_disable(); /* int9901change(); handle9901(); */ } return 0; } static u32 cruwCS1Motor(u32 addr, u32 data, u32 num) { cassette_set_motor(0, data); return 0; } static u32 cruwCS2Motor(u32 addr, u32 data, u32 num) { cassette_set_motor(1, data); return 0; } /* Enable/disable audio gate */ static u32 cruwAudioGate(u32 addr, u32 data, u32 num) { audiogate.play = data; logger(_L | L_2, "audiogate.play=%d\n", data); return 0; } /* Send bit to cassette */ static u32 cruwCSOut(u32 addr, u32 data, u32 num) { /* purposefully avoid gathering more than 1/100 second of info at a time */ if (audiogate.latch != data || currenttime < audiogate.base_time) // "a lot of" time passed { audiogate.latch = data; audiogate.hertz = baseclockhz / 64 / BASE_EMULATOR_HZ; logger(_L | L_2, "currenttime=%d, base_time=%d\n", currenttime, audiogate.base_time); audiogate.length = currenttime - audiogate.base_time; if (currenttime < audiogate.base_time) { // flush buffer -- we will generate data too fast to play it! //SOUNDPLAY(vms_AG, NULL, 0, 0); audiogate.length += audiogate.hertz; } /* write to "cassette"... */ cassette_write(audiogate.length, baseclockhz / 64); /* also play */ SOUNDPLAY(vms_AGw, NULL, audiogate.length, baseclockhz / 64); audiogate.base_time = currenttime; } audiogate.last_time = currenttime; return 0; } /***********************************************/ /* Note: the keyboard module is only allowed to set the crukeyboardmap[], not take over the I/O for it. */ static u32 cruwkeyboard_0(u32 addr, u32 data, u32 num) { crukeyboardcol = (crukeyboardcol & 3) | (data << 2); return 0; } static u32 cruwkeyboard_1(u32 addr, u32 data, u32 num) { crukeyboardcol = (crukeyboardcol & 5) | (data << 1); return 0; } static u32 cruwkeyboard_2(u32 addr, u32 data, u32 num) { crukeyboardcol = (crukeyboardcol & 6) | (data); return 0; } static u32 cruwAlpha(u32 addr, u32 data, u32 num) { AlphaLock = data; return 0; } /*****************************************/ int setup_9901(void) { clock9901tag = TM_UniqueTag(); if (cruadddevice(CRU_WRITE, 0x0, 1, cruw9901_0) && cruadddevice(CRU_WRITE, 0x2, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0x4, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0x6, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0x8, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0xa, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0xc, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0xe, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0x10, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0x12, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0x14, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0x16, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0x18, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0x1a, 1, cruw9901_S) && cruadddevice(CRU_WRITE, 0x1c, 1, cruw9901_S) && // cruadddevice(CRU_WRITE,0x1a,1,cruwAudioGate) && // primary use as timer bit // cruadddevice(CRU_WRITE,0x1c,1,cruwCS2Motor) && // primary use as timer bit // cruadddevice(CRU_WRITE,0x1e,1,cruwCS1Motor) && cruadddevice(CRU_WRITE, 0x24, 1, cruwkeyboard_2) && cruadddevice(CRU_WRITE, 0x26, 1, cruwkeyboard_1) && cruadddevice(CRU_WRITE, 0x28, 1, cruwkeyboard_0) && cruadddevice(CRU_WRITE, 0x2A, 1, cruwAlpha) && cruadddevice(CRU_WRITE, 0x2c, 1, cruwCS1Motor) && cruadddevice(CRU_WRITE, 0x2e, 1, cruwCS2Motor) && cruadddevice(CRU_WRITE, 0x30, 1, cruwAudioGate) && cruadddevice(CRU_WRITE, 0x32, 1, cruwCSOut) && cruadddevice(CRU_READ, 0x0, 1, crur9901_0) && cruadddevice(CRU_READ, 0x2, 1, crur9901_1) && cruadddevice(CRU_READ, 0x4, 1, crur9901_2) && cruadddevice(CRU_READ, 0x6, 1, crur9901_KS) && cruadddevice(CRU_READ, 0x8, 1, crur9901_KS) && cruadddevice(CRU_READ, 0xa, 1, crur9901_KS) && cruadddevice(CRU_READ, 0xc, 1, crur9901_KS) && cruadddevice(CRU_READ, 0xe, 1, crur9901_KS) && cruadddevice(CRU_READ, 0x10, 1, crur9901_KS) && cruadddevice(CRU_READ, 0x12, 1, crur9901_KS) && cruadddevice(CRU_READ, 0x14, 1, crur9901_KS) && cruadddevice(CRU_READ, 0x18, 1, crur9901_LS) && // "Pull up 10K to +5V" // cruadddevice(CRU_READ,0x1a,1,crur9901_LS) && // output only // cruadddevice(CRU_READ,0x1c,1,crur9901_LS) && // output only // cruadddevice(CRU_READ,0x1e,1,crur9901_LS) && // output only cruadddevice(CRU_READ, 0x20, 1, crur9901_LS) && // not connected cruadddevice(CRU_READ, 0x22, 1, crur9901_LS) && // not connected cruadddevice(CRU_READ, 0x2a, 1, cruralpha) && cruadddevice(CRU_READ, 0x36, 1, crurCSIn)) { return 1; } else return 0; } DECL_SYMBOL_ACTION(hw9901_machine_state) { char *str; if (task == csa_READ) { char tmp[(sizeof(hw9901)+sizeof(audiogate))*2+1]; if (iter) return 0; emulate_bin2hex((u8 *)&hw9901, tmp, sizeof(hw9901)); command_arg_set_string(sym->args, tmp); emulate_bin2hex((u8 *)&audiogate, tmp, sizeof(audiogate)); command_arg_set_string(sym->args->next, tmp); return 1; } command_arg_get_string(sym->args, &str); emulate_hex2bin(str, (u8 *)&hw9901, sizeof(hw9901)); command_arg_get_string(sym->args->next, &str); emulate_hex2bin(str, (u8 *)&audiogate, sizeof(audiogate)); setclockmode9901(hw9901.clockmode); return 1; }