// license:BSD-3-Clause
// copyright-holders:Carl
#include "emu.h"
#include "dp8390.h"

#define DP8390_BYTE_ORDER(w) ((m_regs.dcr & 3) == 3 ? swapendian_int16(data) : data)
#define LOOPBACK (!(m_regs.dcr & 8) && (m_regs.tcr & 6))

DEFINE_DEVICE_TYPE(DP8390D,  dp8390d_device,  "dp8390d",  "DP8390D NIC")
DEFINE_DEVICE_TYPE(RTL8019A, rtl8019a_device, "rtl8019a", "RTL8019A Ethernet Controller")

dp8390d_device::dp8390d_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: dp8390_device(mconfig, DP8390D, tag, owner, clock, TYPE::DP8390D, 10.0f)
{
}

rtl8019a_device::rtl8019a_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: dp8390_device(mconfig, RTL8019A, tag, owner, clock, TYPE::RTL8019A, 10.0f)
{
}

dp8390_device::dp8390_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, TYPE variant, u32 bandwidth)
	: device_t(mconfig, type, tag, owner, clock)
	, device_network_interface(mconfig, *this, bandwidth)
	, m_variant(variant)
	, m_irq_cb(*this)
	, m_breq_cb(*this)
	, m_mem_read_cb(*this, 0)
	, m_mem_write_cb(*this)
	, m_reset(0)
	, m_rdma_active(0)
{
}

void dp8390_device::device_start() {
}

void dp8390_device::stop() {
	m_regs.isr = 0x80; // is this right?
	m_regs.cr |= 1;
	m_irq_cb(CLEAR_LINE);
	m_reset = 1;
}

void dp8390_device::device_reset() {
	memset(&m_regs, 0, sizeof(m_regs));
	m_regs.cr =  0x21;
	m_regs.isr = 0x80;
	m_regs.dcr = 0x04;
	memset(&m_8019regs, 0, sizeof(m_8019regs));
	m_8019regs.config1 = 0x80;
	m_8019regs.config3 = 0x01;
	m_irq_cb(CLEAR_LINE);

	m_reset = 1;
}

void dp8390_device::check_dma_complete() {
	if(m_regs.rbcr) return;
	m_regs.isr |= 0x40;
	check_irq();
	m_rdma_active = 0;
}

void dp8390_device::do_tx() {
	std::vector<uint8_t> buf;
	int i;
	uint32_t high16 = (m_regs.dcr & 4)?m_regs.rsar<<16:0;
	if(m_reset) return;
	if(LOOPBACK) return;  // TODO: loopback
	m_regs.tsr = 0;
	if(m_regs.tbcr > 1518) logerror("dp8390: trying to send overlong frame\n");
	if(!m_regs.tbcr) { // ? Bochs says solaris actually does this
		m_regs.tsr = 1;
		m_regs.cr &= ~4;
		return;
	}

	buf.resize(m_regs.tbcr);
	for(i = 0; i < m_regs.tbcr; i++) buf[i] = m_mem_read_cb(high16 + (m_regs.tpsr << 8) + i);

	if(send(&buf[0], m_regs.tbcr)) {
		m_regs.tsr = 1;
		m_regs.isr |= 2;
	} else {
		m_regs.tsr = 8; // not quite right but there isn't a generic "tx failed"
		m_regs.isr |= 8;
	}
	m_regs.cr &= ~4;
	check_irq();
}

void dp8390_device::set_cr(uint8_t newcr) {
	int ostate = ((m_regs.cr & 3) == 2);
	m_regs.cr = newcr;
	if((newcr & 1) && (ostate == 1)) return stop();
	if((newcr & 3) == 2) {
		m_reset = 0;
		m_regs.isr &= ~0x80;
	}
	if(newcr & 0x20) m_rdma_active = 0;
	if(m_reset) return;
	if(newcr & 4) do_tx();
	if((newcr & 0x38) == 8) {
		m_rdma_active = 1;
		check_dma_complete();
	}
	if((newcr & 0x38) == 0x10) m_rdma_active = 2;
}

void dp8390_device::recv_overflow() {
	m_regs.rsr = 0x10;
	m_regs.isr |= 0x10;
	check_irq();
	m_regs.cntr2++;
	return;
}

