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

	Amiga cdrom controller emulation

Notes:
Many thanks to Toni Willem for all the help and information about the
DMAC controller.

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

#include "mame.h"
#include "driver.h"
#include "amiga.h"
#include "amigacd.h"
#include "machine/6526cia.h"
#include "machine/wd33c93.h"
#include "tpi6525.h"
#include "matsucd.h"

#define VERBOSE_DMAC 0

#if VERBOSE_DMAC
#define LOG logerror
#else
INLINE void dummy_log( const char *a, ... ) {}
#define LOG dummy_log
#endif

/* constants */
#define CD_SECTOR_TIME		(1000/((300*1024)/2048))	/* 2X CDROM sector time in msec (300KBps) */

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

	DMAC

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

/*
 * value to go into DAWR
 */
#define DAWR_ATZSC      3       /* according to A3000T service-manual */

/*
 * bits defined for CNTR
 */
#define CNTR_TCEN       (1<<7)  /* Terminal Count Enable */
#define CNTR_PREST      (1<<6)  /* Perp Reset (not implemented :-((( ) */
#define CNTR_PDMD       (1<<5)  /* Perp Device Mode Select (1=SCSI,0=XT/AT) */
#define CNTR_INTEN      (1<<4)  /* Interrupt Enable */
#define CNTR_DDIR       (1<<3)  /* Device Direction. 1==rd host, wr to perp */

/*
 * bits defined for ISTR
 */
#define ISTR_INTX       (1<<8)  /* XT/AT Interrupt pending */
#define ISTR_INT_F      (1<<7)  /* Interrupt Follow */
#define ISTR_INTS       (1<<6)  /* SCSI Peripheral Interrupt */
#define ISTR_E_INT      (1<<5)  /* End-Of-Process Interrupt */
#define ISTR_INT_P      (1<<4)  /* Interrupt Pending */
#define ISTR_UE_INT     (1<<3)  /* Under-Run FIFO Error Interrupt */
#define ISTR_OE_INT     (1<<2)  /* Over-Run FIFO Error Interrupt */
#define ISTR_FF_FLG     (1<<1)  /* FIFO-Full Flag */
#define ISTR_FE_FLG     (1<<0)  /* FIFO-Empty Flag */

typedef struct
{
	UINT16		istr;		/* Interrupt Status Register (R) */
 	UINT16		cntr;		/* Control Register (RW) */
	UINT32		wtc;		/* Word Transfer Count Register (RW) */
	UINT32		acr;		/* Address Count Register (RW) */
	UINT16		dawr;		/* DACK Width Register (W) */
	emu_timer *dma_timer;
} _dmac_data;

static _dmac_data dmac_data;

static void check_interrupts( void )
{
	/* if interrupts are disabled, bail */
	if ( (dmac_data.cntr & CNTR_INTEN) == 0 )
		return;

	/* if no interrupts are pending, bail */
	if ( (dmac_data.istr & ISTR_INT_P) == 0 )
		return;

	/* otherwise, generate the IRQ */
	amiga_custom_w(REG_INTREQ, 0x8000 | INTENA_PORTS, 0);
}

static TIMER_CALLBACK(dmac_dma_proc)
{
	while( dmac_data.wtc > 0 )
	{
		UINT16	dat16;
		UINT8	dat8;

		if ( matsucd_get_next_byte( &dat8 ) < 0 )
			break;

		dat16 = dat8;

		if ( matsucd_get_next_byte( &dat8 ) < 0 )
			break;

		dat16 <<= 8;
		dat16 |= dat8;

		amiga_chip_ram_w(dmac_data.acr, dat16);

		dmac_data.acr += 2;
		dmac_data.wtc--;
	}

	if ( dmac_data.wtc > 0 )
	{
		matsucd_read_next_block();
		timer_adjust( dmac_data.dma_timer, ATTOTIME_IN_MSEC( CD_SECTOR_TIME ), 0, attotime_zero );
	}
	else
	{
		dmac_data.istr |= ISTR_INT_P | ISTR_E_INT;
		check_interrupts();
	}
}

