// license:BSD-3-Clause
// copyright-holders:Patrick Mackinlay

/*
 * 3Com EtherLink Plus (3C505) ethernet adapter. The documentation refers to 3
 * revisions of the hardware and firmware; this implementation and the firmware
 * are from a revision 3 board. The card was designed to work when installed in
 * both 8-bit and 16-bit ISA bus slots.
 *
 * Sources:
 *
 *   http://lxr.free-electrons.com/source/drivers/net/3c505.h
 *   http://lxr.free-electrons.com/source/drivers/net/3c505.c
 *   http://stason.org/TULARC/pc/network-cards/O/OLIVETTI-Ethernet-NPU-9144-3C505.html
 *   http://www.bitsavers.org/pdf/3Com/3c505_Etherlink_Plus_Developers_Guide_May86.pdf
 *   http://www.bitsavers.org/pdf/3Com/1569-03_EtherLink_Plus_Technical_Reference_Jan89.pdf
 *
 * TODO
 *   - resolve intermittent diagnostics bug on 8-bit dma channels
 *   - 8-bit isa slot support
 *   - revision 1.0 and 2.0 hardware/firmware variants
 *   - 8023 loopback mode
 */

#include "emu.h"
#include "3c505.h"

#define LOG_REG     (1U << 1)
#define LOG_DATA    (1U << 2)

//#define VERBOSE (LOG_GENERAL|LOG_REG|LOG_DATA)
#include "logmacro.h"

DEFINE_DEVICE_TYPE(ISA16_3C505, isa16_3c505_device, "3c505", "3Com EtherLink Plus")

ROM_START(3c505)
	ROM_REGION16_LE(0x04000, "system", 0)
	// this system firmware reports revision 0x0301 (3.1)
	ROM_LOAD16_BYTE("0729-12_a.3h", 0x00000, 0x02000, CRC(5415fccd) SHA1(6a42d7f3acdb3e0213e1037fee1864819ac33991))
	ROM_LOAD16_BYTE("0729-62_a.3f", 0x00001, 0x02000, CRC(4240bd9d) SHA1(015d2f7282def85681bcf1a7c5a7f501a16d5a6c))

	ROM_SYSTEM_BIOS(0, "unused", "Unused")
	ROM_SYSTEM_BIOS(1, "apollo", "Apollo")
	ROM_SYSTEM_BIOS(2, "netware", "3C505-NW EtherLink Plus NetWare Boot PROM")

	// host firmware
	ROM_REGION(0x02000, "host", 0)
	ROMX_LOAD("3000_3c505_010728-00.bin", 0x00000, 0x02000, CRC(69b77ec6) SHA1(7ac36cc6fc90b90ddfc56c45303b514cbe18ae58), ROM_BIOS(1))
	ROMX_LOAD("3c505-nw.bin", 0x00000, 0x02000, NO_DUMP, ROM_BIOS(2))

	// station address prom
	ROM_REGION16_LE(0x10, "mac", 0)
	ROM_LOAD("3com.9h", 0x00, 0x10, CRC(8e207354) SHA1(ca5ddcb272ab2851e00473dc960b23039746e3d2))
	ROMX_LOAD("apollo.9h", 0x00, 0x10, CRC(490f283e) SHA1(fe4c26b6a41e643f4397990b066fdc04b6f4c5ae), ROM_BIOS(1))
ROM_END

