// license:BSD-3-Clause
// copyright-holders:Ryan Holtz
/******************************************************************************
*
*   Sony Playstation 2 GS interface (GIF) device skeleton
*
*   To Do:
*     Everything
*
*/

#include "emu.h"
#include "ps2gif.h"

DEFINE_DEVICE_TYPE(SONYPS2_GIF, ps2_gif_device, "ps2gif", "Playstation 2 GIF")

ps2_gif_device::ps2_gif_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, SONYPS2_GIF, tag, owner, clock)
	, device_execute_interface(mconfig, *this)
	, m_gs(*this, finder_base::DUMMY_TAG)
	, m_vu1(*this, finder_base::DUMMY_TAG)
{
}

ps2_gif_device::~ps2_gif_device()
{
}

ps2_gif_device::tag_t::tag_t(const uint64_t hi, const uint64_t lo)
	: m_nloop(lo & 0x7fffULL)
	, m_end((lo >> 15) & 1)
	, m_prim_enable((lo >> 46) & 1)
	, m_prim_output(false)
	, m_prim((lo >> 47) & 0x7ff)
	, m_format((format_t)((lo >> 58) & 3))
	, m_reg_count((uint8_t)(lo >> 60))
	, m_curr_reg(0)
{
	if (!m_reg_count)
		m_reg_count = 16;

	for (uint32_t shift = 0, index = 0; shift < 64; shift += 4, index++)
	{
		m_regs[index] = (uint8_t)(hi >> shift) & 0xf;
	}

	m_words[0] = (uint32_t)(hi >> 32);
	m_words[1] = (uint32_t)hi;
	m_words[2] = (uint32_t)(lo >> 32);
	m_words[3] = (uint32_t)lo;
}

void ps2_gif_device::device_start()
{
	set_icountptr(m_icount);

	save_item(NAME(m_icount));

	save_item(NAME(m_ctrl));
	save_item(NAME(m_mode));
	save_item(NAME(m_stat));
	save_item(NAME(m_cnt));
	save_item(NAME(m_p3cnt));
	save_item(NAME(m_p3tag));
	save_item(NAME(m_p3mask));
	save_item(NAME(m_next_path3_hi));
	save_item(NAME(m_next_path3_lo));
	save_item(NAME(m_path3_available));
	save_item(NAME(m_vu_mem_address));

	// TODO: Save current tag
}

void ps2_gif_device::device_reset()
{
	gif_reset();
}

void ps2_gif_device::gif_reset()
{
	m_ctrl = CTRL_RST;
	m_mode = 0;
	m_stat = 0;
	m_cnt = 0;
	m_p3cnt = 0;
	m_p3tag = 0;
	m_p3mask = false;
	m_current_path1_tag = tag_t();
	m_current_path3_tag = tag_t();
	m_last_tag = tag_t();
	m_path3_available = true;
	m_next_path3_hi = 0;
	m_next_path3_lo = 0;
	m_vu_mem_address = 0;
}

uint32_t ps2_gif_device::read(offs_t offset)
{
	uint32_t ret = 0;
	switch (offset)
	{
		case 0x00/4: // GIF_CTRL
			if (BIT(m_ctrl, 3))
			{
				ret = m_ctrl;
				logerror("%s: Read: GIF_CTRL (%08x)\n", machine().describe_context(), ret);
			}
			else
			{
				logerror("%s: Read: GIF_CTRL (00000000) (read-only; actual value %08x)\n", machine().describe_context(), m_ctrl);
			}
			break;

		case 0x10/4: // GIF_MODE
			ret = m_mode;
			logerror("%s: Read: GIF_MODE (00000000) (read-only; actual value %08x)\n", machine().describe_context(), ret);
			break;

		case 0x20/4: // GIF_STAT
			ret = m_stat;
			logerror("%s: Read: GIF_STAT (%08x)\n", machine().describe_context(), ret);
			break;

		case 0x40/4: // GIF_TAG0
			ret = m_last_tag.word(0);
			logerror("%s: Read: GIF_TAG0 (%08x)\n", machine().describe_context(), ret);
			break;

		case 0x50/4: // GIF_TAG1
			ret = m_last_tag.word(1);
			logerror("%s: Read: GIF_TAG1 (%08x)\n", machine().describe_context(), ret);
			break;

		case 0x60/4: // GIF_TAG2
			ret = m_last_tag.word(2);
			logerror("%s: Read: GIF_TAG2 (%08x)\n", machine().describe_context(), ret);
			break;

		case 0x70/4: // GIF_TAG3
			ret = m_last_tag.word(0);
			logerror("%s: Read: GIF_TAG3 (%08x)\n", machine().describe_context(), ret);
			break;

		case 0x80/4: // GIF_CNT
			ret = m_cnt;
			logerror("%s: Read: GIF_CNT (%08x)\n", machine().describe_context(), ret);
			break;

		case 0x90/4: // GIF_P3CNT
			ret = m_p3cnt;
			logerror("%s: Read: GIF_P3CNT (%08x)\n", machine().describe_context(), ret);
			break;

		case 0xa0/4: // GIF_P3TAG
			ret = m_p3tag;
			logerror("%s: Read: GIF_P3TAG (%08x)\n", machine().describe_context(), ret);
			break;

		default:
			logerror("%s: Read: Unknown %08x\n", machine().describe_context(), 0x10003000 + (offset << 2));
			break;
	}
	return ret;
}