static READ16_HANDLER( amiga_dmac_r )
{
	offset &= 0xff;

	switch( offset )
	{
		case 0x20:
		{
			UINT8	v = dmac_data.istr;
			LOG( "DMAC: PC=%08x - ISTR Read(%04x)\n", activecpu_get_pc(), dmac_data.istr );

			dmac_data.istr &= ~0x0f;
			return v;
		}
		break;

		case 0x21:
		{
			LOG( "DMAC: PC=%08x - CNTR Read(%04x)\n", activecpu_get_pc(), dmac_data.cntr );
			return dmac_data.cntr;
		}
		break;

		case 0x40:	/* wtc hi */
		{
			LOG( "DMAC: PC=%08x - WTC HI Read\n", activecpu_get_pc() );
			return (dmac_data.wtc >> 16);
		}
		break;

		case 0x41:	/* wtc lo */
		{
			LOG( "DMAC: PC=%08x - WTC LO Read\n", activecpu_get_pc() );
			return dmac_data.wtc;
		}
		break;

		case 0x42:	/* acr hi */
		{
			LOG( "DMAC: PC=%08x - ACR HI Read\n", activecpu_get_pc() );
			return (dmac_data.acr >> 16);
		}
		break;

		case 0x43:	/* acr lo */
		{
			LOG( "DMAC: PC=%08x - ACR LO Read\n", activecpu_get_pc() );
			return dmac_data.acr;
		}
		break;

		case 0x48:	/* wd33c93 SCSI expansion */
		case 0x49:
		{
			LOG( "DMAC: PC=%08x - WD33C93 Read(%d)\n", activecpu_get_pc(), offset & 1 );
			return 0x00;	/* Not available without SCSI expansion */
		}
		break;

		case 0x50:
		{
			LOG( "DMAC: PC=%08x - CDROM RESP Read\n", activecpu_get_pc() );
			return matsucd_response_r();
		}
		break;

		case 0x51:	/* XT IO */
		case 0x52:
		case 0x53:
		{
			LOG( "DMAC: PC=%08x - XT IO Read(%d)\n", activecpu_get_pc(), (offset & 3)-1 );
			return 0xff;
		}
		break;

		case 0x58:	/* TPI6525 */
		case 0x59:
		case 0x5A:
		case 0x5B:
		case 0x5C:
		case 0x5D:
		case 0x5E:
		case 0x5F:
		case 0x60:
		case 0x61:
		case 0x62:
		case 0x63:
		case 0x64:
		case 0x65:
		case 0x66:
		case 0x67:
		{
			LOG( "DMAC: PC=%08x - TPI6525 Read(%d)\n", activecpu_get_pc(), (offset - 0x58) );
			return tpi6525_0_port_r(offset - 0x58);
		}
		break;

		case 0x70:	/* DMA start strobe */
		{
			LOG( "DMAC: PC=%08x - DMA Start Strobe\n", activecpu_get_pc() );
			timer_adjust( dmac_data.dma_timer, ATTOTIME_IN_MSEC( CD_SECTOR_TIME ), 0, attotime_zero );
		}
		break;

		case 0x71:	/* DMA stop strobe */
		{
			LOG( "DMAC: PC=%08x - DMA Stop Strobe\n", activecpu_get_pc() );
			timer_reset( dmac_data.dma_timer, attotime_never );
		}
		break;

		case 0x72:	/* Clear IRQ strobe */
		{
			LOG( "DMAC: PC=%08x - IRQ Clear Strobe\n", activecpu_get_pc() );
			dmac_data.istr &= ~ISTR_INT_P;
		}
		break;

		case 0x74:	/* Flush strobe */
		{
			LOG( "DMAC: PC=%08x - Flush Strobe\n", activecpu_get_pc() );
			dmac_data.istr |= ISTR_FE_FLG;
		}
		break;

		default:
			logerror( "DMAC-READ: PC=%08x, offset = %02x\n", activecpu_get_pc(), offset );
		break;
	}

	return 0;
}

