/***************************************************************************
    mos tri port interface 6525
	mos triple interface adapter 6523

    peter.trauner@jk.uni-linz.ac.at

	used in commodore b series
	used in commodore c1551 floppy disk drive
***************************************************************************/

/*
 mos tpi 6525
 40 pin package
 3 8 bit ports (pa, pb, pc)
 8 registers to pc
 0 port a 0 in low
 1 port a data direction register 1 output
 2 port b
 3 port b ddr
 4 port c
  handshaking, interrupt mode
  0 interrupt 0 input, 1 interrupt enabled
   interrupt set on falling edge
  1 interrupt 1 input
  2 interrupt 2 input
  3 interrupt 3 input
  4 interrupt 4 input
  5 irq output
  6 ca handshake line (read handshake answer on I3 preferred)
  7 cb handshake line (write handshake clear on I4 preferred)
 5 port c ddr

 6 cr configuration register
  0 mc
    0 port c normal input output mode like port a and b
    1 port c used for handshaking and interrupt input
  1 1 interrupt prioritized
  2 i3 configure edge
    1 interrupt set on positive edge
  3 i4 configure edge
  5,4 ca handshake
   00 on read
      rising edge of i3 sets ca high
      read a data from computers sets ca low
   01 pulse output
      1 microsecond low after read a operation
   10 manual output low
   11 manual output high
  7,6 cb handshake
   00 handshake on write
      write b data from computer sets cb 0
      rising edge of i4 sets cb high
   01 pulse output
      1 microsecond low after write b operation
   10 manual output low
   11 manual output high
 7 air active interrupt register
   0 I0 occurred
   1 I1 occurred
   2 I2 occurred
   3 I3 occurred
   4 I4 occurred
   read clears interrupt

 non prioritized interrupts
  interrupt is set when occurred
  read clears all interrupts

 prioritized interrupts
  I4>I3>I2>I1>I0
  highest interrupt can be found in air register
  read clears highest interrupt
*/

#include "driver.h"
#include "6525tpi.h"


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

#define VERBOSE_LEVEL 0
#define DBG_LOG(N,M,A) \
	do { \
		if(VERBOSE_LEVEL >= N) \
		{ \
			if( M ) \
				logerror("%11.6f: %-24s", attotime_to_double(timer_get_time(device->machine)), (char*) M ); \
			logerror A; \
		} \
	} while (0)


#define INTERRUPT_MODE (tpi6525->cr & 1)
#define PRIORIZED_INTERRUPTS (tpi6525->cr & 2)
#define INTERRUPT3_RISING_EDGE (tpi6525->cr & 4)
#define INTERRUPT4_RISING_EDGE (tpi6525->cr & 8)
#define CA_MANUAL_OUT (tpi6525->cr & 0x20)
#define CA_MANUAL_LEVEL ((tpi6525->cr & 0x10) ? 1 : 0)
#define CB_MANUAL_OUT (tpi6525->cr & 0x80)
#define CB_MANUAL_LEVEL ((tpi6525->cr & 0x40) ? 1 : 0)


/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

typedef struct _tpi6525_state tpi6525_state;
struct _tpi6525_state
{
	const tpi6525_interface *intf;

	UINT8 port_a, ddr_a, in_a;
	UINT8 port_b, ddr_b, in_b;
	UINT8 port_c, ddr_c, in_c;
	
	UINT8 ca_level, cb_level, interrupt_level;
	
	UINT8 cr;
	UINT8 air;

	UINT8 irq_level[5];
};


/*****************************************************************************
    INLINE FUNCTIONS
*****************************************************************************/

INLINE tpi6525_state *get_safe_token(const device_config *device)
{
	assert(device != NULL);
	assert(device->token != NULL);
	assert(device->type == TPI6525);

	return (tpi6525_state *)device->token;
}


/*****************************************************************************
    DEVICE INTERFACE
*****************************************************************************/

