summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/machine/coco.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/machine/coco.cpp')
-rw-r--r--src/mame/machine/coco.cpp1387
1 files changed, 1387 insertions, 0 deletions
diff --git a/src/mame/machine/coco.cpp b/src/mame/machine/coco.cpp
new file mode 100644
index 00000000000..f1177b8d9e8
--- /dev/null
+++ b/src/mame/machine/coco.cpp
@@ -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;
+}