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

    National Semiconductor INS8154

    N-Channel 128-by-8 Bit RAM Input/Output (RAM I/O)


    TODO: Strobed modes

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

#include "emu.h"
#include "ins8154.h"
#include "devhelpr.h"


/***************************************************************************
    CONSTANTS
***************************************************************************/

#define VERBOSE 1

/* Mode Definition Register */
enum
{
	MDR_BASIC                 = 0x00,
	MDR_STROBED_INPUT         = 0x20,
	MDR_STROBED_OUTPUT        = 0x60,
	MDR_STROBED_OUTPUT_3STATE = 0xe0
};



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

// device type definition
const device_type INS8154 = &device_creator<ins8154_device>;

//-------------------------------------------------
//  ins8154_device - constructor
//-------------------------------------------------

ins8154_device::ins8154_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
    : device_t(mconfig, INS8154, "INS8154", tag, owner, clock)
{

}


//-------------------------------------------------
//  device_config_complete - perform any
//  operations now that the configuration is
//  complete
//-------------------------------------------------

void ins8154_device::device_config_complete()
{
	// inherit a copy of the static data
	const ins8154_interface *intf = reinterpret_cast<const ins8154_interface *>(static_config());
	if (intf != NULL)
	{
		*static_cast<ins8154_interface *>(this) = *intf;
	}

	// or initialize to defaults if none provided
	else
	{
    	memset(&m_in_a_cb, 0, sizeof(m_in_a_cb));
    	memset(&m_in_b_cb, 0, sizeof(m_in_b_cb));
    	memset(&m_out_a_cb, 0, sizeof(m_out_a_cb));
    	memset(&m_out_b_cb, 0, sizeof(m_out_b_cb));
    	memset(&m_out_irq_cb, 0, sizeof(m_out_irq_cb));
	}
}


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

void ins8154_device::device_start()
{
	/* resolve callbacks */
	m_in_a_func.resolve(m_in_a_cb, *this);
	m_out_a_func.resolve(m_out_a_cb, *this);
	m_in_b_func.resolve(m_in_b_cb, *this);
	m_out_b_func.resolve(m_out_b_cb, *this);
	m_out_irq_func.resolve(m_out_irq_cb, *this);

	/* register for state saving */
	save_item(NAME(m_in_a));
	save_item(NAME(m_in_b));
	save_item(NAME(m_out_a));
	save_item(NAME(m_out_b));
	save_item(NAME(m_mdr));
	save_item(NAME(m_odra));
	save_item(NAME(m_odrb));
}


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

void ins8154_device::device_reset()
{
	m_in_a = 0;
	m_in_b = 0;
	m_out_a = 0;
	m_out_b = 0;
	m_mdr = 0;
	m_odra = 0;
	m_odrb = 0;
}


READ8_DEVICE_HANDLER_TRAMPOLINE(ins8154, ins8154_r)
{
	UINT8 val = 0xff;

	if (offset > 0x24)
	{
		if (VERBOSE)
		{
			logerror("%s: INS8154 '%s' Read from invalid offset %02x!\n", machine().describe_context(), tag(), offset);
		}
		return 0xff;
	}

	switch (offset)
	{
	case 0x20:
		if(!m_in_a_func.isnull())
		{
			val = m_in_a_func(0);
		}
		m_in_a = val;
		break;

	case 0x21:
		if(!m_in_b_func.isnull())
		{
			val = m_in_b_func(0);
		}
		m_in_b = val;
		break;

	default:
		if (offset < 0x08)
		{
			if(!m_in_a_func.isnull())
			{
				val = (m_in_a_func(0) << (8 - offset)) & 0x80;
			}
			m_in_a = val;
		}
		else
		{
			if(!m_in_b_func.isnull())
			{
				val = (m_in_b_func(0) << (8 - (offset >> 4))) & 0x80;
			}
			m_in_b = val;
		}
		break;
	}

	return val;
}

WRITE8_DEVICE_HANDLER_TRAMPOLINE(ins8154, ins8154_porta_w)
{
	m_out_a = data;

	/* Test if any pins are set as outputs */
	if (m_odra)
	{
		m_out_a_func(0, (data & m_odra) | (m_odra ^ 0xff));
	}
}

WRITE8_DEVICE_HANDLER_TRAMPOLINE(ins8154, ins8154_portb_w)
{
	m_out_b = data;

	/* Test if any pins are set as outputs */
	if (m_odrb)
	{
		m_out_b_func(0, (data & m_odrb) | (m_odrb ^ 0xff));
	}
}

WRITE8_DEVICE_HANDLER_TRAMPOLINE(ins8154, ins8154_w)
{
	if (offset > 0x24)
	{
		if (VERBOSE)
		{
			logerror("%s: INS8154 '%s' Write %02x to invalid offset %02x!\n", machine().describe_context(), tag(), data, offset);
		}
		return;
	}

	switch (offset)
	{
	case 0x20:
		ins8154_porta_w(0, data);
		break;

	case 0x21:
		ins8154_portb_w(0, data);
		break;

	case 0x22:
		if (VERBOSE)
		{
			logerror("%s: INS8154 '%s' ODRA set to %02x\n", machine().describe_context(), tag(), data);
		}

		m_odra = data;
		break;

	case 0x23:
		if (VERBOSE)
		{
			logerror("%s: INS8154 '%s' ODRB set to %02x\n", machine().describe_context(), tag(), data);
		}

		m_odrb = data;
		break;

	case 0x24:
		if (VERBOSE)
		{
			logerror("%s: INS8154 '%s' MDR set to %02x\n", machine().describe_context(), tag(), data);
		}

		m_mdr = data;
		break;

	default:
		if (offset & 0x10)
		{
			/* Set bit */
			if (offset < 0x08)
			{
				ins8154_porta_w(0, m_out_a |= offset & 0x07);
			}
			else
			{
				ins8154_portb_w(0, m_out_b |= (offset >> 4) & 0x07);
			}
		}
		else
		{
			/* Clear bit */
			if (offset < 0x08)
			{
				ins8154_porta_w(0, m_out_a & ~(offset & 0x07));
			}
			else
			{
				ins8154_portb_w(0, m_out_b & ~((offset >> 4) & 0x07));
			}
		}

		break;
	}
}
