diff options
Diffstat (limited to 'src/mame/machine/coco.c')
-rw-r--r-- | src/mame/machine/coco.c | 1387 |
1 files changed, 1387 insertions, 0 deletions
diff --git a/src/mame/machine/coco.c b/src/mame/machine/coco.c new file mode 100644 index 00000000000..f1177b8d9e8 --- /dev/null +++ b/src/mame/machine/coco.c @@ -0,0 +1,1387 @@ +// license:BSD-3-Clause +// copyright-holders:Nathan Woods +/*************************************************************************** + + coco.c + + TRS-80 Radio Shack Color Computer Family + + Functions to emulate general aspects of the machine (RAM, ROM, interrupts, + I/O ports) + + References: + There are two main references for the info for this driver + - Tandy Color Computer Unravelled Series + (http://www.giftmarket.org/unravelled/unravelled.shtml) + - Assembly Language Programming For the CoCo 3 by Laurence A. Tepolt + - Kevin K. Darlings GIME reference + (http://www.cris.com/~Alxevans/gime.txt) + - Sock Masters's GIME register reference + (http://www.axess.com/twilight/sock/gime.html) + - Robert Gault's FAQ + (http://home.att.net/~robert.gault/Coco/FAQ/FAQ_main.htm) + - Discussions with L. Curtis Boyle (LCB) and John Kowalski (JK) + + TODO: + - Implement unimplemented SAM registers + - Choose and implement more appropriate ratios for the speed up poke + - Handle resets correctly + + In the CoCo, all timings should be exactly relative to each other. This + table shows how all clocks are relative to each other (info: JK): + - Main CPU Clock 0.89 MHz + - Horizontal Sync Interrupt 15.7 kHz/63.5us (57 clock cycles) + - Vertical Sync Interrupt 60 Hz (14934 clock cycles) + - Composite Video Color Carrier 3.58 MHz/279ns (1/4 clock cycles) + + It is also noting that the CoCo 3 had two sets of VSync interrupts. To quote + John Kowalski: + + One other thing to mention is that the old vertical interrupt and the new + vertical interrupt are not the same.. The old one is triggered by the + video's vertical sync pulse, but the new one is triggered on the next scan + line *after* the last scan line of the active video display. That is : new + vertical interrupt triggers somewheres around scan line 230 of the 262 line + screen (if a 200 line graphics mode is used, a bit earlier if a 192 line + mode is used and a bit later if a 225 line mode is used). The old vsync + interrupt triggers on scanline zero. + + 230 is just an estimate [(262-200)/2+200]. I don't think the active part + of the screen is exactly centered within the 262 line total. I can + research that for you if you want an exact number for scanlines before the + screen starts and the scanline that the v-interrupt triggers..etc. + +Added bi-directional bitbanger support. Also fixed reading PIA 1, port A. The +DAC and bitbanger values written should be reflected in the read. + tim lindner, October 2010 + +***************************************************************************/ + +#include "includes/coco.h" +#include "cpu/m6809/m6809.h" +#include "debug/debugcpu.h" + + + +//************************************************************************** +// CONSTANTS +//************************************************************************** + +#define LOG_INTERRUPTS 0 + + + +//************************************************************************** +// BODY +//************************************************************************** + +//------------------------------------------------- +// ctor +//------------------------------------------------- + +coco_state::coco_state(const machine_config &mconfig, device_type type, const char *tag) + : driver_device(mconfig, type, tag), + m_maincpu(*this, MAINCPU_TAG), + m_pia_0(*this, PIA0_TAG), + m_pia_1(*this, PIA1_TAG), + m_dac(*this, DAC_TAG), + m_wave(*this, WAVE_TAG), + m_cococart(*this, CARTRIDGE_TAG), + m_ram(*this, RAM_TAG), + m_cassette(*this, "cassette"), + m_rs232(*this, RS232_TAG), + m_vhd_0(*this, VHD0_TAG), + m_vhd_1(*this, VHD1_TAG), + m_beckerport(*this, DWSOCK_TAG), + m_beckerportconfig(*this, BECKERPORT_TAG) +{ +} + + +//------------------------------------------------- +// analog_port_start +//------------------------------------------------- + +void coco_state::analog_port_start(analog_input_t *analog, const char *rx_tag, const char *ry_tag, const char *lx_tag, const char *ly_tag, const char *buttons_tag) +{ + analog->m_input[0][0] = ioport(rx_tag); + analog->m_input[0][1] = ioport(ry_tag); + analog->m_input[1][0] = ioport(lx_tag); + analog->m_input[1][1] = ioport(ly_tag); + analog->m_buttons = ioport(buttons_tag); +} + + + +//------------------------------------------------- +// device_start +//------------------------------------------------- + +void coco_state::device_start() +{ + /* call base device_start */ + driver_device::device_start(); + + /* look up keyboard ports */ + for (int i = 0; i < ARRAY_LENGTH(m_keyboard); i++) + { + char name[32]; + snprintf(name, ARRAY_LENGTH(name), "row%d", i); + m_keyboard[i] = ioport(name); + } + + /* look up analog ports */ + analog_port_start(&m_joystick, JOYSTICK_RX_TAG, JOYSTICK_RY_TAG, + JOYSTICK_LX_TAG, JOYSTICK_LY_TAG, JOYSTICK_BUTTONS_TAG); + analog_port_start(&m_rat_mouse, RAT_MOUSE_RX_TAG, RAT_MOUSE_RY_TAG, + RAT_MOUSE_LX_TAG, RAT_MOUSE_LY_TAG, RAT_MOUSE_BUTTONS_TAG); + analog_port_start(&m_diecom_lightgun, DIECOM_LIGHTGUN_RX_TAG, DIECOM_LIGHTGUN_RY_TAG, + DIECOM_LIGHTGUN_LX_TAG, DIECOM_LIGHTGUN_LY_TAG, DIECOM_LIGHTGUN_BUTTONS_TAG); + + /* look up miscellaneous controls */ + m_joystick_type_control = ioport(CTRL_SEL_TAG); + m_joystick_hires_control = ioport(HIRES_INTF_TAG); + + /* timers */ + m_hiresjoy_transition_timer[0] = timer_alloc(TIMER_HIRES_JOYSTICK_X); + m_hiresjoy_transition_timer[1] = timer_alloc(TIMER_HIRES_JOYSTICK_Y); + m_diecom_lightgun_timer = timer_alloc(TIMER_DIECOM_LIGHTGUN); + + /* cart base update */ + m_cococart->set_cart_base_update(cococart_base_update_delegate(FUNC(coco_state::update_cart_base), this)); + + /* save state support */ + save_item(NAME(m_dac_output)); + save_item(NAME(m_hiresjoy_ca)); + save_item(NAME(m_dclg_previous_bit)); + save_item(NAME(m_dclg_output_h)); + save_item(NAME(m_dclg_output_v)); + save_item(NAME(m_dclg_state)); + save_item(NAME(m_dclg_timer)); + save_item(NAME(m_vhd_select)); + + /* set up disassembly override */ + if (m_maincpu->debug()) + { + m_maincpu->debug()->set_dasm_override(dasm_override); + } + + // miscellaneous + m_in_floating_bus_read = false; +} + + + +//------------------------------------------------- +// device_reset +//------------------------------------------------- + +void coco_state::device_reset() +{ + /* call base device_start */ + driver_device::device_reset(); + + /* reset state */ + m_dac_output = 0; + m_analog_audio_level = 0; + m_hiresjoy_ca = false; + m_dclg_previous_bit = false; + m_dclg_output_h = 0; + m_dclg_output_v = 0; + m_dclg_state = 0; + m_dclg_timer = 0; + m_vhd_select = 0; +} + + + +//------------------------------------------------- +// device_timer +//------------------------------------------------- + +void coco_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch(id) + { + case TIMER_HIRES_JOYSTICK_X: + case TIMER_HIRES_JOYSTICK_Y: + case TIMER_DIECOM_LIGHTGUN: + poll_keyboard(); + break; + } +} + + + +//------------------------------------------------- +// floating_bus_read +//------------------------------------------------- +// +// From Darren A on the CoCo list: +// +// Whenever you read from an un-mapped hardware address or from an +// address where some of the bits are undefined (like the GIME's palette +// and MMU registers), the value obtained for those undefined bits is +// predictable on the CoCo. +// +// There are two possibilites which depend on the addressing mode you use +// to read from such an address. If you use the "no-offset" indexed mode +// (as in LDA ,X) the undefined bits will come from the first byte of the +// next instruction. For any other addressing mode, the undefined bits +// will come from the byte at $FFFF (LSB of the Reset vector). +// +// When you use BASIC's PEEK command to read from an un-mapped address +// (such as $FF70), you get a value of 126. This is because PEEK reads +// the address with a LDA ,X instruction, so the value returned is the +// opcode of the next instruction (JMP Extended = $7E = 126). If you +// patch the PEEK command to use a 5-bit offset (LDA 0,X), the value +// returned for an un-mapped address will instead come from $FFFF (27 on +// a CoCo 3 or 39 on a CoCo 1/2). +// +// The reason for this behavior is that the 6809 normally does a VMA +// cycle just before reading the instruction's effective address. The +// exception is the "no-offset" indexed mode in which case the next +// instruction byte is read just prior to the effective address. During +// a VMA cycle the address bus goes to Hi Impedance and the R/W line is +// HI. This has the effect of loading the value from $FFFF onto the data +// bus. This stale data from the previous cycle supplies the value for +// the undefined bits. +// +// Here is a small routine which will demonstrate this behavior: +// +// ldx #$FF70 +// lda ,x +// ldb $FF70 +// std $400 +// rts +// +// On a CoCo 3, you should end up with the value $F61B at $400-401. On a +// CoCo 1/2 you should get $F627 instead. +//------------------------------------------------- + +UINT8 coco_state::floating_bus_read(void) +{ + UINT8 result; + + // this method calls program.read_byte() - therefore we run the risk of a stack overflow if we don't check for + // a reentrant invocation + if (m_in_floating_bus_read) + { + // not sure what should really happen in this extremely degenerate scenario (the PC is probably + // in $FFxx never-never land), but I guess 0xFF is as good as anything. + result = 0xFF; + } + else + { + // prevent stack overflows + m_in_floating_bus_read = true; + + // set up the ability to read address spaces + address_space &program = m_maincpu->space(AS_PROGRAM); + + // get the previous and current PC + UINT16 prev_pc = m_maincpu->pcbase(); + UINT16 pc = m_maincpu->pc(); + + // get the byte; and skip over header bytes + UINT8 byte = program.read_byte(prev_pc); + if ((byte == 0x10) || (byte == 0x11)) + byte = program.read_byte(++prev_pc); + + // check to see if the opcode specifies the indexed addressing mode, and the secondary byte + // specifies no-offset + bool is_nooffset_indexed = (((byte & 0xF0) == 0x60) || ((byte & 0xF0) == 0xA0) || ((byte & 0xF0) == 0xE0)) + && ((program.read_byte(prev_pc + 1) & 0xBF) == 0x84); + + // finally read the byte + result = program.read_byte(is_nooffset_indexed ? pc : 0xFFFF); + + // we're done reading + m_in_floating_bus_read = false; + } + return result; +} + + + +/*************************************************************************** + PIA0 ($FF00-$FF1F) (Chip U8) + + PIA0 PA0-PA7 - Keyboard/Joystick read + PIA0 PB0-PB7 - Keyboard write + PIA0 CA1 - MC6847 HS (Horizontal Sync) + PIA0 CA2 - SEL1 (Used by sound mux and joystick) + PIA0 CB1 - MC6847 FS (Field Sync) + PIA0 CB2 - SEL2 (Used by sound mux and joystick) +***************************************************************************/ + +//------------------------------------------------- +// ff00_write +//------------------------------------------------- + +READ8_MEMBER( coco_state::ff00_read ) +{ + return m_pia_0->read(space, offset, mem_mask); +} + + + +//------------------------------------------------- +// ff00_write +//------------------------------------------------- + +WRITE8_MEMBER( coco_state::ff00_write ) +{ + m_pia_0->write(space, offset, data, mem_mask); +} + + + +//------------------------------------------------- +// pia0_pa_w +//------------------------------------------------- + +WRITE8_MEMBER( coco_state::pia0_pa_w ) +{ + poll_keyboard(); +} + + + +//------------------------------------------------- +// pia0_pb_w +//------------------------------------------------- + +WRITE8_MEMBER( coco_state::pia0_pb_w ) +{ + poll_keyboard(); +} + + + +//------------------------------------------------- +// pia0_ca2_w +//------------------------------------------------- + +WRITE_LINE_MEMBER( coco_state::pia0_ca2_w ) +{ + update_sound(); // analog mux SEL1 is tied to PIA0 CA2 + poll_keyboard(); +} + + + +//------------------------------------------------- +// pia0_cb2_w +//------------------------------------------------- + +WRITE_LINE_MEMBER( coco_state::pia0_cb2_w ) +{ + update_sound(); // analog mux SEL2 is tied to PIA0 CB2 + poll_keyboard(); +} + + + +//------------------------------------------------- +// pia0_irq_a +//------------------------------------------------- + +WRITE_LINE_MEMBER( coco_state::pia0_irq_a ) +{ + recalculate_irq(); +} + + + +//------------------------------------------------- +// pia0_irq_b +//------------------------------------------------- + +WRITE_LINE_MEMBER( coco_state::pia0_irq_b ) +{ + recalculate_irq(); +} + + + +/*************************************************************************** + PIA1 ($FF20-$FF3F) (Chip U4) + + PIA1 PA0 - CASSDIN + PIA1 PA1 - RS232 OUT (CoCo), Printer Strobe (Dragon) + PIA1 PA2-PA7 - DAC + PIA1 PB0 - RS232 IN + PIA1 PB1 - Single bit sound + PIA1 PB2 - RAMSZ (32/64K, 16K, and 4K three position switch) + PIA1 PB3 - M6847 CSS + PIA1 PB4 - M6847 INT/EXT and M6847 GM0 + PIA1 PB5 - M6847 GM1 + PIA1 PB6 - M6847 GM2 + PIA1 PB7 - M6847 A/G + PIA1 CA1 - CD (Carrier Detect; NYI) + PIA1 CA2 - CASSMOT (Cassette Motor) + PIA1 CB1 - CART (Cartridge Detect) + PIA1 CB2 - SNDEN (Sound Enable) +***************************************************************************/ + +//------------------------------------------------- +// ff20_read +//------------------------------------------------- + +READ8_MEMBER( coco_state::ff20_read ) +{ + return m_pia_1->read(space, offset, mem_mask); +} + + + +//------------------------------------------------- +// ff20_write +//------------------------------------------------- + +WRITE8_MEMBER( coco_state::ff20_write ) +{ + /* write to the PIA */ + m_pia_1->write(space, offset, data, mem_mask); + + /* we have to do this to do something that approximates the cartridge Q line behavior */ + m_cococart->twiddle_q_lines(); +} + + + +//------------------------------------------------- +// pia1_pa_r +//------------------------------------------------- + +READ8_MEMBER( coco_state::pia1_pa_r ) +{ + // Port A: we need to specify the values of all the lines, regardless of whether + // they are in input or output mode in the DDR + return (m_cassette->input() >= 0 ? 0x01 : 0x00) + | (dac_output() << 2); +} + + + +//------------------------------------------------- +// pia1_pb_r - this handles the reading of the +// memory sense switch (PB2) for the CoCo 1 and +// serial-in (PB0) +//------------------------------------------------- + +READ8_MEMBER( coco_state::pia1_pb_r ) +{ + // Port B: lines in output mode are handled automatically by the PIA object. + // We only need to specify the input lines here + UINT32 ram_size = m_ram->size(); + + // For the CoCo 1, the logic has been changed to only select 64K rams + // if there is more than 16K of memory, as the Color Basic 1.0 rom + // can only configure 4K or 16K ram banks (as documented in "Color + // Basic Unreveled"), doing this allows this allows the coco driver + // to access 32K of ram, and also allows the cocoe driver to access + // the full 64K, as this uses Color Basic 1.2, which can configure 64K rams + bool memory_sense = (ram_size >= 0x4000 && ram_size <= 0x7FFF) + || (ram_size >= 0x8000 && (m_pia_0->b_output() & 0x40)); + + // serial in (PB0) + bool serial_in = (m_rs232 != NULL) && (m_rs232->rxd_r() ? true : false); + + // composite the results + return (memory_sense ? 0x04 : 0x00) + | (serial_in ? 0x01 : 0x00); +} + + + +//------------------------------------------------- +// pia1_pa_w +//------------------------------------------------- + +WRITE8_MEMBER( coco_state::pia1_pa_w ) +{ + pia1_pa_changed(data); +} + + + +//------------------------------------------------- +// pia1_pb_w +//------------------------------------------------- + +WRITE8_MEMBER( coco_state::pia1_pb_w ) +{ + pia1_pb_changed(data); +} + + + +//------------------------------------------------- +// pia1_ca2_w +//------------------------------------------------- + +WRITE_LINE_MEMBER( coco_state::pia1_ca2_w ) +{ + m_cassette->change_state( + state ? CASSETTE_MOTOR_ENABLED : CASSETTE_MOTOR_DISABLED, + CASSETTE_MASK_MOTOR); +} + + + +//------------------------------------------------- +// pia1_cb2_w +//------------------------------------------------- + +WRITE_LINE_MEMBER( coco_state::pia1_cb2_w ) +{ + update_sound(); // SOUND_ENABLE is connected to PIA1 CB2 +} + + + +//------------------------------------------------- +// pia1_firq_a +//------------------------------------------------- + +WRITE_LINE_MEMBER( coco_state::pia1_firq_a ) +{ + recalculate_firq(); +} + + + +//------------------------------------------------- +// pia1_firq_b +//------------------------------------------------- + +WRITE_LINE_MEMBER( coco_state::pia1_firq_b ) +{ + recalculate_firq(); +} + + + +/*************************************************************************** + CPU INTERRUPTS + + The Dragon/CoCo2 have two PIAs. These PIAs can trigger interrupts. PIA0 + is set up to trigger IRQ on the CPU, and PIA1 can trigger FIRQ. Each PIA + has two output lines, and an interrupt will be triggered if either of these + lines are asserted. + + ----- IRQ + 6809 |-<----------- PIA0 + | + | + | + | + | + |-<----------- PIA1 + ----- + +***************************************************************************/ + +//------------------------------------------------- +// irq_get_line - gets the value of the FIRQ line +// passed into the CPU +//------------------------------------------------- + +bool coco_state::irq_get_line(void) +{ + return m_pia_0->irq_a_state() || m_pia_0->irq_b_state(); +} + + + +//------------------------------------------------- +// recalculate_irq +//------------------------------------------------- + +void coco_state::recalculate_irq(void) +{ + bool line = irq_get_line(); + if (LOG_INTERRUPTS) + logerror("recalculate_irq(): line=%d\n", line ? 1 : 0); + m_maincpu->set_input_line(M6809_IRQ_LINE, line ? ASSERT_LINE : CLEAR_LINE); +} + + + +//------------------------------------------------- +// firq_get_line - gets the value of the FIRQ line +// passed into the CPU +//------------------------------------------------- + +bool coco_state::firq_get_line(void) +{ + return m_pia_1->irq_a_state() || m_pia_1->irq_b_state(); +} + + + +//------------------------------------------------- +// recalculate_firq +//------------------------------------------------- + +void coco_state::recalculate_firq(void) +{ + bool line = firq_get_line(); + if (LOG_INTERRUPTS) + logerror("recalculate_firq(): line=%d\n", line ? 1 : 0); + m_maincpu->set_input_line(M6809_FIRQ_LINE, line ? ASSERT_LINE : CLEAR_LINE); +} + + + +/*************************************************************************** + SOUND / KEYBOARD / JOYSTICK + + The sound MUX has 4 possible settings, depend on SELA and SELB inputs: + + 00 - DAC (digital - analog converter) + 01 - CSN (cassette) + 10 - SND input from cartridge (NYI because we only support the FDC) + 11 - Grounded (0) + + Source - Tandy Color Computer Service Manual + + Note on the Dragon Alpha state 11, selects the AY-3-8912, this is currently + un-implemented - phs. + +***************************************************************************/ + +//------------------------------------------------- +// soundmux_status +//------------------------------------------------- + +coco_state::soundmux_status_t coco_state::soundmux_status(void) +{ + return (soundmux_status_t) ( + (snden() ? SOUNDMUX_ENABLE : 0) | + (sel1() ? SOUNDMUX_SEL1 : 0) | + (sel2() ? SOUNDMUX_SEL2 : 0)); +} + + + +//------------------------------------------------- +// update_sound +//------------------------------------------------- + +void coco_state::update_sound(void) +{ + /* PB1 will drive the sound output. This is a rarely + * used single bit sound mode. It is always connected thus + * cannot be disabled. + * + * Source: Page 31 of the Tandy Color Computer Serice Manual + */ + UINT8 single_bit_sound = (m_pia_1->b_output() & 0x02) ? 0x80 : 0x00; + + /* determine the sound mux status */ + soundmux_status_t status = soundmux_status(); + + /* the SC77526 DAC chip internally biases the AC-coupled sound inputs for Cassette and Cartridge at the midpoint of the 3.9v output range */ + bool bCassSoundEnable = (status == (SOUNDMUX_ENABLE | SOUNDMUX_SEL1)); + bool bCartSoundEnable = (status == (SOUNDMUX_ENABLE | SOUNDMUX_SEL2)); + UINT8 cassette_sound = (bCassSoundEnable ? 0x40 : 0); + UINT8 cart_sound = (bCartSoundEnable ? 0x40 : 0); + + /* determine the value to send to the DAC (this is used by the Joystick read as well as audio out) */ + m_dac_output = (m_pia_1->a_output() & 0xFC) >> 2; + UINT8 dac_sound = (status == SOUNDMUX_ENABLE ? m_dac_output << 1 : 0); + + /* The CoCo uses a single DAC for both audio output and joystick axis position measurement. + * To avoid introducing artifacts while reading the axis positions, some software will disable + * the audio output while using the DAC to read the joystick. On a real CoCo, there is a low-pass + * filter (C57 on the CoCo 3) which will hold the audio level for very short periods of time, + * preventing the introduction of artifacts while the joystick value is being read. We are not going + * to simulate the exponential decay of a capacitor here. Instead, we will store and hold the last + * used analog audio output value while the audio is disabled, to avoid introducing artifacts in + * software such as Tandy's Popcorn and Sock Master's Donkey Kong. + */ + if ((status & SOUNDMUX_ENABLE) != 0) + { + m_analog_audio_level = dac_sound + cassette_sound + cart_sound; + } + + m_dac->write_unsigned8(single_bit_sound + m_analog_audio_level); + + /* determine the cassette sound status */ + cassette_state cas_sound = bCassSoundEnable ? CASSETTE_SPEAKER_ENABLED : CASSETTE_SPEAKER_MUTED; + m_cassette->change_state(cas_sound, CASSETTE_MASK_SPEAKER); + + /* determine the cartridge sound status */ + m_cococart->cart_set_line(COCOCART_LINE_SOUND_ENABLE, bCartSoundEnable ? COCOCART_LINE_VALUE_ASSERT : COCOCART_LINE_VALUE_CLEAR); +} + + + +//------------------------------------------------- +// joystick_type - returns the type of joystick +// in the specified port +//------------------------------------------------- + +coco_state::joystick_type_t coco_state::joystick_type(int index) +{ + assert((index == 0) || (index == 1)); + return (m_joystick_type_control != NULL) + ? (joystick_type_t) ((m_joystick_type_control->read() >> (index * 4)) & 0x0F) + : JOYSTICK_NONE; +} + + + +//------------------------------------------------- +// hires_interface_type +//------------------------------------------------- + +coco_state::hires_type_t coco_state::hires_interface_type(void) +{ + return (m_joystick_hires_control != NULL) + ? (hires_type_t) m_joystick_hires_control->read() + : HIRES_NONE; +} + + + +//------------------------------------------------- +// is_joystick_hires +//------------------------------------------------- + +bool coco_state::is_joystick_hires(int joystick_index) +{ + bool result; + assert((joystick_index == 0) || (joystick_index == 1)); + + switch(hires_interface_type()) + { + case HIRES_RIGHT: + case HIRES_RIGHT_COCOMAX3: + result = (joystick_index == 0); + break; + + case HIRES_LEFT: + case HIRES_LEFT_COCOMAX3: + result = (joystick_index == 1); + break; + + default: + result = false; + break; + } + return result; +} + + + +//------------------------------------------------- +// poll_joystick +//------------------------------------------------- + +void coco_state::poll_joystick(bool *joyin, UINT8 *buttons) +{ + static const analog_input_t s_empty = {}; + static const int joy_rat_table[] = {15, 24, 42, 33 }; + static const int dclg_table[] = {0, 14, 30, 49 }; + + /* identify the joystick and axis */ + int joystick_axis = sel1() ? 1 : 0; + int joystick = sel2() ? 1 : 0; + + /* determine the JOYIN value */ + const analog_input_t *analog; + bool joyin_value; + UINT8 joyval; + int dclg_vpos; + switch(joystick_type(joystick)) + { + case JOYSTICK_NORMAL: + analog = &m_joystick; + + /* is any Hi-Res Interface turned on? prepare masks to check it */ + if (is_joystick_hires(joystick)) + { + /* hi-res joystick or hi-res CoCo3Max joystick */ + attotime remaining = m_hiresjoy_transition_timer[joystick_axis]->remaining(); + joyin_value = remaining.is_zero() || remaining.is_never(); + } + else + { + /* conventional joystick */ + joyval = analog->input(joystick, joystick_axis); + joyin_value = (dac_output() <= (joyval >> 2)); + } + break; + + case JOYSTICK_RAT_MOUSE: + analog = &m_rat_mouse; + joyval = analog->input(joystick, joystick_axis); + joyin_value = dac_output() <= joy_rat_table[joyval]; + break; + + case JOYSTICK_DIECOM_LIGHT_GUN: + analog = &m_diecom_lightgun; + + /* get the vertical position of the lightgun */ + dclg_vpos = analog->input(joystick, 1); + + if (machine().first_screen()->vpos() == dclg_vpos) + { + /* if gun is pointing at the current scan line, set hit bit and cache horizontal timer value */ + m_dclg_output_h |= 0x02; + m_dclg_timer = analog->input(joystick, 0) << 1; + } + + joyin_value = (dac_output() <= dclg_table[(joystick_axis ? m_dclg_output_h : m_dclg_output_v) & 0x03]); + + if (m_dclg_state == 7) + { + /* while in state 7, prepare to check next video frame for a hit */ + attotime dclg_time = machine().first_screen()->time_until_pos(dclg_vpos, 0); + m_diecom_lightgun_timer->adjust(dclg_time); + } + break; + + default: /* None */ + analog = &s_empty; + joyin_value = false; + break; + } + + *joyin = joyin_value; + *buttons = analog->buttons(); +} + + + +//------------------------------------------------- +// poll_keyboard +//------------------------------------------------- + +void coco_state::poll_keyboard(void) +{ + UINT8 pia0_pb = m_pia_0->b_output(); + UINT8 pia0_pb_z = m_pia_0->port_b_z_mask(); + + UINT8 pia0_pa = 0x7F; + UINT8 pia0_pa_z = 0x7F; + + /* poll the keyboard, and update PA6-PA0 accordingly*/ + for (int i = 0; i < ARRAY_LENGTH(m_keyboard); i++) + { + int value = m_keyboard[i]->read(); + if ((value | pia0_pb) != 0xFF) + { + pia0_pa &= ~(0x01 << i); + } + if ((value | pia0_pb_z) != 0xFF) + { + pia0_pa_z &= ~(0x01 << i); + } + } + + /* hires joystick */ + poll_hires_joystick(); + + /* poll the joystick (*/ + bool joyin; + UINT8 buttons; + poll_joystick(&joyin, &buttons); + + /* PA7 comes from JOYIN */ + pia0_pa |= joyin ? 0x80 : 0x00; + + /* mask out the buttons */ + pia0_pa &= ~buttons; + pia0_pa_z &= ~buttons; + + /* and write the result to PIA0 */ + update_keyboard_input(pia0_pa, pia0_pa_z); +} + + + +//------------------------------------------------- +// update_keyboard_input - writes to PIA0 PA, but +// on the CoCo 3 controls a GIME input +//------------------------------------------------- + +void coco_state::update_keyboard_input(UINT8 value, UINT8 z) +{ + m_pia_0->set_a_input(value, z); +} + + + +//------------------------------------------------- +// update_cassout - called when CASSOUT changes +//------------------------------------------------- + +void coco_state::update_cassout(int cassout) +{ + m_cassette->output((cassout - 0x20) / 32.0); +} + + + +//------------------------------------------------- +// diecom_lightgun_clock - called the diecom +// lightgun undergoes a high to low transition +//------------------------------------------------- + +void coco_state::diecom_lightgun_clock(void) +{ + /* clock Diecom Light gun interface on a high to low transistion */ + m_dclg_state++; + m_dclg_state &= 0x0f; + + /* clear hit bit for every transistion */ + m_dclg_output_h &= ~0x02; + + if (m_dclg_state > 7) + { + /* Bit shift timer data on state 8 thru 15 */ + if (((m_dclg_timer >> (m_dclg_state - 8 + 1)) & 0x01) == 1) + m_dclg_output_v |= 0x01; + else + m_dclg_output_v &= ~0x01; + + /* Bit 9 of timer is only avaiable if state == 8*/ + if (m_dclg_state == 8 && (((m_dclg_timer >> 9) & 0x01) == 1)) + m_dclg_output_v |= 0x02; + else + m_dclg_output_v &= ~0x02; + } + + /* During state 15, this bit is high. */ + if (m_dclg_state == 15) + m_dclg_output_h |= 0x01; + else + m_dclg_output_h &= ~0x01; +} + + + +//------------------------------------------------- +// update_prinout - called when PRINOUT changes +//------------------------------------------------- + +void coco_state::update_prinout(bool prinout) +{ + if ((joystick_type(0) == JOYSTICK_DIECOM_LIGHT_GUN) || (joystick_type(1) == JOYSTICK_DIECOM_LIGHT_GUN)) + { + /* printer port is connected to diecom light gun */ + if (m_dclg_previous_bit && !prinout) + { + diecom_lightgun_clock(); + } + m_dclg_previous_bit = prinout; + } + else + { + /* output bitbanger if present (only on CoCos) */ + if (m_rs232 != NULL) + { + m_rs232->write_txd(prinout ? 1 : 0); + } + } +} + + + +//------------------------------------------------- +// pia1_pa_changed - called when PIA1 PA changes +//------------------------------------------------- + +void coco_state::pia1_pa_changed(UINT8 data) +{ + update_sound(); // DAC is connected to PIA1 PA2-PA7 + poll_keyboard(); + update_cassout(dac_output()); + update_prinout(data & 0x02 ? true : false); +} + + + +//------------------------------------------------- +// pia1_pb_changed - called when PIA1 PB changes +//------------------------------------------------- + +void coco_state::pia1_pb_changed(UINT8 data) +{ + update_sound(); // singe_bit_sound is connected to PIA1 PB1 +} + + + +//------------------------------------------------- +// keyboard_changed +//------------------------------------------------- + +INPUT_CHANGED_MEMBER(coco_state::keyboard_changed) +{ + poll_keyboard(); +} + + + +//------------------------------------------------- +// joystick_mode_changed +//------------------------------------------------- + +INPUT_CHANGED_MEMBER(coco_state::joystick_mode_changed) +{ + poll_keyboard(); +} + +//------------------------------------------------- +// poll_hires_joystick +//------------------------------------------------- + +void coco_state::poll_hires_joystick(void) +{ + bool newvalue; + bool is_cocomax3; + int joystick_index, axis; + + /* we do different things based on the type of hires interface */ + switch(hires_interface_type()) + { + case HIRES_RIGHT: + newvalue = (dac_output() >= 0x20); + joystick_index = 0; + is_cocomax3 = false; + break; + + case HIRES_RIGHT_COCOMAX3: + newvalue = (m_pia_0->a_output() & 0x04); + joystick_index = 0; + is_cocomax3 = true; + break; + + case HIRES_LEFT: + newvalue = (dac_output() >= 0x20); + joystick_index = 1; + is_cocomax3 = false; + break; + + case HIRES_LEFT_COCOMAX3: + newvalue = (m_pia_0->a_output() & 0x08); + joystick_index = 1; + is_cocomax3 = true; + break; + + default: + newvalue = true; + joystick_index = -1; + is_cocomax3 = false; + break; + } + + /* if the joystick isn't selected, newvalue is true */ + newvalue = newvalue || (joystick_index < 0) || (joystick_type(joystick_index) != JOYSTICK_NORMAL); + + /* make the transition */ + for (axis = 0; axis <= 1; axis++) + { + if (m_hiresjoy_ca && !newvalue) + { + /* hi to lo */ + double value = m_joystick.input(joystick_index, axis) / 255.0; + value *= is_cocomax3 ? 2500.0 : 4160.0; + value += is_cocomax3 ? 400.0 : 592.0; + attotime duration = m_maincpu->clocks_to_attotime((UINT64) value) * 8; + m_hiresjoy_transition_timer[axis]->adjust(duration); + } + else if (!m_hiresjoy_ca && newvalue) + { + /* lo to hi */ + m_hiresjoy_transition_timer[axis]->reset(); + } + } + m_hiresjoy_ca = newvalue; +} + + + +/*************************************************************************** + VHD + ***************************************************************************/ + +//------------------------------------------------- +// current_vhd +//------------------------------------------------- + +coco_vhd_image_device *coco_state::current_vhd(void) +{ + switch(m_vhd_select) + { + case 0: return m_vhd_0; + case 1: return m_vhd_1; + default: return NULL; + } +} + + + +//------------------------------------------------- +// ff60_read +//------------------------------------------------- + +READ8_MEMBER( coco_state::ff60_read ) +{ + UINT8 result; + + if ((current_vhd() != NULL) && (offset >= 32) && (offset <= 37)) + { + result = current_vhd()->read(offset - 32); + } + else + { + result = floating_bus_read(); + } + + return result; +} + + + +//------------------------------------------------- +// ff60_write +//------------------------------------------------- + +WRITE8_MEMBER( coco_state::ff60_write ) +{ + if ((current_vhd() != NULL) && (offset >= 32) && (offset <= 37)) + { + current_vhd()->write(offset - 32, data); + } + else if (offset == 38) + { + /* writes to $FF86 will switch the VHD */ + m_vhd_select = data; + } +} + + + +/*************************************************************************** + CARTRIDGE & CASSETTE + ***************************************************************************/ + +//------------------------------------------------- +// ff40_read +//------------------------------------------------- + +READ8_MEMBER( coco_state::ff40_read ) +{ + if (offset >= 1 && offset <= 2 && m_beckerportconfig && m_beckerportconfig->read() == 1) + { + return m_beckerport->read(space, offset-1, mem_mask); + } + + return m_cococart->read(space, offset, mem_mask); +} + + + +//------------------------------------------------- +// ff40_write +//------------------------------------------------- + +WRITE8_MEMBER( coco_state::ff40_write ) +{ + if (offset >= 1 && offset <= 2 && m_beckerportconfig && m_beckerportconfig->read() == 1) + { + return m_beckerport->write(space, offset-1, data, mem_mask); + } + + m_cococart->write(space, offset, data, mem_mask); +} + + + +//------------------------------------------------- +// cart_w +//------------------------------------------------- + +void coco_state::cart_w(bool state) +{ + m_pia_1->cb1_w(state); +} + +/*************************************************************************** + DISASSEMBLY OVERRIDE (OS9 syscalls) + ***************************************************************************/ + +static const char *const os9syscalls[] = +{ + "F$Link", /* Link to Module */ + "F$Load", /* Load Module from File */ + "F$UnLink", /* Unlink Module */ + "F$Fork", /* Start New Process */ + "F$Wait", /* Wait for Child Process to Die */ + "F$Chain", /* Chain Process to New Module */ + "F$Exit", /* Terminate Process */ + "F$Mem", /* Set Memory Size */ + "F$Send", /* Send Signal to Process */ + "F$Icpt", /* Set Signal Intercept */ + "F$Sleep", /* Suspend Process */ + "F$SSpd", /* Suspend Process */ + "F$ID", /* Return Process ID */ + "F$SPrior", /* Set Process Priority */ + "F$SSWI", /* Set Software Interrupt */ + "F$PErr", /* Print Error */ + "F$PrsNam", /* Parse Pathlist Name */ + "F$CmpNam", /* Compare Two Names */ + "F$SchBit", /* Search Bit Map */ + "F$AllBit", /* Allocate in Bit Map */ + "F$DelBit", /* Deallocate in Bit Map */ + "F$Time", /* Get Current Time */ + "F$STime", /* Set Current Time */ + "F$CRC", /* Generate CRC */ + "F$GPrDsc", /* get Process Descriptor copy */ + "F$GBlkMp", /* get System Block Map copy */ + "F$GModDr", /* get Module Directory copy */ + "F$CpyMem", /* Copy External Memory */ + "F$SUser", /* Set User ID number */ + "F$UnLoad", /* Unlink Module by name */ + "F$Alarm", /* Color Computer Alarm Call (system wide) */ + NULL, + NULL, + "F$NMLink", /* Color Computer NonMapping Link */ + "F$NMLoad", /* Color Computer NonMapping Load */ + NULL, + NULL, + "F$TPS", /* Return System's Ticks Per Second */ + "F$TimAlm", /* COCO individual process alarm call */ + "F$VIRQ", /* Install/Delete Virtual IRQ */ + "F$SRqMem", /* System Memory Request */ + "F$SRtMem", /* System Memory Return */ + "F$IRQ", /* Enter IRQ Polling Table */ + "F$IOQu", /* Enter I/O Queue */ + "F$AProc", /* Enter Active Process Queue */ + "F$NProc", /* Start Next Process */ + "F$VModul", /* Validate Module */ + "F$Find64", /* Find Process/Path Descriptor */ + "F$All64", /* Allocate Process/Path Descriptor */ + "F$Ret64", /* Return Process/Path Descriptor */ + "F$SSvc", /* Service Request Table Initialization */ + "F$IODel", /* Delete I/O Module */ + "F$SLink", /* System Link */ + "F$Boot", /* Bootstrap System */ + "F$BtMem", /* Bootstrap Memory Request */ + "F$GProcP", /* Get Process ptr */ + "F$Move", /* Move Data (low bound first) */ + "F$AllRAM", /* Allocate RAM blocks */ + "F$AllImg", /* Allocate Image RAM blocks */ + "F$DelImg", /* Deallocate Image RAM blocks */ + "F$SetImg", /* Set Process DAT Image */ + "F$FreeLB", /* Get Free Low Block */ + "F$FreeHB", /* Get Free High Block */ + "F$AllTsk", /* Allocate Process Task number */ + "F$DelTsk", /* Deallocate Process Task number */ + "F$SetTsk", /* Set Process Task DAT registers */ + "F$ResTsk", /* Reserve Task number */ + "F$RelTsk", /* Release Task number */ + "F$DATLog", /* Convert DAT Block/Offset to Logical */ + "F$DATTmp", /* Make temporary DAT image (Obsolete) */ + "F$LDAXY", /* Load A [X,[Y]] */ + "F$LDAXYP", /* Load A [X+,[Y]] */ + "F$LDDDXY", /* Load D [D+X,[Y]] */ + "F$LDABX", /* Load A from 0,X in task B */ + "F$STABX", /* Store A at 0,X in task B */ + "F$AllPrc", /* Allocate Process Descriptor */ + "F$DelPrc", /* Deallocate Process Descriptor */ + "F$ELink", /* Link using Module Directory Entry */ + "F$FModul", /* Find Module Directory Entry */ + "F$MapBlk", /* Map Specific Block */ + "F$ClrBlk", /* Clear Specific Block */ + "F$DelRAM", /* Deallocate RAM blocks */ + "F$GCMDir", /* Pack module directory */ + "F$AlHRam", /* Allocate HIGH RAM Blocks */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "F$RegDmp", /* Ron Lammardo's debugging register dump call */ + "F$NVRAM", /* Non Volatile RAM (RTC battery backed static) read/write */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "I$Attach", /* Attach I/O Device */ + "I$Detach", /* Detach I/O Device */ + "I$Dup", /* Duplicate Path */ + "I$Create", /* Create New File */ + "I$Open", /* Open Existing File */ + "I$MakDir", /* Make Directory File */ + "I$ChgDir", /* Change Default Directory */ + "I$Delete", /* Delete File */ + "I$Seek", /* Change Current Position */ + "I$Read", /* Read Data */ + "I$Write", /* Write Data */ + "I$ReadLn", /* Read Line of ASCII Data */ + "I$WritLn", /* Write Line of ASCII Data */ + "I$GetStt", /* Get Path Status */ + "I$SetStt", /* Set Path Status */ + "I$Close", /* Close Path */ + "I$DeletX" /* Delete from current exec dir */ +}; + + +offs_t coco_state::dasm_override(device_t &device, char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, int options) +{ + unsigned call; + unsigned result = 0; + + /* check for SWI2 instruction */ + if ((oprom[0] == 0x10) && (oprom[1] == 0x3F)) + { + call = oprom[2]; + if ((call < ARRAY_LENGTH(os9syscalls)) && os9syscalls[call]) + { + sprintf(buffer, "OS9 %s", os9syscalls[call]); + result = 3; + } + } + return result; +} |