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

    DECO Cassette System machine

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

#include "driver.h"
#include "cpu/m6502/m6502.h"
#include "cpu/i8x41/i8x41.h"
#include "machine/decocass.h"

/* tape direction, speed and timing (used also in vidhrdw/decocass.c) */
INT32 tape_dir;
INT32 tape_speed;
double tape_time0;
void *tape_timer;

static INT32 firsttime = 1;
static INT32 tape_present;
static INT32 tape_blocks;
static INT32 tape_length;
static INT32 tape_bot_eot;
static UINT8 crc16_lsb;
static UINT8 crc16_msb;

/* pre-calculated crc16 of the tape blocks */
static UINT8 tape_crc16_lsb[256];
static UINT8 tape_crc16_msb[256];

static UINT8 (*decocass_dongle_r)(offs_t offset);
static void (*decocass_dongle_w)(offs_t offset, UINT8 data);

static UINT8 decocass_reset;
static UINT8 i8041_p1;
static UINT8 i8041_p2;

/* dongle type #1: jumpers C and D assignments */
#define MAKE_MAP(m0,m1,m2,m3,m4,m5,m6,m7)	\
	((UINT32)(m0)) | \
	((UINT32)(m1) << 3) | \
	((UINT32)(m2) << 6) | \
	((UINT32)(m3) << 9) | \
	((UINT32)(m4) << 12) | \
	((UINT32)(m5) << 15) | \
	((UINT32)(m6) << 18) | \
	((UINT32)(m7) << 21)

#define MAP0(m) ((m)&7)
#define MAP1(m) (((m)>>3)&7)
#define MAP2(m) (((m)>>6)&7)
#define MAP3(m) (((m)>>9)&7)
#define MAP4(m) (((m)>>12)&7)
#define MAP5(m) (((m)>>15)&7)
#define MAP6(m) (((m)>>18)&7)
#define MAP7(m) (((m)>>21)&7)

static UINT32 type1_inmap;
static UINT32 type1_outmap;

static INT32 de0091_enable;	/* DE-0091xx daughter board enable */

/* dongle type #2: status of the latches */
static INT32 type2_d2_latch;	/* latched 8041-STATUS D2 value */
static INT32 type2_xx_latch;	/* latched value (D7-4 == 0xc0) ? 1 : 0 */
static INT32 type2_promaddr;	/* latched PROM address A0-A7 */

/* dongle type #3: status and patches */
static INT32 type3_ctrs;		/* 12 bit counter stage */
static INT32 type3_d0_latch;	/* latched 8041-D0 value */
static INT32 type3_pal_19;		/* latched 1 for PAL input pin-19 */
static INT32 type3_swap;
enum {
	TYPE3_SWAP_01,
	TYPE3_SWAP_12,
	TYPE3_SWAP_13,
	TYPE3_SWAP_24,
	TYPE3_SWAP_25,
	TYPE3_SWAP_34_0,
	TYPE3_SWAP_34_7,
	TYPE3_SWAP_23_56,
	TYPE3_SWAP_56,
	TYPE3_SWAP_67
};

/* dongle type #4: status */
static INT32 type4_ctrs;	/* latched PROM address (E5x0 LSB, E5x1 MSB) */
static INT32 type4_latch; 	/* latched enable PROM (1100xxxx written to E5x1) */

/* dongle type #5: status */
static INT32 type5_latch; 	/* latched enable PROM (1100xxxx written to E5x1) */

/* four inputs from the quadrature decoder (H1, V1, H2, V2) */
static UINT8 decocass_quadrature_decoder[4];

/* sound latches, ACK status bits and NMI timer */
static UINT8 decocass_sound_ack;
static void *decocass_sound_timer;

WRITE8_HANDLER( decocass_coin_counter_w )
{
}

WRITE8_HANDLER( decocass_sound_command_w )
{
	LOG(2,("CPU #%d sound command -> $%02x\n", cpu_getactivecpu(), data));
	soundlatch_w(0,data);
	decocass_sound_ack |= 0x80;
	/* remove snd cpu data ack bit. i don't see it in the schems, but... */
	decocass_sound_ack &= ~0x40;
	cpunum_set_input_line(1, M6502_IRQ_LINE, ASSERT_LINE);
}

READ8_HANDLER( decocass_sound_data_r )
{
	UINT8 data = soundlatch2_r(0);
	LOG(2,("CPU #%d sound data    <- $%02x\n", cpu_getactivecpu(), data));
	return data;
}

READ8_HANDLER( decocass_sound_ack_r )
{
	UINT8 data = decocass_sound_ack;	/* D6+D7 */
	LOG(4,("CPU #%d sound ack     <- $%02x\n", cpu_getactivecpu(), data));
	return data;
}

WRITE8_HANDLER( decocass_sound_data_w )
{
	LOG(2,("CPU #%d sound data    -> $%02x\n", cpu_getactivecpu(), data));
	soundlatch2_w(0, data);
	decocass_sound_ack |= 0x40;
}

READ8_HANDLER( decocass_sound_command_r )
{
	UINT8 data = soundlatch_r(0);
	LOG(4,("CPU #%d sound command <- $%02x\n", cpu_getactivecpu(), data));
	cpunum_set_input_line(1, M6502_IRQ_LINE, CLEAR_LINE);
	decocass_sound_ack &= ~0x80;
	return data;
}

static void decocass_sound_nmi_pulse( int param )
{
	cpunum_set_input_line(1, INPUT_LINE_NMI, PULSE_LINE);
}

WRITE8_HANDLER( decocass_sound_nmi_enable_w )
{
	LOG(2,("CPU #%d sound NMI enb -> $%02x\n", cpu_getactivecpu(), data));
	timer_adjust(decocass_sound_timer, TIME_IN_HZ(256 * 57 / 8 / 2), 0, TIME_IN_HZ(256 * 57 / 8 / 2));
}

READ8_HANDLER( decocass_sound_nmi_enable_r )
{
	UINT8 data = 0xff;
	LOG(2,("CPU #%d sound NMI enb <- $%02x\n", cpu_getactivecpu(), data));
	timer_adjust(decocass_sound_timer, TIME_IN_HZ(256 * 57 / 8 / 2), 0, TIME_IN_HZ(256 * 57 / 8 / 2));
	return data;
}

READ8_HANDLER( decocass_sound_data_ack_reset_r )
{
	UINT8 data = 0xff;
	LOG(2,("CPU #%d sound ack rst <- $%02x\n", cpu_getactivecpu(), data));
	decocass_sound_ack &= ~0x40;
	return data;
}

WRITE8_HANDLER( decocass_sound_data_ack_reset_w )
{
	LOG(2,("CPU #%d sound ack rst -> $%02x\n", cpu_getactivecpu(), data));
	decocass_sound_ack &= ~0x40;
}

WRITE8_HANDLER( decocass_nmi_reset_w )
{
	cpunum_set_input_line(0, INPUT_LINE_NMI, CLEAR_LINE );
}

WRITE8_HANDLER( decocass_quadrature_decoder_reset_w )
{
	/* just latch the analog controls here */
	decocass_quadrature_decoder[0] = input_port_3_r(0);
	decocass_quadrature_decoder[1] = input_port_4_r(0);
	decocass_quadrature_decoder[2] = input_port_5_r(0);
	decocass_quadrature_decoder[3] = input_port_6_r(0);
}

WRITE8_HANDLER( decocass_adc_w )
{
}

/*
 * E6x0    inputs
 * E6x1    inputs
 * E6x2    coin inp
 * E6x3    quadrature decoder read
 * E6x4    ""
 * E6x5    ""
 * E6x6    ""
 * E6x7    a/d converter read
 */
READ8_HANDLER( decocass_input_r )
{
	UINT8 data = 0xff;
	switch (offset & 7)
	{
	case 0: case 1: case 2:
		data = readinputport(offset & 7);
		break;
	case 3: case 4: case 5: case 6:
		data = decocass_quadrature_decoder[(offset & 7) - 3];
		break;
	default:
		break;
	}

	return data;
}

/*
 * D0 - REQ/ data request     (8041 pin 34 port 1.7)
 * D1 - FNO/ function number  (8041 pin 21 port 2.0)
 * D2 - EOT/ end-of-tape      (8041 pin 22 port 2.1)
 * D3 - ERR/ error condition  (8041 pin 23 port 2.2)
 * D4 - BOT-EOT from tape
 * D5 -
 * D6 -
 * D7 - cassette present
 */
/* Note on a tapes leader-BOT-data-EOT-trailer format:
 * A cassette has a transparent piece of tape on both ends,
 * leader and trailer. And data tapes also have BOT and EOT
 * holes, shortly before the the leader and trailer.
 * The holes and clear tape are detected using a photo-resitor.
 * When rewinding, the BOT/EOT signal will show a short
 * pulse and if rewind continues a constant high signal later.
 * The specs say the holes are "> 2ms" in length.
 */

#define TAPE_CLOCKRATE	4800	/* clock pulses per second */

/* duration of the clear LEADER (and trailer) of the tape */
#define TAPE_LEADER 	TAPE_CLOCKRATE		/* 1s */
/* duration of the GAP between leader and BOT/EOT */
#define TAPE_GAP		TAPE_CLOCKRATE*3/2	/* 1.5s */
/* duration of BOT/EOT holes */
#define TAPE_HOLE		TAPE_CLOCKRATE/400	/* 0.0025s */