static INPUT_PORTS_START(3c505)
	PORT_START("IO_BASE")
	PORT_DIPNAME(0x3f0, 0x300, "I/O Base")
	PORT_DIPSETTING(    0x010, "010h")
	PORT_DIPSETTING(    0x020, "020h")
	PORT_DIPSETTING(    0x030, "030h")
	PORT_DIPSETTING(    0x040, "040h")
	PORT_DIPSETTING(    0x050, "050h")
	PORT_DIPSETTING(    0x060, "060h")
	PORT_DIPSETTING(    0x070, "070h")
	PORT_DIPSETTING(    0x080, "080h")
	PORT_DIPSETTING(    0x090, "090h")
	PORT_DIPSETTING(    0x0a0, "0a0h")
	PORT_DIPSETTING(    0x0b0, "0b0h")
	PORT_DIPSETTING(    0x0c0, "0c0h")
	PORT_DIPSETTING(    0x0d0, "0d0h")
	PORT_DIPSETTING(    0x0e0, "0e0h")
	PORT_DIPSETTING(    0x0f0, "0f0h")
	PORT_DIPSETTING(    0x100, "0100h")
	PORT_DIPSETTING(    0x110, "0110h")
	PORT_DIPSETTING(    0x120, "0120h")
	PORT_DIPSETTING(    0x130, "0130h")
	PORT_DIPSETTING(    0x140, "0140h")
	PORT_DIPSETTING(    0x150, "0150h")
	PORT_DIPSETTING(    0x160, "0160h")
	PORT_DIPSETTING(    0x170, "0170h")
	PORT_DIPSETTING(    0x180, "0180h")
	PORT_DIPSETTING(    0x190, "0190h")
	PORT_DIPSETTING(    0x1a0, "01a0h")
	PORT_DIPSETTING(    0x1b0, "01b0h")
	PORT_DIPSETTING(    0x1c0, "01c0h")
	PORT_DIPSETTING(    0x1d0, "01d0h")
	PORT_DIPSETTING(    0x1e0, "01e0h")
	PORT_DIPSETTING(    0x1f0, "01f0h")
	PORT_DIPSETTING(    0x200, "0200h")
	PORT_DIPSETTING(    0x210, "0210h")
	PORT_DIPSETTING(    0x220, "0220h")
	PORT_DIPSETTING(    0x230, "0230h")
	PORT_DIPSETTING(    0x240, "0240h")
	PORT_DIPSETTING(    0x250, "0250h")
	PORT_DIPSETTING(    0x260, "0260h")
	PORT_DIPSETTING(    0x270, "0270h")
	PORT_DIPSETTING(    0x280, "0280h")
	PORT_DIPSETTING(    0x290, "0290h")
	PORT_DIPSETTING(    0x2a0, "02a0h")
	PORT_DIPSETTING(    0x2b0, "02b0h")
	PORT_DIPSETTING(    0x2c0, "02c0h")
	PORT_DIPSETTING(    0x2d0, "02d0h")
	PORT_DIPSETTING(    0x2e0, "02e0h")
	PORT_DIPSETTING(    0x2f0, "02f0h")
	PORT_DIPSETTING(    0x300, "0300h")
	PORT_DIPSETTING(    0x310, "0310h")
	PORT_DIPSETTING(    0x320, "0320h")
	PORT_DIPSETTING(    0x330, "0330h")
	PORT_DIPSETTING(    0x340, "0340h")
	PORT_DIPSETTING(    0x350, "0350h")
	PORT_DIPSETTING(    0x360, "0360h")
	PORT_DIPSETTING(    0x370, "0370h")
	PORT_DIPSETTING(    0x380, "0380h")
	PORT_DIPSETTING(    0x390, "0390h")
	PORT_DIPSETTING(    0x3a0, "03a0h")
	PORT_DIPSETTING(    0x3b0, "03b0h")
	PORT_DIPSETTING(    0x3c0, "03c0h")
	PORT_DIPSETTING(    0x3d0, "03d0h")
	PORT_DIPSETTING(    0x3e0, "03e0h")
	PORT_DIPSETTING(    0x3f0, "03f0h")

	PORT_START("IRQ_DRQ")
	PORT_DIPNAME(0x0f, 0x0a, "IRQ")
	// 8 or 16 bit slots
	PORT_DIPSETTING(   0x03, "IRQ 3")
	PORT_DIPSETTING(   0x04, "IRQ 4")
	PORT_DIPSETTING(   0x05, "IRQ 5")
	PORT_DIPSETTING(   0x06, "IRQ 6")
	PORT_DIPSETTING(   0x07, "IRQ 7")
	PORT_DIPSETTING(   0x09, "IRQ 9")
	// 16 bit slots only
	PORT_DIPSETTING(   0x0a, "IRQ 10")
	PORT_DIPSETTING(   0x0b, "IRQ 11")
	PORT_DIPSETTING(   0x0c, "IRQ 12")
	PORT_DIPSETTING(   0x0e, "IRQ 14")
	PORT_DIPSETTING(   0x0f, "IRQ 15")

	// TODO: uses two jumpers for each channel: dma mode selection?
	PORT_DIPNAME(0x70, 0x50, "DRQ")
	PORT_DIPSETTING(   0x00, "none")
	// 8 or 16 bit slots
	PORT_DIPSETTING(   0x10, "DRQ 1")
	PORT_DIPSETTING(   0x30, "DRQ 3")
	// 16 bit slots only
	PORT_DIPSETTING(   0x50, "DRQ 5")
	PORT_DIPSETTING(   0x60, "DRQ 6")
	PORT_DIPSETTING(   0x70, "DRQ 7")

	// 8-position jumper block marked EN,13-19 decodes address lines
	PORT_START("ROM_OPTS")
	PORT_DIPNAME(0x01, 0x00, "ROM Enable")
	PORT_DIPSETTING(   0x00, DEF_STR(Off))
	PORT_DIPSETTING(   0x01, DEF_STR(On))

	PORT_DIPNAME(0xfe, 0x00, "ROM Base")
	// Apollo host ROM addresses
	PORT_DIPSETTING(   0x00, "80000h")
	PORT_DIPSETTING(   0x02, "82000h")
	PORT_DIPSETTING(   0x04, "84000h")
	PORT_DIPSETTING(   0x06, "86000h")

	// conventional PC option ROM addresses
	PORT_DIPSETTING(   0xc8, "C8000h")
	PORT_DIPSETTING(   0xca, "CA000h")
	PORT_DIPSETTING(   0xcc, "CC000h")
	PORT_DIPSETTING(   0xce, "CE000h")
	PORT_DIPSETTING(   0xd0, "D0000h")
	PORT_DIPSETTING(   0xd2, "D2000h")
	PORT_DIPSETTING(   0xd4, "D4000h")
	PORT_DIPSETTING(   0xd6, "D6000h")
	PORT_DIPSETTING(   0xd8, "D8000h")
	PORT_DIPSETTING(   0xda, "DA000h")
	PORT_DIPSETTING(   0xdc, "DC000h")
	PORT_DIPSETTING(   0xde, "DE000h")
	PORT_DIPSETTING(   0xe0, "E0000h")
	PORT_DIPSETTING(   0xe2, "E2000h")
	PORT_DIPSETTING(   0xe4, "E4000h")
	PORT_DIPSETTING(   0xe6, "E6000h")
	PORT_DIPSETTING(   0xe8, "E8000h")
	PORT_DIPSETTING(   0xea, "EA000h")
	PORT_DIPSETTING(   0xec, "EC000h")
	PORT_DIPSETTING(   0xee, "EE000h")
	PORT_DIPSETTING(   0xf0, "F0000h")
	PORT_DIPSETTING(   0xf2, "F2000h")
	PORT_DIPSETTING(   0xf4, "F4000h")

	PORT_START("TEST")
	PORT_DIPNAME(0x01, 0x00, "TEST Mode")
	PORT_DIPSETTING(   0x00, DEF_STR(Off))
	PORT_DIPSETTING(   0x01, DEF_STR(On))
