summaryrefslogblamecommitdiffstatshomepage
path: root/src/mame/machine/trs80m3.cpp
blob: 4c68d07e4de8a0f1fc68c9b3b4d2badf0707a2aa (plain) (tree)























































































                                                                                                                            


                                    









































































                                                                                               




                                             



























                                                                     


                                                             

























































































































































                                                                                                      
                                                                                      
 
                                                                                                                        
















                                                                             
                         
         










                                                                                    




                                                             






                                             
                                 








































                                                                                   
                                                             
                                        









                                                                   




                                                       
                                           

                  

                                                                           

                                       
                                           



                                                                                      




                                                                             
                                         
                  
         
                                                                           

                               












                                        
                                         






































                                                           
                       
                      
                   














































                                                                                                                                            
                                        

                            

                           
                                                          






                                                                      























                                                                            
                                                  






















































                                                                                                                     
// 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.
 *
 *************************************/


READ8_MEMBER( 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);
}

READ8_MEMBER( 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);
}

READ8_MEMBER( 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;
}

READ8_MEMBER( 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;
}

READ8_MEMBER( trs80m3_state::port_ec_r )
{
/* Reset the RTC interrupt */
	m_irq &= ~IRQ_M4_RTC;
	return 0;
}

READ8_MEMBER( 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;
}

READ8_MEMBER( 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?!
}


WRITE8_MEMBER( trs80m3_state::port_84_w ) // 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;
		}
	}
}

WRITE8_MEMBER( trs80m3_state::port_90_w )
{
	m_speaker->level_w(!(BIT(data, 0)));
}

WRITE8_MEMBER( trs80m3_state::port_9c_w )     /* 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;
	}
}

WRITE8_MEMBER( trs80m3_state::port_e0_w )
{
/* 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;
}

WRITE8_MEMBER( trs80m3_state::port_e4_w )
{
/* 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;
}

WRITE8_MEMBER( trs80m3_state::port_e8_w )
{
/* d1 when '1' enables control register load (see below) */

	m_reg_load = BIT(data, 1);
}

WRITE8_MEMBER( trs80m3_state::port_ea_w )
{
	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 */

	}
}

WRITE8_MEMBER( trs80m3_state::port_ec_w )
{
/* 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 */
WRITE8_MEMBER( trs80m3_state::port_f4_w )
{
	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));
}

WRITE8_MEMBER( trs80m3_state::port_ff_w )
{
/* 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              *
 *                                   *
 *************************************/

READ8_MEMBER( trs80m3_state::wd179x_r )
{
	uint8_t data = 0xff;
	if (BIT(m_io_config->read(), 7))
		data = m_fdc->status_r();

	return data;
}

READ8_MEMBER( trs80m3_state::printer_r )
{
	return m_cent_status_in->read();
}

WRITE8_MEMBER( trs80m3_state::printer_w )
{
	m_cent_data_out->write(data);
	m_centronics->write_strobe(0);
	m_centronics->write_strobe(1);
}

/*************************************
 *      Keyboard                     *
 *************************************/
READ8_MEMBER( trs80m3_state::keyboard_r )
{
	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()
{
	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;
	address_space &mem = m_maincpu->space(AS_PROGRAM);

	if (m_model4 & 4)
		port_9c_w(mem, 0, 1);    // 4P - enable rom
	if (m_model4 & 6)
		port_84_w(mem, 0, 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;
}