// license:BSD-3-Clause
// copyright-holders:Sven Schnelle
/***************************************************************************

  HP Human interface board

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

#include "emu.h"
#include "human_interface.h"
#include "cpu/mcs48/mcs48.h"
#include "machine/tms9914.h"
#include "machine/msm58321.h"
#include "sound/sn76496.h"
#include "bus/hp_hil/hp_hil.h"
#include "bus/hp_hil/hil_devices.h"
#include "bus/ieee488/ieee488.h"
#include "speaker.h"
//#define VERBOSE 1
#include "logmacro.h"

DEFINE_DEVICE_TYPE(HPDIO_HUMAN_INTERFACE, bus::hp_dio::human_interface_device, "human_interface", "HP human interface card")

namespace bus::hp_dio {

void human_interface_device::device_add_mconfig(machine_config &config)
{
	i8042ah_device &iocpu(I8042AH(config, "iocpu", XTAL(5'000'000)));
	iocpu.set_addrmap(AS_PROGRAM, &human_interface_device::iocpu_map);
	iocpu.p1_out_cb().set(FUNC(human_interface_device::iocpu_port1_w));
	iocpu.p2_out_cb().set(FUNC(human_interface_device::iocpu_port2_w));
	iocpu.p1_in_cb().set(FUNC(human_interface_device::iocpu_port1_r));
	iocpu.t0_in_cb().set(FUNC(human_interface_device::iocpu_test0_r));
	iocpu.p2_in_cb().set_constant(0xdf);
	iocpu.t1_in_cb().set_constant(1);

	HP_HIL_MLC(config, m_mlc, XTAL(8'000'000));
	HP_HIL_SLOT(config, "hil1", m_mlc, hp_hil_devices, "hp_46021a");
	HP_HIL_SLOT(config, "hil2", m_mlc, hp_hil_devices, "hp_46060b");

	SPEAKER(config, "mono").front_center();
	sn76494_device &sound(SN76494(config, "sn76494", 333333));
	sound.add_route(ALL_OUTPUTS, "mono", 0.75);

	msm58321_device &rtc(MSM58321(config, "rtc", 32.768_kHz_XTAL));
	rtc.set_default_24h(false);
	rtc.d0_handler().set(FUNC(human_interface_device::rtc_d0_w));
	rtc.d1_handler().set(FUNC(human_interface_device::rtc_d1_w));
	rtc.d2_handler().set(FUNC(human_interface_device::rtc_d2_w));
	rtc.d3_handler().set(FUNC(human_interface_device::rtc_d3_w));

	tms9914_device &gpib(TMS9914(config, "tms9914", XTAL(5'000'000)));
	gpib.eoi_write_cb().set(IEEE488_TAG, FUNC(ieee488_device::host_eoi_w));
	gpib.dav_write_cb().set(IEEE488_TAG, FUNC(ieee488_device::host_dav_w));
	gpib.nrfd_write_cb().set(IEEE488_TAG, FUNC(ieee488_device::host_nrfd_w));
	gpib.ndac_write_cb().set(IEEE488_TAG, FUNC(ieee488_device::host_ndac_w));
	gpib.ifc_write_cb().set(IEEE488_TAG, FUNC(ieee488_device::host_ifc_w));
	gpib.srq_write_cb().set(IEEE488_TAG, FUNC(ieee488_device::host_srq_w));
	gpib.atn_write_cb().set(IEEE488_TAG, FUNC(ieee488_device::host_atn_w));
	gpib.ren_write_cb().set(IEEE488_TAG, FUNC(ieee488_device::host_ren_w));
	gpib.dio_write_cb().set(IEEE488_TAG, FUNC(ieee488_device::host_dio_w));
	gpib.dio_read_cb().set(IEEE488_TAG, FUNC(ieee488_device::dio_r));
	gpib.int_write_cb().set(FUNC(human_interface_device::gpib_irq));
	gpib.accrq_write_cb().set(FUNC(human_interface_device::gpib_dreq));

	ieee488_device &ieee488(IEEE488(config, IEEE488_TAG, 0));
	ieee488.eoi_callback().set(m_tms9914, FUNC(tms9914_device::eoi_w));
	ieee488.dav_callback().set(m_tms9914, FUNC(tms9914_device::dav_w));
	ieee488.nrfd_callback().set(m_tms9914, FUNC(tms9914_device::nrfd_w));
	ieee488.ndac_callback().set(m_tms9914, FUNC(tms9914_device::ndac_w));
	ieee488.ifc_callback().set(m_tms9914, FUNC(tms9914_device::ifc_w));
	ieee488.srq_callback().set(m_tms9914, FUNC(tms9914_device::srq_w));
	ieee488.atn_callback().set(m_tms9914, FUNC(tms9914_device::atn_w));
	ieee488.ren_callback().set(m_tms9914, FUNC(tms9914_device::ren_w));
	ieee488.dio_callback().set(FUNC(human_interface_device::ieee488_dio_w));

	ieee488_slot_device &slot0(IEEE488_SLOT(config, "ieee0", 0));
	hp_ieee488_devices(slot0);
	slot0.set_default_option("hp9122c");
}

ROM_START(human_interface)
	ROM_REGION(0x800 , "iocpu" , 0)
	ROM_LOAD( "1820-4784.bin", 0x000000, 0x000800, CRC(e929044a) SHA1(90849a10bdb8c6e38e73ce027c9c0ad8b3956b1b))
ROM_END

const tiny_rom_entry *human_interface_device::device_rom_region() const
{
	return ROM_NAME(human_interface);
}

void human_interface_device::iocpu_map(address_map& map)
{
	map(0x0000, 0x07ff).rom().region("iocpu", 0);
}

human_interface_device::human_interface_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	human_interface_device(mconfig, HPDIO_HUMAN_INTERFACE, tag, owner, clock)
{
}

human_interface_device::human_interface_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
	device_t(mconfig, type, tag, owner, clock),
	device_dio16_card_interface(mconfig, *this),
	m_iocpu(*this, "iocpu"),
	m_mlc(*this, "mlc"),
	m_sound(*this, "sn76494"),
	m_tms9914(*this, "tms9914"),
	m_rtc(*this, "rtc"),
	m_ieee488(*this, IEEE488_TAG),
	m_hil_read(false),
	m_kbd_nmi(false),
	m_gpib_irq_line(false),
	m_gpib_dma_line(false),
	m_old_latch_enable(false),
	m_gpib_dma_enable(false),
	m_hil_data(0),
	m_latch_data(0),
	m_rtc_data(0),
	m_ppoll_sc(0),
	m_ppoll_mask(0)
{
}

void human_interface_device::device_start()
{
	program_space().install_readwrite_handler(0x420000, 0x420003, 0x0003, 0xfffc, 0,
			read8sm_delegate(*m_iocpu, FUNC(upi41_cpu_device::upi41_master_r)),
			write8sm_delegate(*m_iocpu, FUNC(upi41_cpu_device::upi41_master_w)), 0x00ff00ff);

	program_space().install_readwrite_handler(0x470000, 0x47001f, 0x1f, 0xffe0, 0,
			read8sm_delegate(*this, FUNC(human_interface_device::gpib_r)),
			write8sm_delegate(*this, FUNC(human_interface_device::gpib_w)), 0x00ff00ff);

	save_item(NAME(m_hil_read));
	save_item(NAME(m_kbd_nmi));
	save_item(NAME(m_gpib_irq_line));
	save_item(NAME(m_gpib_dma_line));
	save_item(NAME(m_old_latch_enable));
	save_item(NAME(m_hil_data));
	save_item(NAME(m_latch_data));
	save_item(NAME(m_rtc_data));
	save_item(NAME(m_ppoll_mask));
	save_item(NAME(m_ppoll_sc));
	save_item(NAME(m_gpib_dma_enable));
}

void human_interface_device::device_reset()
{
	m_hil_read = false;
	m_kbd_nmi = false;
	m_gpib_irq_line = false;
	m_gpib_dma_line = false;
	m_gpib_dma_enable = false;
	m_old_latch_enable = true;
	m_ppoll_sc = 0;
	m_hil_data = 0;
	m_latch_data = 0;
	m_rtc_data = 0;
	m_ppoll_mask = 0;
	m_rtc->cs1_w(ASSERT_LINE);
	m_rtc->cs2_w(CLEAR_LINE);
	m_rtc->write_w(CLEAR_LINE);
	m_rtc->read_w(CLEAR_LINE);
	m_rtc->cs2_w(CLEAR_LINE);
	m_iocpu->reset();
}

void human_interface_device::reset_in(int state)
{
	if (state)
		device_reset();
}

void human_interface_device::update_gpib_irq()
{
	irq3_out((m_gpib_irq_line ||
		((m_ppoll_sc & (PPOLL_IR|PPOLL_IE)) == (PPOLL_IR|PPOLL_IE))) ? ASSERT_LINE : CLEAR_LINE);
}

void human_interface_device::gpib_irq(int state)
{
	m_gpib_irq_line = state;
	update_gpib_irq();
}

void human_interface_device::update_gpib_dma()
{
	dmar0_out(m_gpib_dma_enable && m_gpib_dma_line);
}

void human_interface_device::gpib_dreq(int state)
{
	m_gpib_dma_line = state;
	update_gpib_dma();
}

void human_interface_device::ieee488_dio_w(uint8_t data)
{
	if (m_ieee488->atn_r() || m_ieee488->eoi_r())
		return;

	if ((m_ppoll_mask & ~data) && (m_ppoll_sc & PPOLL_IE)) {
		LOG("%s: parallel poll triggered\n", __func__);
		if (m_ppoll_sc & PPOLL_IE)
			m_ppoll_sc |= PPOLL_IR;
		update_gpib_irq();
	}
}

void human_interface_device::gpib_w(offs_t offset, uint8_t data)
{
	if (offset & 0x08) {
		m_tms9914->write(offset & 0x07, data);
		return;
	}

	switch (offset) {
	case 0:
		device_reset();
		break;

	case 1:
		m_gpib_dma_enable = data & 0x01;
		update_gpib_dma();
		break;
	case 3:
		m_ppoll_sc = data & PPOLL_IE;

		if (!(data & PPOLL_IR)) {
			m_ppoll_sc &= ~PPOLL_IR;
			update_gpib_irq();
		}

		if (m_ppoll_sc & PPOLL_IE) {
			LOG("%s: start parallel poll\n", __func__);
			ieee488_dio_w(m_ieee488->dio_r());
		}
		break;
	case 4:
		m_ppoll_mask = data;
		break;
	default:
		break;
	}
	LOG("gpib_w: %s %02X = %02X\n", machine().describe_context().c_str(), offset, data);
}

uint8_t human_interface_device::gpib_r(offs_t offset)
{
	uint8_t data = 0xff;

	if (offset & 0x8) {
		data = m_tms9914->read(offset & 0x07);
		return data;
	}

	switch(offset) {
	case 0: /* ID */
		data = 0x80;
		break;
	case 1:
		/* Int control */
		data = 0x80 | (m_gpib_irq_line ? 0x40 : 0) | (m_gpib_dma_enable ? 0x01 : 0);
		break;
	case 2:
		/* Address */
		data = (m_tms9914->cont_r() ? 0x0 : 0x40) | 0x81;
		if (m_kbd_nmi)
			data |= 0x04;
		break;
	case 3:
		/* Parallel poll status/control */
		data = m_ppoll_sc;
		break;
	case 4:
		/* Parallel poll mask */
		data = m_ppoll_mask;
		break;
	}
	LOG("gpib_r: %s %02X = %02X\n", machine().describe_context().c_str(), offset, data);
	return data;
}