INPUT_PORTS_END

isa16_3c505_device::isa16_3c505_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
	: device_t(mconfig, ISA16_3C505, tag, owner, clock)
	, device_isa16_card_interface(mconfig, *this)
	, m_cpu(*this, "cpu")
	, m_net(*this, "net")
	, m_ram(*this, "ram")
	, m_led(*this, "led%u", 0U)
	, m_iobase(*this, "IO_BASE")
	, m_irqdrq(*this, "IRQ_DRQ")
	, m_romopts(*this, "ROM_OPTS")
	, m_test(*this, "TEST")
	, m_installed(false)
{
}

const tiny_rom_entry *isa16_3c505_device::device_rom_region() const
{
	return ROM_NAME(3c505);
}

void isa16_3c505_device::device_add_mconfig(machine_config &config)
{
	I80186(config, m_cpu, 16_MHz_XTAL);
	m_cpu->set_addrmap(AS_PROGRAM, &isa16_3c505_device::map_main);
	m_cpu->set_addrmap(AS_IO, &isa16_3c505_device::map_io);

	I82586(config, m_net, 8_MHz_XTAL);
	m_net->set_addrmap(0, &isa16_3c505_device::map_main);
	m_net->out_irq_cb().set(m_cpu, FUNC(i80186_cpu_device::int1_w));

	// 1986 document indicates 128KiB is default, but 1988 document does not
	// include it as an option
	RAM(config, m_ram);
	m_ram->set_default_size("256KiB");
	m_ram->set_extra_options("128KiB,256KiB,384KiB,512KiB");
}

