#include "emu.h"
#include "dp8390.h"

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

const device_type DP8390D = &device_creator<dp8390d_device>;
const device_type RTL8019A = &device_creator<rtl8019a_device>;

void dp8390_device::device_config_complete() {
	const dp8390_interface *intf = reinterpret_cast<const dp8390_interface *>(static_config());
	if(intf != NULL)
		*static_cast<dp8390_interface *>(this) = *intf;
	else {
		memset(&irq_cb, 0, sizeof(irq_cb));
		memset(&breq_cb, 0, sizeof(breq_cb));
		memset(&mem_read_cb, 0, sizeof(mem_read_cb));
		memset(&mem_write_cb, 0, sizeof(mem_write_cb));
	}
}

dp8390d_device::dp8390d_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: dp8390_device(mconfig, DP8390D, "DP8390D", tag, owner, clock, 10.0f) {
		m_type = TYPE_DP8390D;
}

rtl8019a_device::rtl8019a_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: dp8390_device(mconfig, RTL8019A, "RTL8019A", tag, owner, clock, 10.0f) {
		m_type = TYPE_RTL8019A;
}

dp8390_device::dp8390_device(const machine_config &mconfig, device_type type, const char* name, const char *tag, device_t *owner, UINT32 clock, float bandwidth)
	: device_t(mconfig, type, name, tag, owner, clock),
	  device_network_interface(mconfig, *this, bandwidth) {
}

void dp8390_device::device_start() {
	irq_func.resolve(irq_cb, *this->owner());
	breq_func.resolve(breq_cb, *this->owner());
	mem_read.resolve(mem_read_cb, *this->owner());
	mem_write.resolve(mem_write_cb, *this->owner());
}

void dp8390_device::stop() {
	m_regs.isr = 0x80; // is this right?
	m_regs.cr |= 1;
	irq_func(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;
	irq_func(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() {
	UINT8 *buf;
	int i;
	UINT32 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 = global_alloc_array(UINT8, m_regs.tbcr);
	for(i = 0; i < m_regs.tbcr; i++) buf[i] = mem_read(high16 + (m_regs.tpsr << 8) + i);

	if(send(buf, 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();
	global_free(buf);
}

void dp8390_device::set_cr(UINT8 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 *buf, int len) {
	int i;
	UINT16 start = (m_regs.curr << 8), offset;
	UINT32 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(!(m_regs.rcr & 4) && !memcmp((const char *)buf, "\xff\xff\xff\xff\xff\xff", 6)) return;
		m_regs.rsr = 0x20;
	} else m_regs.rsr = 0;
	len &= 0xffff;

	for(i = 0; i < len; i++) {
		mem_write(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();
		}
	}
	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;
	mem_write(start, m_regs.rsr);
	mem_write(start+1, m_regs.curr);
	mem_write(start+2, len & 0xff);
	mem_write(start+3, len >> 8);
	check_irq();
}

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

bool dp8390_device::mcast_chk(const UINT8 *buf, int len) {
	if(!(m_regs.rcr & 8)) return false;

	return false; // TODO: multicast
}

WRITE_LINE_MEMBER(dp8390_device::dp8390_cs) {
	m_cs = state;
}

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

READ16_MEMBER(dp8390_device::dp8390_r) {
	UINT16 data;
	if(m_cs) {
		UINT32 high16 = (m_regs.dcr & 4)?m_regs.rsar<<16:0;
		if(m_regs.dcr & 1) {
			m_regs.crda &= ~1;
			data = mem_read(high16 + m_regs.crda++);
			data |= mem_read(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;
			data = mem_read(high16 + m_regs.crda++);
			check_dma_complete();
			return 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;
	default:
		if(m_type == TYPE_RTL8019A) {
			switch((offset & 0x0f)|(m_regs.cr & 0xc0)) {
				case 0x0a:
					data = 'P';
					break;
				case 0x0b:
					data = 'p';
					break;
				case 0xc0:
					data = m_regs.cr;
					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;
}

WRITE16_MEMBER(dp8390_device::dp8390_w) {
	if(m_cs) {
		UINT32 high16 = (m_regs.dcr & 4)?m_regs.rsar<<16:0;
		if(m_regs.dcr & 1) {
			data = DP8390_BYTE_ORDER(data);
			m_regs.crda &= ~1;
			mem_write(high16 + m_regs.crda++, data & 0xff);
			mem_write(high16 + m_regs.crda++, data >> 8);
			m_regs.rbcr -= (m_regs.rbcr < 2)?m_regs.rbcr:2;
			check_dma_complete();
		} else {
			data &= 0xff;
			mem_write(high16 + m_regs.crda++, data);
			m_regs.rbcr -= (m_regs.rbcr)?1:0;
			check_dma_complete();
		}
		return;
	}

	data &= 0xff;
	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;
	default:
		if(m_type == TYPE_RTL8019A) {
			switch((offset & 0x0f)|(m_regs.cr & 0xc0)) {
				case 0xc0:
					set_cr(data);
					break;
				// 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;
		}
	}
}
