/* LSI Logic LSI53C810A PCI to SCSI I/O Processor */

#include "driver.h"
#include "53c810.h"

#define DMA_MAX_ICOUNT	512	/* Maximum number of DMA Scripts opcodes to run */
#define DASM_OPCODES 1

typedef struct
{
	void *data;		/* device's "this" pointer */
	pSCSIDispatch handler;	/* device's handler routine */
} SCSIDev;

static SCSIDev devices[8];	/* SCSI IDs 0-7 */
static struct LSI53C810interface *intf;
static UINT8 last_id;

static struct {
	UINT8 scntl0;
	UINT8 scntl1;
	UINT8 scntl2;
	UINT8 scntl3;
	UINT8 scid;
	UINT8 istat;
	UINT8 dstat;
	UINT8 dien;
	UINT8 dcntl;
	UINT8 dmode;
	UINT32 dsa;
	UINT32 dsp;
	UINT32 dsps;
	UINT32 dcmd;
	UINT8 sien0;
	UINT8 sien1;
	UINT8 stime0;
	UINT8 respid;
	UINT8 stest1;
	UINT8 scratch_a[4];
	UINT8 scratch_b[4];
	int dma_icount;
	int halted;
} lsi810;

static void (* dma_opcode[256])(void);


INLINE UINT32 FETCH(void)
{
	UINT32 r = intf->fetch(lsi810.dsp);
	lsi810.dsp += 4;
	return r;
}

static UINT32 sign_extend24(UINT32 val)
{
	if (val & 0x00800000)
		val |= 0xFF000000;
	else
		val &= ~0xFF000000;
	return val;
}

static void dmaop_invalid(void)
{
	osd_die("LSI53C810: Invalid SCRIPTS DMA opcode %08X at %08X\n", lsi810.dcmd, lsi810.dsp);
}

static void dmaop_move_memory(void)
{
	UINT32 src = FETCH();
	UINT32 dst = FETCH();
	int count;

	count = lsi810.dcmd & 0xffffff;
	if(intf->dma_callback != NULL) {
		intf->dma_callback(src, dst, count, 1);
	}
}

static void dmaop_interrupt(void)
{
	if(lsi810.dcmd & 0x100000) {
		osd_die("LSI53C810: INTFLY opcode not implemented\n");
	}
	lsi810.dsps = FETCH();

	lsi810.istat |= 0x1;	/* DMA interrupt pending */
	lsi810.dstat |= 0x4;	/* SIR (SCRIPTS Interrupt Instruction Received) */

	if(intf->irq_callback != NULL) {
		intf->irq_callback();
	}
	lsi810.dma_icount = 0;
	lsi810.halted = 1;
}

static void dmaop_block_move(void)
{
	UINT32 address;
	UINT32 count;

	address = FETCH();

	if (lsi810.dcmd & 0x10000000)
	{
		/* table indirect addressing */
		address = lsi810.dsa + sign_extend24(address & 0x00ffffff);

		count = intf->fetch(address + 0) & 0x00ffffff;
		address = intf->fetch(address + 4);
	}
	else
	{
		/* direct or indirect addressing? */
		if (lsi810.dcmd & 0x20000000)
			address = intf->fetch(address);

		count = lsi810.dcmd & 0x00ffffff;
	}

	if (lsi810.scntl0 & 0x01)
	{
		/* target mode */
		osd_die("LSI53C810: dmaop_block_move not implemented\n");
	}
	else
	{
		/* initiator mode */
		osd_die("LSI53C810: dmaop_block_move not implemented\n");
	}
}

static void dmaop_select(void)
{
	UINT32 operand;

	operand = FETCH();

	if (lsi810.scntl0 & 0x01)
	{
		/* target mode */
//		osd_die("LSI53C810: dmaop_select not implemented\n");
		logerror("LSI53C810: reselect ID #%d\n", (lsi810.dcmd >> 16) & 0x07);
	}
	else
	{
		/* initiator mode */
//		osd_die("LSI53C810: dmaop_select not implemented\n");
		logerror("LSI53C810: select ID #%d\n", (lsi810.dcmd >> 16) & 0x07);
	}
}

static void dmaop_wait_disconnect(void)
{
	UINT32 operand;

	operand = FETCH();

	if (lsi810.scntl0 & 0x01)
	{
		/* target mode */
		osd_die("LSI53C810: dmaop_wait_disconnect not implemented\n");
	}
	else
	{
		/* initiator mode */
		osd_die("LSI53C810: dmaop_wait_disconnect not implemented\n");
	}
}