ioport_constructor isa16_3c505_device::device_input_ports() const
{
	return INPUT_PORTS_NAME(3c505);
}

void isa16_3c505_device::device_start()
{
	set_isa_device();

	// install ram in both i80186 and i82586 address spaces
	if (!m_ram->started())
		throw device_missing_dependencies();

	m_cpu->space(0).install_ram(0x00000, m_ram->mask() & 0xfffff, m_ram->pointer());
	m_net->space(0).install_ram(0x00000, m_ram->mask() & 0xfffff, m_ram->pointer());

	m_led.resolve();

	save_item(NAME(m_acmdr));
	save_item(NAME(m_acr));
	save_item(NAME(m_asr));

	save_item(NAME(m_hcmdr));
	save_item(NAME(m_hcr));
	save_item(NAME(m_hsr));
	save_item(NAME(m_hdr));

	//save_item(NAME(m_data);

	save_item(NAME(m_cpu_drq_asserted));
	save_item(NAME(m_cpu_irq_asserted));
	save_item(NAME(m_isa_drq_asserted));
	save_item(NAME(m_isa_irq_asserted));
}

void isa16_3c505_device::device_reset()
{
	if (!m_installed)
	{
		u16 const base = m_iobase->read();
		m_isa->install_device(base, base | 0xf, *this, &isa16_3c505_device::map_isa);

		m_isa_irq = m_irqdrq->read() & 0xf;
		m_isa_drq = (m_irqdrq->read() >> 4) & 0x7;

		if (m_romopts->read() & 1)
		{
			offs_t const rom_base = (m_romopts->read() & 0xfe) << 12;

			if (m_isa->is_option_rom_space_available(rom_base, 0x2000))
				m_isa->install_rom(this, rom_base, rom_base | 0x01fff, "host");
		}

		m_isa->set_dma_channel(m_isa_drq, this, true);

		m_installed = true;
	}

	m_cpu->reset();

	// adapter registers
	m_acmdr = 0;
	m_acr = 0;
	m_asr = ASR_ACRE | ASR_8_16;
	if (m_test->read())
		m_asr |= ASR_SWTC;

	// host registers
	m_hcmdr = 0;
	m_hcr = 0;
	m_hsr = HSR_HCRE;
	m_hdr = 0;

	m_data.clear();

	m_cpu_drq_asserted = false;
	m_cpu_irq_asserted = false;
	m_isa_drq_asserted = false;
	m_isa_irq_asserted = false;

	update_rdy(m_acr, m_hcr);
}

void isa16_3c505_device::map_main(address_map &map)
{
	// i82586 upper 4 address lines are ignored
	map.global_mask(0x0fffff);

	// suppress logging when sizing RAM
	map(0x00000, 0x7ffff).noprw();

	map(0xfc000, 0xfffff).rom().region("system", 0);
}

