// license:BSD-3-Clause
// copyright-holders:Vas Crabb
#include "emu.h"
#include "taitosjsec.h"

DEFINE_DEVICE_TYPE(TAITO_SJ_SECURITY_MCU,  taito_sj_security_mcu_device, "taitosjsecmcu", "Taito SJ Security MCU Interface")


taito_sj_security_mcu_device::taito_sj_security_mcu_device(
		machine_config const &mconfig,
		char const *tag,
		device_t *owner,
		u32 clock)
	: device_t(mconfig,TAITO_SJ_SECURITY_MCU, tag, owner, clock)
	, m_mcu(*this, "mcu")
	, m_int_mode(int_mode::NONE)
	, m_68read_cb(*this)
	, m_68write_cb(*this)
	, m_68intrq_cb(*this)
	, m_busrq_cb(*this)
	, m_addr(0U)
	, m_mcu_data(0U)
	, m_host_data(0U)
	, m_read_data(0U)
	, m_zaccept(false)
	, m_zready(false)
	, m_pa_val(0U)
	, m_pb_val(0U)
	, m_busak(false)
	, m_reset(false)
{
}

u8 taito_sj_security_mcu_device::data_r(address_space &space, offs_t offset)
{
	if (BIT(offset, 0))
	{
		// ZLSTATUS
		machine().scheduler().boost_interleave(attotime::zero, attotime::from_usec(10));
		return
				(u8(space.unmap()) & 0xfc) |
				u8(m_zaccept ? 0x00 : 0x02) |
				u8(m_zready ? 0x00 : 0x01);
	}
	else
	{
		// ZLREAD
		if (!machine().side_effects_disabled())
			m_zaccept = true;
		return m_mcu_data;
	}
}

void taito_sj_security_mcu_device::data_w(offs_t offset, u8 data)
{
	if (BIT(offset, 0))
	{
		// ZINTRQ
		// if jumpered this way, the Z80 write strobe pulses the MCU interrupt line
		// should be PULSE_LINE because it's edge sensitive, but diexec only allows PULSE_LINE on reset and NMI
		if (int_mode::WRITE == m_int_mode)
			m_mcu->set_input_line(M68705_IRQ_LINE, HOLD_LINE);
	}
	else
	{
		// ZLWRITE
		device_scheduler &sched(machine().scheduler());
		sched.synchronize(timer_expired_delegate(FUNC(taito_sj_security_mcu_device::do_host_write), this), data);
		sched.boost_interleave(attotime::zero, attotime::from_usec(10));
	}
}

WRITE_LINE_MEMBER(taito_sj_security_mcu_device::busak_w)
{
	m_busak = (ASSERT_LINE == state);
}

WRITE_LINE_MEMBER(taito_sj_security_mcu_device::reset_w)
{
	m_reset = (ASSERT_LINE == state);
	if (CLEAR_LINE != state)
	{
		m_zaccept = true;
		m_zready = false;
		if (int_mode::LATCH == m_int_mode)
			m_mcu->set_input_line(M68705_IRQ_LINE, CLEAR_LINE);
	}
	m_mcu->set_input_line(INPUT_LINE_RESET, state);
}

void taito_sj_security_mcu_device::device_start()
{
	m_68read_cb.resolve_safe(0xff);
	m_68write_cb.resolve_safe();
	m_68intrq_cb.resolve_safe();
	m_busrq_cb.resolve_safe();

	save_item(NAME(m_addr));
	save_item(NAME(m_mcu_data));
	save_item(NAME(m_host_data));
	save_item(NAME(m_read_data));
	save_item(NAME(m_zaccept));
	save_item(NAME(m_zready));
	save_item(NAME(m_pa_val));
	save_item(NAME(m_pb_val));
	save_item(NAME(m_busak));
	save_item(NAME(m_reset));

	m_addr = 0xffffU;
	m_mcu_data = 0xffU;
	m_host_data = 0xffU;
	m_read_data = 0xffU;
	m_pb_val = 0xffU;
	m_busak = false;
	m_reset = false;
}

