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

	Amiga CD-ROM controller emulation

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

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


#include "driver.h"
#include "amiga.h"
#include "amigacd.h"
#include "machine/6525tpi.h"
#include "machine/6526cia.h"
#include "machine/wd33c93.h"
#include "devices/chd_cd.h"
#include "matsucd.h"


#define VERBOSE_DMAC 0

#define LOG(x) do { if (VERBOSE_DMAC) logerror x; } while (0)

/* 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( running_machine *machine )
{
	/* 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(cpu_get_address_space(machine->cpu[0], ADDRESS_SPACE_PROGRAM), REG_INTREQ, 0x8000 | INTENA_PORTS, 0xffff);
}

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_oneshot(dmac_data.dma_timer, ATTOTIME_IN_MSEC( CD_SECTOR_TIME ), 0);
	}
	else
	{
		dmac_data.istr |= ISTR_INT_P | ISTR_E_INT;
		check_interrupts( machine );
	}
}

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", cpu_get_pc(space->cpu), dmac_data.istr ));

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

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

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

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

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

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

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

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

		case 0x51:	/* XT IO */
		case 0x52:
		case 0x53:
		{
			LOG(( "DMAC: PC=%08x - XT IO Read(%d)\n", cpu_get_pc(space->cpu), (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:
		{
			const device_config *tpi = devtag_get_device(space->machine, "tpi6525");
			LOG(( "DMAC: PC=%08x - TPI6525 Read(%d)\n", cpu_get_pc(space->cpu), (offset - 0x58) ));
			return tpi6525_r(tpi, offset - 0x58);
		}
		break;

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

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

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

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

		default:
			logerror( "DMAC-READ: PC=%08x, offset = %02x\n", cpu_get_pc(space->cpu), 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", cpu_get_pc(space->cpu), data ));
			dmac_data.cntr = data;
			check_interrupts( space->machine );
		}
		break;

		case 0x40:	/* wtc hi */
		{
			LOG(( "DMAC: PC=%08x - WTC HI Write - data = %04x\n", cpu_get_pc(space->cpu), 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", cpu_get_pc(space->cpu), data ));
			dmac_data.wtc &= 0xffff0000;
			dmac_data.wtc |= data;
		}
		break;

		case 0x42:	/* acr hi */
		{
			LOG(( "DMAC: PC=%08x - ACR HI Write - data = %04x\n", cpu_get_pc(space->cpu), 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", cpu_get_pc(space->cpu), data ));
			dmac_data.acr &= 0xffff0000;
			dmac_data.acr |= data;
		}
		break;

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

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

		case 0x50:
		{
			LOG(( "DMAC: PC=%08x - CDROM CMD Write - data = %04x\n", cpu_get_pc(space->cpu), data ));
			matsucd_command_w( space->machine, 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:
		{
			const device_config *tpi = devtag_get_device(space->machine, "tpi6525");
			LOG(( "DMAC: PC=%08x - TPI6525 Write(%d) - data = %04x\n", cpu_get_pc(space->cpu), (offset - 0x58), data ));
			tpi6525_w(tpi, offset - 0x58, data);
		}
		break;

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

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

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

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

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

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

	Autoconfig

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

static void	dmac_install(running_machine *machine, offs_t base)
{
	const address_space *space = cpu_get_address_space(machine->cpu[0], ADDRESS_SPACE_PROGRAM);
	memory_install_read16_handler(space, base, base + 0xFFFF, 0, 0, amiga_dmac_r);
	memory_install_write16_handler(space, base, base + 0xFFFF, 0, 0, amiga_dmac_w);
}

static void	dmac_uninstall(running_machine *machine, offs_t base)
{
	const address_space *space = cpu_get_address_space(machine->cpu[0], ADDRESS_SPACE_PROGRAM);
	memory_install_read16_handler(space, base, base + 0xFFFF, 0, 0, SMH_UNMAP);
	memory_install_write16_handler(space, base, base + 0xFFFF, 0, 0, SMH_UNMAP);
}

static const 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

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

READ8_DEVICE_HANDLER( amigacd_tpi6525_portc_r )
{
	int	ret = 0;

	if ( (tpi6525_get_ddr_c(device) & 0x04) == 0 ) /* if pin 2 is set to input */
	{
		ret |= matsucd_stch_r() ? 0x00 : 0x04;	/* read status change signal */
	}

	if ( (tpi6525_get_ddr_c(device) & 0x08) == 0 ) /* if pin 3 is set to input */
		ret |= matsucd_sten_r() ? 0x08 : 0x00;	/* read enable signal */

	return ret;
}

WRITE8_DEVICE_HANDLER( amigacd_tpi6525_portb_w )
{
	if ( tpi6525_get_ddr_b(device) & 0x01 ) /* if pin 0 is set to output */
		matsucd_cmd_w( data & 1 ); /* write to the /CMD signal */

	if ( tpi6525_get_ddr_b(device) & 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(cpu_get_address_space(machine->cpu[0], ADDRESS_SPACE_PROGRAM), REG_INTREQ, 0x8000 | INTENA_PORTS, 0xffff);
	}
	else
	{
		timer_adjust_oneshot(tp6525_delayed_timer, ATTOTIME_IN_MSEC(1), 0);
	}
}

void amigacd_tpi6525_irq(const device_config *device, int level)
{
	LOG(( "TPI6525 Interrupt: level = %d\n", level ));

	if ( level )
	{
		if ( (CUSTOM_REG(REG_INTREQ) & INTENA_PORTS) == 0 )
		{
			amiga_custom_w(cpu_get_address_space(device->machine->cpu[0], ADDRESS_SPACE_PROGRAM), REG_INTREQ, 0x8000 | INTENA_PORTS, 0xffff);
		}
		else
		{
			/* we *have to* deliver the irq, so if we can't, delay it and try again later */
			timer_adjust_oneshot(tp6525_delayed_timer, ATTOTIME_IN_MSEC(1), 0);
		}
	}
}

static void cdrom_status_enabled( running_machine *machine, int level )
{
	const device_config *tpi = devtag_get_device(machine, "tpi6525");

	/* PC3 on the 6525 */
	tpi6525_irq3_level(tpi, level);
}

static void cdrom_status_change( running_machine *machine, int level )
{
	const device_config *tpi = devtag_get_device(machine, "tpi6525");

	/* invert */
	level = level ? 0 : 1;

	/* PC2 on the 6525 */
	tpi6525_irq2_level(tpi, level);
}

static void cdrom_subcode_ready( running_machine *machine, int level )
{
	const device_config *tpi = devtag_get_device(machine, "tpi6525");

	/* PC1 on the 6525 */
	tpi6525_irq1_level(tpi, level);
}

MACHINE_START( amigacd )
{
	/* initialize the dmac */
	memset( &dmac_data, 0, sizeof( dmac_data ) );

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

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

MACHINE_RESET( amigacd )
{
	/* initialize the cdrom */
	matsucd_init( devtag_get_device(machine, "cdrom") );
	matsucd_set_status_enabled_callback( cdrom_status_enabled );
	matsucd_set_status_changed_callback( cdrom_status_change );
	matsucd_set_subcode_ready_callback( cdrom_subcode_ready );
}
