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


 Fairchild Channel F cart emulation

 Three kind of carts:
 - ROM only (the vast majority of carts)
 - ROM + 2102 RAM chip (used by carts 10 and 18, with different I/O ports)
 - ROM + 3853 RAM chip (used by Schach + some homebrew)

 Based on Sean Riddle's documentation (especially for the 2102 RAM!)

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


#include "emu.h"
#include "rom.h"


//-------------------------------------------------
//  chanf_rom_device - constructor
//-------------------------------------------------

DEFINE_DEVICE_TYPE(CHANF_ROM_STD,         chanf_rom_device,         "chanf_rom",       "Channel F Standard Carts")
DEFINE_DEVICE_TYPE(CHANF_ROM_MAZE,        chanf_maze_device,        "chanf_maze",      "Channel F Maze Cart")
DEFINE_DEVICE_TYPE(CHANF_ROM_HANGMAN,     chanf_hangman_device,     "chanf_hang",      "Channel F Hangman Cart")
DEFINE_DEVICE_TYPE(CHANF_ROM_CHESS,       chanf_chess_device,       "chanf_chess",     "Channel F Chess Cart")
DEFINE_DEVICE_TYPE(CHANF_ROM_MULTI_OLD,   chanf_multi_old_device,   "chanf_multi_old", "Channel F Multigame (Earlier Version) Cart")
DEFINE_DEVICE_TYPE(CHANF_ROM_MULTI_FINAL, chanf_multi_final_device, "chanf_multi_fin", "Channel F Multigame (Final Version) Cart")


chanf_rom_device::chanf_rom_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_channelf_cart_interface(mconfig, *this)
	, m_addr_latch(0), m_addr(0), m_read_write(0), m_data0(0)
{
}

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

chanf_maze_device::chanf_maze_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: chanf_rom_device(mconfig, CHANF_ROM_MAZE, tag, owner, clock)
{
}

chanf_hangman_device::chanf_hangman_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: chanf_rom_device(mconfig, CHANF_ROM_HANGMAN, tag, owner, clock)
{
}

chanf_chess_device::chanf_chess_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: chanf_rom_device(mconfig, CHANF_ROM_CHESS, tag, owner, clock)
{
}

chanf_multi_old_device::chanf_multi_old_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: chanf_rom_device(mconfig, CHANF_ROM_MULTI_OLD, tag, owner, clock), m_base_bank(0)
{
}

chanf_multi_final_device::chanf_multi_final_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: chanf_rom_device(mconfig, CHANF_ROM_MULTI_FINAL, tag, owner, clock), m_base_bank(0), m_half_bank(0)
{
}



//-------------------------------------------------
//  mapper specific start/reset
//-------------------------------------------------


void chanf_maze_device::device_start()
{
	// 2012 RAM related
	save_item(NAME(m_latch));
	save_item(NAME(m_addr_latch));
	save_item(NAME(m_addr));
	save_item(NAME(m_read_write));
	save_item(NAME(m_data0));
}

void chanf_maze_device::device_reset()
{
	m_latch[0] = 0;
	m_latch[1] = 0;
	m_addr = 0;
	m_addr_latch = 0;
	m_read_write = 0;
	m_data0 = 0;
}


void chanf_hangman_device::device_start()
{
	// 2012 RAM related
	save_item(NAME(m_latch));
	save_item(NAME(m_addr_latch));
	save_item(NAME(m_addr));
	save_item(NAME(m_read_write));
	save_item(NAME(m_data0));
}

void chanf_hangman_device::device_reset()
{
	m_latch[0] = 0;
	m_latch[1] = 0;
	m_addr = 0;
	m_addr_latch = 0;
	m_read_write = 0;
	m_data0 = 0;
}


void chanf_multi_old_device::device_start()
{
	save_item(NAME(m_base_bank));
}

void chanf_multi_old_device::device_reset()
{
	m_base_bank = 0;
}


void chanf_multi_final_device::device_start()
{
	save_item(NAME(m_base_bank));
	save_item(NAME(m_half_bank));
}

void chanf_multi_final_device::device_reset()
{
	m_base_bank = 0;
	m_half_bank = 0;
}


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

uint8_t chanf_rom_device::read_rom(offs_t offset)
{
	if (offset < m_rom_size)
		return m_rom[offset];
	else
		return 0xff;
}

// These are shared among Maze & Hangman cart types (not directly used by base chanf_rom_device)
uint8_t chanf_rom_device::common_read_2102(uint32_t offset)
{
	if (offset == 0)
	{
		if (m_read_write == 0)
		{
			m_addr = m_addr_latch;
			m_data0 = m_ram[m_addr] & 1;
			return (m_latch[0] & 0x7f) | (m_data0 << 7);
		}

		return m_latch[0];
	}
	else
		return m_latch[1];
}

void chanf_rom_device::common_write_2102(uint32_t offset, uint8_t data)
{
	if (offset == 0)
	{
		m_latch[0] = data;

		m_read_write = BIT(data, 0);

		m_addr_latch = (m_addr_latch & 0x3f3) | (BIT(data, 2) << 2) | (BIT(data, 1) << 3);  // bits 2,3 come from this write!
		m_addr = m_addr_latch;

		m_data0 = BIT(data, 3);

		if (m_read_write == 1)
			m_ram[m_addr] = m_data0;
	}
	else
	{
		m_latch[1] = data;
		// all bits but 2,3 come from this write, but they are shuffled
		// notice that data is 8bits, so when swapping bit8 & bit9 are always 0!
		m_addr_latch = (m_addr_latch & 0x0c) | (bitswap<16>((uint16_t) data, 15, 14, 13, 12, 11, 10, 7, 6, 5, 3, 2, 1, 9, 8, 4, 0));
	}
}


// These are shared among Schach & Multigame cart types (not directly used by base chanf_rom_device)
uint8_t chanf_rom_device::common_read_3853(uint32_t offset)
{
	if (offset < m_ram.size())
		return m_ram[offset];
	else
		return 0xff;
}

void chanf_rom_device::common_write_3853(uint32_t offset, uint8_t data)
{
	if (offset < m_ram.size())
		m_ram[offset] = data;
}

uint8_t chanf_multi_old_device::read_rom(offs_t offset)
{
	if (offset < 0x2000)
		return m_rom[offset + m_base_bank * 0x2000];
	else
		return 0xff;
}

void chanf_multi_old_device::write_bank(uint8_t data)
{
	//printf("0x%x\n", data);
	m_base_bank = data & 0x1f;
}

uint8_t chanf_multi_final_device::read_rom(offs_t offset)
{
	if (offset < 0x2000)
		return m_rom[offset + (m_base_bank * 0x2000) + (m_half_bank * 0x1000)];
	else
		return 0xff;
}

void chanf_multi_final_device::write_bank(uint8_t data)
{
	//printf("0x%x\n", data);
	m_base_bank = data & 0x1f;
	m_half_bank = BIT(data, 5);
}