void isa16_3c505_device::map_io(address_map &map)
{
	/*
	 * A read or write to I/O location 00H will cause an active transition on
	 * the CA input.
	 */
	map(0x0000, 0x0000).lrw8(
			NAME([this]()
			{
				m_net->ca(1);
				m_net->ca(0);

				return 0;
			}),
			NAME([this](u8 data)
			{
				m_net->ca(1);
				m_net->ca(0);
			}));

	/*
	 * A read or write to I/O location 80H will produce a CAS before RAS cycle
	 * in all banks simultaneously.
	 */
	map(0x0080, 0x0081).noprw();

	map(0x0100, 0x0100).rw(FUNC(isa16_3c505_device::acmd_r), FUNC(isa16_3c505_device::acmd_w));
	map(0x0102, 0x0102).r(FUNC(isa16_3c505_device::acr_r));
	map(0x0103, 0x0103).rw(FUNC(isa16_3c505_device::asr_r), FUNC(isa16_3c505_device::acr_w));
	map(0x0104, 0x0105).rw(FUNC(isa16_3c505_device::adata_r), FUNC(isa16_3c505_device::adata_w));

	map(0x0180, 0x018f).rom().region("mac", 0);
}

void isa16_3c505_device::map_isa(address_map &map)
{
	map(0, 0).rw(FUNC(isa16_3c505_device::hcmd_r), FUNC(isa16_3c505_device::hcmd_w));
	map(2, 2).rw(FUNC(isa16_3c505_device::hsr_r), FUNC(isa16_3c505_device::hdr_w));
	map(4, 5).rw(FUNC(isa16_3c505_device::hdata_r), FUNC(isa16_3c505_device::hdata_w));
	map(6, 6).rw(FUNC(isa16_3c505_device::hcr_r), FUNC(isa16_3c505_device::hcr_w));
}

u8 isa16_3c505_device::acmd_r()
{
	u8 const data = m_hcmdr;

	m_asr &= ~ASR_HCRF;
	m_hsr |= HSR_HCRE;

	update_cpu_irq(0);

	return data;
}

void isa16_3c505_device::acmd_w(u8 data)
{
	LOGMASKED(LOG_REG, "acmd_w 0x%02x (%s)\n", data, machine().describe_context());

	m_asr &= ~ASR_ACRE;
	m_hsr |= HSR_ACRF;

	m_acmdr = data;

	if (m_hcr & HCR_CMDE)
		update_isa_irq(1);
}

void isa16_3c505_device::acr_w(u8 data)
{
	LOGMASKED(LOG_REG, "acr_w 0x%02x (%s)\n", data, machine().describe_context());

	// update adapter status flags
	if ((data ^ m_acr) & ACR_ASF)
		m_hsr = (m_hsr & ~HSR_ASF) | (data & ACR_ASF);

	if ((data ^ m_acr) & ACR_LED1)
		m_led[0] = bool(data & ACR_LED1);

	if ((data ^ m_acr) & ACR_LED2)
		m_led[1] = bool(data & ACR_LED2);

	if ((data ^ m_acr) & ACR_R586)
		m_net->reset_w((data & ACR_R586) ? 1 : 0);

	if ((data ^ m_acr) & ACR_FLSH)
	{
		if (data & ACR_FLSH)
		{
			LOGMASKED(LOG_REG, "adapter flushed data fifo (%d bytes)\n", m_data.queue_length());
			m_data.clear();
		}

		update_rdy(data, m_hcr);
	}

	// loopback is active low
	if ((m_acr & ACR_LPBK) && !(data & ACR_LPBK))
	{
		LOGMASKED(LOG_REG, "loopback enabled\n");

		// TODO: enable loopback on 8023
	}

	m_acr = data;
}

u16 isa16_3c505_device::adata_r()
{
	if (!(m_asr & ASR_DIR) && m_data.queue_length() > 1)
	{
		u16 data = m_data.dequeue();
		data |= u16(m_data.dequeue()) << 8;

		LOGMASKED(LOG_DATA, "adata_r 0x%04x\n", data);

		update_rdy(m_acr, m_hcr);

		return data;
	}
	else
		fatalerror("%s: adata_r read fifo while %s (%s)\n",
			tag(), (m_asr & ASR_DIR) ? "write-only" : "empty", machine().describe_context().c_str());
}