void taito_sj_security_mcu_device::device_reset()
{
	m_zaccept = true;
	m_zready = false;
	if (int_mode::LATCH == m_int_mode)
		m_mcu->set_input_line(M68705_IRQ_LINE, CLEAR_LINE);
}

void taito_sj_security_mcu_device::device_add_mconfig(machine_config &config)
{
	M68705P5(config, m_mcu, DERIVED_CLOCK(1, 1));
	m_mcu->porta_r().set(FUNC(taito_sj_security_mcu_device::mcu_pa_r));
	m_mcu->portc_r().set(FUNC(taito_sj_security_mcu_device::mcu_pc_r));
	m_mcu->porta_w().set(FUNC(taito_sj_security_mcu_device::mcu_pa_w));
	m_mcu->portb_w().set(FUNC(taito_sj_security_mcu_device::mcu_pb_w));
}

u8 taito_sj_security_mcu_device::mcu_pa_r()
{
	return get_bus_val();
}

u8 taito_sj_security_mcu_device::mcu_pc_r()
{
	// FIXME 68INTAK is on PC3 but we're ignoring it
	return
			(m_zready ? 0x01U : 0x00U) |
			(m_zaccept ? 0x02U : 0x00U) |
			(m_busak ? 0x00U : 0x04U);
}

void taito_sj_security_mcu_device::mcu_pa_w(u8 data)
{
	m_pa_val = data;
	if (BIT(~m_pb_val, 6))
		m_addr = (m_addr & 0xff00U) | u16(get_bus_val());
}

void taito_sj_security_mcu_device::mcu_pb_w(u8 data)
{
	bool inc_addr(false);
	u8 const diff(m_pb_val ^ data);

	// 68INTRQ
	if (BIT(diff, 0))
		m_68intrq_cb(BIT(data, 0) ? CLEAR_LINE : ASSERT_LINE);

	// 68LRD
	u8 const bus_val(get_bus_val());
	if (BIT(diff & data, 1))
	{
		machine().scheduler().synchronize(timer_expired_delegate(FUNC(taito_sj_security_mcu_device::do_mcu_read), this));
		if (int_mode::LATCH == m_int_mode)
			m_mcu->set_input_line(M68705_IRQ_LINE, CLEAR_LINE);
	}

	// 68LWR
	if (BIT(diff & data, 2))
		machine().scheduler().synchronize(timer_expired_delegate(FUNC(taito_sj_security_mcu_device::do_mcu_write), this), bus_val);

	// BUSRQ
	if (BIT(diff, 3))
		m_busrq_cb(BIT(data, 3) ? CLEAR_LINE : ASSERT_LINE);

	// 68WRITE
	if (BIT(diff, 4))
	{
		if (BIT(~data, 4))
			m_68write_cb(m_addr, bus_val);
		else if (BIT(data, 5))
			inc_addr = true;
	}

	// 68READ
	if (BIT(diff, 5))
	{
		if (BIT(~data, 5))
			m_read_data = m_68read_cb(m_addr);
		else if (BIT(data, 4))
			inc_addr = true;
	}

	// LAL
	if (BIT(~data, 6))
		m_addr = (m_addr & 0xff00U) | u16(bus_val);
	else if (inc_addr)
		m_addr = (m_addr & 0xff00U) | ((m_addr + 1) & 0x00ffU);

	// UAL
	if (BIT(~data, 7))
		m_addr = (m_addr & 0x00ffU) | (u16(bus_val) << 8);

	m_pb_val = data;
}

TIMER_CALLBACK_MEMBER(taito_sj_security_mcu_device::do_mcu_read)
{
	m_zready = false;
}

TIMER_CALLBACK_MEMBER(taito_sj_security_mcu_device::do_mcu_write)
{
	m_mcu_data = u8(param);
	if (!m_reset)
		m_zaccept = false;
}

TIMER_CALLBACK_MEMBER(taito_sj_security_mcu_device::do_host_write)
{
	m_host_data = u8(param);
	if (!m_reset)
	{
		m_zready = true;
		if (int_mode::LATCH == m_int_mode)
			m_mcu->set_input_line(M68705_IRQ_LINE, ASSERT_LINE);
	}
}