static DEVICE_START( tpi6525 )
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	/* verify that we have an interface assigned */
	assert(device->static_config != NULL);

	/* register for state saving */
	state_save_register_device_item(device, 0, tpi6525->port_a);
	state_save_register_device_item(device, 0, tpi6525->ddr_a);
	state_save_register_device_item(device, 0, tpi6525->in_a);
	state_save_register_device_item(device, 0, tpi6525->port_b);
	state_save_register_device_item(device, 0, tpi6525->ddr_b);
	state_save_register_device_item(device, 0, tpi6525->in_b);
	state_save_register_device_item(device, 0, tpi6525->port_c);
	state_save_register_device_item(device, 0, tpi6525->ddr_c);
	state_save_register_device_item(device, 0, tpi6525->in_c);
	state_save_register_device_item(device, 0, tpi6525->ca_level);
	state_save_register_device_item(device, 0, tpi6525->cb_level);
	state_save_register_device_item(device, 0, tpi6525->interrupt_level);
	state_save_register_device_item(device, 0, tpi6525->cr);
	state_save_register_device_item(device, 0, tpi6525->air);
	state_save_register_device_item_array(device, 0, tpi6525->irq_level);
}


static DEVICE_RESET( tpi6525 )
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	/* clear old values */
	memset(tpi6525, 0, sizeof(*tpi6525));

	/* copy interface pointer */
	tpi6525->intf = device->static_config;

	/* setup some initial values */
	tpi6525->in_a = 0xff;
	tpi6525->in_b = 0xff;
	tpi6525->in_c = 0xff;
}


static DEVICE_SET_INFO( tpi6525 )
{
	switch (state)
	{
		/* no parameters to set */
	}
}


DEVICE_GET_INFO( tpi6525 )
{
	switch (state)
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_INT_TOKEN_BYTES:			info->i = sizeof(tpi6525_state);		break;
		case DEVINFO_INT_CLASS:					info->i = DEVICE_CLASS_PERIPHERAL;		break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_SET_INFO:				info->set_info = DEVICE_SET_INFO_NAME( tpi6525 );	break;
		case DEVINFO_FCT_START:					info->start = DEVICE_START_NAME( tpi6525 );			break;
		case DEVINFO_FCT_STOP:					/* Nothing */										break;
		case DEVINFO_FCT_RESET:					info->reset = DEVICE_RESET_NAME( tpi6525 );			break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:					strcpy(info->s, "6525 TPI");			break;
		case DEVINFO_STR_FAMILY:				strcpy(info->s, "6525 TPI");			break;
		case DEVINFO_STR_VERSION:				strcpy(info->s, "1.0");					break;
		case DEVINFO_STR_SOURCE_FILE:			strcpy(info->s, __FILE__);				break;
		case DEVINFO_STR_CREDITS:				strcpy(info->s, "Copyright MESS Team");	break;
	}
}


/***************************************************************************
    IMPLEMENTATION
***************************************************************************/

static void tpi6525_set_interrupt(const device_config *device)
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	if (!tpi6525->interrupt_level && (tpi6525->air != 0))
	{
		tpi6525->interrupt_level = 1;

		DBG_LOG(3, "tpi6525", ("%s set interrupt\n", device->tag));

		if (tpi6525->intf->irq_func != NULL)
			tpi6525->intf->irq_func(device, tpi6525->interrupt_level);
	}
}


static void tpi6525_clear_interrupt(const device_config *device)
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	if (tpi6525->interrupt_level && (tpi6525->air == 0))
	{
		tpi6525->interrupt_level = 0;

		DBG_LOG (3, "tpi6525", ("%s clear interrupt\n", device->tag));

		if (tpi6525->intf->irq_func != NULL)
			tpi6525->intf->irq_func(device, tpi6525->interrupt_level);
	}
}


void tpi6525_irq0_level(const device_config *device, int level)
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	if (INTERRUPT_MODE && (level != tpi6525->irq_level[0]))
	{
		tpi6525->irq_level[0] = level;

		if ((level == 0) && !(tpi6525->air & 1) && (tpi6525->ddr_c & 1))
		{
			tpi6525->air |= 1;
			tpi6525_set_interrupt(device);
		}
	}
}


void tpi6525_irq1_level(const device_config *device, int level)
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	if (INTERRUPT_MODE && (level != tpi6525->irq_level[1]))
	{
		tpi6525->irq_level[1] = level;

		if ((level == 0) && !(tpi6525->air & 2) && (tpi6525->ddr_c & 2))
		{
			tpi6525->air |= 2;
			tpi6525_set_interrupt(device);
		}
	}
}


void tpi6525_irq2_level(const device_config *device, int level)
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	if (INTERRUPT_MODE && (level != tpi6525->irq_level[2]))
	{
		tpi6525->irq_level[2] = level;

		if ((level == 0) && !(tpi6525->air & 4) && (tpi6525->ddr_c & 4))
		{
			tpi6525->air |= 4;
			tpi6525_set_interrupt(device);
		}
	}
}


