// license:BSD-3-Clause
// copyright-holders: Angelo Salese
/**************************************************************************************************

Melco/Buffalo LGY-98 NIC (NE2000 based)

LANMAN.DOS setup described here:
https://pokug.net/entry/2022/01/22/221536

TODO:
- Has no dipswitches on board, the port and INT lines are PnP configured thru LGYSETUP.
  Once LANMAN is setup it will try $00d0 base even if EEPROM says otherwise at CONFIG.SYS boot,
  and even if PROTOCOL.INI is explicitly  $10d0, definitely needs a BIOS.
  wpset 0xa294,2,w,wpdata==0xd0
- Wants CR bit 5 high for enough time on DMA transfers at CONFIG.SYS loading
- Untested beyond this.

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

#include "emu.h"
#include "lgy98.h"

#include "multibyte.h"

DEFINE_DEVICE_TYPE(LGY98, lgy98_device, "lgy98", "Melco LGY-98 NIC interface")

lgy98_device::lgy98_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, LGY98, tag, owner, clock)
	, device_memory_interface(mconfig, *this)
	, device_pc98_cbus_slot_interface(mconfig, *this)
	, m_nic(*this, "nic")
	, m_eeprom(*this, "eeprom")
	, m_space_mem_config("internal_memory", ENDIANNESS_LITTLE, 8, 15, 0, address_map_constructor(FUNC(lgy98_device::memory_map), this))
{
}

void lgy98_device::device_add_mconfig(machine_config &config)
{
	// MX98902A, "8390 with built-in 10BaseT transceiver"
	DP8390D(config, m_nic, 20'000'000);
	m_nic->irq_callback().set([this](int state) { m_bus->int_w(0, state); });
	m_nic->mem_read_callback().set([this] (offs_t offset) { return space(0).read_byte(offset); });
	m_nic->mem_write_callback().set([this] (offs_t offset, u8 data) { space(0).write_byte(offset, data); });

	// ATMEL829 93C46 PC
	EEPROM_93C46_16BIT(config, m_eeprom);

	// unpopulated 2x 27C256 sockets
}

void lgy98_device::device_start()
{
	// NOTE: identical to isa/ne2000.cpp
	uint8_t mac[6];
	uint32_t num = machine().rand();
	memset(m_prom, 0x57, 16);
	mac[2] = 0x1b;
	put_u24be(mac+3, num);
	mac[0] = 0; mac[1] = 0;  // avoid gcc warning
	memcpy(m_prom, mac, 6);
	m_nic->set_mac(mac);
}

void lgy98_device::device_reset()
{
	// TODO: this clears the PROM (?)
	//memcpy(m_prom, &m_nic->get_mac()[0], 6);
}

device_memory_interface::space_config_vector lgy98_device::memory_space_config() const
{
	return space_config_vector{
		std::make_pair(0, &m_space_mem_config)
	};
}

ROM_START( lgy98 )
	// either 0x2000 or 0x4000 in size, internal to the Buffalo custom?
	ROM_REGION16_LE( 0x2000, "bios", ROMREGION_ERASEFF )
	ROM_LOAD("bios.bin", 0x0000, 0x2000, NO_DUMP )
ROM_END

const tiny_rom_entry *lgy98_device::device_rom_region() const
{
	return ROM_NAME( lgy98 );
}


void lgy98_device::remap(int space_id, offs_t start, offs_t end)
{
	// TODO: seems to employ an (undumped) ROM socket too
	if (space_id == AS_IO)
	{
		m_bus->install_device(0x0000, 0xffff, *this, &lgy98_device::io_map);
	}
}

void lgy98_device::io_map(address_map &map)
{
	// 0*d* ~ 7*d*
	// TODO: force to $00d0 for now
	const u16 base_io = 0x00d0;

	map(base_io, base_io + 0xf).rw(m_nic, FUNC(dp8390_device::cs_read), FUNC(dp8390_device::cs_write));

	// TODO: +0x18 reset (on reads?)

	map(base_io + 0x200, base_io + 0x201).rw(m_nic, FUNC(dp8390_device::remote_read), FUNC(dp8390_device::remote_write));

	// identification flags
	map(base_io + 0x30a, base_io + 0x30a).lr8(NAME([] () { return 0x00; }));
	map(base_io + 0x30b, base_io + 0x30b).lr8(NAME([] () { return 0x40; }));
	map(base_io + 0x30c, base_io + 0x30c).lr8(NAME([] () { return 0x26; }));
	map(base_io + 0x30d, base_io + 0x30d).lrw8(
		NAME([this] () {
			// TODO: bit 5 required low during ID sequence
			return 0x0a | (m_eeprom->do_read());
		}),
		NAME([this] (offs_t offset, u8 data) {
			// TODO: other bits
			m_eeprom->di_write(BIT(data, 1));
			m_eeprom->cs_write(BIT(data, 2));
			m_eeprom->clk_write(BIT(data, 3));
		})
	);
}

// NOTE: again identical to isa/ne2000.cpp
void lgy98_device::memory_map(address_map &map)
{
	map.unmap_value_high();
	map(0x0000, 0x001f).lr8(NAME([this] (offs_t offset) { return m_prom[offset >> 1]; }));
	map(0x4000, 0x7fff).ram();
}