void dp8390_device::recv(uint8_t *buf, int len) {
	int i;
	uint16_t start = (m_regs.curr << 8), offset;
	uint32_t high16;
	if(m_reset) return;
	if(m_regs.curr == m_regs.pstop) start = m_regs.pstart << 8;
	offset = start + 4;
	high16 = (m_regs.dcr & 4)?m_regs.rsar<<16:0;
	if(buf[0] & 1) {
		if(!memcmp((const char *)buf, "\xff\xff\xff\xff\xff\xff", 6)) {
			if(!(m_regs.rcr & 4)) return;
		} else if (memcmp((const char *)buf, "\x09\x00\x07\xff\xff\xff", 6) != 0) { // not AppleTalk broadcast
			return; // multicast
		}
		m_regs.rsr = 0x20;
	} else m_regs.rsr = 0;
	len &= 0xffff;

	for(i = 0; i < len; i++) {
		m_mem_write_cb(high16 + offset, buf[i]);
		offset++;
		if(!(offset & 0xff)) {
			if((offset >> 8) == m_regs.pstop) offset = m_regs.pstart << 8;
			if((offset >> 8) == m_regs.bnry) return recv_overflow();
		}
	}
	if(len < 60) {
		// this can't pass to the next page
		for(; i < 60; i++) {
			m_mem_write_cb(high16 + offset, 0);
			offset++;
		}
		len = 60;
	}

	m_regs.rsr |= 1;
	m_regs.isr |= 1;
	m_regs.curr = (offset >> 8) + ((offset & 0xff)?1:0);
	if(m_regs.curr == m_regs.pstop) m_regs.curr = m_regs.pstart;
	len += 4;
	m_mem_write_cb((offs_t)start, m_regs.rsr);
	m_mem_write_cb((offs_t)start+1, m_regs.curr);
	m_mem_write_cb((offs_t)start+2, len & 0xff);
	m_mem_write_cb((offs_t)start+3, len >> 8);
	check_irq();
}

void dp8390_device::recv_cb(uint8_t *buf, int len) {
	if(!LOOPBACK) recv(buf, len);
}

void dp8390_device::dp8390_reset(int state) {
	if(!state) device_reset();
}

uint16_t dp8390_device::remote_read() {
	uint32_t high16 = (m_regs.dcr & 4)?m_regs.rsar<<16:0;
	if(m_regs.dcr & 1) {
		m_regs.crda &= ~1;
		uint16_t data = m_mem_read_cb(high16 + m_regs.crda++);
		data |= m_mem_read_cb(high16 + m_regs.crda++) << 8;
		m_regs.rbcr -= (m_regs.rbcr < 2)?m_regs.rbcr:2;
		check_dma_complete();
		return DP8390_BYTE_ORDER(data);
	} else {
		m_regs.rbcr -= (m_regs.rbcr)?1:0;
		uint16_t data = m_mem_read_cb(high16 + m_regs.crda++);
		check_dma_complete();
		return data;
	}
}

uint8_t dp8390_device::cs_read(offs_t offset) {
	uint8_t data;
	switch((offset & 0x0f)|(m_regs.cr & 0xc0)) {
	case 0x00:
	case 0x40:
	case 0x80:
		data = m_regs.cr;
		break;
	case 0x01:
		data = m_regs.clda & 0xff;
		break;
	case 0x02:
		data = m_regs.clda >> 8;
		break;
	case 0x03:
		data = m_regs.bnry;
		break;
	case 0x04:
		data = m_regs.tsr;
		break;
	case 0x05:
		data = m_regs.ncr;
		break;
	case 0x06:
		data = m_regs.fifo;
		break;
	case 0x07:
		data = m_regs.isr;
		break;
	case 0x08:
		data = m_regs.crda & 0xff;
		break;
	case 0x09:
		data = m_regs.crda >> 8;
		break;
	case 0x0c:
		data = m_regs.rsr;
		break;
	case 0x0d:
		data = m_regs.cntr0;
		break;
	case 0x0e:
		data = m_regs.cntr1;
		break;
	case 0x0f:
		data = m_regs.cntr2;
		break;
	case 0x41:
	case 0x42:
	case 0x43:
	case 0x44:
	case 0x45:
	case 0x46:
		data = m_regs.par[(offset & 0x7)-1];
		break;
	case 0x47:
		data = m_regs.curr;
		break;
	case 0x48:
	case 0x49:
	case 0x4a:
	case 0x4b:
	case 0x4c:
	case 0x4d:
	case 0x4e:
	case 0x4f:
		data = m_regs.mar[offset & 0x7];
		break;
	case 0x81:
		data = m_regs.pstart;
		break;
	case 0x82:
		data = m_regs.pstop;
		break;
	case 0x83:
		data = m_regs.rnpp;
		break;
	case 0x84:
		data = m_regs.tpsr;
		break;
	case 0x85:
		data = m_regs.lnpp;
		break;
	case 0x86:
		data = m_regs.ac >> 8;
		break;
	case 0x87:
		data = m_regs.ac & 0xff;
		break;
	case 0x8c:
		data = m_regs.rcr;
		break;
	case 0x8d:
		data = m_regs.tcr;
		break;
	case 0x8e:
		data = m_regs.dcr;
		break;
	case 0x8f:
		data = m_regs.imr;
		break;
	case 0xc0:
		data = m_regs.cr;
		break;
	default:
		if(m_variant == TYPE::RTL8019A) {
			switch((offset & 0x0f)|(m_regs.cr & 0xc0)) {
				case 0x0a:
					data = 'P';
					break;
				case 0x0b:
					data = 'p';
					break;

				case 0xc1:
					data = m_8019regs.cr9346;
					break;
				case 0xc2:
					data = m_8019regs.bpage;
					break;
				case 0xc3:
					data = m_8019regs.config0;
					break;
				case 0xc4:
					data = m_8019regs.config1;
					break;
				case 0xc5:
					data = m_8019regs.config2;
					break;
				case 0xc6:
					data = m_8019regs.config3;
					break;
				case 0xcd:
					data = m_8019regs.config4;
					break;
				case 0xc8:
					data = m_8019regs.csnsav;
					break;
				case 0xcb:
					data = m_8019regs.intr;
					break;
				default:
					logerror("rtl8019: invalid read page %01X reg %02X\n", (m_regs.cr & 0xc0) >> 6, offset & 0x0f);
					return 0;
			}
		} else {
			logerror("dp8390: invalid read page %01X reg %02X\n", (m_regs.cr & 0xc0) >> 6, offset & 0x0f);
			return 0;
		}
	}
	return data;
}

