// license:BSD-3-Clause // copyright-holders:Robbbert //************************************************************************************************************************** #include "emu.h" #include "includes/trs80m3.h" #define IRQ_M4_RTC 0x04 /* RTC on Model 4 */ #define CASS_RISE 0x01 /* high speed cass on Model III/4) */ #define CASS_FALL 0x02 /* high speed cass on Model III/4) */ #define MODEL4_MASTER_CLOCK 20275200 TIMER_CALLBACK_MEMBER(trs80m3_state::cassette_data_callback) { /* This does all baud rates. 250 baud (trs80), and 500 baud (all others) set bit 7 of "cassette_data". 1500 baud (trs80m3, trs80m4) is interrupt-driven and uses bit 0 of "cassette_data" */ double new_val = (m_cassette->input()); /* Check for HI-LO transition */ if ( m_old_cassette_val > -0.2 && new_val < -0.2 ) { m_cassette_data |= 0x80; /* 500 baud */ if (m_mask & CASS_FALL) /* see if 1500 baud */ { m_cassette_data = 0; m_irq |= CASS_FALL; m_maincpu->set_input_line(0, HOLD_LINE); } } else if ( m_old_cassette_val < -0.2 && new_val > -0.2 ) { if (m_mask & CASS_RISE) /* 1500 baud */ { m_cassette_data = 1; m_irq |= CASS_RISE; m_maincpu->set_input_line(0, HOLD_LINE); } } m_old_cassette_val = new_val; } /************************************* * * Port handlers. * *************************************/ uint8_t trs80m3_state::port_e0_r() { /* Indicates which devices are interrupting - d6..d3 not emulated. Whenever an interrupt occurs, this port is immediately read to find out which device requires service. Lowest-numbered bit takes precedence. We take this opportunity to clear the cpu INT line. d6 RS232 Error (Any of {FE, PE, OR} errors has occurred) d5 RS232 Rcv (DAV indicates a char ready to be picked up from uart) d4 RS232 Xmit (TBMT indicates ready to accept another char from cpu) d3 I/O Bus d2 RTC d1 Cass 1500 baud Falling d0 Cass 1500 baud Rising */ m_maincpu->set_input_line(0, CLEAR_LINE); return ~(m_mask & m_irq); } uint8_t trs80m3_state::port_e4_r() { /* Indicates which devices are interrupting - d6..d5 not emulated. Whenever an NMI occurs, this port is immediately read to find out which device requires service. Lowest-numbered bit takes precedence. We take this opportunity to clear the cpu NMI line. d7 status of FDC INTREQ (0=true) d6 status of Motor Timeout (0=true) d5 status of Reset signal (0=true - this will reboot the computer) */ m_maincpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE); u8 data = m_nmi_data; m_nmi_data = 0; return ~(m_nmi_mask & data); } uint8_t trs80m3_state::port_e8_r() { /* not emulated d7 Clear-to-Send (CTS), Pin 5 d6 Data-Set-Ready (DSR), pin 6 d5 Carrier Detect (CD), pin 8 d4 Ring Indicator (RI), pin 22 d3,d2,d0 Not used d1 UART Receiver Input, pin 20 (pin 20 is also DTR) */ return 0; } uint8_t trs80m3_state::port_ea_r() { /* UART Status Register d7 Data Received ('1'=condition true) d6 Transmitter Holding Register empty ('1'=condition true) d5 Overrun Error ('1'=condition true) d4 Framing Error ('1'=condition true) d3 Parity Error ('1'=condition true) d2..d0 Not used */ uint8_t data=7; m_uart->write_swe(0); data |= m_uart->tbmt_r() ? 0x40 : 0; data |= m_uart->dav_r( ) ? 0x80 : 0; data |= m_uart->or_r( ) ? 0x20 : 0; data |= m_uart->fe_r( ) ? 0x10 : 0; data |= m_uart->pe_r( ) ? 0x08 : 0; m_uart->write_swe(1); return data; } uint8_t trs80m3_state::port_ec_r() { /* Reset the RTC interrupt */ m_irq &= ~IRQ_M4_RTC; return 0; } uint8_t trs80m3_state::port_ff_r() { /* Return of cassette data stream from tape d7 Low-speed data d6..d1 info from write of port EC d0 High-speed data */ m_irq &= 0xfc; /* clear cassette interrupts */ return m_port_ec | m_cassette_data; } uint8_t trs80m3_state::cp500_port_f4_r() { /* The A11 flipflop is used for enabling access to the system monitor code at the EPROM address range 3800-3fff */ uint8_t *rom = memregion("maincpu")->base(); uint8_t *bootrom = memregion("bootrom")->base(); m_a11_flipflop ^= 1; //toggle the flip-flop at every read at io addresses 0xf4-f7 for (u8 block=0; block<8; block++) memcpy(&rom[block * 0x800], &bootrom[(block | m_a11_flipflop) * 0x800], 0x800); return 0x00; //really?! } void trs80m3_state::port_84_w(uint8_t data) // Model 4 & 4P only { /* Memory banking control, video mode control d7 Video Page Control d6 Despage (see p129 of service manual) d5 Enable page mapping (1=enabled) d4 Srcpage (see p129 of service manual) d3 Invert Video d2 80/64 width d1 Select bit 1 d0 Select bit 0 */ if (!(m_model4 & 6)) // Model 3 leave now return; m_mode = (m_mode & 0x73) | (data & 0x8c); m_model4 &= 0xce; m_model4 |= (data & 3) << 4; if (BIT(m_model4, 1)) // Model 4 { if (m_mainram->size() >= (64 * 1024)) { m_m4_bank->set_bank(data & 0x03); m_32kbanks[0]->set_entry((data >> 4) & 0x07); m_32kbanks[1]->set_entry((data >> 4) & 0x07); m_16kbank->set_entry((data >> 4) & 0x07); } m_vidbank->set_entry(BIT(data, 7)); return; } if (BIT(m_model4, 2)) // Model 4P { m_32kbanks[0]->set_entry((data >> 4) & 0x07); m_32kbanks[1]->set_entry((data >> 4) & 0x07); m_16kbank->set_entry((data >> 4) & 0x07); m_vidbank->set_entry(BIT(data, 7)); switch (data & 3) { case 0: /* normal operation */ if (BIT(m_model4, 3)) m_m4p_bank->set_bank(0); else m_m4p_bank->set_bank(4); break; case 1: /* write-only ram backs up the rom */ if (BIT(m_model4, 3)) m_m4p_bank->set_bank(1); else m_m4p_bank->set_bank(5); break; case 2: /* keyboard and video are moved to high memory, and the rest is ram */ m_m4p_bank->set_bank(2); m_model4 |= 1; break; case 3: /* 64k of ram */ m_m4p_bank->set_bank(3); break; } } } void trs80m3_state::port_90_w(uint8_t data) { m_speaker->level_w(!(BIT(data, 0))); } void trs80m3_state::port_9c_w(uint8_t data) /* model 4P only - swaps the ROM with read-only RAM */ { /* Meaning of model4 variable: d5..d4 memory mode (as described in section above) d3 rom switch (1=enabled) only effective in mode0 and 1 d2 this is a Model 4P d1 this is a Model 4 d0 Video banking exists yes/no (1=not banked) */ if (!(BIT(m_model4, 2))) // If not Model 4P, leave now return; m_model4 &= 0xf7; m_model4 |= (data << 3); switch (m_model4 & 0x38) { case 0x00: m_m4p_bank->set_bank(4); break; case 0x08: m_m4p_bank->set_bank(0); break; case 0x10: m_m4p_bank->set_bank(5); break; case 0x18: m_m4p_bank->set_bank(1); break; default: break; } } void trs80m3_state::port_e0_w(uint8_t data) { /* Interrupt settings - which devices are allowed to interrupt - bits align with read of E0 d6 Enable Rec Err d5 Enable Rec Data d4 Enable Xmit Emp d3 Enable I/O int d2 Enable RT int d1 C fall Int d0 C Rise Int */ m_mask = data; } void trs80m3_state::port_e4_w(uint8_t data) { /* Disk to NMI interface d7 1=enable disk INTRQ to generate NMI d6 1=enable disk Motor Timeout to generate NMI */ m_nmi_mask = data; } void trs80m3_state::port_e8_w(uint8_t data) { /* d1 when '1' enables control register load (see below) */ m_reg_load = BIT(data, 1); } void trs80m3_state::port_ea_w(uint8_t data) { if (m_reg_load) /* d2..d0 not emulated d7 Even Parity Enable ('1'=even, '0'=odd) d6='1',d5='1' for 8 bits d6='0',d5='1' for 7 bits d6='1',d5='0' for 6 bits d6='0',d5='0' for 5 bits d4 Stop Bit Select ('1'=two stop bits, '0'=one stop bit) d3 Parity Inhibit ('1'=disable; No parity, '0'=parity enabled) d2 Break ('0'=disable transmit data; continuous RS232 'SPACE' condition) d1 Request-to-Send (RTS), pin 4 d0 Data-Terminal-Ready (DTR), pin 20 */ { m_uart->write_cs(0); m_uart->write_nb1(BIT(data, 6)); m_uart->write_nb2(BIT(data, 5)); m_uart->write_tsb(BIT(data, 4)); m_uart->write_eps(BIT(data, 7)); m_uart->write_np(BIT(data, 3)); m_uart->write_cs(1); } else { /* not emulated d7,d6 Not used d5 Secondary Unassigned, pin 18 d4 Secondary Transmit Data, pin 14 d3 Secondary Request-to-Send, pin 19 d2 Break ('0'=disable transmit data; continuous RS232 'SPACE' condition) d1 Data-Terminal-Ready (DTR), pin 20 d0 Request-to-Send (RTS), pin 4 */ } } void trs80m3_state::port_ec_w(uint8_t data) { /* Hardware settings - d5..d4 not emulated d6 CPU fast (1=4MHz, 0=2MHz) d5 1=Enable Video Wait d4 1=Enable External I/O bus d3 1=Enable Alternate Character Set d2 Mode Select (0=64 chars, 1=32chars) d1 Cassette Motor (1=On) */ m_maincpu->set_unscaled_clock(data & 0x40 ? MODEL4_MASTER_CLOCK/5 : MODEL4_MASTER_CLOCK/10); m_mode = (m_mode & 0xde) | (BIT(data, 2) ? 1 : 0) | (BIT(data, 3) ? 0x20 : 0); m_cassette->change_state(( data & 2 ) ? CASSETTE_MOTOR_ENABLED : CASSETTE_MOTOR_DISABLED, CASSETTE_MASK_MOTOR ); m_port_ec = data & 0x7e; } /* Selection of drive and parameters - d6..d5 not emulated. A write also causes the selected drive motor to turn on for about 3 seconds. When the motor turns off, the drive is deselected. d7 1=MFM, 0=FM d6 1=Wait d5 1=Write Precompensation enabled d4 0=Side 0, 1=Side 1 d3 1=select drive 3 d2 1=select drive 2 d1 1=select drive 1 d0 1=select drive 0 */ void trs80m3_state::port_f4_w(uint8_t data) { if (BIT(data, 6)) { if (m_drq_off && m_intrq_off) { m_maincpu->set_input_line(Z80_INPUT_LINE_WAIT, ASSERT_LINE); m_wait = true; } } else { m_maincpu->set_input_line(Z80_INPUT_LINE_WAIT, CLEAR_LINE); m_wait = false; } m_floppy = nullptr; if (BIT(data, 0)) m_floppy = m_floppy0->get_device(); if (BIT(data, 1)) m_floppy = m_floppy1->get_device(); m_fdc->set_floppy(m_floppy); if (m_floppy) { m_floppy->mon_w(0); m_floppy->ss_w(BIT(data, 4)); m_timeout = 1600; } m_fdc->dden_w(!BIT(data, 7)); } void trs80m3_state::port_ff_w(uint8_t data) { /* Cassette port d1, d0 Cassette output */ static const double levels[4] = { 0.0, 1.0, -1.0, 0.0 }; m_cassette->output(levels[data & 3]); m_cassette_data &= ~0x80; } /************************************* * * Interrupt handlers. * *************************************/ INTERRUPT_GEN_MEMBER(trs80m3_state::rtc_interrupt) { /* This enables the processing of interrupts for the clock and the flashing cursor. The OS counts one tick for each interrupt. It is called 30 times per second. */ if (m_mask & IRQ_M4_RTC) { m_irq |= IRQ_M4_RTC; m_maincpu->set_input_line(0, HOLD_LINE); } // While we're here, let's countdown the motor timeout too. if (m_timeout) { m_timeout--; if (m_timeout == 0) if (m_floppy) m_floppy->mon_w(1); // motor off } // Also, if cpu is in wait, unlock it and trigger NMI // Don't, it breaks disk loading // if (m_wait) // { // m_wait = false; // m_maincpu->set_input_line(Z80_INPUT_LINE_WAIT, CLEAR_LINE); // if (BIT(m_nmi_mask, 6)) // { // m_nmi_data |= 0x40; // m_maincpu->set_input_line(INPUT_LINE_NMI, HOLD_LINE); // } // } } // The floppy sector has been read. Enable CPU and NMI. WRITE_LINE_MEMBER(trs80m3_state::intrq_w) { m_intrq_off = state ? false : true; if (state) { m_maincpu->set_input_line(Z80_INPUT_LINE_WAIT, CLEAR_LINE); m_wait = false; if (BIT(m_nmi_mask, 7)) { m_nmi_data |= 0x80; //m_maincpu->pulse_input_line(INPUT_LINE_NMI, attotime::zero); m_maincpu->set_input_line(INPUT_LINE_NMI, HOLD_LINE); } } } // The next byte from floppy is available. Enable CPU so it can get the byte. WRITE_LINE_MEMBER(trs80m3_state::drq_w) { m_drq_off = state ? false : true; if (state) { m_maincpu->set_input_line(Z80_INPUT_LINE_WAIT, CLEAR_LINE); m_wait = false; } } /************************************* * * * Memory handlers * * * *************************************/ uint8_t trs80m3_state::wd179x_r() { uint8_t data = 0xff; if (BIT(m_io_config->read(), 7)) data = m_fdc->status_r(); return data; } uint8_t trs80m3_state::printer_r() { return m_cent_status_in->read(); } void trs80m3_state::printer_w(uint8_t data) { m_cent_data_out->write(data); m_centronics->write_strobe(0); m_centronics->write_strobe(1); } /************************************* * Keyboard * *************************************/ uint8_t trs80m3_state::keyboard_r(offs_t offset) { u8 i, result = 0; for (i = 0; i < 8; i++) if (BIT(offset, i)) result |= m_io_keyboard[i]->read(); return result; } /************************************* * Machine * *************************************/ void trs80m3_state::machine_start() { save_item(NAME(m_model4)); save_item(NAME(m_mode)); save_item(NAME(m_irq)); save_item(NAME(m_mask)); save_item(NAME(m_nmi_mask)); save_item(NAME(m_port_ec)); save_item(NAME(m_reg_load)); save_item(NAME(m_nmi_data)); save_item(NAME(m_cassette_data)); save_item(NAME(m_old_cassette_val)); save_item(NAME(m_start_address)); save_item(NAME(m_crtc_reg)); save_item(NAME(m_size_store)); save_item(NAME(m_a11_flipflop)); save_item(NAME(m_timeout)); save_item(NAME(m_wait)); save_item(NAME(m_drq_off)); save_item(NAME(m_intrq_off)); m_mode = 0; m_reg_load = 1; m_nmi_data = 0; m_timeout = 1; m_wait = 0; m_cassette_data_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(trs80m3_state::cassette_data_callback),this)); m_cassette_data_timer->adjust( attotime::zero, 0, attotime::from_hz(11025) ); if (!(m_model4 & 6)) // Model 3 leave now return; if (m_mainram->size() < (64 * 1024)) { if (BIT(m_model4, 1)) // Model 4 { m_m4_bank->set_stride(0x00000); m_m4_bank->space(0).unmap_readwrite(0x08000, 0x0ffff); } else if (BIT(m_model4, 2)) // Model 4P { m_m4p_bank->set_stride(0x00000); m_m4p_bank->space(0).unmap_readwrite(0x08000, 0x0ffff); } m_16kbank->configure_entries(0, 8, m_mainram->pointer() + 0x00000, 0x00000); } else if (m_mainram->size() < (128 * 1024)) { m_32kbanks[0]->configure_entries(0, 8, m_mainram->pointer() + 0x00000, 0x00000); m_32kbanks[1]->configure_entries(0, 8, m_mainram->pointer() + 0x08000, 0x00000); m_16kbank->configure_entries(0, 8, m_mainram->pointer() + 0x04000, 0x00000); } else { m_32kbanks[0]->configure_entries(0, 4, m_mainram->pointer() + 0x00000, 0x00000); m_32kbanks[0]->configure_entries(4, 2, m_mainram->pointer() + 0x00000, 0x00000); m_32kbanks[0]->configure_entries(6, 2, m_mainram->pointer() + 0x10000, 0x08000); m_32kbanks[1]->configure_entries(0, 2, m_mainram->pointer() + 0x08000, 0x00000); m_32kbanks[1]->configure_entries(2, 2, m_mainram->pointer() + 0x10000, 0x08000); m_32kbanks[1]->configure_entries(4, 4, m_mainram->pointer() + 0x08000, 0x00000); m_16kbank->configure_entries(0, 4, m_mainram->pointer() + 0x04000, 0x00000); m_16kbank->configure_entries(4, 2, m_mainram->pointer() + 0x04000, 0x00000); m_16kbank->configure_entries(6, 2, m_mainram->pointer() + 0x14000, 0x08000); } m_vidbank->configure_entries(0, 2, &m_p_videoram[0], 0x0400); } void trs80m3_state::machine_reset() { m_a11_flipflop = 0; // for cp500 m_cassette_data = 0; m_size_store = 0xff; m_drq_off = true; m_intrq_off = true; if (m_model4 & 4) port_9c_w(1); // 4P - enable rom if (m_model4 & 6) port_84_w(0); // 4 & 4P - switch in devices } /*************************************************************************** PARAMETERS ***************************************************************************/ #define LOG 1 #define CMD_TYPE_OBJECT_CODE 0x01 #define CMD_TYPE_TRANSFER_ADDRESS 0x02 #define CMD_TYPE_END_OF_PARTITIONED_DATA_SET_MEMBER 0x04 #define CMD_TYPE_LOAD_MODULE_HEADER 0x05 #define CMD_TYPE_PARTITIONED_DATA_SET_HEADER 0x06 #define CMD_TYPE_PATCH_NAME_HEADER 0x07 #define CMD_TYPE_ISAM_DIRECTORY_ENTRY 0x08 #define CMD_TYPE_END_OF_ISAM_DIRECTORY_ENTRY 0x0a #define CMD_TYPE_PDS_DIRECTORY_ENTRY 0x0c #define CMD_TYPE_END_OF_PDS_DIRECTORY_ENTRY 0x0e #define CMD_TYPE_YANKED_LOAD_BLOCK 0x10 #define CMD_TYPE_COPYRIGHT_BLOCK 0x1f /*************************************************************************** IMPLEMENTATION ***************************************************************************/ QUICKLOAD_LOAD_MEMBER(trs80m3_state::quickload_cb) { address_space &program = m_maincpu->space(AS_PROGRAM); uint8_t type, length; uint8_t data[0x100]; uint8_t addr[2]; void *ptr; while (!image.image_feof()) { image.fread( &type, 1); image.fread( &length, 1); length -= 2; int block_length = length ? length : 256; switch (type) { case CMD_TYPE_OBJECT_CODE: { image.fread( &addr, 2); uint16_t address = (addr[1] << 8) | addr[0]; if (LOG) logerror("/CMD object code block: address %04x length %u\n", address, block_length); ptr = program.get_write_ptr(address); image.fread( ptr, block_length); } break; case CMD_TYPE_TRANSFER_ADDRESS: { image.fread( &addr, 2); uint16_t address = (addr[1] << 8) | addr[0]; if (LOG) logerror("/CMD transfer address %04x\n", address); m_maincpu->set_state_int(Z80_PC, address); } break; case CMD_TYPE_LOAD_MODULE_HEADER: image.fread( &data, block_length); if (LOG) logerror("/CMD load module header '%s'\n", data); break; case CMD_TYPE_COPYRIGHT_BLOCK: image.fread( &data, block_length); if (LOG) logerror("/CMD copyright block '%s'\n", data); break; default: image.fread( &data, block_length); logerror("/CMD unsupported block type %u!\n", type); } } return image_init_result::PASS; }