void human_interface_device::iocpu_port1_w(uint8_t data)
{
	m_hil_data = data;
	m_rtc->d0_w(data & 0x01 ? ASSERT_LINE : CLEAR_LINE);
	m_rtc->d1_w(data & 0x02 ? ASSERT_LINE : CLEAR_LINE);
	m_rtc->d2_w(data & 0x04 ? ASSERT_LINE : CLEAR_LINE);
	m_rtc->d3_w(data & 0x08 ? ASSERT_LINE : CLEAR_LINE);
}

void human_interface_device::iocpu_port2_w(uint8_t data)
{
	bool latch_enable = data & LATCH_EN;

	if ((data & (HIL_CS|HIL_WE)) == 0)
		m_mlc->write((m_latch_data & 0xc0) >> 6, m_hil_data);

	if ((data & SN76494_EN) == 0)
		m_sound->write(m_hil_data);

	m_hil_read = ((data & (HIL_CS|HIL_OE)) == 0);

	m_rtc->address_write_w(data & 0x02 ? ASSERT_LINE : CLEAR_LINE);
	m_rtc->write_w(data & 0x04 ? ASSERT_LINE : CLEAR_LINE);

	if (!m_old_latch_enable && latch_enable) {
		m_latch_data = m_hil_data;
		m_rtc->read_w(m_latch_data & 0x20 ? ASSERT_LINE : CLEAR_LINE);
		m_rtc->cs2_w(m_latch_data & 0x10 ? ASSERT_LINE : CLEAR_LINE);
	}
	irq1_out((data & 0x10) ? ASSERT_LINE : CLEAR_LINE);

	if (!(data & KBD_RESET)) {
		irq7_out(ASSERT_LINE);
		m_kbd_nmi = true;
	} else {
		irq7_out(CLEAR_LINE);
		m_kbd_nmi = false;
	}
	m_old_latch_enable = latch_enable;
}