/* byte offset of the tape chunks (8 clocks per byte = 16 samples) */
/* 300 ms GAP between BOT and first data block (doesn't work.. thus /2) */
#define TAPE_PRE_GAP	34
#define TAPE_LEADIN 	(TAPE_PRE_GAP + 1)
#define TAPE_HEADER 	(TAPE_LEADIN + 1)
#define TAPE_BLOCK		(TAPE_HEADER + 256)
#define TAPE_CRC16_MSB	(TAPE_BLOCK + 1)
#define TAPE_CRC16_LSB	(TAPE_CRC16_MSB + 1)
#define TAPE_TRAILER	(TAPE_CRC16_LSB + 1)
#define TAPE_LEADOUT	(TAPE_TRAILER + 1)
#define TAPE_LONGCLOCK	(TAPE_LEADOUT + 1)
#define TAPE_POST_GAP	(TAPE_LONGCLOCK + 34)

/* size of a tape chunk (block) including gaps */
#define TAPE_CHUNK		TAPE_POST_GAP

#define E5XX_MASK	0x02	/* use 0x0e for old style board */

#define BIT0(x) ((x)&1)
#define BIT1(x) (((x)>>1)&1)
#define BIT2(x) (((x)>>2)&1)
#define BIT3(x) (((x)>>3)&1)
#define BIT4(x) (((x)>>4)&1)
#define BIT5(x) (((x)>>5)&1)
#define BIT6(x) (((x)>>6)&1)
#define BIT7(x) (((x)>>7)&1)

