// 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
-------------------------------------------------*/
uint16_t md_eeprom_stm95_device::read(offs_t offset)
{
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];
}
}
uint16_t md_eeprom_stm95_device::read_a13(offs_t offset)
{
if (offset == 0x0a/2)
{
return m_stm95->get_so_line() & 1;
}
return 0xffff;
}
void md_eeprom_stm95_device::write_a13(offs_t offset, uint16_t data)
{
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));
}
}