static void dmaop_wait_reselect(void)
{
	UINT32 operand;

	operand = FETCH();

	if (lsi810.scntl0 & 0x01)
	{
		/* target mode */
		osd_die("LSI53C810: dmaop_wait_reselect not implemented\n");
	}
	else
	{
		/* initiator mode */
		osd_die("LSI53C810: dmaop_wait_reselect not implemented\n");
	}
}

static void dmaop_set(void)
{
	UINT32 operand;

	operand = FETCH();

	if (lsi810.scntl0 & 0x01)
	{
		/* target mode */
		osd_die("LSI53C810: dmaop_set not implemented\n");
	}
	else
	{
		/* initiator mode */
		osd_die("LSI53C810: dmaop_set not implemented\n");
	}
}

static void dmaop_clear(void)
{
	UINT32 operand;

	operand = FETCH();

	if (lsi810.scntl0 & 0x01)
	{
		/* target mode */
		osd_die("LSI53C810: dmaop_clear not implemented\n");
	}
	else
	{
		/* initiator mode */
		osd_die("LSI53C810: dmaop_clear not implemented\n");
	}
}

static void dmaop_move_from_sfbr(void)
{
	osd_die("LSI53C810: dmaop_move_from_sfbr not implemented\n");
}

static void dmaop_move_to_sfbr(void)
{
	osd_die("LSI53C810: dmaop_move_to_sfbr not implemented\n");
}

static void dmaop_read_modify_write(void)
{
	osd_die("LSI53C810: dmaop_read_modify_write not implemented\n");
}

static void dmaop_jump(void)
{
	UINT32 dsps, dest;

	dsps = FETCH();

	/* relative or absolute addressing? */
	if (lsi810.dcmd & 0x00800000)
		dest = lsi810.dsp + sign_extend24(dsps);
	else
		dest = dsps;

	lsi810.dsp = dest;
}

static void dmaop_call(void)
{
	UINT32 dsps, dest;

	dsps = FETCH();

	/* relative or absolute addressing? */
	if (lsi810.dcmd & 0x08000000)
		dest = dsps + ((lsi810.dcmd & 0x00FFFFFF) | ((lsi810.dcmd & 0x00800000) ? 0xFF000000 : 0));
	else
		dest = dsps;

	osd_die("LSI53C810: dmaop_call not implemented\n");
}

static void dmaop_return(void)
{
	UINT32 dsps, dest;

	dsps = FETCH();

	/* relative or absolute addressing? */
	if (lsi810.dcmd & 0x08000000)
		dest = dsps + ((lsi810.dcmd & 0x00FFFFFF) | ((lsi810.dcmd & 0x00800000) ? 0xFF000000 : 0));
	else
		dest = dsps;

	osd_die("LSI53C810: dmaop_return not implemented\n");
}

static void dmaop_store(void)
{
	osd_die("LSI53C810: dmaop_store not implemented\n");
}

static void dmaop_load(void)
{
	osd_die("LSI53C810: dmaop_load not implemented\n");
}



static void dma_exec(void)
{
	lsi810.dma_icount = DMA_MAX_ICOUNT;

	while(lsi810.dma_icount > 0)
	{
		int op;

		if (DASM_OPCODES)
		{
			char buf[256];
			lsi53c810_dasm(buf, lsi810.dsp);
			logerror("0x%08X: %s\n", lsi810.dsp, buf);
		}

		lsi810.dcmd = FETCH();

		op = (lsi810.dcmd >> 24) & 0xff;
		dma_opcode[op]();

		lsi810.dma_icount--;
	}
}