void tpi6525_irq3_level(const device_config *device, int level)
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	if (INTERRUPT_MODE && (level != tpi6525->irq_level[3]))
	{
		tpi6525->irq_level[3] = level;

		if (((INTERRUPT3_RISING_EDGE && (level == 1))
			|| (!INTERRUPT3_RISING_EDGE && (level == 0)))
			&& !(tpi6525->air & 8) && (tpi6525->ddr_c & 8))
		{
			tpi6525->air |= 8;
			tpi6525_set_interrupt(device);
		}
	}
}


void tpi6525_irq4_level(const device_config *device, int level)
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	if (INTERRUPT_MODE && (level != tpi6525->irq_level[4]) )
	{
		tpi6525->irq_level[4] = level;

		if (((INTERRUPT4_RISING_EDGE && (level == 1))
			||(!INTERRUPT4_RISING_EDGE&&(level == 0)))
			&& !(tpi6525->air & 0x10) && (tpi6525->ddr_c & 0x10))
		{
			tpi6525->air |= 0x10;
			tpi6525_set_interrupt(device);
		}
	}
}


READ8_DEVICE_HANDLER( tpi6525_porta_r )
{
	tpi6525_state *tpi6525 = get_safe_token(device);
	UINT8 data = tpi6525->in_a;

	if (tpi6525->intf->in_a_func)
		data = tpi6525->intf->in_a_func(device, offset);

	data = (data & ~tpi6525->ddr_a) | (tpi6525->ddr_a & tpi6525->port_a);

	return data;	
}


WRITE8_DEVICE_HANDLER( tpi6525_porta_w )
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	tpi6525->in_a = data;
}


READ8_DEVICE_HANDLER( tpi6525_portb_r )
{
	tpi6525_state *tpi6525 = get_safe_token(device);
	UINT8 data = tpi6525->in_b;

	if (tpi6525->intf->in_b_func)
		data = tpi6525->intf->in_b_func(device, offset);

	data = (data & ~tpi6525->ddr_b) | (tpi6525->ddr_b & tpi6525->port_b);

	return data;
}


WRITE8_DEVICE_HANDLER( tpi6525_portb_w )
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	tpi6525->in_b = data;
}


READ8_DEVICE_HANDLER( tpi6525_portc_r )
{
	tpi6525_state *tpi6525 = get_safe_token(device);
	UINT8 data = tpi6525->in_c;

	if (tpi6525->intf->in_c_func)
		data &= tpi6525->intf->in_c_func(device, offset);

	data = (data & ~tpi6525->ddr_c) | (tpi6525->ddr_c & tpi6525->port_c);

	return data;
}


WRITE8_DEVICE_HANDLER( tpi6525_portc_w )
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	tpi6525->in_c = data;
}


READ8_DEVICE_HANDLER( tpi6525_r )
{
	tpi6525_state *tpi6525 = get_safe_token(device);
	UINT8 data = 0xff;

	switch (offset & 7)
	{
	case 0:
		data = tpi6525->in_a;

		if (tpi6525->intf->in_a_func)
			data &= tpi6525->intf->in_a_func(device, 0);

		data = (data & ~tpi6525->ddr_a) | (tpi6525->ddr_a & tpi6525->port_a);

		break;

	case 1:
		data = tpi6525->in_b;
		
		if (tpi6525->intf->in_b_func)
			data &= tpi6525->intf->in_b_func(device, 0);

		data = (data & ~tpi6525->ddr_b) | (tpi6525->ddr_b & tpi6525->port_b);

		break;

	case 2:
		if (INTERRUPT_MODE)
		{
			data = 0;

			if (tpi6525->irq_level[0]) data |= 0x01;
			if (tpi6525->irq_level[1]) data |= 0x02;
			if (tpi6525->irq_level[2]) data |= 0x04;
			if (tpi6525->irq_level[3]) data |= 0x08;
			if (tpi6525->irq_level[4]) data |= 0x10;
			if (!tpi6525->interrupt_level) data |= 0x20;
			if (tpi6525->ca_level) data |= 0x40;
			if (tpi6525->cb_level) data |= 0x80;
		}
		else
		{
			data = tpi6525->in_c;
			
			if (tpi6525->intf->in_c_func)
				data &= tpi6525->intf->in_c_func(device, 0);
			
			data = (data & ~tpi6525->ddr_c) | (tpi6525->ddr_c & tpi6525->port_c);
		}

		DBG_LOG(2, "tpi6525", ("%s read %.2x %.2x\n", device->tag, offset, data));
		break;

	case 3:
		data = tpi6525->ddr_a;
		break;

	case 4:
		data = tpi6525->ddr_b;
		break;

	case 5:
		data = tpi6525->ddr_c;
		break;

	case 6:
		data = tpi6525->cr;
		break;

	case 7: /* air */
		if (PRIORIZED_INTERRUPTS)
		{
			if (tpi6525->air & 0x10)
			{
				data = 0x10;
				tpi6525->air &= ~0x10;
			}
			else if (tpi6525->air & 8)
			{
				data = 8;
				tpi6525->air &= ~8;
			}
			else if (tpi6525->air & 4)
			{
				data = 4;
				tpi6525->air &= ~4;
			}
			else if (tpi6525->air & 2)
			{
				data = 2;
				tpi6525->air &= ~2;
			}
			else if (tpi6525->air & 1)
			{
				data = 1;
				tpi6525->air &= ~1;
			}
		}
		else
		{
			data = tpi6525->air;
			tpi6525->air = 0;
		}
		
		tpi6525_clear_interrupt(device);
		break;

	}

	DBG_LOG(3, "tpi6525", ("%s read %.2x %.2x\n", device->tag, offset, data));

	return data;
}