WRITE8_HANDLER( decocass_reset_w )
{
	LOG(1,("%9.7f 6502-PC: %04x decocass_reset_w(%02x): $%02x\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
	decocass_reset = data;

	/* CPU #1 active hight reset */
	cpunum_set_input_line(1, INPUT_LINE_RESET, data & 0x01 );

	/* on reset also remove the sound timer */
	if (data & 1)
		timer_adjust(decocass_sound_timer, TIME_NEVER, 0, 0);

	/* 8041 active low reset */
	cpunum_set_input_line(2, INPUT_LINE_RESET, (data & 0x08) ^ 0x08 );
}

#ifdef MAME_DEBUG
static const char *dirnm(int speed)
{
	if (speed <  -1) return "fast rewind";
	if (speed == -1) return "rewind";
	if (speed ==  0) return "stop";
	if (speed ==  1) return "forward";
	return "fast forward";
}
#endif

static void tape_crc16(UINT8 data)
{
	UINT8 c0, c1;
	UINT8 old_lsb = crc16_lsb;
	UINT8 old_msb = crc16_msb;
	UINT8 feedback;

	feedback = ((data >> 7) ^ crc16_msb) & 1;

	/* rotate 16 bits */
	c0 = crc16_lsb & 1;
	c1 = crc16_msb & 1;
	crc16_msb = (crc16_msb >> 1) | (c0 << 7);
	crc16_lsb = (crc16_lsb >> 1) | (c1 << 7);

	/* feedback into bit 7 */
	if (feedback)
		crc16_lsb |= 0x80;
	else
		crc16_lsb &= ~0x80;

	/* feedback to bit 6 into bit 5 */
	if (((old_lsb >> 6) ^ feedback) & 1)
		crc16_lsb |= 0x20;
	else
		crc16_lsb &= ~0x20;

	/* feedback to bit 1 into bit 0 */
	if (((old_msb >> 1) ^ feedback) & 1)
		crc16_msb |= 0x01;
	else
		crc16_msb &= ~0x01;
}

static void tape_update(void)
{
	static int last_byte;
	double tape_time = tape_time0;
	int offset, rclk, rdata, tape_bit, tape_byte, tape_block;

	if (tape_timer)
		tape_time += tape_dir * timer_timeelapsed(tape_timer);

	if (tape_time < 0.0)
		tape_time = 0.0;
	else if (tape_time > 999.9)
		tape_time = 999.9;

	offset = (int)(tape_time * TAPE_CLOCKRATE + 0.499995);

	/* reset RCLK and RDATA inputs */
	rclk = 0;
	rdata = 0;

	if (offset < TAPE_LEADER)
	{
		if (offset < 0)
			offset = 0;
		/* LEADER area */
		if (0 == tape_bot_eot)
		{
			tape_bot_eot = 1;
			set_led_status(1, 1);
			LOG(5,("tape %5.4fs: %s found LEADER\n", tape_time, dirnm(tape_dir)));
		}
	}
	else
	if (offset < TAPE_LEADER + TAPE_GAP)
	{
		/* GAP between LEADER and BOT hole */
		if (1 == tape_bot_eot)
		{
			tape_bot_eot = 0;
			set_led_status(1, 0);
			LOG(5,("tape %5.4fs: %s between BOT + LEADER\n", tape_time, dirnm(tape_dir)));
		}
	}
	else
	if (offset < TAPE_LEADER + TAPE_GAP + TAPE_HOLE)
	{
		/* during BOT hole */
		if (0 == tape_bot_eot)
		{
			tape_bot_eot = 1;
			set_led_status(1, 1);
			LOG(5,("tape %5.4fs: %s found BOT\n", tape_time, dirnm(tape_dir)));
		}
	}
	else
	if (offset < tape_length - TAPE_LEADER - TAPE_GAP - TAPE_HOLE)
	{
		offset -= TAPE_LEADER + TAPE_GAP + TAPE_HOLE;

		/* data area */
		if (1 == tape_bot_eot)
		{
			tape_bot_eot = 0;
			set_led_status(1, 0);
			LOG(5,("tape %5.4fs: %s data area\n", tape_time, dirnm(tape_dir)));
		}
		rclk = (offset ^ 1) & 1;
		tape_bit = (offset / 2) % 8;
		tape_byte = (offset / 16) % TAPE_CHUNK;
		tape_block = offset / 16 / TAPE_CHUNK;

		if (tape_byte < TAPE_PRE_GAP)
		{
			rclk = 0;
			rdata = 0;
		}
		else
		if (tape_byte < TAPE_LEADIN)
		{
			rdata = (0x00 >> tape_bit) & 1;
			if (tape_byte != last_byte)
			{
				LOG(5,("tape %5.4fs: LEADIN $00\n", tape_time));
				set_led_status(2, 1);
			}
		}
		else
		if (tape_byte < TAPE_HEADER)
		{
			rdata = (0xaa >> tape_bit) & 1;
			if (tape_byte != last_byte)
				LOG(5,("tape %5.4fs: HEADER $aa\n", tape_time));
		}
		else
		if (tape_byte < TAPE_BLOCK)
		{
			UINT8 *ptr = memory_region(REGION_USER2) + tape_block * 256 + tape_byte - TAPE_HEADER;
			rdata = (*ptr >> tape_bit) & 1;
			if (tape_byte != last_byte)
				LOG(4,("tape %5.4fs: DATA(%02x) $%02x\n", tape_time, tape_byte - TAPE_HEADER, *ptr));
		}
		else
		if (tape_byte < TAPE_CRC16_MSB)
		{
			rdata = (tape_crc16_msb[tape_block] >> tape_bit) & 1;
			if (tape_byte != last_byte)
				LOG(4,("tape %5.4fs: CRC16 MSB $%02x\n", tape_time, tape_crc16_msb[tape_block]));
		}
		else
		if (tape_byte < TAPE_CRC16_LSB)
		{
			rdata = (tape_crc16_lsb[tape_block] >> tape_bit) & 1;
			if (tape_byte != last_byte)
				LOG(4,("tape %5.4fs: CRC16 LSB $%02x\n", tape_time, tape_crc16_lsb[tape_block]));
		}
		else
		if (tape_byte < TAPE_TRAILER)
		{
			rdata = (0xaa >> tape_bit) & 1;
			if (tape_byte != last_byte)
				LOG(4,("tape %5.4fs: TRAILER $aa\n", tape_time));
		}
		else
		if (tape_byte < TAPE_LEADOUT)
		{
			rdata = (0x00 >> tape_bit) & 1;
			if (tape_byte != last_byte)
				LOG(4,("tape %5.4fs: LEADOUT $00\n", tape_time));
		}
		else
		if (tape_byte < TAPE_LONGCLOCK)
		{
			if (tape_byte != last_byte)
			{
				LOG(4,("tape %5.4fs: LONG CLOCK\n", tape_time));
				set_led_status(2, 0);
			}
			rclk = 1;
			rdata = 0;
		}
		last_byte = tape_byte;
	}
	else
	if (offset < tape_length - TAPE_LEADER - TAPE_GAP)
	{
		/* during EOT hole */
		if (0 == tape_bot_eot)
		{
			tape_bot_eot = 1;
			set_led_status(1, 1);
			LOG(5,("tape %5.4fs: %s found EOT\n", tape_time, dirnm(tape_dir)));
		}
	}
	else
	if (offset < tape_length - TAPE_LEADER)
	{
		/* GAP between EOT and trailer */
		if (1 == tape_bot_eot)
		{
			tape_bot_eot = 0;
			set_led_status(1, 0);
			LOG(5,("tape %5.4fs: %s EOT and TRAILER\n", tape_time, dirnm(tape_dir)));
		}
	}
	else
	{
		/* TRAILER area */
		if (0 == tape_bot_eot)
		{
			tape_bot_eot = 1;
			set_led_status(1, 1);
			LOG(5,("tape %5.4fs: %s found TRAILER\n", tape_time, dirnm(tape_dir)));
		}
		offset = tape_length - 1;
	}

	i8041_p2 = (i8041_p2 & ~0xe0) | (tape_bot_eot << 5) | (rclk << 6) | (rdata << 7);
}

#ifdef MAME_DEBUG
static void decocass_fno(offs_t offset, UINT8 data)
{
		/* 8041ENA/ and is this a FNO write (function number)? */
		if (0 == (i8041_p2 & 0x01))
		{
			switch (data)
			{
			case 0x25: logerror("8041 FNO 25: write_block\n"); break;
			case 0x26: logerror("8041 FNO 26: rewind_block\n"); break;
			case 0x27: logerror("8041 FNO 27: read_block_a\n"); break;
			case 0x28: logerror("8041 FNO 28: read_block_b\n"); break;
			case 0x29: logerror("8041 FNO 29: tape_rewind_fast\n"); break;
			case 0x2a: logerror("8041 FNO 2a: tape_forward\n"); break;
			case 0x2b: logerror("8041 FNO 2b: tape_rewind\n"); break;
			case 0x2c: logerror("8041 FNO 2c: force_abort\n"); break;
			case 0x2d: logerror("8041 FNO 2d: tape_erase\n"); break;
			case 0x2e: logerror("8041 FNO 2e: search_tape_mark\n"); break;
			case 0x2f: logerror("8041 FNO 2f: search_eot\n"); break;
			case 0x30: logerror("8041 FNO 30: advance_block\n"); break;
			case 0x31: logerror("8041 FNO 31: write_tape_mark\n"); break;
			case 0x32: logerror("8041 FNO 32: reset_error\n"); break;
			case 0x33: logerror("8041 FNO 33: flag_status_report\n"); break;
			case 0x34: logerror("8041 FNO 34: report_status_to_main\n"); break;
			default:   logerror("8041 FNO %02x: invalid\n", data);
			}
		}
}
#endif

/***************************************************************************
 *
 *  TYPE1 DONGLE (DE-0061)
 *  - Lock 'n Chase
 *  - Treasure Island
 *  - Super Astro Fighter
 *  - Lucky Poker
 *  - Terranian
 *  - Explorer
 *  - Pro Golf
 *
 * Latch bits 2 and 6, pass bit 3, invert bit 2.
 * Lookup PROM DE-0061 using bits 0, 1, 4, 5, and 7 as the
 * address bits; take PROM data 0-4 as data bits 0, 1, 4, 5, and 7.
 *
 ***************************************************************************/

static READ8_HANDLER( decocass_type1_latch_26_pass_3_inv_2_r )
{
	static UINT8 latch1;
	UINT8 data;

	if (1 == (offset & 1))
	{
		if (0 == (offset & E5XX_MASK))
			data = cpunum_get_reg(2, I8X41_STAT);
		else
			data = 0xff;

		data = (BIT0(data) << 0) | (BIT1(data) << 1) | 0x7c;
		LOG(4,("%9.7f 6502-PC: %04x decocass_type1_latch_26_pass_3_inv_2_r(%02x): $%02x <- (%s %s)\n",
			timer_get_time(), activecpu_get_previouspc(), offset, data,
			(data & 1) ? "OBF" : "-",
			(data & 2) ? "IBF" : "-"));
	}
	else
	{
		offs_t promaddr;
		UINT8 save;
		UINT8 *prom = memory_region(REGION_USER1);

		if (firsttime)
		{
			LOG(3,("prom data:\n"));
			for (promaddr = 0; promaddr < 32; promaddr++)
			{
				if (promaddr % 8 == 0)
					LOG(3,("  %02x:", promaddr));
				LOG(3,(" %02x%s", prom[promaddr], (promaddr % 8) == 7 ? "\n" : ""));
			}
			firsttime = 0;
			latch1 = 0; 	 /* reset latch (??) */
		}

		if (0 == (offset & E5XX_MASK))
			data = cpunum_get_reg(2, I8X41_DATA);
		else
			data = 0xff;

		save = data;	/* save the unmodifed data for the latch */

		promaddr =
			(((data >> MAP0(type1_inmap)) & 1) << 0) |
			(((data >> MAP1(type1_inmap)) & 1) << 1) |
			(((data >> MAP4(type1_inmap)) & 1) << 2) |
			(((data >> MAP5(type1_inmap)) & 1) << 3) |
			(((data >> MAP7(type1_inmap)) & 1) << 4);
		/* latch bits 2 and 6, pass bit 3, invert bit 2 */
		data =
			(((prom[promaddr] >> 0) & 1)			   << MAP0(type1_outmap)) |
			(((prom[promaddr] >> 1) & 1)			   << MAP1(type1_outmap)) |
			((1 - ((latch1 >> MAP2(type1_inmap)) & 1)) << MAP2(type1_outmap)) |
			(((data >> MAP3(type1_inmap)) & 1)		   << MAP3(type1_outmap)) |
			(((prom[promaddr] >> 2) & 1)			   << MAP4(type1_outmap)) |
			(((prom[promaddr] >> 3) & 1)			   << MAP5(type1_outmap)) |
			(((latch1 >> MAP6(type1_inmap)) & 1)	   << MAP6(type1_outmap)) |
			(((prom[promaddr] >> 4) & 1)			   << MAP7(type1_outmap));

		LOG(3,("%9.7f 6502-PC: %04x decocass_type1_latch_26_pass_3_inv_2_r(%02x): $%02x\n",
			timer_get_time(), activecpu_get_previouspc(), offset, data));

		latch1 = save;		/* latch the data for the next A0 == 0 read */
	}
	return data;
}


/***************************************************************************
 *
 *  TYPE1 DONGLE (DE-0061)
 *  - Test Tape
 *
 * Pass bits 1, 3, and 6. Lookup PROM DE-0061 using bits 0, 2, 4, 5, and 7
 * as the address bits; take PROM data 0-4 as data bits 0, 2, 4, 5, and 7.
 *
 ***************************************************************************/

static READ8_HANDLER( decocass_type1_pass_136_r )
{
	static UINT8 latch1;
	UINT8 data;

	if (1 == (offset & 1))
	{
		if (0 == (offset & E5XX_MASK))
			data = cpunum_get_reg(2, I8X41_STAT);
		else
			data = 0xff;

		data = (BIT0(data) << 0) | (BIT1(data) << 1) | 0x7c;
		LOG(4,("%9.7f 6502-PC: %04x decocass_type1_pass_136_r(%02x): $%02x <- (%s %s)\n",
			timer_get_time(), activecpu_get_previouspc(), offset, data,
			(data & 1) ? "OBF" : "-",
			(data & 2) ? "IBF" : "-"));
	}
	else
	{
		offs_t promaddr;
		UINT8 save;
		UINT8 *prom = memory_region(REGION_USER1);

		if (firsttime)
		{
			LOG(3,("prom data:\n"));
			for (promaddr = 0; promaddr < 32; promaddr++)
			{
				if (promaddr % 8 == 0)
					LOG(3,("  %02x:", promaddr));
				LOG(3,(" %02x%s", prom[promaddr], (promaddr % 8) == 7 ? "\n" : ""));
			}
			firsttime = 0;
			latch1 = 0; 	 /* reset latch (??) */
		}

		if (0 == (offset & E5XX_MASK))
			data = cpunum_get_reg(2, I8X41_DATA);
		else
			data = 0xff;

		save = data;	/* save the unmodifed data for the latch */

		promaddr =
			(((data >> MAP0(type1_inmap)) & 1) << 0) |
			(((data >> MAP2(type1_inmap)) & 1) << 1) |
			(((data >> MAP4(type1_inmap)) & 1) << 2) |
			(((data >> MAP5(type1_inmap)) & 1) << 3) |
			(((data >> MAP7(type1_inmap)) & 1) << 4);
		/* latch bits 1 and 6, pass bit 3, invert bit 1 */
		data =
			(((prom[promaddr] >> 0) & 1)			   << MAP0(type1_outmap)) |
			(((data >> MAP1(type1_inmap)) & 1)         << MAP1(type1_outmap)) |
			(((prom[promaddr] >> 1) & 1)			   << MAP2(type1_outmap)) |
			(((data >> MAP3(type1_inmap)) & 1)		   << MAP3(type1_outmap)) |
			(((prom[promaddr] >> 2) & 1)			   << MAP4(type1_outmap)) |
			(((prom[promaddr] >> 3) & 1)			   << MAP5(type1_outmap)) |
			(((data >> MAP6(type1_inmap)) & 1)	       << MAP6(type1_outmap)) |
			(((prom[promaddr] >> 4) & 1)			   << MAP7(type1_outmap));

		LOG(3,("%9.7f 6502-PC: %04x decocass_type1_pass_136_r(%02x): $%02x\n",
			timer_get_time(), activecpu_get_previouspc(), offset, data));

		latch1 = save;		/* latch the data for the next A0 == 0 read */
	}
	return data;
}

/***************************************************************************
 *
 *  TYPE1 DONGLE (DE-0061)
 *  - Highway Chase
 *
 * Latch bits 2 and 7, pass bit 3, invert bit 2 to the output.
 * Lookup PROM (Highway Chase) using data bits 0, 1, 4, 5, and 6 as the
 * address bits. Take PROM data 0-4 as data bits 0, 1, 4, 5, and 6.
 *
 ***************************************************************************/

static READ8_HANDLER( decocass_type1_latch_27_pass_3_inv_2_r )
{
	static UINT8 latch1;
	UINT8 data;

	if (1 == (offset & 1))
	{
		if (0 == (offset & E5XX_MASK))
			data = cpunum_get_reg(2, I8X41_STAT);
		else
			data = 0xff;

		data = (BIT0(data) << 0) | (BIT1(data) << 1) | 0x7c;
		LOG(4,("%9.7f 6502-PC: %04x decocass_type1_latch_27_pass_3_inv_2_r(%02x): $%02x <- (%s %s)\n",
			timer_get_time(), activecpu_get_previouspc(), offset, data,
			(data & 1) ? "OBF" : "-",
			(data & 2) ? "IBF" : "-"));
	}
	else
	{
		offs_t promaddr;
		UINT8 save;
		UINT8 *prom = memory_region(REGION_USER1);

		if (firsttime)
		{
			LOG(3,("prom data:\n"));
			for (promaddr = 0; promaddr < 32; promaddr++)
			{
				if (promaddr % 8 == 0)
					LOG(3,("  %02x:", promaddr));
				LOG(3,(" %02x%s", prom[promaddr], (promaddr % 8) == 7 ? "\n" : ""));
			}
			firsttime = 0;
			latch1 = 0; 	 /* reset latch (??) */
		}

		if (0 == (offset & E5XX_MASK))
			data = cpunum_get_reg(2, I8X41_DATA);
		else
			data = 0xff;

		save = data;	/* save the unmodifed data for the latch */

		promaddr =
			(((data >> MAP0(type1_inmap)) & 1) << 0) |
			(((data >> MAP1(type1_inmap)) & 1) << 1) |
			(((data >> MAP4(type1_inmap)) & 1) << 2) |
			(((data >> MAP5(type1_inmap)) & 1) << 3) |
			(((data >> MAP6(type1_inmap)) & 1) << 4);
		/* latch bits 2 and 7, pass bit 3, invert bit 2 */
		data =
			(((prom[promaddr] >> 0) & 1)			   << MAP0(type1_outmap)) |
			(((prom[promaddr] >> 1) & 1)			   << MAP1(type1_outmap)) |
			((1 - ((latch1 >> MAP2(type1_inmap)) & 1)) << MAP2(type1_outmap)) |
			(((data >> MAP3(type1_inmap)) & 1)		   << MAP3(type1_outmap)) |
			(((prom[promaddr] >> 2) & 1)			   << MAP4(type1_outmap)) |
			(((prom[promaddr] >> 3) & 1)			   << MAP5(type1_outmap)) |
			(((prom[promaddr] >> 4) & 1)			   << MAP6(type1_outmap)) |
			(((latch1 >> MAP7(type1_inmap)) & 1)	   << MAP7(type1_outmap));

		LOG(3,("%9.7f 6502-PC: %04x decocass_type1_latch_27_pass_3_inv_2_r(%02x): $%02x\n",
			timer_get_time(), activecpu_get_previouspc(), offset, data));

		latch1 = save;		/* latch the data for the next A0 == 0 read */
	}
	return data;
}

/***************************************************************************
 *
 *  TYPE1 DONGLE (DE-0061)
 *  - Explorer
 *
 * Latch bits 2 and 6, pass bit 5, invert bit 2 to the output.
 * Lookup PROM (Explorer) using bits 0, 1, 3, 4, and 7 as the
 * address bits. Take PROM data 0-4 as data bits 0, 1, 3, 4, and 7.
 *
 ***************************************************************************/

static READ8_HANDLER( decocass_type1_latch_26_pass_5_inv_2_r )
{
	static UINT8 latch1;
	UINT8 data;

	if (1 == (offset & 1))
	{
		if (0 == (offset & E5XX_MASK))
			data = cpunum_get_reg(2, I8X41_STAT);
		else
			data = 0xff;

		data = (BIT0(data) << 0) | (BIT1(data) << 1) | 0x7c;
		LOG(4,("%9.7f 6502-PC: %04x decocass_type1_latch_26_pass_5_inv_2_r(%02x): $%02x <- (%s %s)\n",
			timer_get_time(), activecpu_get_previouspc(), offset, data,
			(data & 1) ? "OBF" : "-",
			(data & 2) ? "IBF" : "-"));
	}
	else
	{
		offs_t promaddr;
		UINT8 save;
		UINT8 *prom = memory_region(REGION_USER1);

		if (firsttime)
		{
			LOG(3,("prom data:\n"));
			for (promaddr = 0; promaddr < 32; promaddr++)
			{
				if (promaddr % 8 == 0)
					LOG(3,("  %02x:", promaddr));
				LOG(3,(" %02x%s", prom[promaddr], (promaddr % 8) == 7 ? "\n" : ""));
			}
			firsttime = 0;
			latch1 = 0; 	 /* reset latch (??) */
		}

		if (0 == (offset & E5XX_MASK))
			data = cpunum_get_reg(2, I8X41_DATA);
		else
			data = 0xff;

		save = data;	/* save the unmodifed data for the latch */

		promaddr =
			(((data >> MAP0(type1_inmap)) & 1) << 0) |
			(((data >> MAP1(type1_inmap)) & 1) << 1) |
			(((data >> MAP3(type1_inmap)) & 1) << 2) |
			(((data >> MAP4(type1_inmap)) & 1) << 3) |
			(((data >> MAP7(type1_inmap)) & 1) << 4);
		/* latch bits 2 and 6, pass bit 5, invert bit 2 */
		data =
			(((prom[promaddr] >> 0) & 1)			   << MAP0(type1_outmap)) |
			(((prom[promaddr] >> 1) & 1)			   << MAP1(type1_outmap)) |
			((1 - ((latch1 >> MAP2(type1_inmap)) & 1)) << MAP2(type1_outmap)) |
			(((prom[promaddr] >> 2) & 1)			   << MAP3(type1_outmap)) |
			(((prom[promaddr] >> 3) & 1)			   << MAP4(type1_outmap)) |
			(((data >> MAP5(type1_inmap)) & 1)		   << MAP5(type1_outmap)) |
			(((latch1 >> MAP6(type1_inmap)) & 1)		   << MAP6(type1_outmap)) |
			(((prom[promaddr] >> 4) & 1)			   << MAP7(type1_outmap));

		LOG(3,("%9.7f 6502-PC: %04x decocass_type1_latch_26_pass_5_inv_2_r(%02x): $%02x\n",
			timer_get_time(), activecpu_get_previouspc(), offset, data));

		latch1 = save;		/* latch the data for the next A0 == 0 read */
	}
	return data;
}



/***************************************************************************
 *
 *  TYPE1 DONGLE (DE-0061)
 *  - Astro Fantazia
 *
 * Latch bits 1 and 6, pass bit 3, invert bit 1.
 * Lookup PROM DE-0061 using bits 0, 2, 4, 5, and 7 as the
 * address bits; take PROM data 0-4 as data bits 0, 2, 4, 5, and 7.
 *
 ***************************************************************************/

READ8_HANDLER( decocass_type1_latch_16_pass_3_inv_1_r )
{
	static UINT8 latch1;
	UINT8 data;

	if (1 == (offset & 1))
	{
		if (0 == (offset & E5XX_MASK))
			data = cpunum_get_reg(2, I8X41_STAT);
		else
			data = 0xff;

		data = (BIT0(data) << 0) | (BIT1(data) << 1) | 0x7c;
		LOG(4,("%9.7f 6502-PC: %04x decocass_type1_latch_16_pass_3_inv_1_r(%02x): $%02x <- (%s %s)\n",
			timer_get_time(), activecpu_get_previouspc(), offset, data,
			(data & 1) ? "OBF" : "-",
			(data & 2) ? "IBF" : "-"));
	}
	else
	{
		offs_t promaddr;
		UINT8 save;
		UINT8 *prom = memory_region(REGION_USER1);

		if (firsttime)
		{
			LOG(3,("prom data:\n"));
			for (promaddr = 0; promaddr < 32; promaddr++)
			{
				if (promaddr % 8 == 0)
					LOG(3,("  %02x:", promaddr));
				LOG(3,(" %02x%s", prom[promaddr], (promaddr % 8) == 7 ? "\n" : ""));
			}
			firsttime = 0;
			latch1 = 0; 	 /* reset latch (??) */
		}

		if (0 == (offset & E5XX_MASK))
			data = cpunum_get_reg(2, I8X41_DATA);
		else
			data = 0xff;

		save = data;	/* save the unmodifed data for the latch */

		promaddr =
			(((data >> MAP0(type1_inmap)) & 1) << 0) |
			(((data >> MAP2(type1_inmap)) & 1) << 1) |
			(((data >> MAP4(type1_inmap)) & 1) << 2) |
			(((data >> MAP5(type1_inmap)) & 1) << 3) |
			(((data >> MAP7(type1_inmap)) & 1) << 4);
		/* latch bits 1 and 6, pass bit 3, invert bit 1 */
		data =
			(((prom[promaddr] >> 0) & 1)			   << MAP0(type1_outmap)) |
			((1 - ((latch1 >> MAP1(type1_inmap)) & 1)) << MAP1(type1_outmap)) |
			(((prom[promaddr] >> 1) & 1)			   << MAP2(type1_outmap)) |
			(((data >> MAP3(type1_inmap)) & 1)		   << MAP3(type1_outmap)) |
			(((prom[promaddr] >> 2) & 1)			   << MAP4(type1_outmap)) |
			(((prom[promaddr] >> 3) & 1)			   << MAP5(type1_outmap)) |
			(((latch1 >> MAP6(type1_inmap)) & 1)	   << MAP6(type1_outmap)) |
			(((prom[promaddr] >> 4) & 1)			   << MAP7(type1_outmap));

		LOG(3,("%9.7f 6502-PC: %04x decocass_type1_latch_16_pass_3_inv_1_r(%02x): $%02x\n",
			timer_get_time(), activecpu_get_previouspc(), offset, data));

		latch1 = save;		/* latch the data for the next A0 == 0 read */
	}
	return data;
}




/***************************************************************************
 *
 *  TYPE2 DONGLE (CS82-007)
 *  - Mission X
 *  - Disco No 1
 *  - Pro Tennis
 *  - Tornado
 *
 ***************************************************************************/
READ8_HANDLER( decocass_type2_r )
{
	UINT8 data;

	if (1 == type2_xx_latch)
	{
		if (1 == (offset & 1))
		{
			UINT8 *prom = memory_region(REGION_USER1);
			data = prom[256 * type2_d2_latch + type2_promaddr];
			LOG(3,("%9.7f 6502-PC: %04x decocass_type2_r(%02x): $%02x <- prom[%03x]\n", timer_get_time(), activecpu_get_previouspc(), offset, data, 256 * type2_d2_latch + type2_promaddr));
		}
		else
		{
			data = 0xff;	/* floating input? */
		}
	}
	else
	{
		if (0 == (offset & E5XX_MASK))
			data = cpunum_get_reg(2, offset & 1 ? I8X41_STAT : I8X41_DATA);
		else
			data = offset & 0xff;

		LOG(3,("%9.7f 6502-PC: %04x decocass_type2_r(%02x): $%02x <- 8041-%s\n", timer_get_time(), activecpu_get_previouspc(), offset, data, offset & 1 ? "STATUS" : "DATA"));
	}
	return data;
}

WRITE8_HANDLER( decocass_type2_w )
{
	if (1 == type2_xx_latch)
	{
		if (1 == (offset & 1))
		{
			LOG(4,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> set PROM+D2 latch", timer_get_time(), activecpu_get_previouspc(), offset, data));
		}
		else
		{
			type2_promaddr = data;
			LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> set PROM addr $%02x\n", timer_get_time(), activecpu_get_previouspc(), offset, data, type2_promaddr));
			return;
		}
	}
	else
	{
		LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> %s ", timer_get_time(), activecpu_get_previouspc(), offset, data, offset & 1 ? "8041-CMND" : "8041 DATA"));
	}
	if (1 == (offset & 1))
	{
		if (0xc0 == (data & 0xf0))
		{
			type2_xx_latch = 1;
			type2_d2_latch = (data & 0x04) ? 1 : 0;
			LOG(3,("PROM:%s D2:%d", type2_xx_latch ? "on" : "off", type2_d2_latch));
		}
	}
	cpunum_set_reg(2, offset & 1 ? I8X41_CMND : I8X41_DATA, data);

#ifdef MAME_DEBUG
	decocass_fno(offset, data);
#endif
}

/***************************************************************************
 *
 *  TYPE3 DONGLE
 *  - Bump 'n Jump
 *  - Burnin' Rubber
 *  - Burger Time
 *  - Graplop
 *  - Cluster Buster
 *  - LaPaPa
 *  - Fighting Ice Hockey
 *  - Pro Bowling
 *  - Night Star
 *  - Pro Soccer
 *  - Peter Pepper's Ice Cream Factory
 *
 ***************************************************************************/
READ8_HANDLER( decocass_type3_r )
{
	UINT8 data, save;

	if (1 == (offset & 1))
	{
		if (1 == type3_pal_19)
		{
			UINT8 *prom = memory_region(REGION_USER1);
			data = prom[type3_ctrs];
			LOG(3,("%9.7f 6502-PC: %04x decocass_type3_r(%02x): $%02x <- prom[$%03x]\n", timer_get_time(), activecpu_get_previouspc(), offset, data, type3_ctrs));
			if (++type3_ctrs == 4096)
				type3_ctrs = 0;
		}
		else
		{
			if (0 == (offset & E5XX_MASK))
			{
				data = cpunum_get_reg(2, I8X41_STAT);
				LOG(4,("%9.7f 6502-PC: %04x decocass_type3_r(%02x): $%02x <- 8041 STATUS\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
			}
			else
			{
				data = 0xff;	/* open data bus? */
				LOG(4,("%9.7f 6502-PC: %04x decocass_type3_r(%02x): $%02x <- open bus\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
			}
		}
	}
	else
	{
		if (1 == type3_pal_19)
		{
			save = data = 0xff;    /* open data bus? */
			LOG(3,("%9.7f 6502-PC: %04x decocass_type3_r(%02x): $%02x <- open bus", timer_get_time(), activecpu_get_previouspc(), offset, data));
		}
		else
		{
			if (0 == (offset & E5XX_MASK))
			{
				save = cpunum_get_reg(2, I8X41_DATA);
				switch (type3_swap)
				{
				case TYPE3_SWAP_01:
					data =
						(BIT1(save) << 0) |
						(type3_d0_latch << 1) |
						(BIT2(save) << 2) |
						(BIT3(save) << 3) |
						(BIT4(save) << 4) |
						(BIT5(save) << 5) |
						(BIT6(save) << 6) |
						(BIT7(save) << 7);
					break;
				case TYPE3_SWAP_12:
					data =
						(type3_d0_latch << 0) |
						(BIT2(save) << 1) |
						(BIT1(save) << 2) |
						(BIT3(save) << 3) |
						(BIT4(save) << 4) |
						(BIT5(save) << 5) |
						(BIT6(save) << 6) |
						(BIT7(save) << 7);
					break;
				case TYPE3_SWAP_13:
					data =
						(type3_d0_latch << 0) |
						(BIT3(save) << 1) |
						(BIT2(save) << 2) |
						(BIT1(save) << 3) |
						(BIT4(save) << 4) |
						(BIT5(save) << 5) |
						(BIT6(save) << 6) |
						(BIT7(save) << 7);
					break;
				case TYPE3_SWAP_24:
					data =
						(type3_d0_latch << 0) |
						(BIT1(save) << 1) |
						(BIT4(save) << 2) |
						(BIT3(save) << 3) |
						(BIT2(save) << 4) |
						(BIT5(save) << 5) |
						(BIT6(save) << 6) |
						(BIT7(save) << 7);
					break;
				case TYPE3_SWAP_25:
					data =
						(type3_d0_latch << 0) |
						(BIT1(save) << 1) |
						(BIT5(save) << 2) |
						(BIT3(save) << 3) |
						(BIT4(save) << 4) |
						(BIT2(save) << 5) |
						(BIT6(save) << 6) |
						(BIT7(save) << 7);
					break;
				case TYPE3_SWAP_34_0:
					data =
						(type3_d0_latch << 0) |
						(BIT1(save) << 1) |
						(BIT2(save) << 2) |
						(BIT3(save) << 4) |
						(BIT4(save) << 3) |
						(BIT5(save) << 5) |
						(BIT6(save) << 6) |
						(BIT7(save) << 7);
					break;
				case TYPE3_SWAP_34_7:
					data =
						(BIT7(save) << 0) |
						(BIT1(save) << 1) |
						(BIT2(save) << 2) |
						(BIT4(save) << 3) |
						(BIT3(save) << 4) |
						(BIT5(save) << 5) |
						(BIT6(save) << 6) |
						(type3_d0_latch << 7);
					break;
				case TYPE3_SWAP_23_56:
					data =
						(type3_d0_latch << 0) |
						(BIT1(save) << 1) |
						(BIT3(save) << 2) |
						(BIT2(save) << 3) |
						(BIT4(save) << 4) |
						(BIT6(save) << 5) |
						(BIT5(save) << 6) |
						(BIT7(save) << 7);
					break;
				case TYPE3_SWAP_56:
					data =
						type3_d0_latch |
						(BIT1(save) << 1) |
						(BIT2(save) << 2) |
						(BIT3(save) << 3) |
						(BIT4(save) << 4) |
						(BIT6(save) << 5) |
						(BIT5(save) << 6) |
						(BIT7(save) << 7);
					break;
				case TYPE3_SWAP_67:
					data =
						type3_d0_latch |
						(BIT1(save) << 1) |
						(BIT2(save) << 2) |
						(BIT3(save) << 3) |
						(BIT4(save) << 4) |
						(BIT5(save) << 5) |
						(BIT7(save) << 6) |
						(BIT6(save) << 7);
					break;
				default:
					data =
						type3_d0_latch |
						(BIT1(save) << 1) |
						(BIT2(save) << 2) |
						(BIT3(save) << 3) |
						(BIT4(save) << 4) |
						(BIT5(save) << 5) |
						(BIT6(save) << 6) |
						(BIT7(save) << 7);
				}
				type3_d0_latch = save & 1;
				LOG(3,("%9.7f 6502-PC: %04x decocass_type3_r(%02x): $%02x '%c' <- 8041-DATA\n", timer_get_time(), activecpu_get_previouspc(), offset, data, (data >= 32) ? data : '.'));
			}
			else
			{
				save = 0xff;	/* open data bus? */
				data =
					type3_d0_latch |
					(BIT1(save) << 1) |
					(BIT2(save) << 2) |
					(BIT3(save) << 3) |
					(BIT4(save) << 4) |
					(BIT5(save) << 5) |
					(BIT6(save) << 7) |
					(BIT7(save) << 6);
				LOG(3,("%9.7f 6502-PC: %04x decocass_type3_r(%02x): $%02x '%c' <- open bus (D0 replaced with latch)\n", timer_get_time(), activecpu_get_previouspc(), offset, data, (data >= 32) ? data : '.'));
				type3_d0_latch = save & 1;
			}
		}
	}

	return data;
}

WRITE8_HANDLER( decocass_type3_w )
{
	if (1 == (offset & 1))
	{
		if (1 == type3_pal_19)
		{
			type3_ctrs = data << 4;
			LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> %s\n", timer_get_time(), activecpu_get_previouspc(), offset, data, "LDCTRS"));
			return;
		}
		else
		if (0xc0 == (data & 0xf0))
			type3_pal_19 = 1;
	}
	else
	{
		if (1 == type3_pal_19)
		{
			/* write nowhere?? */
			LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> %s\n", timer_get_time(), activecpu_get_previouspc(), offset, data, "nowhere?"));
			return;
		}
	}
	LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> %s\n", timer_get_time(), activecpu_get_previouspc(), offset, data, offset & 1 ? "8041-CMND" : "8041-DATA"));
	cpunum_set_reg(2, offset & 1 ? I8X41_CMND : I8X41_DATA, data);
}

/***************************************************************************
 *
 *  TYPE4 DONGLE
 *  - Scrum Try
 *  Contains a 32K (EP)ROM that can be read from any byte
 *  boundary sequentially. The EPROM is enable after writing
 *  1100xxxx to E5x1 once. Then an address is written LSB
 *  to E5x0 MSB to E5x1 and every read from E5x1 returns the
 *  next byte of the contents.
 *
 ***************************************************************************/

READ8_HANDLER( decocass_type4_r )
{
	UINT8 data;

	if (1 == (offset & 1))
	{
		if (0 == (offset & E5XX_MASK))
		{
			data = cpunum_get_reg(2, I8X41_STAT);
			LOG(4,("%9.7f 6502-PC: %04x decocass_type4_r(%02x): $%02x <- 8041 STATUS\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
		}
		else
		{
			data = 0xff;	/* open data bus? */
			LOG(4,("%9.7f 6502-PC: %04x decocass_type4_r(%02x): $%02x <- open bus\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
		}
	}
	else
	{
		if (type4_latch)
		{
			UINT8 *prom = memory_region(REGION_USER1);

			data = prom[type4_ctrs];
			LOG(3,("%9.7f 6502-PC: %04x decocass_type4_r(%02x): $%02x '%c' <- PROM[%04x]\n", timer_get_time(), activecpu_get_previouspc(), offset, data, (data >= 32) ? data : '.', type4_ctrs));
			type4_ctrs = (type4_ctrs+1) & 0x7fff;
		}
		else
		{
			if (0 == (offset & E5XX_MASK))
			{
				data = cpunum_get_reg(2, I8X41_DATA);
				LOG(3,("%9.7f 6502-PC: %04x decocass_type4_r(%02x): $%02x '%c' <- open bus (D0 replaced with latch)\n", timer_get_time(), activecpu_get_previouspc(), offset, data, (data >= 32) ? data : '.'));
			}
			else
			{
				data = 0xff;	/* open data bus? */
				LOG(4,("%9.7f 6502-PC: %04x decocass_type4_r(%02x): $%02x <- open bus\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
			}
		}
	}

	return data;
}

WRITE8_HANDLER( decocass_type4_w )
{
	if (1 == (offset & 1))
	{
		if (1 == type4_latch)
		{
			type4_ctrs = (type4_ctrs & 0x00ff) | ((data & 0x7f) << 8);
			LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> CTRS MSB (%04x)\n", timer_get_time(), activecpu_get_previouspc(), offset, data, type4_ctrs));
			return;
		}
		else
		if (0xc0 == (data & 0xf0))
		{
			type4_latch = 1;
		}
	}
	else
	{
		if (type4_latch)
		{
			type4_ctrs = (type4_ctrs & 0xff00) | data;
			LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> CTRS LSB (%04x)\n", timer_get_time(), activecpu_get_previouspc(), offset, data, type4_ctrs));
			return;
		}
	}
	LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> %s\n", timer_get_time(), activecpu_get_previouspc(), offset, data, offset & 1 ? "8041-CMND" : "8041-DATA"));
	cpunum_set_reg(2, offset & 1 ? I8X41_CMND : I8X41_DATA, data);
}

/***************************************************************************
 *
 *  TYPE5 DONGLE
 *  - Boulder Dash
 *  Actually a NOP dongle returning 0x55 after triggering a latch
 *  by writing 1100xxxx to E5x1
 *
 ***************************************************************************/

READ8_HANDLER( decocass_type5_r )
{
	UINT8 data;

	if (1 == (offset & 1))
	{
		if (0 == (offset & E5XX_MASK))
		{
			data = cpunum_get_reg(2, I8X41_STAT);
			LOG(4,("%9.7f 6502-PC: %04x decocass_type5_r(%02x): $%02x <- 8041 STATUS\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
		}
		else
		{
			data = 0xff;	/* open data bus? */
			LOG(4,("%9.7f 6502-PC: %04x decocass_type5_r(%02x): $%02x <- open bus\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
		}
	}
	else
	{
		if (type5_latch)
		{
			data = 0x55;	/* Only a fixed value? It looks like this is all we need to do */
			LOG(3,("%9.7f 6502-PC: %04x decocass_type5_r(%02x): $%02x '%c' <- fixed value???\n", timer_get_time(), activecpu_get_previouspc(), offset, data, (data >= 32) ? data : '.'));
		}
		else
		{
			if (0 == (offset & E5XX_MASK))
			{
				data = cpunum_get_reg(2, I8X41_DATA);
				LOG(3,("%9.7f 6502-PC: %04x decocass_type5_r(%02x): $%02x '%c' <- open bus (D0 replaced with latch)\n", timer_get_time(), activecpu_get_previouspc(), offset, data, (data >= 32) ? data : '.'));
			}
			else
			{
				data = 0xff;	/* open data bus? */
				LOG(4,("%9.7f 6502-PC: %04x decocass_type5_r(%02x): $%02x <- open bus\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
			}
		}
	}

	return data;
}

WRITE8_HANDLER( decocass_type5_w )
{
	if (1 == (offset & 1))
	{
		if (1 == type5_latch)
		{
			LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> %s\n", timer_get_time(), activecpu_get_previouspc(), offset, data, "latch #2??"));
			return;
		}
		else
		if (0xc0 == (data & 0xf0))
			type5_latch = 1;
	}
	else
	{
		if (type5_latch)
		{
			/* write nowhere?? */
			LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> %s\n", timer_get_time(), activecpu_get_previouspc(), offset, data, "nowhere?"));
			return;
		}
	}
	LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> %s\n", timer_get_time(), activecpu_get_previouspc(), offset, data, offset & 1 ? "8041-CMND" : "8041-DATA"));
	cpunum_set_reg(2, offset & 1 ? I8X41_CMND : I8X41_DATA, data);
}

/***************************************************************************
 *
 *  NO DONGLE
 *  - Flying Ball
 *  A NOP dongle returning the data read from cassette as is.
 *
 ***************************************************************************/

READ8_HANDLER( decocass_nodong_r )
{
	UINT8 data;

	if (1 == (offset & 1))
	{
		if (0 == (offset & E5XX_MASK))
		{
			data = cpunum_get_reg(2, I8X41_STAT);
			LOG(4,("%9.7f 6502-PC: %04x decocass_nodong_r(%02x): $%02x <- 8041 STATUS\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
		}
		else
		{
			data = 0xff;	/* open data bus? */
			LOG(4,("%9.7f 6502-PC: %04x decocass_nodong_r(%02x): $%02x <- open bus\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
		}
	}
	else
	{
		if (0 == (offset & E5XX_MASK))
		{
			data = cpunum_get_reg(2, I8X41_DATA);
			LOG(3,("%9.7f 6502-PC: %04x decocass_nodong_r(%02x): $%02x '%c' <- open bus (D0 replaced with latch)\n", timer_get_time(), activecpu_get_previouspc(), offset, data, (data >= 32) ? data : '.'));
		}
		else
		{
			data = 0xff;	/* open data bus? */
			LOG(4,("%9.7f 6502-PC: %04x decocass_nodong_r(%02x): $%02x <- open bus\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
		}
	}

	return data;
}

/***************************************************************************
 *
 *  Main dongle and 8041 interface
 *
 ***************************************************************************/

READ8_HANDLER( decocass_e5xx_r )
{
	UINT8 data;

	/* E5x2-E5x3 and mirrors */
	if (2 == (offset & E5XX_MASK))
	{
		data =
			(BIT7(i8041_p1) 	  << 0) |	/* D0 = P17 - REQ/ */
			(BIT0(i8041_p2) 	  << 1) |	/* D1 = P20 - FNO/ */
			(BIT1(i8041_p2) 	  << 2) |	/* D2 = P21 - EOT/ */
			(BIT2(i8041_p2) 	  << 3) |	/* D3 = P22 - ERR/ */
			((tape_bot_eot) 	  << 4) |	/* D4 = BOT/EOT (direct from drive) */
			(1					  << 5) |	/* D5 floating input */
			(1					  << 6) |	/* D6 floating input */
			((1 - tape_present)   << 7);	/* D7 = cassette present */

		LOG(4,("%9.7f 6502-PC: %04x decocass_e5xx_r(%02x): $%02x <- STATUS (%s%s%s%s%s%s%s%s)\n",
			timer_get_time(),
			activecpu_get_previouspc(),
			offset, data,
			data & 0x01 ? "" : "REQ/",
			data & 0x02 ? "" : " FNO/",
			data & 0x04 ? "" : " EOT/",
			data & 0x08 ? "" : " ERR/",
			data & 0x10 ? " [BOT-EOT]" : "",
			data & 0x20 ? " [BIT5?]" : "",
			data & 0x40 ? " [BIT6?]" : "",
			data & 0x80 ? "" : " [CASS-PRESENT/]"));
	}
	else
	{
		if (decocass_dongle_r)
			data = (*decocass_dongle_r)(offset);
		else
			data = 0xff;
	}
	return data;
}

WRITE8_HANDLER( decocass_e5xx_w )
{
	if (decocass_dongle_w)
	{
		(*decocass_dongle_w)(offset, data);
		return;
	}

	if (0 == (offset & E5XX_MASK))
	{
		LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> %s\n", timer_get_time(), activecpu_get_previouspc(), offset, data, offset & 1 ? "8041-CMND" : "8041-DATA"));
		cpunum_set_reg(2, offset & 1 ? I8X41_CMND : I8X41_DATA, data);
#ifdef MAME_DEBUG
		decocass_fno(offset, data);
#endif
	}
	else
	{
		LOG(3,("%9.7f 6502-PC: %04x decocass_e5xx_w(%02x): $%02x -> dongle\n", timer_get_time(), activecpu_get_previouspc(), offset, data));
	}
}

/***************************************************************************
 *
 *  DE-0091xx daughter board handler
 *
 *  The DE-0091xx daughter board seems to be a read-only ROM board with
 *  two times five 4K ROMs. The only game using it (so far) is
 *  Treasure Island, which has 4 ROMs.
 *  The board's ROMs are mapped into view for reads between addresses
 *  0x6000 and 0xafff by setting bit0 of address 0xe900.
 *
 ***************************************************************************/

WRITE8_HANDLER( decocass_e900_w )
{
	de0091_enable = data & 1;
	memory_set_bank(1, data & 1);
	/* Perhaps the second row of ROMs is enabled by another bit.
     * There is no way to verify this yet, so for now just look
     * at bit 0 to enable the daughter board at reads between
     * 0x6000 and 0xafff.
     */
}

WRITE8_HANDLER( decocass_de0091_w )
{
	/* don't allow writes to the ROMs */
	if (!de0091_enable)
		decocass_charram_w(offset, data);
}

/***************************************************************************
 *
 *  state save setup
 *
 ***************************************************************************/
static void decocass_state_save_postload(void)
{
#if 0
	/* fix me - this won't work anymore */
	int A;
	unsigned char *mem = memory_region(REGION_CPU1);
	int diff = memory_region_length(REGION_CPU1) / 2;

	memory_set_opcode_base(0, mem + diff);

	for (A = 0;A < 0x10000; A++)
		decocass_w(A, mem[A]);
	/* restart the timer if the tape was playing */
	if (0 != tape_dir)
		timer_adjust(tape_timer, TIME_NEVER, 0, 0);
#endif
}

/* To be called once from driver_init, i.e. decocass_init */
void decocass_machine_state_save_init(void)
{
	state_save_register_func_postload(decocass_state_save_postload);
	state_save_register_global(tape_dir);
	state_save_register_global(tape_speed);
	state_save_register_global(tape_time0);
	state_save_register_global(firsttime);
	state_save_register_global(tape_present);
	state_save_register_global(tape_blocks);
	state_save_register_global(tape_length);
	state_save_register_global(tape_bot_eot);
	state_save_register_global(crc16_lsb);
	state_save_register_global(crc16_msb);
	state_save_register_global_array(tape_crc16_lsb);
	state_save_register_global_array(tape_crc16_msb);
	state_save_register_global(decocass_reset);
	state_save_register_global(i8041_p1);
	state_save_register_global(i8041_p2);
	state_save_register_global(de0091_enable);
	state_save_register_global(type1_inmap);
	state_save_register_global(type1_outmap);
	state_save_register_global(type2_d2_latch);
	state_save_register_global(type2_xx_latch);
	state_save_register_global(type2_promaddr);
	state_save_register_global(type3_ctrs);
	state_save_register_global(type3_d0_latch);
	state_save_register_global(type3_pal_19);
	state_save_register_global(type3_swap);
	state_save_register_global(type4_ctrs);
	state_save_register_global(type4_latch);
	state_save_register_global(type5_latch);
	state_save_register_global(decocass_sound_ack);
}

/***************************************************************************
 *
 *  init machine functions (select dongle and determine tape image size)
 *
 ***************************************************************************/

void decocass_init_common(void)
{
	UINT8 *image = memory_region(REGION_USER2);
	int i, offs;

	tape_dir = 0;
	tape_speed = 0;
	tape_timer = timer_alloc(NULL);

	firsttime = 1;
	tape_present = 1;
	tape_blocks = 0;
	for (i = memory_region_length(REGION_USER2) / 256 - 1; !tape_blocks && i > 0; i--)
		for (offs = 256 * i; !tape_blocks && offs < 256 * i + 256; offs++)
			if (image[offs])
				tape_blocks = i+1;
	for (i = 0; i < tape_blocks; i++)
	{
		crc16_lsb = 0;
		crc16_msb = 0;
		for (offs = 256 * i; offs < 256 * i + 256; offs++)
		{
			tape_crc16(image[offs] << 7);
			tape_crc16(image[offs] << 6);
			tape_crc16(image[offs] << 5);
			tape_crc16(image[offs] << 4);
			tape_crc16(image[offs] << 3);
			tape_crc16(image[offs] << 2);
			tape_crc16(image[offs] << 1);
			tape_crc16(image[offs] << 0);
		}
		tape_crc16_lsb[i] = crc16_lsb;
		tape_crc16_msb[i] = crc16_msb;
	}

	tape_length = tape_blocks * TAPE_CHUNK * 8 * 2 + 2 * (TAPE_LEADER + TAPE_GAP + TAPE_HOLE);
	tape_time0 = (double)(TAPE_LEADER + TAPE_GAP - TAPE_HOLE) / TAPE_CLOCKRATE;
	LOG(0,("tape: %d blocks\n", tape_blocks));
	tape_bot_eot = 0;

	decocass_dongle_r = NULL;
	decocass_dongle_w = NULL;

	decocass_reset = 0;
	i8041_p1 = 0xff;
	i8041_p2 = 0xff;

	type1_inmap = MAKE_MAP(0,1,2,3,4,5,6,7);
	type1_outmap = MAKE_MAP(0,1,2,3,4,5,6,7);

	type2_d2_latch = 0;
	type2_xx_latch = 0;
	type2_promaddr = 0;

	type3_ctrs = 0;
	type3_d0_latch = 0;
	type3_pal_19 = 0;
	type3_swap = 0;

	memset(decocass_quadrature_decoder, 0, sizeof(decocass_quadrature_decoder));
	decocass_sound_ack = 0;
	decocass_sound_timer = timer_alloc(decocass_sound_nmi_pulse);
}

MACHINE_RESET( decocass )
{
	decocass_init_common();
}

MACHINE_RESET( ctsttape )
{
	decocass_init_common();
	LOG(0,("dongle type #1 (DE-0061)\n"));
	decocass_dongle_r = decocass_type1_pass_136_r;
}

MACHINE_RESET( chwy )
{
	decocass_init_common();
	LOG(0,("dongle type #1 (DE-0061 own PROM)\n"));
	decocass_dongle_r = decocass_type1_latch_27_pass_3_inv_2_r;
}

MACHINE_RESET( clocknch )
{
	decocass_init_common();
	LOG(0,("dongle type #1 (DE-0061 flip 2-3)\n"));
	decocass_dongle_r = decocass_type1_latch_26_pass_3_inv_2_r;
	type1_inmap = MAKE_MAP(0,1,3,2,4,5,6,7);
	type1_outmap = MAKE_MAP(0,1,3,2,4,5,6,7);
}

MACHINE_RESET( ctisland )
{
	decocass_init_common();
	LOG(0,("dongle type #1 (DE-0061 flip 0-2)\n"));
	decocass_dongle_r = decocass_type1_latch_26_pass_3_inv_2_r;
	type1_inmap = MAKE_MAP(2,1,0,3,4,5,6,7);
	type1_outmap = MAKE_MAP(2,1,0,3,4,5,6,7);
}

MACHINE_RESET( csuperas )
{
	decocass_init_common();
	LOG(0,("dongle type #1 (DE-0061 flip 4-5)\n"));
	decocass_dongle_r = decocass_type1_latch_26_pass_3_inv_2_r;
	type1_inmap = MAKE_MAP(0,1,2,3,5,4,6,7);
	type1_outmap = MAKE_MAP(0,1,2,3,5,4,6,7);
}

MACHINE_RESET( castfant )
{
	decocass_init_common();
	LOG(0,("dongle type #1 (DE-0061)\n"));
	decocass_dongle_r = decocass_type1_latch_16_pass_3_inv_1_r;
}

MACHINE_RESET( cluckypo )
{
	decocass_init_common();
	LOG(0,("dongle type #1 (DE-0061 flip 1-3)\n"));
	decocass_dongle_r = decocass_type1_latch_26_pass_3_inv_2_r;
	type1_inmap = MAKE_MAP(0,3,2,1,4,5,6,7);
	type1_outmap = MAKE_MAP(0,3,2,1,4,5,6,7);
}

MACHINE_RESET( cterrani )
{
	decocass_init_common();
	LOG(0,("dongle type #1 (DE-0061 straight)\n"));
	decocass_dongle_r = decocass_type1_latch_26_pass_3_inv_2_r;
	type1_inmap = MAKE_MAP(0,1,2,3,4,5,6,7);
	type1_outmap = MAKE_MAP(0,1,2,3,4,5,6,7);
}

MACHINE_RESET( cexplore )
{
	decocass_init_common();
	LOG(0,("dongle type #1 (DE-0061 own PROM)\n"));
	decocass_dongle_r = decocass_type1_latch_26_pass_5_inv_2_r;
}

MACHINE_RESET( cprogolf )
{
	decocass_init_common();
	LOG(0,("dongle type #1 (DE-0061 flip 0-1)\n"));
	decocass_dongle_r = decocass_type1_latch_26_pass_3_inv_2_r;
	type1_inmap = MAKE_MAP(1,0,2,3,4,5,6,7);
	type1_outmap = MAKE_MAP(1,0,2,3,4,5,6,7);
}

MACHINE_RESET( cmissnx )
{
	decocass_init_common();
	LOG(0,("dongle type #2 (CS82-007)\n"));
	decocass_dongle_r = decocass_type2_r;
	decocass_dongle_w = decocass_type2_w;
}

MACHINE_RESET( cdiscon1 )
{
	decocass_init_common();
	LOG(0,("dongle type #2 (CS82-007)\n"));
	decocass_dongle_r = decocass_type2_r;
	decocass_dongle_w = decocass_type2_w;
}

MACHINE_RESET( cptennis )
{
	decocass_init_common();
	LOG(0,("dongle type #2 (CS82-007)\n"));
	decocass_dongle_r = decocass_type2_r;
	decocass_dongle_w = decocass_type2_w;
}

MACHINE_RESET( ctornado )
{
	decocass_init_common();
	LOG(0,("dongle type #2 (CS82-007)\n"));
	decocass_dongle_r = decocass_type2_r;
	decocass_dongle_w = decocass_type2_w;
}

MACHINE_RESET( cbnj )
{
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_67;
}

MACHINE_RESET( cburnrub )
{
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_67;
}

MACHINE_RESET( cbtime )
{
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_12;
}

MACHINE_RESET( cgraplop )
{
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_56;
}

MACHINE_RESET( cgraplp2 )
{
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_67;
}

MACHINE_RESET( clapapa )
{
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_34_7;
}

MACHINE_RESET( cfghtice )
{
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_25;
}

MACHINE_RESET( cprobowl )
{
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_34_0;
}

MACHINE_RESET( cnightst )
{
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_13;
}

MACHINE_RESET( cprosocc )
{
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_24;
}

MACHINE_RESET( cppicf )
{
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_01;
}

MACHINE_RESET( cscrtry )
{
	decocass_init_common();
	LOG(0,("dongle type #4 (32K ROM)\n"));
	decocass_dongle_r = decocass_type4_r;
	decocass_dongle_w = decocass_type4_w;
}

MACHINE_RESET( cbdash )
{
	decocass_init_common();
	LOG(0,("dongle type #5 (NOP)\n"));
	decocass_dongle_r = decocass_type5_r;
	decocass_dongle_w = decocass_type5_w;
}

MACHINE_RESET( cflyball )
{
	decocass_init_common();
	LOG(0,("no dongle\n"));
	decocass_dongle_r = decocass_nodong_r;
}

MACHINE_RESET( czeroize )
{
	UINT8 *mem = memory_region(REGION_USER1);
	decocass_init_common();
	LOG(0,("dongle type #3 (PAL)\n"));
	decocass_dongle_r = decocass_type3_r;
	decocass_dongle_w = decocass_type3_w;
	type3_swap = TYPE3_SWAP_23_56;

	/*
     * FIXME: remove if the original ROM is available.
     * The Zeroize 6502 code at 0x3707 issues LODCTRS with 0x8a,
     * and expects to read 0x18 from 0x08a0 ff. within 7 bytes.
     * This hack seems to be sufficient to get around
     * the missing dongle ROM contents and play the game.
     */
	mem[0x08a0] = 0x18;
}

/***************************************************************************
 *
 *  8041 port handlers
 *
 ***************************************************************************/

static void tape_stop(void)
{
	/* remember time */
	tape_time0 += tape_dir * timer_timeelapsed(tape_timer);
	timer_adjust(tape_timer, TIME_NEVER, 0, 0);
}


WRITE8_HANDLER( i8041_p1_w )
{
	static int i8041_p1_old;

	if (data != i8041_p1_old)
	{
		LOG(4,("%9.7f 8041-PC: %03x i8041_p1_w: $%02x (%s%s%s%s%s%s%s%s)\n",
			timer_get_time(),
			activecpu_get_previouspc(),
			data,
			data & 0x01 ? "" : "DATA-WRT",
			data & 0x02 ? "" : " DATA-CLK",
			data & 0x04 ? "" : " FAST",
			data & 0x08 ? "" : " BIT3",
			data & 0x10 ? "" : " REW",
			data & 0x20 ? "" : " FWD",
			data & 0x40 ? "" : " WREN",
			data & 0x80 ? "" : " REQ"));
		i8041_p1_old = data;
	}

	/* change in REW signal ? */
	if ((data ^ i8041_p1) & 0x10)
	{
		tape_stop();
		if (0 == (data & 0x10))
		{
			LOG(2,("tape %5.4fs: rewind\n", tape_time0));
			tape_dir = -1;
			timer_adjust(tape_timer, TIME_NEVER, 0, 0);
			set_led_status(0, 1);
		}
		else
		{
			tape_dir = 0;
			tape_speed = 0;
			LOG(2,("tape %5.4fs: stopped\n", tape_time0));
#if TAPE_UI_DISPLAY
			ui_popup("   [%05.1fs]   ", tape_time0);
#endif
			set_led_status(0, 0);
		}
	}

	/* change in FWD signal ? */
	if ((data ^ i8041_p1) & 0x20)
	{
		tape_stop();
		if (0 == (data & 0x20))
		{
			LOG(2,("tape %5.4fs: forward\n", tape_time0));
			tape_dir = +1;
			timer_adjust(tape_timer, TIME_NEVER, 0, 0);
			set_led_status(0, 1);
		}
		else
		{
			tape_dir = 0;
			tape_speed = 0;
			LOG(2,("tape %5.4fs: stopped\n", tape_time0));
#if TAPE_UI_DISPLAY
			ui_popup("   [%05.1fs]   ", tape_time0);
#endif
			set_led_status(0, 0);
		}
	}

	/* change in FAST signal ? */
	if (tape_timer && (data ^ i8041_p1) & 0x04)
	{
		tape_stop();
		tape_speed = (0 == (data & 0x04)) ? 1 : 0;

		if (tape_dir < 0)
		{
			LOG(2,("tape: fast rewind %s\n", (0 == (data & 0x04)) ? "on" : "off"));
			tape_dir = (tape_speed) ? -7 : -1;
			timer_adjust(tape_timer, TIME_NEVER, 0, 0);
		}
		else
		if (tape_dir > 0)
		{
			LOG(2,("tape: fast forward %s\n", (0 == (data & 0x04)) ? "on" : "off"));
			tape_dir = (tape_speed) ? +7 : +1;
			timer_adjust(tape_timer, TIME_NEVER, 0, 0);
		}
	}

	i8041_p1 = data;
}

READ8_HANDLER( i8041_p1_r )
{
	UINT8 data = i8041_p1;
	static int i8041_p1_old;

	if (data != i8041_p1_old)
	{
		LOG(4,("%9.7f 8041-PC: %03x i8041_p1_r: $%02x (%s%s%s%s%s%s%s%s)\n",
			timer_get_time(),
			activecpu_get_previouspc(),
			data,
			data & 0x01 ? "" : "DATA-WRT",
			data & 0x02 ? "" : " DATA-CLK",
			data & 0x04 ? "" : " FAST",
			data & 0x08 ? "" : " BIT3",
			data & 0x10 ? "" : " REW",
			data & 0x20 ? "" : " FWD",
			data & 0x40 ? "" : " WREN",
			data & 0x80 ? "" : " REQ"));
		i8041_p1_old = data;
	}
	return data;
}

WRITE8_HANDLER( i8041_p2_w )
{
	static int i8041_p2_old;

	if (data != i8041_p2_old)
	{
		LOG(4,("%9.7f 8041-PC: %03x i8041_p2_w: $%02x (%s%s%s%s%s%s%s%s)\n",
			timer_get_time(),
			activecpu_get_previouspc(),
			data,
			data & 0x01 ? "" : "FNO/",
			data & 0x02 ? "" : " EOT/",
			data & 0x04 ? "" : " ERR/",
			data & 0x08 ? "" : " OUT3?/",
			data & 0x10 ? " [IN4]" : "",
			data & 0x20 ? " [BOT-EOT]" : "",
			data & 0x40 ? " [RCLK]" : "",
			data & 0x80 ? " [RDATA]" : ""));
		i8041_p2_old = data;
	}
	i8041_p2 = data;
}

READ8_HANDLER( i8041_p2_r )
{
	UINT8 data;
	static int i8041_p2_old;

	tape_update();

	data = i8041_p2;

	if (data != i8041_p2_old)
	{
		LOG(4,("%9.7f 8041-PC: %03x i8041_p2_r: $%02x (%s%s%s%s%s%s%s%s)\n",
			timer_get_time(),
			activecpu_get_previouspc(),
			data,
			data & 0x01 ? "" : "FNO/",
			data & 0x02 ? "" : " EOT/",
			data & 0x04 ? "" : " ERR/",
			data & 0x08 ? "" : " OUT3?/",
			data & 0x10 ? " [IN4]" : "",
			data & 0x20 ? " [BOT-EOT]" : "",
			data & 0x40 ? " [RCLK]" : "",
			data & 0x80 ? " [RDATA]" : ""));
		i8041_p2_old = data;
	}
	return data;
}