UINT8 lsi53c810_reg_r(int reg)
{
	switch(reg)
	{
		case 0x00:		/* SCNTL0 */
			return lsi810.scntl0;
		case 0x01:		/* SCNTL1 */
			return lsi810.scntl1;
		case 0x02:		/* SCNTL2 */
			return lsi810.scntl2;
		case 0x03:		/* SCNTL3 */
			return lsi810.scntl3;
		case 0x04:		/* SCID */
			return lsi810.scid;
		case 0x0c:		/* DSTAT */
			return lsi810.dstat;
		case 0x10:		/* DSA [7-0] */
			return lsi810.dsa & 0xff;
		case 0x11:		/* DSA [15-8] */
			return (lsi810.dsa >> 8) & 0xff;
		case 0x12:		/* DSA [23-16] */
			return (lsi810.dsa >> 16) & 0xff;
		case 0x13:		/* DSA [31-24] */
			return (lsi810.dsa >> 24) & 0xff;
		case 0x14:		/* ISTAT */
			return lsi810.istat;
		case 0x2c:		/* DSP [7-0] */
			return lsi810.dsp & 0xff;
		case 0x2d:		/* DSP [15-8] */
			return (lsi810.dsp >> 8) & 0xff;
		case 0x2e:		/* DSP [23-16] */
			return (lsi810.dsp >> 16) & 0xff;
		case 0x2f:		/* DSP [31-24] */
			return (lsi810.dsp >> 24) & 0xff;
		case 0x34:		/* SCRATCH A */
		case 0x35:
		case 0x36:
		case 0x37:
			return lsi810.scratch_a[reg % 4];
		case 0x39:		/* DIEN */
			return lsi810.dien;
		case 0x3b:		/* DCNTL */
			return lsi810.dcntl;
		case 0x40:		/* SIEN0 */
			return lsi810.sien0;
		case 0x41:		/* SIEN1 */
			return lsi810.sien1;
		case 0x48:		/* STIME0 */
			return lsi810.stime0;
		case 0x4a:		/* RESPID */
			return lsi810.respid;
		case 0x4d:		/* STEST1 */
			return lsi810.stest1;
		case 0x5c:		/* SCRATCH B */
		case 0x5d:
		case 0x5e:
		case 0x5f:
			return lsi810.scratch_b[reg % 4];

		default:
			osd_die("LSI53C810: reg_r: Unknown reg %02X\n", reg);
	}

	return 0;
}

void lsi53c810_reg_w(int reg, UINT8 value)
{
	switch(reg)
	{
		case 0x00:		/* SCNTL0 */
			lsi810.scntl0 = value;
			break;
		case 0x01:		/* SCNTL1 */
			lsi810.scntl1 = value;
			break;
		case 0x02:		/* SCNTL2 */
			lsi810.scntl2 = value;
			break;
		case 0x03:		/* SCNTL3 */
			lsi810.scntl3 = value;
			break;
		case 0x04:		/* SCID */
			lsi810.scid = value;
			break;
		case 0x10:		/* DSA [7-0] */
			lsi810.dsa &= 0xffffff00;
			lsi810.dsa |= value;
			break;
		case 0x11:		/* DSA [15-8] */
			lsi810.dsa &= 0xffff00ff;
			lsi810.dsa |= value << 8;
			break;
		case 0x12:		/* DSA [23-16] */
			lsi810.dsa &= 0xff00ffff;
			lsi810.dsa |= value << 16;
			break;
		case 0x13:		/* DSA [31-24] */
			lsi810.dsa &= 0x00ffffff;
			lsi810.dsa |= value << 24;
			break;
		case 0x14:		/* ISTAT */
			lsi810.istat = value;
			break;
		case 0x2c:		/* DSP [7-0] */
			lsi810.dsp &= 0xffffff00;
			lsi810.dsp |= value;
			break;
		case 0x2d:		/* DSP [15-8] */
			lsi810.dsp &= 0xffff00ff;
			lsi810.dsp |= value << 8;
			break;
		case 0x2e:		/* DSP [23-16] */
			lsi810.dsp &= 0xff00ffff;
			lsi810.dsp |= value << 16;
			break;
		case 0x2f:		/* DSP [31-24] */
			lsi810.dsp &= 0x00ffffff;
			lsi810.dsp |= value << 24;
			lsi810.halted = 0;
			if((lsi810.dmode & 0x1) == 0 && !lsi810.halted) {
				dma_exec();
			}
			break;
		case 0x34:		/* SCRATCH A */
		case 0x35:
		case 0x36:
		case 0x37:
			lsi810.scratch_a[reg % 4] = value;
			break;
		case 0x38:		/* DMODE */
			lsi810.dmode = value;
			break;
		case 0x39:		/* DIEN */
			lsi810.dien = value;
			break;
		case 0x3b:		/* DCNTL */
			lsi810.dcntl = value;

			if(lsi810.dcntl & 0x14 && !lsi810.halted)		/* single-step & start DMA */
			{
				int op;
				lsi810.dcmd = FETCH();
				op = (lsi810.dcmd >> 24) & 0xff;
				dma_opcode[op]();

				lsi810.istat |= 0x3;	/* DMA interrupt pending */
				lsi810.dstat |= 0x8;	/* SSI (Single Step Interrupt) */
				if(intf->irq_callback != NULL) {
					intf->irq_callback();
				}
			}
			else if(lsi810.dcntl & 0x04 && !lsi810.halted)	/* manual start DMA */
			{
				dma_exec();
			}
			break;
		case 0x40:		/* SIEN0 */
			lsi810.sien0 = value;
			break;
		case 0x41:		/* SIEN1 */
			lsi810.sien1 = value;
			break;
		case 0x48:		/* STIME0 */
			lsi810.stime0 = value;
			break;
		case 0x4a:		/* RESPID */
			lsi810.respid = value;
			break;
		case 0x4d:		/* STEST1 */
			lsi810.stest1 = value;
			break;
		case 0x5c:		/* SCRATCH B */
		case 0x5d:
		case 0x5e:
		case 0x5f:
			lsi810.scratch_b[reg % 4] = value;
			break;

		default:
			osd_die("LSI53C810: reg_w: Unknown reg %02X, %02X\n", reg, value);
	}
}

