// license: GPL-2.0+
// copyright-holders: Dirk Best
/***************************************************************************

    GAYLE

    Gate array used in the Amiga 600 and Amiga 1200 computers for the
    following functions:
    - Address decoding and timing for ROM, RAM, CIA, RTC
      (and unused: Flash ROM, ArcNet)
    - Data buffers for chip RAM
    - IDE interface
    - Credit card (PCMCIA) interface
    - System reset
    - Floppy helper
    - E clock generation

    TODO:
    - Generate bus errors (REG_CHANGE, bit 0)
    - Disabling the PCMCIA interface should remove access to PCMCIA memory
    - Move more functionality here

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

#include "emu.h"
#include "gayle.h"

#define LOG_REG     (1U << 1)
#define LOG_IDE     (1U << 2)
#define LOG_CC      (1U << 3)
#define LOG_ID      (1U << 4)

//#define VERBOSE (LOG_GENERAL | LOG_REG | LOG_CC)
#include "logmacro.h"


//**************************************************************************
//  DEVICE DEFINITIONS
//**************************************************************************

DEFINE_DEVICE_TYPE(GAYLE, gayle_device, "gayle", "Amiga GAYLE")


//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

//-------------------------------------------------
//  gayle_device - constructor
//-------------------------------------------------

gayle_device::gayle_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	device_t(mconfig, GAYLE, tag, owner, clock),
	m_int2_w(*this),
	m_int6_w(*this),
	m_rst_w(*this),
	m_ide_cs_r_cb(*this),
	m_ide_cs_w_cb(*this),
	m_gayle_id(0xff),
	m_gayle_id_count(0)
{
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void gayle_device::device_start()
{
	// resolve callbacks
	m_int2_w.resolve_safe();
	m_int6_w.resolve_safe();
	m_rst_w.resolve_safe();
	m_ide_cs_r_cb.resolve_all_safe(0xffff);
	m_ide_cs_w_cb.resolve_all_safe();

	save_item(NAME(m_gayle_id_count));
	save_item(NAME(m_gayle_reg));
	save_item(NAME(m_line_state));
}

//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void gayle_device::device_reset()
{
	// internal registers are cleared to 0 on reset
	m_gayle_reg[0] = 0;
	m_gayle_reg[1] = 0;
	m_gayle_reg[2] = 0;
	m_gayle_reg[3] = 0;

	// lower interrupts
	m_int2_w(0);
	m_int6_w(0);
}


//**************************************************************************
//  ADDRESS MAP
//**************************************************************************

void gayle_device::register_map(address_map &map)
{
	// base address 0xdaXXXX
	map(0x0000, 0x001f).mirror(0x0fe0).rw(FUNC(gayle_device::ide_cs_r<1>), FUNC(gayle_device::ide_cs_w<1>));
	map(0x2000, 0x201f).mirror(0x0fe0).rw(FUNC(gayle_device::ide_cs_r<0>), FUNC(gayle_device::ide_cs_w<0>));
	map(0x8000, 0x8001).rw(FUNC(gayle_device::status_r), FUNC(gayle_device::status_w));
	map(0x9000, 0x9001).rw(FUNC(gayle_device::change_r), FUNC(gayle_device::change_w));
	map(0xa000, 0xa001).rw(FUNC(gayle_device::int_r), FUNC(gayle_device::int_w));
	map(0xb000, 0xb001).rw(FUNC(gayle_device::control_r), FUNC(gayle_device::control_w));
}


//**************************************************************************
//  INTERNAL
//**************************************************************************

void gayle_device::dump_register()
{
	LOGMASKED(LOG_REG, "status = %02x, change = %02x, int = %02x, control = %02x\n",
		m_gayle_reg[REG_STATUS], m_gayle_reg[REG_CHANGE], m_gayle_reg[REG_INT], m_gayle_reg[REG_CONTROL]);
}

void gayle_device::line_change(int line, int state, int level)
{
	LOGMASKED(line == LINE_IDE ? LOG_IDE : LOG_CC, "line_change: %d %d %d\n", line, state, level);

	m_line_state &= ~(1 << line);
	m_line_state |= (state << line);

	// ignore pcmcia line changes if the interface is disabled
	if (line != LINE_IDE && BIT(m_gayle_reg[REG_STATUS], 0))
		return;

	// did we change state?
	if (BIT(m_gayle_reg[REG_STATUS], line) != state)
	{
		// indicate a change to the line
		m_gayle_reg[REG_CHANGE] |= 1 << line;

		// special handling for line 6 (credit card detect)
		if (line == LINE_CC_DET && BIT(m_gayle_reg[REG_CHANGE], 1))
		{
			LOG("resetting due to credit card detection change\n");

			m_rst_w(0);
			m_rst_w(1);
			reset();

			return;
		}

		if (state)
			m_gayle_reg[REG_STATUS] |= 1 << line;
		else
			m_gayle_reg[REG_STATUS] &= ~(1 << line);

		// generate interrupt (if enabled)
		if (BIT(m_gayle_reg[REG_INT], line))
		{
			LOGMASKED(line == LINE_IDE ? LOG_IDE : LOG_CC, "line %d int assert\n", line);

			if (level == 2)
				m_int2_w(1);
			else
				m_int6_w(1);
		}
	}
}


//**************************************************************************
//  REGISTER
//**************************************************************************

uint16_t gayle_device::status_r()
{
	// 7-------  ide interrupt status
	// -6------  credit card detect
	// --5-----  battery voltage 1/status change
	// ---4----  battery voltage 2/digital audio
	// ----3---  credit card write enable
	// -----2--  credit card busy/interrupt request
	// ------1-  enable pcmcia digital audio
	// -------0  disable pcmcia

	if (!machine().side_effects_disabled())
		LOGMASKED(LOG_REG, "status_r: %02x\n", m_gayle_reg[REG_STATUS]);

	return (m_gayle_reg[REG_STATUS] << 8) | m_gayle_reg[REG_STATUS];
}

void gayle_device::status_w(uint16_t data)
{
	uint8_t previous = m_gayle_reg[REG_STATUS];
	data >>= 8;
	LOGMASKED(LOG_REG, "status_w: %02x\n", data);

	// pcmcia interface disable?
	if (BIT(data, 0) == 1)
		m_gayle_reg[REG_STATUS] &= 0x03;

	// lowest two bits can be set freely
	m_gayle_reg[REG_STATUS] &= ~0x03;
	m_gayle_reg[REG_STATUS] |= data & 0x03;

	// status bits can only be set
	m_gayle_reg[REG_STATUS] |= data & 0xfc;

	// pcmcia interface re-enabled?
	if (BIT(previous, 0) == 1 && BIT(data, 0) == 0)
	{
		cc_cd_w(BIT(m_line_state, 6));
		cc_bvd1_w(BIT(m_line_state, 5));
		cc_bvd2_w(BIT(m_line_state, 4));
		cc_wp_w(BIT(m_line_state, 3));
	}
}

uint16_t gayle_device::change_r()
{
	// 7-------  ide interrupt changed
	// -6------  credit card detect changed
	// --5-----  battery voltage 1/status change changed
	// ---4----  battery voltage 2/digital audio changed
	// ----3---  credit card write enable changed
	// -----2--  credit card busy/interrupt request changed
	// ------1-  generate reset after cc detect change
	// -------0  generate bus error after cc detect change

	if (!machine().side_effects_disabled())
		LOGMASKED(LOG_REG, "change_r: %02x\n", m_gayle_reg[REG_CHANGE]);

	return (m_gayle_reg[REG_CHANGE] << 8) | m_gayle_reg[REG_CHANGE];
}

void gayle_device::change_w(uint16_t data)
{
	data >>= 8;
	LOGMASKED(LOG_REG, "change_w: %02x\n", data);

	// clear ide interrupt?
	if (BIT(data, 7) == 0)
	{
		LOGMASKED(LOG_REG, "ide int cleared\n", data);
		m_int2_w(0);
	}

	// clear cc detect interrupt?
	if (BIT(data, 6) == 0)
	{
		LOGMASKED(LOG_REG, "cc detect int cleared\n", data);
		m_int6_w(0);
	}

	// clear bvd1/sc interrupt?
	if (BIT(data, 5) == 0)
	{
		LOGMASKED(LOG_REG, "bvd1/sc int cleared\n", data);
		if (BIT(m_gayle_reg[REG_INT], 1))
			m_int6_w(0);
		else
			m_int2_w(0);
	}

	// clear bvd2/da interrupt?
	if (BIT(data, 4) == 0)
	{
		LOGMASKED(LOG_REG, "bvd2/da int cleared\n", data);
		if (BIT(m_gayle_reg[REG_INT], 1))
			m_int6_w(0);
		else
			m_int2_w(0);
	}

	// clear write protect interrupt?
	if (BIT(data, 3) == 0)
	{
		LOGMASKED(LOG_REG, "write protect int cleared\n", data);
		m_int2_w(0);
	}

	// enabling reset on cc detect change can immediately generate a reset
	if (BIT(data, 1) && BIT(m_gayle_reg[REG_CHANGE], 6))
	{
		LOGMASKED(LOG_REG, "resetting\n", data);

		m_rst_w(0);
		m_rst_w(1);
		reset();

		return;
	}

	m_gayle_reg[REG_CHANGE] = (m_gayle_reg[REG_CHANGE] & data) | (data & 0x03);
}

uint16_t gayle_device::int_r()
{
	// 7-------  ide interrupt enable
	// -6------  credit card detect interrupt enable
	// --5-----  battery voltage 1/status change interrupt enable
	// ---4----  battery voltage 2/digital audio interrupt enable
	// ----3---  write enable interrupt enable
	// -----2--  busy/interrupt request interrupt enable
	// ------1-  set interrupt level for battery voltage 1/2
	// -------0  set interrupt level for busy/interrupt request

	if (!machine().side_effects_disabled())
		LOGMASKED(LOG_REG, "int_r: %02x\n", m_gayle_reg[REG_INT]);

	return (m_gayle_reg[REG_INT] << 8) | m_gayle_reg[REG_INT];
}

void gayle_device::int_w(uint16_t data)
{
	data >>= 8;
	LOGMASKED(LOG_REG, "int_w: %02x\n", data);

	m_gayle_reg[REG_INT] = data;
}

uint16_t gayle_device::control_r()
{
	// 7654----  not implemented in hardware
	// ----32--- slow mem (250 ns, 150 ns, 100 ns, 720 ns)
	// ------1-  program 12v
	// -------0  program 5v

	if (!machine().side_effects_disabled())
		LOGMASKED(LOG_REG, "control_r: %02x\n", m_gayle_reg[REG_CONTROL]);

	return (m_gayle_reg[REG_CONTROL] << 8) | m_gayle_reg[REG_CONTROL];
}

void gayle_device::control_w(uint16_t data)
{
	data >>= 8;
	LOGMASKED(LOG_REG, "control_w: %02x\n", data);

	m_gayle_reg[REG_CONTROL] = data;
}


//**************************************************************************
//  IDE
//**************************************************************************

template<int N>
uint16_t gayle_device::ide_cs_r(offs_t offset, uint16_t mem_mask)
{
	return m_ide_cs_r_cb[N]((offset >> 1) & 0x07, mem_mask);
}

template<int N>
void gayle_device::ide_cs_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	m_ide_cs_w_cb[N]((offset >> 1) & 0x07, data, mem_mask);
}

WRITE_LINE_MEMBER( gayle_device::ide_interrupt_w )
{
	LOGMASKED(LOG_IDE, "ide_interrupt_w: %d\n", state);
	line_change(7, state, 2);
}


//**************************************************************************
//  CREDIT CARD
//**************************************************************************

WRITE_LINE_MEMBER( gayle_device::cc_cd_w )
{
	LOGMASKED(LOG_CC, "cc_cd_w: %d\n", state);
	line_change(LINE_CC_DET, state, 6);
}

WRITE_LINE_MEMBER( gayle_device::cc_bvd1_w )
{
	LOGMASKED(LOG_CC, "cc_bvd1_w: %d\n", state);
	line_change(LINE_CC_BVD1_SC, state, BIT(m_gayle_reg[REG_INT], 1) ? 6 : 2);
}

WRITE_LINE_MEMBER( gayle_device::cc_bvd2_w )
{
	LOGMASKED(LOG_CC, "cc_bvd2_w: %d\n", state);
	line_change(LINE_CC_BVD2_DA, state, BIT(m_gayle_reg[REG_INT], 1) ? 6 : 2);
}

WRITE_LINE_MEMBER( gayle_device::cc_wp_w )
{
	LOGMASKED(LOG_CC, "cc_wp_w: %d\n", state);
	line_change(LINE_CC_WP, state, 2);
}


//**************************************************************************
//  ID
//**************************************************************************

uint16_t gayle_device::gayle_id_r(offs_t offset, uint16_t mem_mask)
{
	uint16_t data;

	if (ACCESSING_BITS_8_15)
		data = ((m_gayle_id << m_gayle_id_count++) & 0x80) << 8;
	else
		data = 0xffff;

	LOGMASKED(LOG_ID, "gayle_id_r(%06x): %04x & %04x (id=%02x)\n", offset, data, mem_mask, m_gayle_id);

	return data;
}

void gayle_device::gayle_id_w(offs_t offset, uint16_t data, uint16_t mem_mask)
{
	LOGMASKED(LOG_ID, "gayle_id_w(%06x): %04x & %04x (id=%02x)\n", offset, data, mem_mask, m_gayle_id);

	m_gayle_id_count = 0;
}
