summaryrefslogblamecommitdiffstatshomepage
path: root/src/mame/machine/ioc2.cpp
blob: 70d0d6ef7046331edc2727b26878553b91bb7ad1 (plain) (tree)




























                                                                                                                           
                                                                    



                                                                                                                               
                                                                      
















                                                                                                                                          















                                                                                    










                                                                              
                                                                                   


                  
                                                                                                                           
























                                                    























































































                                                                                                                                   
                                               








































































































































































































































                                                                                                                                                                                                                                      
// license:BSD-3-Clause
// copyright-holders:Ryan Holtz
/**********************************************************************

    SGI IOC2 I/O Controller emulation

**********************************************************************/

#include "emu.h"
#include "bus/rs232/rs232.h"
#include "machine/ioc2.h"

/*static*/ const char *ioc2_device::SCC_TAG = "scc";
/*static*/ const char *ioc2_device::PI1_TAG = "pi1";
/*static*/ const char *ioc2_device::KBDC_TAG = "kbdc";
/*static*/ const char *ioc2_device::PIT_TAG = "pit";
/*static*/ const char *ioc2_device::RS232A_TAG = "rs232a";
/*static*/ const char *ioc2_device::RS232B_TAG = "rs232b";

/*static*/ const XTAL ioc2_device::SCC_PCLK = XTAL(10'000'000);
/*static*/ const XTAL ioc2_device::SCC_RXA_CLK = XTAL(3'686'400); // Needs verification
/*static*/ const XTAL ioc2_device::SCC_TXA_CLK = XTAL(0);
/*static*/ const XTAL ioc2_device::SCC_RXB_CLK = XTAL(3'686'400); // Needs verification
/*static*/ const XTAL ioc2_device::SCC_TXB_CLK = XTAL(0);

DEFINE_DEVICE_TYPE(SGI_IOC2_GUINNESS,   ioc2_guinness_device,   "ioc2g", "SGI IOC2 (Guiness)")
DEFINE_DEVICE_TYPE(SGI_IOC2_FULL_HOUSE, ioc2_full_house_device, "ioc2f", "SGI IOC2 (Full House)")

ioc2_guinness_device::ioc2_guinness_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: ioc2_device(mconfig, SGI_IOC2_GUINNESS, tag, owner, clock)
{
}

ioc2_full_house_device::ioc2_full_house_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: ioc2_device(mconfig, SGI_IOC2_FULL_HOUSE, tag, owner, clock)
{
}

static INPUT_PORTS_START( front_panel )
	PORT_START("panel_buttons")
	PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Power")        PORT_CHANGED_MEMBER(DEVICE_SELF, ioc2_device, power_button, 0)
	PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Volume Down")  PORT_CHANGED_MEMBER(DEVICE_SELF, ioc2_device, volume_down, 0)
	PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYPAD) PORT_NAME("Volume Up")    PORT_CHANGED_MEMBER(DEVICE_SELF, ioc2_device, volume_up, 0)
INPUT_PORTS_END

ioport_constructor ioc2_device::device_input_ports() const
{
	return INPUT_PORTS_NAME(front_panel);
}