uint8_t human_interface_device::iocpu_port1_r()
{
	if (m_hil_read)
		return m_mlc->read((m_latch_data & 0xc0) >> 6);
	if (m_latch_data & 0x20)
		return m_rtc_data;
	return 0xff;
}

uint8_t human_interface_device::iocpu_test0_r()
{
	return !m_mlc->get_int();
}

void human_interface_device::rtc_d0_w(int state)
{
	if (state)
		m_rtc_data |= 1;
	else
		m_rtc_data &= ~1;

}

void human_interface_device::rtc_d1_w(int state)
{
	if (state)
		m_rtc_data |= 2;
	else
		m_rtc_data &= ~2;
}

void human_interface_device::rtc_d2_w(int state)
{
	if (state)
		m_rtc_data |= 4;
	else
		m_rtc_data &= ~4;

}

void human_interface_device::rtc_d3_w(int state)
{
	if (state)
		m_rtc_data |= 8;
	else
		m_rtc_data &= ~8;

}

void human_interface_device::dmack_w_in(int channel, uint8_t data)
{
	if (channel)
		return;
	m_tms9914->write(7, data);
}

uint8_t human_interface_device::dmack_r_in(int channel)
{
	if (channel || !m_gpib_dma_enable)
		return 0xff;
	return m_tms9914->read(7);
}

} // namespace bus::hp_dio