static WRITE16_HANDLER( amiga_dmac_w )
{
	offset &= 0xff;

	switch( offset )
	{
		case 0x21:	/* control write */
		{
			LOG( "DMAC: PC=%08x - CNTR Write(%04x)\n", activecpu_get_pc(), data );
			dmac_data.cntr = data;
			check_interrupts();
		}
		break;

		case 0x40:	/* wtc hi */
		{
			LOG( "DMAC: PC=%08x - WTC HI Write - data = %04x\n", activecpu_get_pc(), data );
			dmac_data.wtc &= 0x0000ffff;
			dmac_data.wtc |= ((UINT32)data) << 16;
		}
		break;

		case 0x41:	/* wtc lo */
		{
			LOG( "DMAC: PC=%08x - WTC LO Write - data = %04x\n", activecpu_get_pc(), data );
			dmac_data.wtc &= 0xffff0000;
			dmac_data.wtc |= data;
		}
		break;

		case 0x42:	/* acr hi */
		{
			LOG( "DMAC: PC=%08x - ACR HI Write - data = %04x\n", activecpu_get_pc(), data );
			dmac_data.acr &= 0x0000ffff;
			dmac_data.acr |= ((UINT32)data) << 16;
		}
		break;

		case 0x43:	/* acr lo */
		{
			LOG( "DMAC: PC=%08x - ACR LO Write - data = %04x\n", activecpu_get_pc(), data );
			dmac_data.acr &= 0xffff0000;
			dmac_data.acr |= data;
		}
		break;

		case 0x47:	/* dawr */
		{
			LOG( "DMAC: PC=%08x - DAWR Write - data = %04x\n", activecpu_get_pc(), data );
			dmac_data.dawr = data;
		}
		break;

		case 0x48:	/* wd33c93 SCSI expansion */
		case 0x49:
		{
			LOG( "DMAC: PC=%08x - WD33C93 Write(%d) - data = %04x\n", activecpu_get_pc(), offset & 1, data );
			/* Not available without SCSI expansion */
		}
		break;

		case 0x50:
		{
			LOG( "DMAC: PC=%08x - CDROM CMD Write - data = %04x\n", activecpu_get_pc(), data );
			matsucd_command_w( data );
		}
		break;

		case 0x58:	/* TPI6525 */
		case 0x59:
		case 0x5A:
		case 0x5B:
		case 0x5C:
		case 0x5D:
		case 0x5E:
		case 0x5F:
		case 0x60:
		case 0x61:
		case 0x62:
		case 0x63:
		case 0x64:
		case 0x65:
		case 0x66:
		case 0x67:
		{
			LOG( "DMAC: PC=%08x - TPI6525 Write(%d) - data = %04x\n", activecpu_get_pc(), (offset - 0x58), data );
			tpi6525_0_port_w(offset - 0x58, data);
		}
		break;

		case 0x70:	/* DMA start strobe */
		{
			LOG( "DMAC: PC=%08x - DMA Start Strobe\n", activecpu_get_pc() );
			timer_adjust( dmac_data.dma_timer, ATTOTIME_IN_MSEC( CD_SECTOR_TIME ), 0, attotime_zero );
		}
		break;

		case 0x71:	/* DMA stop strobe */
		{
			LOG( "DMAC: PC=%08x - DMA Stop Strobe\n", activecpu_get_pc() );
			timer_reset( dmac_data.dma_timer, attotime_never );
		}
		break;

		case 0x72:	/* Clear IRQ strobe */
		{
			LOG( "DMAC: PC=%08x - IRQ Clear Strobe\n", activecpu_get_pc() );
			dmac_data.istr &= ~ISTR_INT_P;
		}
		break;

		case 0x74:	/* Flush Strobe */
		{
			LOG( "DMAC: PC=%08x - Flush Strobe\n", activecpu_get_pc() );
			dmac_data.istr |= ISTR_FE_FLG;
		}
		break;

		default:
			logerror( "DMAC-WRITE: PC=%08x, offset = %02x, data = %04x\n", activecpu_get_pc(), offset, data );
		break;
	}
}

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

	Autoconfig

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

static void	dmac_install(offs_t base)
{
	memory_install_read16_handler(0, ADDRESS_SPACE_PROGRAM, base, base + 0xFFFF, 0, 0, amiga_dmac_r);
	memory_install_write16_handler(0, ADDRESS_SPACE_PROGRAM, base, base + 0xFFFF, 0, 0, amiga_dmac_w);
}

static void	dmac_uninstall(offs_t base)
{
	memory_install_read16_handler(0, ADDRESS_SPACE_PROGRAM, base, base + 0xFFFF, 0, 0, MRA16_UNMAP);
	memory_install_write16_handler(0, ADDRESS_SPACE_PROGRAM, base, base + 0xFFFF, 0, 0, MWA16_UNMAP);
}

