// license:BSD-3-Clause
// copyright-holders:David Haywood, K.Wilkins

/*
TODO:
output support, Golly Ghost is currently hacking this based on DPRAM in the namcos2.cpp driver side!
some of this can likely be moved into the actual MCU core too

*/

#include "emu.h"
#include "namco65.h"

DEFINE_DEVICE_TYPE(NAMCOC65, namcoc65_device, "namcoc65", "Namco C65 I/O")

namcoc65_device::namcoc65_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	device_t(mconfig, NAMCOC65, tag, owner, clock),
	m_mcu(*this, "mcu"),
	m_in_pb_cb(*this),
	m_in_pc_cb(*this),
	m_in_ph_cb(*this),
	m_in_pdsw_cb(*this),
	m_port_analog_in_cb(*this),
	m_port_dial_in_cb(*this),
	m_dp_in(*this),
	m_dp_out(*this)
{
}

ROM_START( namcoc65 )
	ROM_REGION( 0x2000, "mcu", 0 )
	ROM_LOAD( "sys2mcpu.bin",  0x000000, 0x002000, CRC(a342a97e) SHA1(2c420d34dba21e409bf78ddca710fc7de65a6642) )
ROM_END


void namcoc65_device::namcos2_mcu_port_d_w(uint8_t data)
{
	/* Undefined operation on write */
}

uint8_t namcoc65_device::namcos2_mcu_port_d_r()
{
	/* Provides a digital version of the analog ports */
	int threshold = 0x7f;
	int data = 0;

	/* Read/convert the bits one at a time */
	if (m_port_analog_in_cb[0]() > threshold) data |= 0x01;
	if (m_port_analog_in_cb[1]() > threshold) data |= 0x02;
	if (m_port_analog_in_cb[2]() > threshold) data |= 0x04;
	if (m_port_analog_in_cb[3]() > threshold) data |= 0x08;
	if (m_port_analog_in_cb[4]() > threshold) data |= 0x10;
	if (m_port_analog_in_cb[5]() > threshold) data |= 0x20;
	if (m_port_analog_in_cb[6]() > threshold) data |= 0x40;
	if (m_port_analog_in_cb[7]() > threshold) data |= 0x80;

	/* Return the result */
	return data;
}


uint8_t namcoc65_device::namcos2_mcu_analog_ctrl_r()
{
	int data = 0;

	/* ADEF flag is only cleared AFTER a read from control THEN a read from DATA */
	if (m_mcu_analog_complete == 2) m_mcu_analog_complete = 1;
	if (m_mcu_analog_complete) data |= 0x80;

	/* Mask on the lower 6 register bits, Irq EN/Channel/Clock */
	data |= m_mcu_analog_ctrl & 0x3f;
	/* Return the value */
	return data;
}

void namcoc65_device::namcos2_mcu_analog_port_w(uint8_t data)
{
}

uint8_t namcoc65_device::namcos2_mcu_analog_port_r()
{
	if (m_mcu_analog_complete == 1) m_mcu_analog_complete = 0;
	return m_mcu_analog_data;
}


void namcoc65_device::namcos2_mcu_analog_ctrl_w(uint8_t data)
{
	m_mcu_analog_ctrl = data & 0xff;

	/* Check if this is a start of conversion */
	/* Input ports 2 through 9 are the analog channels */

	if (data & 0x40)
	{
		/* Set the conversion complete flag */
		m_mcu_analog_complete = 2;
		/* We convert instantly, good eh! (not really) */
		switch ((data >> 2) & 0x07)
		{
		case 0:
			m_mcu_analog_data = m_port_analog_in_cb[0]();
			break;
		case 1:
			m_mcu_analog_data = m_port_analog_in_cb[1]();
			break;
		case 2:
			m_mcu_analog_data = m_port_analog_in_cb[2]();
			break;
		case 3:
			m_mcu_analog_data = m_port_analog_in_cb[3]();
			break;
		case 4:
			m_mcu_analog_data = m_port_analog_in_cb[4]();
			break;
		case 5:
			m_mcu_analog_data = m_port_analog_in_cb[5]();
			break;
		case 6:
			m_mcu_analog_data = m_port_analog_in_cb[6]();
			break;
		case 7:
			m_mcu_analog_data = m_port_analog_in_cb[7]();
			break;
		default:
			//output().set_value("anunk",data);
			break;
		}

		/* If the interrupt enable bit is set trigger an A/D IRQ */
		if (data & 0x20)
		{
			m_mcu->pulse_input_line(HD63705_INT_ADCONV, m_mcu->minimum_quantum_time());
		}
	}
}