void dp8390_device::remote_write(uint16_t data) {
	uint32_t high16 = (m_regs.dcr & 4)?m_regs.rsar<<16:0;
	if(m_regs.dcr & 1) {
		data = DP8390_BYTE_ORDER(data);
		m_regs.crda &= ~1;
		m_mem_write_cb(high16 + m_regs.crda++, data & 0xff);
		m_mem_write_cb(high16 + m_regs.crda++, data >> 8);
		m_regs.rbcr -= (m_regs.rbcr < 2)?m_regs.rbcr:2;
		check_dma_complete();
	} else {
		data &= 0xff;
		m_mem_write_cb(high16 + m_regs.crda++, data);
		m_regs.rbcr -= (m_regs.rbcr)?1:0;
		check_dma_complete();
	}
}

void dp8390_device::cs_write(offs_t offset, uint8_t data) {
	switch((offset & 0x0f)|(m_regs.cr & 0xc0)) {
	case 0x00:
	case 0x40:
	case 0x80:
		set_cr(data);
		break;
	case 0x01:
		m_regs.pstart = data;
		break;
	case 0x02:
		m_regs.pstop = data;
		break;
	case 0x03:
		m_regs.bnry = data;
		break;
	case 0x04:
		m_regs.tpsr = data;
		break;
	case 0x05:
		m_regs.tbcr = (m_regs.tbcr & 0xff00) | data;
		break;
	case 0x06:
		m_regs.tbcr = (m_regs.tbcr & 0xff) | (data << 8);
		break;
	case 0x07:
		m_regs.isr &= ~data;
		check_irq();
		break;
	case 0x08:
		m_regs.rsar = (m_regs.rsar & 0xff00) | data;
		m_regs.crda = m_regs.rsar;
		break;
	case 0x09:
		m_regs.rsar = (m_regs.rsar & 0xff) | (data << 8);
		m_regs.crda = m_regs.rsar;
		break;
	case 0x0a:
		m_regs.rbcr = (m_regs.rbcr & 0xff00) | data;
		break;
	case 0x0b:
		m_regs.rbcr = (m_regs.rbcr & 0xff) | (data << 8);
		break;
	case 0x0c:
		m_regs.rcr = data;
		set_promisc((data & 0x10)?true:false);
		break;
	case 0x0d:
		m_regs.tcr = data;
		break;
	case 0x0e:
		m_regs.dcr = data;
		break;
	case 0x0f:
		m_regs.imr = data;
		check_irq();
		break;
	case 0x41:
	case 0x42:
	case 0x43:
	case 0x44:
	case 0x45:
	case 0x46:
		m_regs.par[(offset & 0x7)-1] = data;
		set_mac((const char *)m_regs.par);
		break;
	case 0x47:
		m_regs.curr = data;
		break;
	case 0x48:
	case 0x49:
	case 0x4a:
	case 0x4b:
	case 0x4c:
	case 0x4d:
	case 0x4e:
	case 0x4f:
		m_regs.mar[offset & 0x7] = data;
		break;
	case 0x81:
		m_regs.clda = (m_regs.clda & 0xff00) | data;
		break;
	case 0x82:
		m_regs.clda = (m_regs.clda & 0xff) | (data << 8);
		break;
	case 0x83:
		m_regs.rnpp = data;
		break;
	case 0x85:
		m_regs.lnpp = data;
		break;
	case 0x86:
		m_regs.ac = (m_regs.ac & 0xff) | (data << 8);
		break;
	case 0x87:
		m_regs.ac = (m_regs.ac & 0xff00) | data;
		break;
	case 0xc0:
		set_cr(data);
		break;
	default:
		if(m_variant == TYPE::RTL8019A) {
			switch((offset & 0x0f)|(m_regs.cr & 0xc0)) {
				// XXX: rest of the regs
				default:
					logerror("rtl8019: invalid write page %01X reg %02X data %04X\n", (m_regs.cr & 0xc0) >> 6, offset & 0x0f, data);
					return;
			}
		} else {
			logerror("dp8390: invalid write page %01X reg %02X data %04X\n", (m_regs.cr & 0xc0) >> 6, offset & 0x0f, data);
			return;
		}
	}
}