WRITE8_DEVICE_HANDLER( tpi6525_w )
{
	tpi6525_state *tpi6525 = get_safe_token(device);

	DBG_LOG(2, "tpi6525", ("%s write %.2x %.2x\n", device->tag, offset, data));

	switch (offset & 7)
	{
	case 0:
		tpi6525->port_a = data;

		if (tpi6525->intf->out_a_func)
			tpi6525->intf->out_a_func(device, 0, tpi6525->port_a & tpi6525->ddr_a);

		break;
		
	case 1:
		tpi6525->port_b = data;

		if (tpi6525->intf->out_b_func)
			tpi6525->intf->out_b_func(device, 0, tpi6525->port_b & tpi6525->ddr_b);

		break;

	case 2:
		tpi6525->port_c = data;

		if (!INTERRUPT_MODE && tpi6525->intf->out_c_func)
			tpi6525->intf->out_c_func(device, 0, tpi6525->port_c & tpi6525->ddr_c);

		break;

	case 3:
		tpi6525->ddr_a = data;

		if (tpi6525->intf->out_a_func)
			tpi6525->intf->out_a_func(device, 0, tpi6525->port_a & tpi6525->ddr_a);

		break;

	case 4:
		tpi6525->ddr_b = data;

		if (tpi6525->intf->out_b_func)
			tpi6525->intf->out_b_func(device, 0, tpi6525->port_b & tpi6525->ddr_b);

		break;

	case 5:
		tpi6525->ddr_c = data;

		if (!INTERRUPT_MODE && tpi6525->intf->out_c_func)
			tpi6525->intf->out_c_func(device, 0, tpi6525->port_c & tpi6525->ddr_c);

		break;

	case 6:
		tpi6525->cr = data;
		
		if (INTERRUPT_MODE)
		{
			if (CA_MANUAL_OUT)
			{
				if (tpi6525->ca_level != CA_MANUAL_LEVEL)
				{
					tpi6525->ca_level = CA_MANUAL_LEVEL;

					if (tpi6525->intf->out_ca_func)
						tpi6525->intf->out_ca_func(device, 0, tpi6525->ca_level);
				}
			}
			if (CB_MANUAL_OUT)
			{
				if (tpi6525->cb_level != CB_MANUAL_LEVEL)
				{
					tpi6525->cb_level = CB_MANUAL_LEVEL;
					
					if (tpi6525->intf->out_cb_func)
						tpi6525->intf->out_cb_func(device, 0, tpi6525->cb_level);
				}
			}
		}

		break;

	case 7:
		/* tpi6525->air = data; */
		break;
	}
}


/* this should probably be done better, needed for amigacd.c */

UINT8 tpi6525_get_ddr_a(const device_config *device)
{
	tpi6525_state *tpi6525 = get_safe_token(device);
	return tpi6525->ddr_a;
}

UINT8 tpi6525_get_ddr_b(const device_config *device)
{
	tpi6525_state *tpi6525 = get_safe_token(device);
	return tpi6525->ddr_b;
}

UINT8 tpi6525_get_ddr_c(const device_config *device)
{
	tpi6525_state *tpi6525 = get_safe_token(device);
	return tpi6525->ddr_c;
}