void isa16_3c505_device::adata_w(u16 data)
{
	if ((m_asr & ASR_DIR) && m_data.queue_length() < (FIFO_SIZE - 1))
	{
		LOGMASKED(LOG_DATA, "adata_w 0x%04x\n", data);
		m_data.enqueue(u8(data));
		m_data.enqueue(data >> 8);

		update_rdy(m_acr, m_hcr);
	}
	else
		fatalerror("%s: adata_w write fifo while %s (%s)\n",
			tag(), !(m_asr & ASR_DIR) ? "read-only" : "full", machine().describe_context().c_str());
}

u8 isa16_3c505_device::hcmd_r()
{
	u8 const data = m_acmdr;

	m_asr |= ASR_ACRE;
	m_hsr &= ~HSR_ACRF;

	update_isa_irq(0);

	return data;
}

void isa16_3c505_device::hcmd_w(u8 data)
{
	LOGMASKED(LOG_REG, "hcmd_w 0x%02x (%s)\n", data, machine().describe_context());

	m_asr |= ASR_HCRF;
	m_hsr &= ~HSR_HCRE;

	m_hcmdr = data;

	update_cpu_irq(1);
}

void isa16_3c505_device::hcr_w(u8 data)
{
	LOGMASKED(LOG_REG, "hcr_w 0x%02x (%s)\n", data, machine().describe_context());

	// attention condition
	if (!(m_hcr & HCR_ATTN) && (data & HCR_ATTN))
	{
		if (!(data & HCR_FLSH))
		{
			LOGMASKED(LOG_REG, "soft reset\n");

			// soft reset
			m_cpu->set_input_line(INPUT_LINE_NMI, 1);
			m_cpu->set_input_line(INPUT_LINE_NMI, 0);
		}
		else
		{
			LOGMASKED(LOG_REG, "hard reset\n");

			// hard reset
			reset();
		}
	}

	// update host status flags
	if ((data ^ m_hcr) & HCR_HSF)
		m_asr = (m_asr & ~ASR_HSF) | (data & HCR_HSF);

	// update direction flag
	if ((data ^ m_hcr) & HCR_DIR)
	{
		if (data & HCR_DIR)
		{
			LOGMASKED(LOG_REG, "transfer from adapter to host\n");

			// transfer from adapter to host
			m_hsr |= HSR_DIR;
			m_asr |= ASR_DIR;

			update_rdy(m_acr, data);
		}
		else
		{
			LOGMASKED(LOG_REG, "transfer from host to adapter\n");

			// transfer from host to adapter
			m_hsr &= ~HSR_DIR;
			m_asr &= ~ASR_DIR;

			update_rdy(m_acr, data);
		}
	}

	if (!(data & HCR_DMAE))
		m_hsr &= ~HSR_DONE;

	if ((data ^ m_hcr) & HCR_FLSH)
	{
		if (data & HCR_FLSH)
		{
			LOGMASKED(LOG_REG, "host flushed data fifo (%d bytes)\n", m_data.queue_length());
			m_data.clear();
		}

		update_rdy(m_acr, data);
	}

	m_hcr = data;
}

u16 isa16_3c505_device::hdata_r(offs_t offset, u16 mem_mask)
{
	unsigned const word = (mem_mask == 0xffff);
	u16 data = 0;

	if ((m_hsr & HSR_DIR) && (m_data.queue_length() > word))
	{
		if (ACCESSING_BITS_0_7)
			data |= m_data.dequeue();

		if (ACCESSING_BITS_8_15)
			data |= u16(m_data.dequeue()) << 8;

		LOGMASKED(LOG_DATA, "hdata_r 0x%04x mem_mask 0x%04x (%s)\n", data, mem_mask, machine().describe_context());

		update_rdy(m_acr, m_hcr);
	}
	else
		logerror("hdata_r read fifo while %s (%s)\n",
			!(m_hsr & HSR_DIR) ? "write-only" : "empty", machine().describe_context());

	return data;
}