MACHINE_CONFIG_START(ioc2_device::device_add_mconfig)
	MCFG_SCC85230_ADD(SCC_TAG, SCC_PCLK, SCC_RXA_CLK.value(), SCC_TXA_CLK.value(), SCC_RXB_CLK.value(), SCC_TXB_CLK.value())
	MCFG_Z80SCC_OUT_TXDA_CB(WRITELINE(RS232A_TAG, rs232_port_device, write_txd))
	MCFG_Z80SCC_OUT_DTRA_CB(WRITELINE(RS232A_TAG, rs232_port_device, write_dtr))
	MCFG_Z80SCC_OUT_RTSA_CB(WRITELINE(RS232A_TAG, rs232_port_device, write_rts))
	MCFG_Z80SCC_OUT_TXDB_CB(WRITELINE(RS232B_TAG, rs232_port_device, write_txd))
	MCFG_Z80SCC_OUT_DTRB_CB(WRITELINE(RS232B_TAG, rs232_port_device, write_dtr))
	MCFG_Z80SCC_OUT_RTSB_CB(WRITELINE(RS232B_TAG, rs232_port_device, write_rts))

	MCFG_DEVICE_ADD(RS232A_TAG, RS232_PORT, default_rs232_devices, nullptr)
	MCFG_RS232_CTS_HANDLER(WRITELINE(SCC_TAG, scc85230_device, ctsa_w))
	MCFG_RS232_DCD_HANDLER(WRITELINE(SCC_TAG, scc85230_device, dcda_w))
	MCFG_RS232_RXD_HANDLER(WRITELINE(SCC_TAG, scc85230_device, rxa_w))

	MCFG_DEVICE_ADD(RS232B_TAG, RS232_PORT, default_rs232_devices, nullptr)
	MCFG_RS232_CTS_HANDLER(WRITELINE(SCC_TAG, scc85230_device, ctsb_w))
	MCFG_RS232_DCD_HANDLER(WRITELINE(SCC_TAG, scc85230_device, dcdb_w))
	MCFG_RS232_RXD_HANDLER(WRITELINE(SCC_TAG, scc85230_device, rxb_w))

	MCFG_DEVICE_ADD(PI1_TAG, PC_LPT, 0)

	MCFG_DEVICE_ADD(KBDC_TAG, KBDC8042, 0)
	MCFG_KBDC8042_KEYBOARD_TYPE(KBDC8042_PS2)
	MCFG_KBDC8042_SYSTEM_RESET_CB(INPUTLINE("^maincpu", INPUT_LINE_RESET))

	MCFG_DEVICE_ADD(PIT_TAG, PIT8254, 0)
	MCFG_PIT8253_CLK0(1000000)
	MCFG_PIT8253_CLK1(1000000)
	MCFG_PIT8253_CLK2(1000000)
	MCFG_PIT8253_OUT2_HANDLER(WRITELINE(KBDC_TAG, kbdc8042_device, write_out2))
MACHINE_CONFIG_END


ioc2_device::ioc2_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, type, tag, owner, clock)
	, m_maincpu(*this, finder_base::DUMMY_TAG)
	, m_scc(*this, SCC_TAG)
	, m_pi1(*this, PI1_TAG)
	, m_kbdc(*this, KBDC_TAG)
	, m_pit(*this, PIT_TAG)
	, m_gen_ctrl_select_reg(0)
	, m_gen_ctrl_reg(0)
	, m_front_panel_reg(0)
	, m_read_reg(0)
	, m_dma_sel(0)
	, m_reset_reg(0)
	, m_write_reg(0)
	, m_int3_local0_status_reg(0)
	, m_int3_local0_mask_reg(0)
	, m_int3_local1_status_reg(0)
	, m_int3_local1_mask_reg(0)
	, m_int3_map_status_reg(0)
	, m_int3_map_mask0_reg(0)
	, m_int3_map_mask1_reg(0)
	, m_int3_map_pol_reg(0)
	, m_int3_timer_clear_reg(0)
	, m_int3_err_status_reg(0)
	, m_par_read_cnt(0)
	, m_par_cntl(0)
{
}

void ioc2_device::device_start()
{
	m_front_panel_reg = FRONT_PANEL_POWER_STATE;
}

void ioc2_device::device_reset()
{
	m_par_read_cnt = 0;
	m_par_cntl = 0;

	m_gen_ctrl_select_reg = 0;
	m_gen_ctrl_reg = 0;
	m_front_panel_reg = FRONT_PANEL_POWER_STATE;

	m_read_reg = 0;
	m_dma_sel = 0;
	m_reset_reg = 0;
	m_write_reg = 0;

	m_int3_local0_status_reg = 0;
	m_int3_local0_mask_reg = 0;
	m_int3_local1_status_reg = 0;
	m_int3_local1_mask_reg = 0;
	m_int3_map_status_reg = 0;
	m_int3_map_mask0_reg = 0;
	m_int3_map_mask1_reg = 0;
	m_int3_map_pol_reg = 0;
	m_int3_timer_clear_reg = 0;
	m_int3_err_status_reg = 0;
}

