// license:BSD-3-Clause
// copyright-holders:Juergen Buchmueller, Robbbert
//***************************************************************************
#include "emu.h"
#include "includes/trs80.h"
#define IRQ_M1_RTC 0x80 /* RTC on Model I */
#define IRQ_M1_FDC 0x40 /* FDC on Model I */
TIMER_CALLBACK_MEMBER(trs80_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 = true;
m_old_cassette_val = new_val;
}
/*************************************
*
* Port handlers.
*
*************************************/
READ8_MEMBER( trs80_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( trs80_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;
}
WRITE8_MEMBER( trs80_state::port_e8_w )
{
m_reg_load = BIT(data, 1);
}
WRITE8_MEMBER( trs80_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 */
}
}
READ8_MEMBER( trs80_state::sys80_f9_r )
{
/* UART Status Register - d6..d4 not emulated
d7 Transmit buffer empty (inverted)
d6 CTS pin
d5 DSR pin
d4 CD pin
d3 Parity Error
d2 Framing Error
d1 Overrun
d0 Data Available */
uint8_t data = 0x70;
m_uart->write_swe(0);
data |= m_uart->tbmt_r() ? 0 : 0x80;
data |= m_uart->dav_r( ) ? 0x01 : 0;
data |= m_uart->or_r( ) ? 0x02 : 0;
data |= m_uart->fe_r( ) ? 0x04 : 0;
data |= m_uart->pe_r( ) ? 0x08 : 0;
m_uart->write_swe(1);
return data;
}
READ8_MEMBER( trs80_state::lnw80_fe_r )
{
return m_lnw_mode;
}
READ8_MEMBER( trs80_state::port_ff_r )
{
/* ModeSel and cassette data
d7 cassette data from tape
d6 modesel setting */
return (BIT(m_mode, 0) ? 0 : 0x40) | (m_cassette_data ? 0x80 : 0) | 0x3f;
}
WRITE8_MEMBER( trs80_state::sys80_f8_w )
{
/* not emulated
d2 reset UART (XR pin)
d1 DTR
d0 RTS */
}
WRITE8_MEMBER( trs80_state::sys80_fe_w )
{
/* not emulated
d4 select internal or external cassette player */
m_tape_unit = BIT(data, 4) ? 2 : 1;
}
/* lnw80 can switch out all the devices, roms and video ram to be replaced by graphics ram. */
WRITE8_MEMBER( trs80_state::lnw80_fe_w )
{
/* lnw80 video options
d3 bankswitch lower 16k between roms and hires ram (1=hires)
d2 enable colour \
d1 hres / these 2 are the bits from the MODE command of LNWBASIC
d0 inverse video (entire screen) */
m_lnw_mode = data;
m_lnw_bank->set_bank(BIT(data, 3));
}
WRITE8_MEMBER( trs80_state::port_ff_w )
{
/* Standard output port of Model I
d3 ModeSel bit
d2 Relay
d1, d0 Cassette output */
static const double levels[4] = { 0.0, 1.0, -1.0, 0.0 };
static bool init = 0;
m_cassette->change_state(BIT(data, 2) ? CASSETTE_MOTOR_ENABLED : CASSETTE_MOTOR_DISABLED, CASSETTE_MASK_MOTOR );
m_cassette->output(levels[data & 3]);
m_cassette_data = false;
m_mode = (m_mode & 0xfe) | BIT(data, 3);
if (!init)
{
init = 1;
static int16_t speaker_levels[4] = { 0, -32767, 0, 32767 };
m_speaker->set_levels(4, speaker_levels);
}
/* Speaker for System-80 MK II - only sounds if relay is off */
if (!(BIT(data, 2)))
m_speaker->level_w(data & 3);
}
/*************************************
*
* Interrupt handlers.
*
*************************************/
INTERRUPT_GEN_MEMBER(trs80_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 40 times per second. */
m_irq |= IRQ_M1_RTC;
m_maincpu->set_input_line(0, HOLD_LINE);
// While we're here, let's countdown the motor timeout too.
// Let's not... LDOS often freezes
// if (m_timeout)
// {
// m_timeout--;
// if (m_timeout == 0)
// if (m_floppy)
// m_floppy->mon_w(1); // motor off
// }
}
WRITE_LINE_MEMBER(trs80_state::intrq_w)
{
if (state)
{
m_irq |= IRQ_M1_FDC;
m_maincpu->set_input_line(0, HOLD_LINE);
}
else
m_irq &= ~IRQ_M1_FDC;
}
/*************************************
* *
* Memory handlers *
* *
*************************************/
READ8_MEMBER( trs80_state::wd179x_r )
{
uint8_t data = 0xff;
if (BIT(m_io_config->read(), 7))
data = m_fdc->status_r();
return data;
}
READ8_MEMBER( trs80_state::printer_r )
{
return m_cent_status_in->read();
}
WRITE8_MEMBER( trs80_state::printer_w )
{
m_cent_data_out->write(data);
m_centronics->write_strobe(0);
m_centronics->write_strobe(1);
}
WRITE8_MEMBER( trs80_state::cassunit_w )
{
/* not emulated
01 for unit 1 (default)
02 for unit 2 */
m_tape_unit = data;
}
READ8_MEMBER( trs80_state::irq_status_r )
{
/* (trs80l2) Whenever an interrupt occurs, 37E0 is read to see what devices require service.
d7 = RTC
d6 = FDC
d2 = Communications (not emulated)
All interrupting devices are serviced in a single interrupt. There is a mask byte,
which is dealt with by the DOS. We take the opportunity to reset the cpu INT line. */
u8 result = m_irq;
m_maincpu->set_input_line(0, CLEAR_LINE);
m_irq = 0;
return result;
}
WRITE8_MEMBER( trs80_state::motor_w )
{
m_floppy = nullptr;
if (BIT(data, 0)) m_floppy = m_floppy0->get_device();
if (BIT(data, 1)) m_floppy = m_floppy1->get_device();
if (BIT(data, 2)) m_floppy = m_floppy2->get_device();
if (BIT(data, 3)) m_floppy = m_floppy3->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 = 200;
}
// switch to fm
m_fdc->dden_w(1);
}
/*************************************
* Keyboard *
*************************************/
READ8_MEMBER( trs80_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 trs80_state::machine_start()
{
m_size_store = 0xff;
m_tape_unit=1;
m_reg_load=1;
m_cassette_data_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(trs80_state::cassette_data_callback),this));
m_cassette_data_timer->adjust( attotime::zero, 0, attotime::from_hz(11025) );
}
void trs80_state::machine_reset()
{
m_cassette_data = false;
// if machine has a uart but no brg, the baud is determined by dipswitch
if (m_io_baud)
{
const uint16_t s_bauds[8]={ 110, 300, 600, 1200, 2400, 4800, 9600, 19200 };
u16 s_clock = s_bauds[m_io_baud->read()] << 4;
m_uart_clock->set_unscaled_clock(s_clock);
}
}
MACHINE_RESET_MEMBER(trs80_state,lnw80)
{
machine_reset();
address_space &space = m_maincpu->space(AS_IO);
m_reg_load = 1;
m_lnw_mode = 0;
lnw80_fe_w(space, 0, 0);
}
/***************************************************************************
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(trs80_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;
}