void isa16_3c505_device::hdata_w(offs_t offset, u16 data, u16 mem_mask)
{
	unsigned const word = (mem_mask == 0xffff);

	if (!(m_hsr & HSR_DIR) && (m_data.queue_length() < (FIFO_SIZE - word)))
	{
		if (ACCESSING_BITS_0_7)
			m_data.enqueue(u8(data));

		if (ACCESSING_BITS_8_15)
			m_data.enqueue(data >> 8);

		LOGMASKED(LOG_DATA, "hdata_w 0x%04x mem_mask 0x%04x (%s)\n", data, mem_mask, machine().describe_context());

		update_rdy(m_acr, m_hcr);
	}
	else
		logerror("hdata_w write fifo while %s (%s)\n",
			(m_hsr & HSR_DIR) ? "read-only" : "full", machine().describe_context());
}

void isa16_3c505_device::update_rdy(u8 const acr, u8 const hcr)
{
	if (!(acr & ACR_FLSH) && !(hcr & HCR_FLSH))
	{
		if (hcr & HCR_DIR)
		{
			// adapter to host
			if (m_data.empty())
				m_hsr &= ~HSR_HRDY;
			else
				m_hsr |= HSR_HRDY;

			if (m_data.queue_length() > (FIFO_SIZE - 2))
				m_asr &= ~ASR_ARDY;
			else
				m_asr |= ASR_ARDY;
		}
		else
		{
			// host to adapter
			if (m_data.queue_length() < 2)
				m_asr &= ~ASR_ARDY;
			else
				m_asr |= ASR_ARDY;

			if (m_data.full())
				m_hsr &= ~HSR_HRDY;
			else
				m_hsr |= HSR_HRDY;
		}
	}
	else
	{
		m_asr &= ~ASR_ARDY;
		m_hsr &= ~HSR_HRDY;
	}

	update_cpu_drq(bool(m_asr & ASR_ARDY));
	update_isa_drq((m_hsr & HSR_HRDY) && (hcr & HCR_DMAE));
}

void isa16_3c505_device::update_cpu_drq(int state)
{
	if (bool(state) != m_cpu_drq_asserted)
	{
		m_cpu_drq_asserted = bool(state);
		m_cpu->drq1_w(state);
	}
}

void isa16_3c505_device::update_cpu_irq(int state)
{
	if (bool(state) != m_cpu_irq_asserted)
	{
		m_cpu_irq_asserted = bool(state);
		m_cpu->int0_w(state);
	}
}

void isa16_3c505_device::update_isa_drq(int state)
{
	if (bool(state) != m_isa_drq_asserted)
	{
		LOG("update_isa_drq %d\n", state);

		switch (m_isa_drq)
		{
		case 1: m_isa->drq1_w(state); break;
		case 3: m_isa->drq3_w(state); break;
		case 5: m_isa->drq5_w(state); break;
		case 6: m_isa->drq6_w(state); break;
		case 7: m_isa->drq7_w(state); break;

		default:
			fatalerror("%s: invalid isa drq %d\n", tag(), m_isa_drq);
		}

		m_isa_drq_asserted = bool(state);
	}
}

void isa16_3c505_device::update_isa_irq(int state)
{
	if (bool(state) != m_isa_irq_asserted)
	{
		LOG("update_isa_irq %d\n", state);

		switch (m_isa_irq)
		{
		case 3: m_isa->irq3_w(state); break;
		case 4: m_isa->irq4_w(state); break;
		case 5: m_isa->irq5_w(state); break;
		case 6: m_isa->irq6_w(state); break;
		case 7: m_isa->irq7_w(state); break;
		case 9: m_isa->irq2_w(state); break;
		case 10: m_isa->irq10_w(state); break;
		case 11: m_isa->irq11_w(state); break;
		case 12: m_isa->irq12_w(state); break;
		case 14: m_isa->irq14_w(state); break;
		case 15: m_isa->irq15_w(state); break;

		default:
			fatalerror("%s: invalid isa irq %d\n", tag(), m_isa_irq);
		}

		m_isa_irq_asserted = bool(state);
	}
}

void isa16_3c505_device::eop_w(int state)
{
	LOG("eop_w %d fifo %d\n", state, m_data.queue_length());

	if (state)
	{
		m_hsr |= HSR_DONE;

		update_isa_drq(0);

		if (m_hcr & HCR_TCEN)
			update_isa_irq(1);
	}
}