void ps2_gif_device::write(offs_t offset, uint32_t data)
{
	switch (offset)
	{
		case 0x00/4: // GIF_CTRL
			m_ctrl = data;
			if (BIT(data, 0))
			{
				gif_reset();
			}
			logerror("%s: Write: GIF_CTRL = %08x: STOP=%d, RESET=%d\n", machine().describe_context(), data, BIT(data, 3), BIT(data, 0));
			break;

		case 0x10/4: // GIF_MODE
			m_mode = data;
			logerror("%s: Write: GIF_MODE = %08x: IMT=%s, MASK=%s\n", machine().describe_context(), data, BIT(data, 2) ? "Intermittent" : "Continuous", BIT(data, 0) ? "Yes" : "No");
			break;

		case 0x20/4: // GIF_STAT
			logerror("%s: Write: GIF_STAT = %08x (ignored)\n", machine().describe_context(), data);
			break;

		case 0x40/4: // GIF_TAG0
			logerror("%s: Write: GIF_TAG0 = %08x (ignored)\n", machine().describe_context(), data);
			break;

		case 0x50/4: // GIF_TAG1
			logerror("%s: Write: GIF_TAG1 = %08x (ignored)\n", machine().describe_context(), data);
			break;

		case 0x60/4: // GIF_TAG2
			logerror("%s: Write: GIF_TAG2 = %08x (ignored)\n", machine().describe_context(), data);
			break;

		case 0x70/4: // GIF_TAG3
			logerror("%s: Write: GIF_TAG3 = %08x (ignored)\n", machine().describe_context(), data);
			break;

		case 0x80/4: // GIF_CNT
			logerror("%s: Write: GIF_CNT = %08x (ignored)\n", machine().describe_context(), data);
			break;

		case 0x90/4: // GIF_P3CNT
			logerror("%s: Write: GIF_P3CNT = %08x (ignored)\n", machine().describe_context(), data);
			break;

		case 0xa0/4: // GIF_P3TAG
			logerror("%s: Write: GIF_P3TAG = %08x (ignored)\n", machine().describe_context(), data);
			break;

		default:
			logerror("%s: Write: Unknown %08x = %08x\n", machine().describe_context(), 0x10003000 + (offset << 2), data);
			break;
	}
}

void ps2_gif_device::kick_path1(uint32_t address)
{
	m_vu_mem_address = address <<= 2;

	uint64_t hi, lo;
	fetch_path1(hi, lo);
	write_path1(hi, lo);
}

void ps2_gif_device::fetch_path1(uint64_t &hi, uint64_t &lo)
{
	uint32_t *tag = &m_vu1->vu_mem()[m_vu_mem_address];
	lo = ((uint64_t)tag[0] << 32) | (uint64_t)tag[1];
	hi = ((uint64_t)tag[2] << 32) | (uint64_t)tag[3];
	m_vu_mem_address += 4;
	m_vu_mem_address &= m_vu1->mem_mask() >> 2;
}

void ps2_gif_device::write_path1(uint64_t hi, uint64_t lo)
{
	if (!m_current_path1_tag.valid())
	{
		m_current_path1_tag = tag_t(hi, lo);
		printf("Starting PATH1: %08x%08x%08x%08x\n", (uint32_t)(hi >> 32), (uint32_t)hi, (uint32_t)(lo >> 32), (uint32_t)lo);
		return;
	}

	printf("Processing PATH1: %08x%08x%08x%08x\n", (uint32_t)(hi >> 32), (uint32_t)hi, (uint32_t)(lo >> 32), (uint32_t)lo);
	process_tag(m_current_path1_tag, hi, lo);
}

void ps2_gif_device::write_path3(uint64_t hi, uint64_t lo)
{
	if (m_p3mask)
	{
		return;
	}

	if (!m_current_path3_tag.valid())
	{
		m_current_path3_tag = tag_t(hi, lo);
		return;
	}

	m_next_path3_hi = hi;
	m_next_path3_lo = lo;
	m_path3_available = false;
}

void ps2_gif_device::process_tag(tag_t &tag, uint64_t hi, uint64_t lo)
{
	m_last_tag = tag;
	switch (tag.format())
	{
		case tag_t::format_t::FMT_PACKED:
			if (tag.is_prim() && !tag.is_prim_output())
			{
				tag.set_prim_output();
				logerror("%s: GIF: PACKED: Not yet implemented: PRIM\n", machine().describe_context());
			}
			else if (tag.nloop())
			{
				const uint8_t reg = tag.reg();
				m_gs->write_packed(reg, hi, lo);
				tag.next_reg();
			}
			break;
		case tag_t::format_t::FMT_REGLIST:
			logerror("%s: GIF: Not yet implemented: REGLIST (%08x%08x%08x%08x)\n", machine().describe_context(), (uint32_t)(hi >> 32), (uint32_t)hi, (uint32_t)(lo >> 32), (uint32_t)lo);
			tag.loop();
			break;
		default:
			m_gs->regs_w(0x54, lo);
			m_gs->regs_w(0x54, hi);
			tag.loop();
			break;
	}
}

void ps2_gif_device::tag_t::next_reg()
{
	m_curr_reg++;
	if (m_curr_reg == m_reg_count)
	{
		m_curr_reg = 0;
		loop();
	}
}

void ps2_gif_device::set_path3_mask(bool masked)
{
	m_p3mask = masked;
}

void ps2_gif_device::execute_run()
{
	while (m_icount > 0)
	{
		if (m_current_path1_tag.valid())
		{
			uint64_t hi, lo;
			fetch_path1(hi, lo);
			write_path1(hi, lo);
		}
		else if (!m_path3_available)
		{
			write_path3(m_next_path3_hi, m_next_path3_lo);
			m_path3_available = false;
		}
		else
		{
			m_icount = 0;
			break;
		}
		m_icount--;
	}
}