static amiga_autoconfig_device dmac_device =
{
	0,				/* link into free memory list */
	0,				/* ROM vector offset valid */
	0,				/* multiple devices on card */
	1,				/* number of 64k pages */
	3,				/* product number (DMAC) */
	0,				/* prefer 8MB address space */
	0,				/* can be shut up */
	0x0202,			/* manufacturers number (Commodore) */
	0,				/* serial number */
	0,				/* ROM vector offset */
	NULL,			/* interrupt control read */
	NULL,			/* interrupt control write */
	dmac_install,	/* memory installation */
	dmac_uninstall	/* memory uninstallation */
};

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

	TPI6525

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

static int tp6525_portc_r( void )
{
	int	ret = 0;

	if ( (tpi6525[0].c.ddr & 0x04) == 0 ) /* if pin 2 is set to input */
	{
		ret |= matsucd_stch_r() ? 0x00 : 0x04;	/* read status change signal */
	}

	if ( (tpi6525[0].c.ddr & 0x08) == 0 ) /* if pin 3 is set to input */
		ret |= matsucd_sten_r() ? 0x08 : 0x00;	/* read enable signal */

	return ret;
}

static void tp6525_portb_w( int data )
{
	if ( tpi6525[0].b.ddr & 0x01 ) /* if pin 0 is set to output */
		matsucd_cmd_w( data & 1 ); /* write to the /CMD signal */

	if ( tpi6525[0].b.ddr & 0x02 ) /* if pin 1 is set to output */
		matsucd_enable_w( data & 2 ); /* write to the /ENABLE signal */
}

static emu_timer *tp6525_delayed_timer;

static TIMER_CALLBACK(tp6525_delayed_irq)
{
	(void)param;

	if ( (CUSTOM_REG(REG_INTREQ) & INTENA_PORTS) == 0 )
	{
		amiga_custom_w(REG_INTREQ, 0x8000 | INTENA_PORTS, 0);
	}
	else
	{
		timer_adjust( tp6525_delayed_timer, ATTOTIME_IN_MSEC(1), 0, attotime_zero );
	}
}

static void tp6525_irq( int level )
{
	LOG( "TPI6525 Interrupt: level = %d\n", level);

	if ( level )
	{
		if ( (CUSTOM_REG(REG_INTREQ) & INTENA_PORTS) == 0 )
		{
			amiga_custom_w(REG_INTREQ, 0x8000 | INTENA_PORTS, 0);
		}
		else
		{
			/* we *have to* deliver the irq, so if we can't, delay it and try again later */
			timer_adjust( tp6525_delayed_timer, ATTOTIME_IN_MSEC(1), 0, attotime_zero );
		}
	}
}

static void cdrom_status_enabled( int level )
{
	/* PC3 on the 6525 */
	tpi6525_0_irq3_level( level );
}

static void cdrom_status_change( int level )
{
	/* invert */
	level = level ? 0 : 1;

	/* PC2 on the 6525 */
	tpi6525_0_irq2_level( level );
}

static void cdrom_subcode_ready( int level )
{
	/* PC1 on the 6525 */
	tpi6525_0_irq1_level( level );
}

void amigacd_init( void )
{
	/* initialize the dmac */
	memset( &dmac_data, 0, sizeof( dmac_data ) );

	dmac_data.dma_timer = timer_alloc(dmac_dma_proc, NULL);
	tp6525_delayed_timer = timer_alloc(tp6525_delayed_irq, NULL);

	/* initialize the 6525 TPI */
	tpi6525_0_reset();

	tpi6525[0].a.read = NULL;
	tpi6525[0].b.read = NULL;
	tpi6525[0].c.read = tp6525_portc_r;

	tpi6525[0].a.output = NULL;
	tpi6525[0].b.output = tp6525_portb_w;
	tpi6525[0].c.output = NULL;

	tpi6525[0].interrupt.output = tp6525_irq;
	tpi6525[0].ca.output = NULL;
	tpi6525[0].cb.output = NULL;

	/* initialize the cdrom */
	matsucd_init();
	matsucd_set_status_enabled_callback( cdrom_status_enabled );
	matsucd_set_status_changed_callback( cdrom_status_change );
	matsucd_set_subcode_ready_callback( cdrom_subcode_ready );

	/* set up DMAC with autoconfig */
	amiga_add_autoconfig( &dmac_device );
}
