// license:BSD-3-Clause
// copyright-holders:Fabio Priuli, MetalliC
/***************************************************************************


 MegaDrive / Genesis Cart + STM95 EEPROM device


 TO DO: split STM95 to a separate device...

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


#include "emu.h"
#include "stm95.h"


stm95_eeprom_device::stm95_eeprom_device(running_machine &machine, uint8_t *eeprom) :
	stm_state(IDLE),
	stream_pos(0),
	m_machine(machine)
{
	eeprom_data = eeprom;
	m_machine.save().save_item(latch, "STM95/latch");
	m_machine.save().save_item(reset_line, "STM95/reset_line");
	m_machine.save().save_item(sck_line, "STM95/sck_line");
	m_machine.save().save_item(WEL, "STM95/WEL");
	m_machine.save().save_item(stream_pos, "STM95/stream_pos");
	m_machine.save().save_item(stream_data, "STM95/stream_data");
	m_machine.save().save_item(eeprom_addr, "STM95/eeprom_addr");
}

void stm95_eeprom_device::set_cs_line(int state)
{
	reset_line = state;
	if (reset_line != CLEAR_LINE)
	{
		stream_pos = 0;
		stm_state = IDLE;
	}
}

void stm95_eeprom_device::set_si_line(int state)
{
	latch = state;
}

int stm95_eeprom_device::get_so_line(void)
{
	if (stm_state == READING || stm_state == CMD_RDSR)
		return (stream_data >> 8) & 1;
	else
		return 0;
}

void stm95_eeprom_device::set_sck_line(int state)
{
	if (reset_line == CLEAR_LINE)
	{
		if (state == ASSERT_LINE && sck_line == CLEAR_LINE)
		{
			switch (stm_state)
			{
				case IDLE:
					stream_data = (stream_data << 1) | (latch ? 1 : 0);
					stream_pos++;
					if (stream_pos == 8)
					{
						stream_pos = 0;
						//printf("STM95 EEPROM: got cmd %02X\n", stream_data&0xff);
						switch(stream_data & 0xff)
						{
							case 0x01:  // write status register
								if (WEL != 0)
									stm_state = CMD_WRSR;
								WEL = 0;
								break;
							case 0x02:  // write
								if (WEL != 0)
									stm_state = CMD_WRITE;
								stream_data = 0;
								WEL = 0;
								break;
							case 0x03:  // read
								stm_state = M95320_CMD_READ;
								stream_data = 0;
								break;
							case 0x04:  // write disable
								WEL = 0;
								break;
							case 0x05:  // read status register
								stm_state = CMD_RDSR;
								stream_data = WEL<<1;
								break;
							case 0x06:  // write enable
								WEL = 1;
								break;
							default:
								machine().logerror("STM95 EEPROM: unknown cmd %02X\n", stream_data&0xff);
						}
					}
					break;
				case CMD_WRSR:
					stream_pos++;       // just skip, don't care block protection
					if (stream_pos == 8)
					{
						stm_state = IDLE;
						stream_pos = 0;
					}
					break;
				case CMD_RDSR:
					stream_data = stream_data<<1;
					stream_pos++;
					if (stream_pos == 8)
					{
						stm_state = IDLE;
						stream_pos = 0;
					}
					break;
				case M95320_CMD_READ:
					stream_data = (stream_data << 1) | (latch ? 1 : 0);
					stream_pos++;
					if (stream_pos == 16)
					{
						eeprom_addr = stream_data & (M95320_SIZE - 1);
						stream_data = eeprom_data[eeprom_addr];
						stm_state = READING;
						stream_pos = 0;
					}
					break;
				case READING:
					stream_data = stream_data<<1;
					stream_pos++;
					if (stream_pos == 8)
					{
						if (++eeprom_addr == M95320_SIZE)
							eeprom_addr = 0;
						stream_data |= eeprom_data[eeprom_addr];
						stream_pos = 0;
					}
					break;
				case CMD_WRITE:
					stream_data = (stream_data << 1) | (latch ? 1 : 0);
					stream_pos++;
					if (stream_pos == 16)
					{
						eeprom_addr = stream_data & (M95320_SIZE - 1);
						stm_state = WRITING;
						stream_pos = 0;
					}
					break;
				case WRITING:
					stream_data = (stream_data << 1) | (latch ? 1 : 0);
					stream_pos++;
					if (stream_pos == 8)
					{
						eeprom_data[eeprom_addr] = stream_data;
						if (++eeprom_addr == M95320_SIZE)
							eeprom_addr = 0;
						stream_pos = 0;
					}
					break;
			}
		}
	}
	sck_line = state;
}



//-------------------------------------------------
//  md_rom_device - constructor
//-------------------------------------------------

DEFINE_DEVICE_TYPE(MD_EEPROM_STM95, md_eeprom_stm95_device, "md_eeprom_stm95", "MD Cart + EEPROM STM95")


md_eeprom_stm95_device::md_eeprom_stm95_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_md_cart_interface(mconfig, *this)
	, m_rdcnt(0)
{
}

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


void md_eeprom_stm95_device::device_start()
{
	nvram_alloc(stm95_eeprom_device::M95320_SIZE);
	m_stm95 = std::make_unique<stm95_eeprom_device>(machine(), (uint8_t*)get_nvram_base());

	save_item(NAME(m_rdcnt));
	save_item(NAME(m_bank));
}

void md_eeprom_stm95_device::device_reset()
{
	m_rdcnt = 0;
	m_bank[0] = 0;
	m_bank[1] = 0;
	m_bank[2] = 0;
}

/*-------------------------------------------------
 mapper specific handlers
 -------------------------------------------------*/

READ16_MEMBER(md_eeprom_stm95_device::read)
{
	if (offset == 0x0015e6/2 || offset == 0x0015e8/2)
	{
		// ugly hack until we don't know much about game protection
		// first 3 reads from 15e6 return 0x00000010, then normal 0x00018010 value for crc check
		uint16_t res;
		offset -= 0x0015e6/2;
		logerror("read 0x15e6 %d\n", m_rdcnt);
		if (m_rdcnt < 6)
		{
			m_rdcnt++;
			res = offset ? 0x10 : 0;
		}
		else
			res = offset ? 0x8010 : 0x0001;
		return res;
	}
	if (offset < 0x280000/2)
		return m_rom[offset];
	else    // last 0x180000 are bankswitched
	{
		uint8_t bank = (offset - 0x280000/2) >> 18;
		return m_rom[(offset & 0x7ffff/2) + (m_bank[bank] * 0x80000)/2];
	}
}

READ16_MEMBER(md_eeprom_stm95_device::read_a13)
{
	if (offset == 0x0a/2)
	{
		return m_stm95->get_so_line() & 1;
	}
	return 0xffff;
}

WRITE16_MEMBER(md_eeprom_stm95_device::write_a13)
{
	if (offset == 0x00/2)
	{
		logerror("A13001 write %02x\n", data);
	}
	else if (offset < 0x08/2)
	{
		m_bank[offset - 1] = data & 0x0f;
	}
	else if (offset < 0x0a/2)
	{
		m_stm95->set_si_line(BIT(data, 0));
		m_stm95->set_sck_line(BIT(data, 1));
		m_stm95->set_halt_line(BIT(data, 2));
		m_stm95->set_cs_line(BIT(data, 3));
	}
}