void ioc2_device::raise_local0_irq(uint8_t source_mask)
{
	m_int3_local0_status_reg |= source_mask;
	m_maincpu->set_input_line(MIPS3_IRQ0, (m_int3_local0_mask_reg & m_int3_local0_status_reg) != 0 ? ASSERT_LINE : CLEAR_LINE);
}

void ioc2_device::lower_local0_irq(uint8_t source_mask)
{
	m_int3_local0_status_reg &= ~source_mask;
}

void ioc2_device::raise_local1_irq(uint8_t source_mask)
{
	m_int3_local1_status_reg |= source_mask;
	m_maincpu->set_input_line(MIPS3_IRQ1, (m_int3_local1_mask_reg & m_int3_local1_status_reg) != 0 ? ASSERT_LINE : CLEAR_LINE);
}

void ioc2_device::lower_local1_irq(uint8_t source_mask)
{
	m_int3_local1_status_reg &= ~source_mask;
}

READ32_MEMBER( ioc2_device::read )
{
	switch (offset)
	{
		case PI1_DATA_REG:
		case PI1_CTRL_REG:
		case PI1_STATUS_REG:
			return m_pi1->read(space, offset, 0xff);

		case PI1_DMA_CTRL_REG:
		case PI1_INT_STATUS_REG:
		case PI1_INT_MASK_REG:
		case PI1_TIMER1_REG:
		case PI1_TIMER2_REG:
		case PI1_TIMER3_REG:
		case PI1_TIMER4_REG:
			return 0;

		case SERIAL1_CMD_REG:
		case SERIAL1_DATA_REG:
		case SERIAL2_CMD_REG:
		case SERIAL2_DATA_REG:
			return m_scc->ba_cd_r(space, (offset - SERIAL1_CMD_REG) ^ 3);

		case KBD_MOUSE_REGS1:
		case KBD_MOUSE_REGS2:
			return m_kbdc->data_r(space, (offset - KBD_MOUSE_REGS1) * 4);

		case PANEL_REG:
			return m_front_panel_reg;

		case SYSID_REG:
			return get_system_id();

		case READ_REG:
			return m_read_reg;

		case DMA_SEL_REG:
			// Bits 2-0 not quite understood, seem to be copy/paste error in SGI's own documents:
			//
			// 2    RW      Parallel Port DMA Select. A high bit selects the Parallel Port DMA channel. 0\h is the default after reset. [this makes sense. -ed.]
			// 1    RW      ISDN Channel B DMA Select. A high bit selects the Parallel Port DMA channel. 0\h is the default after reset. [is this a copy/paste error? perhaps "Parallel Port" should be "ISDN Channel B"?]
			// 0    RW      [same text as above. Another copy/paste error, maybe? Should be channel A, with the bit selecting DMA channel 0/1 for ISDN channel A, the and the same for ISDN channel B in bit 1?]
			return m_dma_sel;

		case RESET_REG:
			return m_reset_reg;

		case WRITE_REG:
			// Not yet implemented, some bits unnecessary:
			//
			// Bit  Oper    Description
			// 7    RW      Margin High. Set low for normal +5V operation, high to step supply up to +5.5V. Cleared at reset.
			// 6    RW      Margin Low. Set lowf or normal +5V operation, high to step supply down to +4.5V. Cleared at reset.
			// 5    RW      UART1 PC Mode. Set low to configure Port1 for RS422 Mac mode, high to select RS232 PC mode. Cleared at reset.
			// 4    RW      UART2 PC Mode. Set low to configure Port2 for RS422 Mac mode, high to select RS232 PC mode. Cleared at reset.
			// 3    RW      Ethernet Auto Select (active high). Set low for manual mode, high to have LXT901 automatically select TP or AUI based on link integrity. Cleared at reset.
			// 2    RW      Ethernet Port Select. Set low for TP, high for AUI. This setting is only used when Auto Select is in manual mode. Cleared at reset.
			// 1    RW      Ethernet UTP/STP select. Set low to select 150 ohm termination fro shielded TP (default), set high to select 100 ohm termination for unshielded TP. Cleared at reset.
			// 0    RW      Ethernet Normal Threshold (NTH) select. Set low to select the normal TP squelch threshold (default), high to reduce threshold by 4.5 dB (set low when reset).
			return m_write_reg;

		case INT3_LOCAL0_STATUS_REG:
			return m_int3_local0_status_reg;

		case INT3_LOCAL0_MASK_REG:
			return m_int3_local0_mask_reg;

		case INT3_LOCAL1_STATUS_REG:
			return m_int3_local1_status_reg;

		case INT3_LOCAL1_MASK_REG:
			return m_int3_local1_mask_reg;

		case INT3_MAP_STATUS_REG:
			return m_int3_map_status_reg;

		case INT3_MAP_MASK0_REG:
			return m_int3_map_mask0_reg;

		case INT3_MAP_MASK1_REG:
			return m_int3_map_mask1_reg;

		case INT3_MAP_POLARITY_REG:
			return m_int3_map_pol_reg;

		case INT3_TIMER_CLEAR_REG:
			return m_int3_timer_clear_reg;

		case INT3_ERROR_STATUS_REG:
			return m_int3_err_status_reg;

		case TIMER_COUNT0_REG:
		case TIMER_COUNT1_REG:
		case TIMER_COUNT2_REG:
		case TIMER_CONTROL_REG:
			return m_pit->read(space, offset - TIMER_COUNT0_REG);
	}

	return 0;
}