static void add_opcode(UINT8 op, UINT8 mask, void (* handler)(void))
{
	int i;
	for(i=0; i < 256; i++) {
		if((i & mask) == op) {
			dma_opcode[i] = handler;
		}
	}
}

extern void lsi53c810_init(struct LSI53C810interface *interface)
{
	int i;

	// save interface pointer for later
	intf = interface;

	memset(&lsi810, 0, sizeof(lsi810));
	for(i = 0; i < 256; i++)
	{
		dma_opcode[i] = dmaop_invalid;
	}

	add_opcode(0x00, 0xc0, dmaop_block_move);
	add_opcode(0x40, 0xf8, dmaop_select);
	add_opcode(0x48, 0xf8, dmaop_wait_disconnect);
	add_opcode(0x50, 0xf8, dmaop_wait_reselect);
	add_opcode(0x58, 0xf8, dmaop_set);
	add_opcode(0x60, 0xf8, dmaop_clear);
	add_opcode(0x68, 0xf8, dmaop_move_from_sfbr);
	add_opcode(0x70, 0xf8, dmaop_move_to_sfbr);
	add_opcode(0x78, 0xf8, dmaop_read_modify_write);
	add_opcode(0x80, 0xf8, dmaop_jump);
	add_opcode(0x88, 0xf8, dmaop_call);
	add_opcode(0x90, 0xf8, dmaop_return);
	add_opcode(0x98, 0xf8, dmaop_interrupt);
	add_opcode(0xc0, 0xfe, dmaop_move_memory);
	add_opcode(0xe0, 0xed, dmaop_store);
	add_opcode(0xe1, 0xed, dmaop_load);

	memset(devices, 0, sizeof(devices));

	// try to open the devices
	for (i = 0; i < interface->scsidevs->devs_present; i++)
	{
		devices[interface->scsidevs->devices[i].scsiID].handler = interface->scsidevs->devices[i].handler;
		interface->scsidevs->devices[i].handler(SCSIOP_ALLOC_INSTANCE, &devices[interface->scsidevs->devices[i].scsiID].data, interface->scsidevs->devices[i].diskID, (UINT8 *)NULL);
	}
}

void lsi53c810_read_data(int bytes, UINT8 *pData)
{
	if (devices[last_id].handler)
	{
		devices[last_id].handler(SCSIOP_READ_DATA, devices[last_id].data, bytes, pData);
	}
	else
	{
		logerror("lsi53c810: read unknown device SCSI ID %d\n", last_id);
	}
}

void lsi53c810_write_data(int bytes, UINT8 *pData)
{
	if (devices[last_id].handler)
	{
		devices[last_id].handler(SCSIOP_WRITE_DATA, devices[last_id].data, bytes, pData);
	}
	else
	{
		logerror("lsi53c810: write to unknown device SCSI ID %d\n", last_id);
	}
}

void *lsi53c810_get_device(int id)
{
	void *ret;

	if (devices[id].handler)
	{
		logerror("lsi53c810: fetching dev pointer for SCSI ID %d\n", id);
		devices[id].handler(SCSIOP_GET_DEVICE, devices[id].data, 0, (UINT8 *)&ret);

		return ret;
	}

	return NULL;
}

/*************************************
 *
 *  Disassembler
 *
 *************************************/