uint8_t namcoc65_device::dpram_byte_r(offs_t offset)
{
	return m_dp_in(offset);
}

void namcoc65_device::dpram_byte_w(offs_t offset, uint8_t data)
{
	m_dp_out(offset,data);
}

void namcoc65_device::mcu_map(address_map &map)
{
	map(0x0000, 0x003f).ram(); /* Fill in register to stop logging */
	map(0x0000, 0x0000).nopr(); /* Keep logging quiet */
	map(0x0001, 0x0001).r(FUNC(namcoc65_device::mcub_r)); /* Usually P1/P2 direction inputs (UDL) + start buttons */
	map(0x0002, 0x0002).r(FUNC(namcoc65_device::mcuc_r)); /* Usually coins + start */
	map(0x0003, 0x0003).rw(FUNC(namcoc65_device::namcos2_mcu_port_d_r), FUNC(namcoc65_device::namcos2_mcu_port_d_w));
	map(0x0007, 0x0007).r(FUNC(namcoc65_device::mcuh_r)); /* Usually P1/P2 direction input (R) + Buttons 1,2,3 */
	map(0x0010, 0x0010).rw(FUNC(namcoc65_device::namcos2_mcu_analog_ctrl_r), FUNC(namcoc65_device::namcos2_mcu_analog_ctrl_w));
	map(0x0011, 0x0011).rw(FUNC(namcoc65_device::namcos2_mcu_analog_port_r), FUNC(namcoc65_device::namcos2_mcu_analog_port_w));
	map(0x0040, 0x01bf).ram();
	map(0x01c0, 0x1fff).rom(); /* internal ROM */
	map(0x2000, 0x2000).r(FUNC(namcoc65_device::mcudsw_r)); /* Dipswitch, including service mode */
	map(0x3000, 0x3000).r(FUNC(namcoc65_device::mcudi0_r));
	map(0x3001, 0x3001).r(FUNC(namcoc65_device::mcudi1_r));
	map(0x3002, 0x3002).r(FUNC(namcoc65_device::mcudi2_r));
	map(0x3003, 0x3003).r(FUNC(namcoc65_device::mcudi3_r));
	map(0x5000, 0x57ff).rw(FUNC(namcoc65_device::dpram_byte_r), FUNC(namcoc65_device::dpram_byte_w));
	map(0x6000, 0x6fff).nopr(); /* watchdog */
	map(0x8000, 0xffff).rom().region("external", 0); /* external ROM socket */
}


void namcoc65_device::device_add_mconfig(machine_config &config)
{
	HD63705(config, m_mcu, DERIVED_CLOCK(1, 1));
	m_mcu->set_addrmap(AS_PROGRAM, &namcoc65_device::mcu_map);
}

void namcoc65_device::device_resolve_objects()
{
	m_in_pb_cb.resolve_safe(0xff);
	m_in_pc_cb.resolve_safe(0xff);
	m_in_ph_cb.resolve_safe(0xff);
	m_in_pdsw_cb.resolve_safe(0xff);
	m_port_analog_in_cb.resolve_all_safe(0xff);
	m_port_dial_in_cb.resolve_all_safe(0xff);

	m_dp_in.resolve_safe(0xff);
	m_dp_out.resolve_safe();
}

void namcoc65_device::device_start()
{
	save_item(NAME(m_mcu_analog_ctrl));
	save_item(NAME(m_mcu_analog_data));
	save_item(NAME(m_mcu_analog_complete));
}

void namcoc65_device::device_reset()
{
	m_mcu_analog_ctrl = 0;
	m_mcu_analog_data = 0xaa;
	m_mcu_analog_complete = 0;
}

const tiny_rom_entry *namcoc65_device::device_rom_region() const
{
	return ROM_NAME(namcoc65);
}