WRITE32_MEMBER( ioc2_device::write )
{
	switch (offset)
	{
		case PI1_DATA_REG:
		case PI1_CTRL_REG:
		case PI1_STATUS_REG:
			m_pi1->write(space, offset, data & 0xff, 0xff);
			return;

		case PI1_DMA_CTRL_REG:
		case PI1_INT_STATUS_REG:
		case PI1_INT_MASK_REG:
		case PI1_TIMER1_REG:
		case PI1_TIMER2_REG:
		case PI1_TIMER3_REG:
		case PI1_TIMER4_REG:
			return;

		case SERIAL1_CMD_REG:
		case SERIAL1_DATA_REG:
		case SERIAL2_CMD_REG:
		case SERIAL2_DATA_REG:
			m_scc->ba_cd_w(space, (offset - SERIAL1_CMD_REG) ^ 3, data & 0xff);
			return;

		case KBD_MOUSE_REGS1:
		case KBD_MOUSE_REGS2:
			m_kbdc->data_w(space, (offset - KBD_MOUSE_REGS1) * 4, data & 0xff);
			return;

		case PANEL_REG:
			m_front_panel_reg &= ~(data & (FRONT_PANEL_VOL_UP_INT | FRONT_PANEL_VOL_DOWN_INT | FRONT_PANEL_POWER_BUTTON_INT));
			return;

		case DMA_SEL_REG:
		{
			// Bits 2-0 not quite understood, seem to be copy/paste error in SGI's own documents:
			//
			// 5:4  RW      Serial Port Clock Select: 00 selects a 10MHz internal clock (default), 01 selects a 6.67MHz internal clock, and 02 or 03 selects the external clock input.
			// 2    RW      Parallel Port DMA Select. A high bit selects the Parallel Port DMA channel. 0\h is the default after reset. [this makes sense. -ed.]
			// 1    RW      ISDN Channel B DMA Select. A high bit selects the Parallel Port DMA channel. 0\h is the default after reset. [is this a copy/paste error? perhaps "Parallel Port" should be "ISDN Channel B"?]
			// 0    RW      [same text as above. Another copy/paste error, maybe? Should be channel A, with the bit selecting DMA channel 0/1 for ISDN channel A, the and the same for ISDN channel B in bit 1?]
			uint8_t old = m_dma_sel;
			m_dma_sel = data;
			uint8_t diff = old ^ m_dma_sel;
			if (diff & DMA_SEL_CLOCK_SEL_MASK)
			{
				if (diff & DMA_SEL_CLOCK_SEL_EXT)
				{
					logerror("%s: External clock select %sselected\n", machine().describe_context(), (old & DMA_SEL_CLOCK_SEL_EXT) != 0 ? "de" : "");
					// TODO: verify the external Rx/Tx clock, is it fixed or programmable?
				}
			}
			// TODO: Currently we always assume a 10MHz clock as PCLK
			return;
		}

		case RESET_REG:
			handle_reset_reg_write(data);
			return;

		case WRITE_REG:
			m_write_reg = data;
			return;

		case INT3_LOCAL0_STATUS_REG:
		case INT3_LOCAL1_STATUS_REG:
		case INT3_MAP_STATUS_REG:
		case INT3_ERROR_STATUS_REG:
			// Read-only registers
			return;

		case INT3_LOCAL0_MASK_REG:
		{
			uint8_t old = m_int3_local0_mask_reg;
			m_int3_local0_mask_reg = data;
			bool old_line = (old & m_int3_local0_status_reg) != 0;
			bool new_line = (m_int3_local0_mask_reg & m_int3_local0_status_reg) != 0;
			if (old_line != new_line)
			{
				const uint32_t int_bits = (m_int3_local1_mask_reg & m_int3_local1_status_reg) | (m_int3_local0_mask_reg & m_int3_local0_status_reg);
				m_maincpu->set_input_line(MIPS3_IRQ0, int_bits != 0 ? ASSERT_LINE : CLEAR_LINE);
			}
			return;
		}

		case INT3_LOCAL1_MASK_REG:
		{
			uint8_t old = m_int3_local1_mask_reg;
			m_int3_local1_mask_reg = data;
			bool old_line = (old & m_int3_local1_status_reg) != 0;
			bool new_line = (m_int3_local1_mask_reg & m_int3_local1_status_reg) != 0;
			if (old_line != new_line)
			{
				const uint32_t int_bits = (m_int3_local1_mask_reg & m_int3_local1_status_reg) | (m_int3_local0_mask_reg & m_int3_local0_status_reg);
				m_maincpu->set_input_line(MIPS3_IRQ0, int_bits != 0 ? ASSERT_LINE : CLEAR_LINE);
			}
			return;
		}

		case INT3_MAP_MASK0_REG:
			// TODO: Implement mappable interrupts
			m_int3_map_mask0_reg = data;
			return;

		case INT3_MAP_MASK1_REG:
			// TODO: Implement mappable interrupts
			m_int3_map_mask1_reg = data;
			return;

		case INT3_MAP_POLARITY_REG:
			// TODO: Mappable interrupt polarity select
			m_int3_map_pol_reg = data;
			return;

		case TIMER_COUNT0_REG:
		case TIMER_COUNT1_REG:
		case TIMER_COUNT2_REG:
		case TIMER_CONTROL_REG:
			m_pit->write(space, offset - TIMER_COUNT0_REG, data & 0xff);
			return;
	}
}