static UINT32 lsi53c810_dasm_fetch(UINT32 pc)
{
	return intf->fetch(pc);
}

unsigned lsi53c810_dasm(char *buf, UINT32 pc)
{
	unsigned result = 0;
	const char *op_mnemonic = NULL;
	UINT32 op = lsi53c810_dasm_fetch(pc);
	UINT32 dest;
	int i;

	static const char *phases[] =
	{
		"DATA_OUT", "DATA_IN", "CMD", "STATUS",
		"RESERVED_OUT??", "RESERVED_IN??", "MSG_OUT", "MSG_IN"
	};

	if ((op & 0xF8000000) == 0x40000000)
	{
		/* SELECT */
		dest = lsi53c810_dasm_fetch(pc + 4);

		buf += sprintf(buf, "SELECT%s %d, 0x%08X",
			(op & 0x01000000) ? " ATN" : "",
			(op >> 16) & 0x07,
			dest);

		result = 8;
	}
	else if (((op & 0xF8000000) == 0x58000000)
		| ((op & 0xF8000000) == 0x60000000))
	{
		static struct
		{
			UINT32 flag;
			const char *text;
		} flags[] =
		{
			{ 0x00000008, "ATN" },
			{ 0x00000040, "ACK" },
			{ 0x00000200, "TARGET" },
			{ 0x00000400, "CARRY" }
		};
		int need_cojunction = FALSE;

		/* SET/CLEAR */
		switch(op & 0xF8000000)
		{
			case 0x58000000: op_mnemonic = "SET"; break;
			case 0x60000000: op_mnemonic = "CLEAR"; break;
		}

		buf += sprintf(buf, "%s ", op_mnemonic);
		need_cojunction = FALSE;

		for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++)
		{
			if (op & flags[i].flag)
			{
				if (need_cojunction)
					buf += sprintf(buf, " AND ");
				else
					need_cojunction = TRUE;
				buf += sprintf(buf, "%s", flags[i].text);
			}
		}
	}
	else if (((op & 0xF8000000) == 0x80000000)
		| ((op & 0xF8000000) == 0x88000000)
		| ((op & 0xF8000000) == 0x98000000))
	{
		/* JUMP/CALL/INT */
		switch(op & 0xF8000000)
		{
			case 0x80000000: op_mnemonic = "JUMP"; break;
			case 0x88000000: op_mnemonic = "CALL"; break;
			case 0x98000000: op_mnemonic = "INT"; break;
		}

		dest = lsi53c810_dasm_fetch(pc + 4);

		if (op & 0x00800000)
		{
			/* relative */
			if (dest & 0x00800000)
				dest |= 0xFF000000;
			else
				dest &= 0x00FFFFFF;
			dest = (pc + 8) + dest;
			buf += sprintf(buf, "%s REL(0x%08X)", op_mnemonic, dest);
		}
		else
		{
			/* absolute */
			buf += sprintf(buf, "%s 0x%08X", op_mnemonic, dest);
		}

		switch(op & 0x000B0000)
		{
			case 0x00000000:
				buf += sprintf(buf, ", NOT??");
				break;

			case 0x00080000:
				break;

			case 0x00020000:
			case 0x00030000:
			case 0x000A0000:
			case 0x000B0000:
				buf += sprintf(buf, ", %s%s %s",
					(op & 0x00010000) ? "WHEN" : "IF",
					(op & 0x00080000) ? "" : " NOT",
					phases[(op >> 24) & 0x07]);
				break;

			default:
				osd_die("unknown op 0x%08X", op);
				break;
		}
		result = 8;
	}
	else if ((op & 0xE0000000) == 0x00000000)
	{
		/* MOVE FROM */
		dest = lsi53c810_dasm_fetch(pc + 4);

		buf += sprintf(buf, "MOVE FROM 0x%08X, WHEN %s",
			dest, phases[(op >> 24) & 0x07]);

		result = 8;
	}
	else if ((op & 0xE0000000) == 0x20000000)
	{
		/* MOVE PTR */
		dest = lsi53c810_dasm_fetch(pc + 4);

		buf += sprintf(buf, "MOVE 0x%08X, PTR 0x%08X, WHEN %s",
			(op & 0x00FFFFFF), dest, phases[(op >> 24) & 0x07]);

		result = 8;
	}
	else
	{
		osd_die("unknown op 0x%08X", op);
	}
	return result;
}