void ioc2_device::handle_reset_reg_write(uint8_t data)
{
	// guinness/fullhouse-specific implementations can handle bit 3 being used for ISDN reset on Indy only and bit 2 for EISA reset on Indigo 2 only, but for now we do nothing with it
	m_reset_reg = data;
}

INPUT_CHANGED_MEMBER( ioc2_device::power_button )
{
	if (!newval)
	{
		m_front_panel_reg |= FRONT_PANEL_POWER_BUTTON_INT;
	}
}

INPUT_CHANGED_MEMBER( ioc2_device::volume_up )
{
	if (!newval)
	{
		m_front_panel_reg |= FRONT_PANEL_VOL_UP_INT;
		m_front_panel_reg |= FRONT_PANEL_VOL_UP_HOLD;
	}
	else
	{
		m_front_panel_reg &= ~FRONT_PANEL_VOL_UP_HOLD;
	}
}

INPUT_CHANGED_MEMBER( ioc2_device::volume_down )
{
	if (!newval)
	{
		m_front_panel_reg |= FRONT_PANEL_VOL_DOWN_INT;
		m_front_panel_reg |= FRONT_PANEL_VOL_DOWN_HOLD;
	}
	else
	{
		m_front_panel_reg &= ~FRONT_PANEL_VOL_DOWN_HOLD;
	}
}