/*
* UAE - The Un*x Amiga Emulator
*
* CDTV-CR emulation
* (4510 microcontroller only simulated)
*
* Copyright 2014 Toni Wilen
*
*
*/

#define CDTVCR_4510_EMULATION 0

#define CDTVCR_DEBUG 0

#include "sysconfig.h"
#include "sysdeps.h"

#include "options.h"
#include "memory.h"
#include "newcpu.h"
#include "debug.h"
#include "zfile.h"
#include "gui.h"
#include "cdtvcr.h"
#include "blkdev.h"
#include "threaddep/thread.h"
#include "uae.h"
#include "custom.h"
#include "rommgr.h"
#include "devices.h"

#if CDTVCR_4510_EMULATION
static void init_65c02(void);
static void cpu_4510(void);
#endif

#define CDTVCR_MASK 0xffff

#define CDTVCR_RAM_OFFSET 0x1000
#define CDTVCR_RAM_SIZE 4096
#define CDTVCR_RAM_MASK (CDTVCR_RAM_SIZE - 1)

#define CDTVCR_CLOCK 0xc10
#define CDTVCR_ID 0x9dc

#define CDTVCR_RESET 0xc00

#define CDTVCR_CD_CMD 0xc40
#define CDTVCR_CD_CMD_DO 0xc52
#define CDTVCR_CD_CMD_STATUS 0xc4e
#define CDTVCR_CD_CMD_STATUS2 0xc4f

#define CDTVCR_CD_STATE 0xc53
#define CDTVCR_SYS_STATE 0xc54
#define CDTVCR_CD_SPEED 0xc59
#define CDTVCR_CD_PLAYING 0xc5b
#define CDTVCR_CD_SUBCODES 0xc60
#define CDTVCR_CD_ALLOC 0xc61

#define CDTVCR_INTDISABLE 0xc04
#define CDTVCR_INTACK 0xc05
#define CDTVCR_INTENA 0xc55
#define CDTVCR_INTREQ 0xc56
#define CDTVCR_4510_TRIGGER 0x4000

#define CDTVCR_KEYCMD 0xc80

#define CDTVCR_SUBQ 0x906
#define CDTVCR_SUBBANK 0x917
#define CDTVCR_SUBC 0x918
#define CDTVCR_TOC 0xa00

#define CDTVCR_PLAYLIST_TRACK 0xc2f
#define CDTVCR_PLAYLIST_TIME_MINS 0xc35
#define CDTVCR_PLAYLIST_TIME_SECS 0xc36
#define CDTVCR_PLAYLIST_TIME_MODE 0xc22
#define CDTVCR_PLAYLIST_TIME_MODE2 0xc84
#define CDTVCR_PLAYLIST_STATE 0xc86
#define CDTVCR_PLAYLIST_CURRENT 0xc8a
#define CDTVCR_PLAYLIST_ENTRIES 0xc8b
#define CDTVCR_PLAYLIST_DATA 0xc8c


static uae_u8 cdtvcr_4510_ram[CDTVCR_RAM_OFFSET];
static uae_u8 cdtvcr_ram[CDTVCR_RAM_SIZE];
static uae_u8 cdtvcr_clock[2];

static smp_comm_pipe requests;
static uae_sem_t sub_sem;
static volatile int thread_alive;
static int unitnum = -1;
static struct cd_toc_head toc;
static int datatrack;
static int cdtvcr_media;
static int subqcnt;
static int cd_audio_status;
static int cdtvcr_wait_sectors;
static int cd_led;

#define MAX_SUBCODEBUFFER 36
static volatile int subcodebufferoffset, subcodebufferoffsetw;
static uae_u8 subcodebufferinuse[MAX_SUBCODEBUFFER];
static uae_u8 subcodebuffer[MAX_SUBCODEBUFFER * SUB_CHANNEL_SIZE];
static TCHAR flashfilepath[MAX_DPATH];

static void cdtvcr_battram_reset (void)
{
	struct zfile *f;
	size_t v;

	memset (cdtvcr_ram, 0, CDTVCR_RAM_SIZE);
	cfgfile_resolve_path_out_load(currprefs.flashfile, flashfilepath, MAX_DPATH, PATH_ROM);
	f = zfile_fopen (flashfilepath, _T("rb+"), ZFD_NORMAL);
	if (!f) {
		f = zfile_fopen (flashfilepath, _T("wb"), 0);
		if (f) {
			zfile_fwrite (cdtvcr_ram, CDTVCR_RAM_SIZE, 1, f);
			zfile_fclose (f);
		}
		return;
	}
	v = zfile_fread (cdtvcr_ram, 1, CDTVCR_RAM_SIZE, f);
	if (v < CDTVCR_RAM_SIZE)
		zfile_fwrite (cdtvcr_ram + v, 1, CDTVCR_RAM_SIZE - v, f);
	zfile_fclose (f);
}

static void cdtvcr_battram_write (int addr, int v)
{
	struct zfile *f;
	int offset = addr & CDTVCR_RAM_MASK;

	if (offset >= CDTVCR_RAM_SIZE)
		return;
	gui_flicker_led (LED_MD, 0, 2);
	if (cdtvcr_ram[offset] == v)
		return;
	cdtvcr_ram[offset] = v;
	f = zfile_fopen (flashfilepath, _T("rb+"), ZFD_NORMAL);
	if (!f)
		return;
	zfile_fseek (f, offset, SEEK_SET);
	zfile_fwrite (cdtvcr_ram + offset, 1, 1, f);
	zfile_fclose (f);
}

static uae_u8 cdtvcr_battram_read (int addr)
{
	uae_u8 v;
	int offset;
	offset = addr & CDTVCR_RAM_MASK;
	if (offset >= CDTVCR_RAM_SIZE)
		return 0;
	gui_flicker_led (LED_MD, 0, 1);
	v = cdtvcr_ram[offset];
	return v;
}

static int ismedia (void)
{
	cdtvcr_4510_ram[CDTVCR_SYS_STATE] &= ~8;
	if (unitnum < 0)
		return 0;
	if (sys_command_ismedia (unitnum, 0)) {
		cdtvcr_4510_ram[CDTVCR_SYS_STATE] |= 8;
		return 1;
	}
	return 0;
}

static int get_qcode (void)
{
#if 0
	if (!sys_command_cd_qcode (unitnum, cdrom_qcode))
		return 0;
	cdrom_qcode[1] = cd_audio_status;
#endif
	return 1;
}

static int get_toc (void)
{
	uae_u32 msf;
	uae_u8 *p, *pl;

	cdtvcr_4510_ram[CDTVCR_SYS_STATE] &= ~4;
	datatrack = 0;
	if (!sys_command_cd_toc (unitnum, &toc))
		return 0;
	cdtvcr_4510_ram[CDTVCR_SYS_STATE] |= 4 | 8;
	if (toc.first_track == 1 && (toc.toc[toc.first_track_offset].control & 0x0c) == 0x04)
		datatrack = 1;
	p = &cdtvcr_4510_ram[CDTVCR_TOC];
	cdtvcr_4510_ram[CDTVCR_PLAYLIST_CURRENT] = 0;
	cdtvcr_4510_ram[CDTVCR_PLAYLIST_ENTRIES] = 0;
	pl = &cdtvcr_4510_ram[CDTVCR_PLAYLIST_DATA];
	p[0] = toc.first_track;
	p[1] = toc.last_track;
	msf = lsn2msf(toc.lastaddress);
	p[2] = msf >> 16;
	p[3] = msf >>  8;
	p[4] = msf >>  0;
	p += 5;
	for (int j = toc.first_track_offset; j <= toc.last_track_offset; j++) {
		struct cd_toc *s = &toc.toc[j];
		p[0] = (s->adr << 0) | (s->control << 4);
		p[1] = s->track;
		msf = lsn2msf(s->address);
		p[2] = msf >> 16;
		p[3] = msf >>  8;
		p[4] = msf >>  0;
		p += 5;
		*pl++ = s->track | 0x80;
		cdtvcr_4510_ram[CDTVCR_PLAYLIST_ENTRIES]++;
	}
	return 1;
}

static void cdtvcr_4510_reset(uae_u8 v)
{
	cdtvcr_4510_ram[CDTVCR_ID + 0] = 'C';
	cdtvcr_4510_ram[CDTVCR_ID + 1] = 'D';
	cdtvcr_4510_ram[CDTVCR_ID + 2] = 'T';
	cdtvcr_4510_ram[CDTVCR_ID + 3] = 'V';

	write_log(_T("4510 reset %d\n"), v);
	if (v == 3) {
		sys_command_cd_pause (unitnum, 0);
		sys_command_cd_stop (unitnum);
		cdtvcr_4510_ram[CDTVCR_CD_PLAYING] = 0;
		cdtvcr_4510_ram[CDTVCR_CD_STATE] = 0;
		return;
	} else if (v == 2 || v == 1) {
		cdtvcr_4510_ram[CDTVCR_INTENA] = 0;
		cdtvcr_4510_ram[CDTVCR_INTREQ] = 0;
		if (v == 1) {
			memset(cdtvcr_4510_ram, 0, 4096);
		}
		cdtvcr_4510_ram[CDTVCR_INTDISABLE] = 1;
		cdtvcr_4510_ram[CDTVCR_CD_STATE] = 1;
	}
	cdtvcr_4510_ram[CDTVCR_PLAYLIST_TIME_MODE] = 2;
	uae_sem_wait (&sub_sem);
	memset (subcodebufferinuse, 0, sizeof subcodebufferinuse);
	subcodebufferoffsetw = subcodebufferoffset = 0;
	uae_sem_post (&sub_sem);

	if (ismedia())
		get_toc();
}

static void rethink_cdtvcr(void)
{
	if ((cdtvcr_4510_ram[CDTVCR_INTREQ] & cdtvcr_4510_ram[CDTVCR_INTENA]) && !cdtvcr_4510_ram[CDTVCR_INTDISABLE]) {
		safe_interrupt_set(IRQ_SOURCE_CD32CDTV, 0, false);
		cd_led ^= LED_CD_ACTIVE2;
	}
}

static void cdtvcr_cmd_done(void)
{
	cdtvcr_4510_ram[CDTVCR_SYS_STATE] &= ~3;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_DO] = 0;
	cdtvcr_4510_ram[CDTVCR_INTREQ] |= 0x40;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_STATUS] = 0;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_STATUS2] = 0;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_STATUS + 2] = 0;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_STATUS2 + 2] = 0;
}

static void cdtvcr_play_done(void)
{
	cdtvcr_4510_ram[CDTVCR_SYS_STATE] &= ~3;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_DO] = 0;
	cdtvcr_4510_ram[CDTVCR_INTREQ] |= 0x80;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_STATUS] = 0;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_STATUS2] = 0;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_STATUS + 2] = 0;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_STATUS2 + 2] = 0;
}

static void cdtvcr_state_stopped(void)
{
	cdtvcr_4510_ram[CDTVCR_CD_PLAYING] = 0;
	cdtvcr_4510_ram[CDTVCR_CD_STATE] = 1;
}

static void cdtvcr_state_play(void)
{
	cdtvcr_4510_ram[CDTVCR_CD_PLAYING] = 1;
	cdtvcr_4510_ram[CDTVCR_CD_STATE] = 3;
}

static void cdtvcr_cmd_play_started(void)
{
	cdtvcr_cmd_done();
	cdtvcr_state_play();
}

static void cdtvcr_start (void)
{
	if (unitnum < 0)
		return;
	cdtvcr_4510_ram[CDTVCR_CD_STATE] = 0;
}

static void cdtvcr_stop (void)
{
	if (unitnum < 0)
		return;
	sys_command_cd_pause (unitnum, 0);
	sys_command_cd_stop (unitnum);
	if (cdtvcr_4510_ram[CDTVCR_CD_PLAYING]) {
		cdtvcr_4510_ram[CDTVCR_INTREQ] |= 0x80;
	}
	cdtvcr_state_stopped();
}

static void cdtvcr_state_pause(bool pause)
{
	cdtvcr_4510_ram[CDTVCR_CD_STATE] = 3;
	if (pause)
		cdtvcr_4510_ram[CDTVCR_CD_STATE] = 2;
}

static void cdtvcr_pause(bool pause)
{
	sys_command_cd_pause (unitnum, pause ? 1 : 0);
	cdtvcr_state_pause(pause);
	cdtvcr_cmd_done();
}

static void setsubchannel(uae_u8 *s)
{
	uae_u8 *d;

	// q-channel
	d = &cdtvcr_4510_ram[CDTVCR_SUBQ];
	s += SUB_ENTRY_SIZE;
	int track = frombcd(s[1]);
	/* CtlAdr */
	d[0] = s[0];
	/* Track */
	d[1] = s[1];
	cdtvcr_4510_ram[CDTVCR_PLAYLIST_TRACK] = track;
	/* Index */
	d[2] = s[2];
	/* TrackPos */
	d[3] = s[3];
	d[4] = s[4];
	d[5] = s[5];
	/* DiskPos */
	d[6] = s[6];
	d[7] = s[7];
	d[8] = s[8];
	d[9] = s[9];

	uae_u8 mins = 0, secs = 0;
	uae_s32 trackpos = msf2lsn(fromlongbcd(s + 3));
	if (trackpos < 0)
		trackpos = 0;
	uae_s32 diskpos = msf2lsn(fromlongbcd(s + 7));
	if (diskpos < 0)
		diskpos = 0;
	switch (cdtvcr_4510_ram[CDTVCR_PLAYLIST_TIME_MODE2])
	{
		case 0:
		secs = (trackpos / 75) % 60;
		mins = trackpos / (75 * 60);
		break;
		case 1:
		trackpos = toc.toc[toc.first_track_offset + track].paddress - diskpos;
		secs = (trackpos / 75) % 60;
		mins = trackpos / (75 * 60);
		break;
		case 2:
		secs = (diskpos / 75) % 60;
		mins = diskpos / (75 * 60);
		break;
		case 3:
		diskpos = toc.lastaddress - diskpos;
		secs = (diskpos / 75) % 60;
		mins = diskpos / (75 * 60);
		break;
	}
	cdtvcr_4510_ram[CDTVCR_PLAYLIST_TIME_MINS] = mins;
	cdtvcr_4510_ram[CDTVCR_PLAYLIST_TIME_SECS] = secs;

	cdtvcr_4510_ram[CDTVCR_SUBQ - 2] = 1; // qcode valid
	cdtvcr_4510_ram[CDTVCR_SUBQ - 1] = 0;

	if (cdtvcr_4510_ram[CDTVCR_CD_SUBCODES])
		cdtvcr_4510_ram[CDTVCR_INTREQ] |= 4;
}

static void subfunc(uae_u8 *data, int cnt)
{
	uae_u8 out[SUB_CHANNEL_SIZE];
	sub_to_deinterleaved(data, out);
	setsubchannel(out);

	uae_sem_wait(&sub_sem);
	if (subcodebufferinuse[subcodebufferoffsetw]) {
		memset (subcodebufferinuse, 0,sizeof (subcodebufferinuse));
		subcodebufferoffsetw = subcodebufferoffset = 0;
	} else {
		int offset = subcodebufferoffsetw;
		while (cnt > 0) {
			if (subcodebufferinuse[offset]) {
				write_log (_T("CD32: subcode buffer overflow 2\n"));
				break;
			}
			subcodebufferinuse[offset] = 1;
			memcpy (&subcodebuffer[offset * SUB_CHANNEL_SIZE], data, SUB_CHANNEL_SIZE);
			data += SUB_CHANNEL_SIZE;
			offset++;
			if (offset >= MAX_SUBCODEBUFFER)
				offset = 0;
			cnt--;
		}
		subcodebufferoffsetw = offset;
	}
	uae_sem_post(&sub_sem);
}

static int statusfunc(int status, int playpos)
{
	if (status == -1)
		return 75;
	if (status == -2)
		return 75;
	if (status < 0)
		return 0;
	if (cd_audio_status != status) {
		if (status == AUDIO_STATUS_PLAY_COMPLETE || status == AUDIO_STATUS_PLAY_ERROR) {
			cdtvcr_play_done();
		} else if (status == AUDIO_STATUS_IN_PROGRESS) {
			cdtvcr_cmd_play_started();
		}
	}
	cd_audio_status = status;
	return 0;
}

static void cdtvcr_play(uae_u32 start, uae_u32 end)
{
	sys_command_cd_pause(unitnum, 0);
	if (!sys_command_cd_play(unitnum, start, end, 0, statusfunc, subfunc))
		cdtvcr_play_done();
}

static void cdtvcr_play_track(uae_u32 track_start, uae_u32 track_end)
{
	int start_found, end_found;
	uae_u32 start, end;

	start_found = end_found = 0;
	for (int j = toc.first_track_offset; j <= toc.last_track_offset; j++) {
		struct cd_toc *s = &toc.toc[j];
		if (track_start == s->track) {
			start_found++;
			start = s->paddress;
			end = toc.toc[toc.last_track_offset].paddress;
		}
		if (track_end == s->track) {
			end = s->paddress;
			end_found++;
		}
	}
	if (start_found == 0) {
		write_log (_T("PLAY CD AUDIO: illegal start track %d\n"), track_start);
		cdtvcr_stop();
		cdtvcr_cmd_done();
		return;
	}
	cdtvcr_play(start, end);
}

static void cdtvcr_read_data(uae_u32 start, uae_u32 addr, uae_u32 len)
{
	uae_u8 buffer[2048];
	int didread;
	
	cdtvcr_wait_sectors = 0;
	while (len) {
		didread = sys_command_cd_read (unitnum, buffer, start, 1);
		if (!didread)
			break;
		for (int i = 0; i < 2048 && len > 0; i++) {
			put_byte(addr + i, buffer[i]);
			len--;
		}
		addr += 2048;
		start++;
		cdtvcr_wait_sectors++;
	}
}

static void cdtvcr_player_state_change(void)
{
	cdtvcr_4510_ram[CDTVCR_INTREQ] |= 0x10;
}

static void cdtvcr_player_pause(void)
{
	if (cdtvcr_4510_ram[CDTVCR_PLAYLIST_STATE] == 4)
		cdtvcr_4510_ram[CDTVCR_PLAYLIST_STATE] = 3;
	else if (cdtvcr_4510_ram[CDTVCR_PLAYLIST_STATE] == 3)
		cdtvcr_4510_ram[CDTVCR_PLAYLIST_STATE] = 4;
	else
		return;
	cdtvcr_pause(cdtvcr_4510_ram[CDTVCR_PLAYLIST_STATE] == 4);
	cdtvcr_state_pause(cdtvcr_4510_ram[CDTVCR_PLAYLIST_STATE] == 4);
	cdtvcr_player_state_change();
}

static void cdtvcr_player_stop(void)
{
	cdtvcr_4510_ram[CDTVCR_PLAYLIST_STATE] = 1;
	cdtvcr_stop();
	cdtvcr_state_stopped();
	cdtvcr_player_state_change();
}

static void cdtvcr_player_play(void)
{
	int start_found, end_found;
	uae_u32 start, end;

	int entry = cdtvcr_4510_ram[CDTVCR_PLAYLIST_CURRENT];
	int track = cdtvcr_4510_ram[CDTVCR_PLAYLIST_DATA + entry] & 0x7f;
	start_found = end_found = 0;
	for (int j = toc.first_track_offset; j <= toc.last_track_offset; j++) {
		struct cd_toc *s = &toc.toc[j];
		if (track == s->track) {
			start_found++;
			start = s->paddress;
			end = s[1].paddress;
		}
	}
	if (start_found == 0) {
		cdtvcr_player_stop();
		return;
	}
	cdtvcr_play(start, end);
	cdtvcr_state_play();
	cdtvcr_4510_ram[CDTVCR_PLAYLIST_STATE] = 3;
	cdtvcr_player_state_change();
}

static void cdtvcr_do_cmd(void)
{
	uae_u32 addr, len, start, end, datalen;
	uae_u32 startlsn, endlsn;
	uae_u8 starttrack, endtrack;
	uae_u8 *p = &cdtvcr_4510_ram[CDTVCR_CD_CMD];

	cdtvcr_4510_ram[CDTVCR_SYS_STATE] |= 2;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_STATUS] = 2;
	cdtvcr_4510_ram[CDTVCR_CD_CMD_STATUS2] = 0;
	write_log(_T("CDTVCR CD command %02x\n"), p[0]);
	for(int i = 0; i < 14; i++)
		write_log(_T(".%02x"), p[i]);
	write_log(_T("\n"));

	start = (p[1] << 16) | (p[2] << 8) | (p[3] << 0);
	startlsn = msf2lsn(start);
	end = (p[4] << 16) | (p[5] << 8) | (p[6] << 0);
	endlsn = msf2lsn(end);
	addr = (p[7] << 24) | (p[8] << 16) | (p[9] << 8) | (p[10] << 0);
	len = (p[4] << 16) | (p[5] << 8) | (p[6] << 0);
	datalen = (p[11] << 16) | (p[12] << 8) | (p[13] << 0);
	starttrack = p[1];
	endtrack = p[3];

	switch(p[0])
	{
		case 2: // start
		cdtvcr_start();
		cdtvcr_cmd_done();
		break;
		case 3: // stop
		cdtvcr_stop();
		cdtvcr_cmd_done();
		break;
		case 4: // toc
		cdtvcr_stop();
		get_toc();
		cdtvcr_cmd_done();
		break;
		case 6: // play
		cdtvcr_stop();
		cdtvcr_play(startlsn, endlsn);
		break;
		case 7: // play track
		cdtvcr_stop();
		cdtvcr_play_track(starttrack, endtrack);
		break;
		case 8: // read
		cdtvcr_stop();
		cdtvcr_read_data(start, addr, datalen);
		break;
		case 9:
		cdtvcr_pause(true);
		break;
		case 10:
		cdtvcr_pause(false);
		break;
		case 11: // stop play
		cdtvcr_stop();
		cdtvcr_cmd_done();
		break;
		default:
		write_log(_T("unsupported command!\n"));
		break;
	}
}

static void cdtvcr_4510_do_something(void)
{
	if (cdtvcr_4510_ram[CDTVCR_INTACK]) {
		cdtvcr_4510_ram[CDTVCR_INTACK] = 0;
		rethink_cdtvcr();
	}
	if (cdtvcr_4510_ram[CDTVCR_CD_CMD_DO]) {
		cdtvcr_4510_ram[CDTVCR_CD_CMD_DO] = 0;
		cdtvcr_4510_ram[CDTVCR_SYS_STATE] |= 2;
		write_comm_pipe_u32 (&requests, 0x0100, 1);
	}
}

static bool cdtvcr_debug(uaecptr addr)
{
	addr &= CDTVCR_MASK;
	return !(addr >= CDTVCR_RAM_OFFSET && addr < CDTVCR_RAM_OFFSET + CDTVCR_RAM_SIZE);
}

static void cdtvcr_bput2 (uaecptr addr, uae_u8 v)
{
	addr &= CDTVCR_MASK;
	if (addr == CDTVCR_4510_TRIGGER) {
		if (v & 0x80)
			cdtvcr_4510_do_something();
	} else if (addr >= CDTVCR_RAM_OFFSET && addr < CDTVCR_RAM_OFFSET + CDTVCR_RAM_SIZE) {
		cdtvcr_battram_write(addr - CDTVCR_RAM_OFFSET, v);
	} else if (addr >= CDTVCR_CLOCK && addr < CDTVCR_CLOCK + 0x20) {
		int reg = addr - CDTVCR_CLOCK;
		switch (reg)
		{
			case 0:
			cdtvcr_clock[0] = v;
			case 1:
			cdtvcr_clock[1] = v;
			break;
		}
	} else {
		switch(addr)
		{
			case CDTVCR_RESET:
			cdtvcr_4510_reset(v);
			break;
		}
	}
	if (addr >= 0xc00 && addr < CDTVCR_RAM_OFFSET) {
		switch (addr)
		{
			case CDTVCR_KEYCMD:
			{
				uae_u8 keycode = cdtvcr_4510_ram[CDTVCR_KEYCMD + 1];
				write_log(_T("Got keycode %x\n"), keycode);
				cdtvcr_4510_ram[CDTVCR_KEYCMD + 1] = 0;
				switch(keycode)
				{
					case 0x73: // PLAY
					if (cdtvcr_4510_ram[CDTVCR_PLAYLIST_STATE] >= 3) {
						cdtvcr_player_pause();
					} else {
						cdtvcr_player_play();
					}
					break;
					case 0x72: // STOP
					cdtvcr_player_stop();
					break;
					case 0x74: // <-
					if (cdtvcr_4510_ram[CDTVCR_PLAYLIST_CURRENT] > 0) {
						cdtvcr_4510_ram[CDTVCR_PLAYLIST_CURRENT]--;
						if (cdtvcr_4510_ram[CDTVCR_PLAYLIST_STATE] >= 3) {
							cdtvcr_player_play();
						}
					}
					break;
					case 0x75: // ->
					if (cdtvcr_4510_ram[CDTVCR_PLAYLIST_CURRENT] < cdtvcr_4510_ram[CDTVCR_PLAYLIST_ENTRIES] - 1) {
						cdtvcr_4510_ram[CDTVCR_PLAYLIST_CURRENT]++;
						if (cdtvcr_4510_ram[CDTVCR_PLAYLIST_STATE] >= 3) {
							cdtvcr_player_play();
						}
					}
					break;
				}
				v = 0;
				break;
			}
		}
		cdtvcr_4510_ram[addr] = v;

		if (addr >= 0xc80)
			write_log(_T("%04x %02x\n"), addr, v);

	}
}

static uae_u8 cdtvcr_bget2 (uaecptr addr)
{
	uae_u8 v = 0;
	addr &= CDTVCR_MASK;
	if (addr < CDTVCR_RAM_OFFSET) {
		v = cdtvcr_4510_ram[addr];
	}
	if (addr >= CDTVCR_RAM_OFFSET && addr < CDTVCR_RAM_OFFSET + CDTVCR_RAM_SIZE) {
		v = cdtvcr_battram_read(addr - CDTVCR_RAM_OFFSET);
	} else if (addr >= CDTVCR_CLOCK && addr < CDTVCR_CLOCK + 0x20) {
		int reg = addr - CDTVCR_CLOCK;
		int days, mins, ticks;
		int tickcount = currprefs.ntscmode ? 60 : 50;
		struct timeval tv;
		struct mytimeval mtv;
		gettimeofday (&tv, NULL);
		tv.tv_sec -= _timezone;
		mtv.tv_sec = tv.tv_sec;
		mtv.tv_usec = tv.tv_usec;
		timeval_to_amiga(&mtv, &days, &mins, &ticks, tickcount);
		switch (reg)
		{
			case 0:
			case 1:
			v = cdtvcr_clock[reg];
			break;
			case 2:
			v = days >> 8;
			break;
			case 3:
			v = days;
			break;
			case 4:
			v = mins / 60;
			break;
			case 5:
			v = mins % 60;
			break;
			case 6:
			v = ticks / tickcount;
			break;
			case 7:
			v = ticks % tickcount;
			break;
			case 8:
			v = tickcount;
			break;

		}
	} else {
		switch(addr)
		{
			case CDTVCR_RESET:
			v = 0;
			break;
		}
	}
	return v;
}

static uae_u32 REGPARAM2 cdtvcr_lget (uaecptr addr)
{
	uae_u32 v;
	v = (cdtvcr_bget2 (addr) << 24) | (cdtvcr_bget2 (addr + 1) << 16) |
		(cdtvcr_bget2 (addr + 2) << 8) | (cdtvcr_bget2 (addr + 3));
#if CDTVCR_DEBUG
	if (cdtvcr_debug(addr))
		write_log (_T("cdtvcr_lget %08X=%08X PC=%08X\n"), addr, v, M68K_GETPC);
#endif
	return v;
}

static uae_u32 REGPARAM2 cdtvcr_wgeti (uaecptr addr)
{
	uae_u32 v = 0xffff;
	return v;
}
static uae_u32 REGPARAM2 cdtvcr_lgeti (uaecptr addr)
{
	uae_u32 v = 0xffff;
	return v;
}

static uae_u32 REGPARAM2 cdtvcr_wget (uaecptr addr)
{
	uae_u32 v;
	v = (cdtvcr_bget2 (addr) << 8) | cdtvcr_bget2 (addr + 1);
#if CDTVCR_DEBUG
	if (cdtvcr_debug(addr))
		write_log (_T("cdtvcr_wget %08X=%04X PC=%08X\n"), addr, v, M68K_GETPC);
#endif
	return v;
}

static uae_u32 REGPARAM2 cdtvcr_bget (uaecptr addr)
{
	uae_u32 v;
	v = cdtvcr_bget2 (addr);
#if CDTVCR_DEBUG
	if (cdtvcr_debug(addr))
		write_log (_T("cdtvcr_bget %08X=%02X PC=%08X\n"), addr, v, M68K_GETPC);
#endif
	return v;
}

static void REGPARAM2 cdtvcr_lput (uaecptr addr, uae_u32 l)
{
#if CDTVCR_DEBUG
	if (cdtvcr_debug(addr))
		write_log (_T("cdtvcr_lput %08X=%08X PC=%08X\n"), addr, l, M68K_GETPC);
#endif
	cdtvcr_bput2 (addr, l >> 24);
	cdtvcr_bput2 (addr + 1, l >> 16);
	cdtvcr_bput2 (addr + 2, l >> 8);
	cdtvcr_bput2 (addr + 3, l);
}

static void REGPARAM2 cdtvcr_wput (uaecptr addr, uae_u32 w)
{
#if CDTVCR_DEBUG
	if (cdtvcr_debug(addr))
		write_log (_T("cdtvcr_wput %08X=%04X PC=%08X\n"), addr, w & 65535, M68K_GETPC);
#endif
	cdtvcr_bput2 (addr, w >> 8);
	cdtvcr_bput2 (addr + 1, w);
}

static void REGPARAM2 cdtvcr_bput (uaecptr addr, uae_u32 b)
{
#if CDTVCR_DEBUG
	if (cdtvcr_debug(addr))
		write_log (_T("cdtvcr_bput %08X=%02X PC=%08X\n"), addr, b & 255, M68K_GETPC);
#endif
	cdtvcr_bput2 (addr, b);
}


static addrbank cdtvcr_bank = {
	cdtvcr_lget, cdtvcr_wget, cdtvcr_bget,
	cdtvcr_lput, cdtvcr_wput, cdtvcr_bput,
	default_xlate, default_check, NULL, NULL, _T("CDTV-CR"),
	cdtvcr_lgeti, cdtvcr_wgeti,
	ABFLAG_IO | ABFLAG_SAFE, S_READ, S_WRITE
};

static int dev_thread (void *p)
{
	write_log (_T("CDTV-CR: CD thread started\n"));
	thread_alive = 1;
	for (;;) {

		uae_u32 b = read_comm_pipe_u32_blocking (&requests);
		if (b == 0xffff) {
			thread_alive = -1;
			return 0;
		}
		if (unitnum < 0)
			continue;
		switch (b)
		{
			case 0x0100:
				cdtvcr_do_cmd();
			break;
			case 0x0101:
			{
				int m = ismedia ();
				if (m < 0) {
					write_log (_T("CDTV: device %d lost\n"), unitnum);
					cdtvcr_4510_ram[CDTVCR_SYS_STATE] &= ~(4 | 8);
					cdtvcr_4510_ram[CDTVCR_INTREQ] |= 0x10 | 8;
					cdtvcr_4510_ram[CDTVCR_CD_STATE] = 0;
				} else if (m != cdtvcr_media) {
					cdtvcr_media = m;
					get_toc ();
					cdtvcr_4510_ram[CDTVCR_INTREQ] |= 0x10 | 8;
					if (cdtvcr_media) {
						cdtvcr_4510_ram[CDTVCR_CD_STATE] = 2;
					} else {
						cdtvcr_4510_ram[CDTVCR_CD_STATE] = 1;
						cdtvcr_4510_ram[CDTVCR_SYS_STATE] &= ~(4 | 8);
					}
				}
				if (cdtvcr_media)
					get_qcode ();
			}
			break;
		}
	}
}

static void CDTVCR_hsync_handler (void)
{
	static int subqcnt, readcnt;

	if (!currprefs.cs_cdtvcr)
		return;

#if CDTVCR_4510_EMULATION
	for (int i = 0; i < 10; i++) {
		cpu_4510();
	}
#endif

	if (cdtvcr_wait_sectors > 0 && currprefs.cd_speed == 0) {
		cdtvcr_wait_sectors = 0;
		cdtvcr_cmd_done();
	}
	readcnt--;
	if (readcnt <= 0) {
		int cdspeed = cdtvcr_4510_ram[CDTVCR_CD_SPEED] ? 150 : 75;
		readcnt = (int)(maxvpos * vblank_hz / cdspeed);
		if (cdtvcr_wait_sectors > 0) {
			cdtvcr_wait_sectors--;
			if (cdtvcr_wait_sectors == 0)
				cdtvcr_cmd_done();
		}
	}
	subqcnt--;
	if (subqcnt <= 0) {
		write_comm_pipe_u32 (&requests, 0x0101, 1);
		subqcnt = (int)(maxvpos * vblank_hz / 75 - 1);
		if (subcodebufferoffset != subcodebufferoffsetw) {
			uae_sem_wait (&sub_sem);
			cdtvcr_4510_ram[CDTVCR_SUBBANK] = cdtvcr_4510_ram[CDTVCR_SUBBANK] ? 0 : SUB_CHANNEL_SIZE + 2;
			uae_u8 *d = &cdtvcr_4510_ram[CDTVCR_SUBC];
			d[cdtvcr_4510_ram[CDTVCR_SUBBANK] + SUB_CHANNEL_SIZE + 0] = 0x1f;
			d[cdtvcr_4510_ram[CDTVCR_SUBBANK] + SUB_CHANNEL_SIZE + 1] = 0x3d;
			for (int i = 0; i < SUB_CHANNEL_SIZE; i++) {
				d[cdtvcr_4510_ram[CDTVCR_SUBBANK] + i] = subcodebuffer[subcodebufferoffset * SUB_CHANNEL_SIZE + i] & 0x3f;
			}
			subcodebufferinuse[subcodebufferoffset] = 0;
			subcodebufferoffset++;
			if (subcodebufferoffset >= MAX_SUBCODEBUFFER)
				subcodebufferoffset -= MAX_SUBCODEBUFFER;
			uae_sem_post (&sub_sem);
			if (cdtvcr_4510_ram[CDTVCR_CD_SUBCODES])
				cdtvcr_4510_ram[CDTVCR_INTREQ] |= 2;
		} else if (cdtvcr_4510_ram[CDTVCR_CD_SUBCODES] && !cdtvcr_4510_ram[CDTVCR_CD_PLAYING]) {
			// want subcodes but not playing?
			// just return fake stuff, for some reason cdtv-cr driver requires something
			// that looks valid, even when not playing or it gets stuck in infinite loop
			uae_u8 dst[SUB_CHANNEL_SIZE];
			// regenerate Q-subchannel
			uae_u8 *s = dst + 12;
			struct cd_toc *cdtoc = &toc.toc[toc.first_track];
			int sector = 150;
			memset (dst, 0, SUB_CHANNEL_SIZE);
			s[0] = (cdtoc->control << 4) | (cdtoc->adr << 0);
			s[1] = tobcd (cdtoc->track);
			s[2] = tobcd (1);
			int msf = lsn2msf (sector);
			tolongbcd (s + 7, msf);
			msf = lsn2msf (sector - cdtoc->address - 150);
			tolongbcd (s + 3, msf);
			setsubchannel(dst);
		}
	}

	if (cdtvcr_wait_sectors)
		cd_led |= LED_CD_ACTIVE;
	else
		cd_led &= ~LED_CD_ACTIVE;
	if ((cd_led & ~LED_CD_ACTIVE2) && !cdtvcr_4510_ram[CDTVCR_CD_PLAYING])
		gui_flicker_led(LED_CD, 0, cd_led);

	rethink_cdtvcr();
}

static void open_unit (void)
{
	struct device_info di;
	unitnum = get_standard_cd_unit (CD_STANDARD_UNIT_CDTV);
	sys_command_info (unitnum, &di, 0);
	write_log (_T("using drive %s (unit %d, media %d)\n"), di.label, unitnum, di.media_inserted);
}

static void close_unit (void)
{
	if (unitnum >= 0)
		sys_command_close (unitnum);
	unitnum = -1;
}

static void cdtvcr_reset(int hardreset)
{
	if (!currprefs.cs_cdtvcr)
		return;
	close_unit ();
	if (!thread_alive) {
		init_comm_pipe (&requests, 100, 1);
		uae_start_thread (_T("cdtv-cr"), dev_thread, NULL, NULL);
		while (!thread_alive)
			sleep_millis (10);
		uae_sem_init (&sub_sem, 0, 1);
	}
	open_unit ();
	gui_flicker_led (LED_CD, 0, -1);

	cdtvcr_4510_reset(0);
	cdtvcr_battram_reset();
	cdtvcr_clock[0] = 0xe3;
	cdtvcr_clock[1] = 0x1b;

#if CDTVCR_4510_EMULATION
	init_65c02();
#endif
}

static void cdtvcr_free(void)
{
	if (thread_alive > 0) {
		write_comm_pipe_u32 (&requests, 0xffff, 1);
		while (thread_alive > 0)
			sleep_millis (10);
		uae_sem_destroy (&sub_sem);
	}
	thread_alive = 0;
	close_unit ();
}

bool cdtvcr_init(struct autoconfig_info *aci)
{
	aci->start = 0xb80000;
	aci->size = 0x10000;
	aci->zorro = 0;
	device_add_reset(cdtvcr_reset);
	if (!aci->doinit)
		return true;
	map_banks(&cdtvcr_bank, 0xB8, 1, 0);

	device_add_hsync(CDTVCR_hsync_handler);
	device_add_rethink(rethink_cdtvcr);
	device_add_exit(cdtvcr_free, NULL);

	return true;
}

#if CDTVCR_4510_EMULATION

// VICE 65C02 emulator, waiting for future full CDTV-CR 4510 emulation.

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int CLOCK;

static void cpu65c02_reset(void)
{
}

#define CLOCK_MAX (~((CLOCK)0))
#define TRAP_OPCODE 0x02
#define STATIC_ASSERT(_x)

#define NUM_MEMSPACES e_invalid_space

enum mon_int {
	MI_NONE = 0,
	MI_BREAK = 1 << 0,
	MI_WATCH = 1 << 1,
	MI_STEP = 1 << 2
};

enum t_memspace {
	e_default_space = 0,
	e_comp_space,
	e_disk8_space,
	e_disk9_space,
	e_disk10_space,
	e_disk11_space,
	e_invalid_space
};
typedef enum t_memspace MEMSPACE;
static unsigned monitor_mask[NUM_MEMSPACES];
static void monitor_check_icount_interrupt(void)
{
}
static void monitor_check_icount(WORD a)
{
}
static int monitor_force_import(int mem)
{
	return 0;
}
static int monitor_check_breakpoints(int mem, WORD addr)
{
	return 0;
}
static void monitor_check_watchpoints(unsigned int lastpc, unsigned int pc)
{
}
static void monitor_startup(int mem)
{
}

#define CYCLE_EXACT_ALARM

#define INTERRUPT_DELAY 2

#define INTRRUPT_MAX_DMA_PER_OPCODE (7+10000)

enum cpu_int {
	IK_NONE = 0,
	IK_NMI = 1 << 0,
	IK_IRQ = 1 << 1,
	IK_RESET = 1 << 2,
	IK_TRAP = 1 << 3,
	IK_MONITOR = 1 << 4,
	IK_DMA = 1 << 5,
	IK_IRQPEND = 1 << 6
};

struct interrupt_cpu_status_s {
	/* Number of interrupt lines.  */
	unsigned int num_ints;

	/* Define, for each interrupt source, whether it has a pending interrupt
	(IK_IRQ, IK_NMI, IK_RESET and IK_TRAP) or not (IK_NONE).  */
	unsigned int *pending_int;

	/* Name for each interrupt source */
	char **int_name;

	/* Number of active IRQ lines.  */
	int nirq;

	/* Tick when the IRQ was triggered.  */
	CLOCK irq_clk;

	/* Number of active NMI lines.  */
	int nnmi;

	/* Tick when the NMI was triggered.  */
	CLOCK nmi_clk;

	/* If an opcode is intercepted by a DMA, save the number of cycles
	left at the start of this particular DMA (needed by *_set_irq() to
	calculate irq_clk).  */
	unsigned int num_dma_per_opcode;
	unsigned int num_cycles_left[INTRRUPT_MAX_DMA_PER_OPCODE];
	CLOCK dma_start_clk[INTRRUPT_MAX_DMA_PER_OPCODE];

	/* counters for delay between interrupt request and handler */
	unsigned int irq_delay_cycles;
	unsigned int nmi_delay_cycles;

	/* If 1, do a RESET.  */
	int reset;

	/* If 1, call the trapping function.  */
	int trap;

	/* Debugging function.  */
	void(*trap_func)(WORD, void *data);

	/* Data to pass to the debugging function when called.  */
	void *trap_data;

	/* Pointer to the last executed opcode information.  */
	unsigned int *last_opcode_info_ptr;

	/* Number of cycles we have stolen to the processor last time.  */
	int num_last_stolen_cycles;

	/* Clock tick at which these cycles have been stolen.  */
	CLOCK last_stolen_cycles_clk;

	/* Clock tick where just ACK'd IRQs may still trigger an interrupt.
	Set to CLOCK_MAX when irrelevant.  */
	CLOCK irq_pending_clk;

	unsigned int global_pending_int;

	void(*nmi_trap_func)(void);

	void(*reset_trap_func)(void);
};
typedef struct interrupt_cpu_status_s interrupt_cpu_status_t;

/* Masks to extract information. */
#define OPINFO_DELAYS_INTERRUPT_MSK     (1 << 8)
#define OPINFO_DISABLES_IRQ_MSK         (1 << 9)
#define OPINFO_ENABLES_IRQ_MSK          (1 << 10)

/* Return nonzero if `opinfo' causes a 1-cycle interrupt delay.  */
#define OPINFO_DELAYS_INTERRUPT(opinfo)         \
    ((opinfo) & OPINFO_DELAYS_INTERRUPT_MSK)

/* Return nonzero if `opinfo' has changed the I flag from 0 to 1, so that an
IRQ that happened 2 or more cycles before the end of the opcode should be
allowed.  */
#define OPINFO_DISABLES_IRQ(opinfo)             \
    ((opinfo) & OPINFO_DISABLES_IRQ_MSK)

/* Return nonzero if `opinfo' has changed the I flag from 1 to 0, so that an
IRQ that happened 2 or more cycles before the end of the opcode should not
be allowed.  */
#define OPINFO_ENABLES_IRQ(opinfo)              \
    ((opinfo) & OPINFO_ENABLES_IRQ_MSK)

/* Set the information for `opinfo'.  `number' is the opcode number,
`delays_interrupt' must be non-zero if it causes a 1-cycle interrupt
delay, `disables_interrupts' must be non-zero if it disabled IRQs.  */
#define OPINFO_SET(opinfo,                                                \
                   number, delays_interrupt, disables_irq, enables_irq)   \
    ((opinfo) = ((number)                                                 \
                 | ((delays_interrupt) ? OPINFO_DELAYS_INTERRUPT_MSK : 0) \
                 | ((disables_irq) ? OPINFO_DISABLES_IRQ_MSK : 0)         \
                 | ((enables_irq) ? OPINFO_ENABLES_IRQ_MSK : 0)))

/* Set whether the opcode causes the 1-cycle interrupt delay according to
`delay'.  */
#define OPINFO_SET_DELAYS_INTERRUPT(opinfo, delay)   \
    do {                                             \
        if ((delay))                                 \
            (opinfo) |= OPINFO_DELAYS_INTERRUPT_MSK; \
    } while (0)

/* Set whether the opcode disables previously enabled IRQs according to
`disable'.  */
#define OPINFO_SET_DISABLES_IRQ(opinfo, disable) \
    do {                                         \
        if ((disable))                           \
            (opinfo) |= OPINFO_DISABLES_IRQ_MSK; \
    } while (0)

/* Set whether the opcode enables previously disabled IRQs according to
`enable'.  */
#define OPINFO_SET_ENABLES_IRQ(opinfo, enable)  \
    do {                                        \
        if ((enable))                           \
            (opinfo) |= OPINFO_ENABLES_IRQ_MSK; \
    } while (0)



typedef struct mos6510_regs_s {
	unsigned int pc;        /* `unsigned int' required by the drive code. */
	BYTE a;
	BYTE x;
	BYTE y;
	BYTE sp;
	BYTE p;
	BYTE n;
	BYTE z;
} mos6510_regs_t;

typedef struct R65C02_regs_s
{
	unsigned int pc;
	BYTE a;
	BYTE x;
	BYTE y;
	BYTE sp;
	BYTE p;
	BYTE n;
	BYTE z;
} R65C02_regs_t;

#define DRIVE_RAM_SIZE 65536

typedef BYTE drive_read_func_t(struct drive_context_s *, WORD);
typedef void drive_store_func_t(struct drive_context_s *, WORD,
	BYTE);

typedef struct drivecpud_context_s {
	/* Drive RAM */
	BYTE drive_ram[DRIVE_RAM_SIZE];

	/* functions */
	drive_read_func_t  *read_func[0x101];
	drive_store_func_t *store_func[0x101];
//	drive_read_func_t  *read_func_watch[0x101];
//	drive_store_func_t *store_func_watch[0x101];
//	drive_read_func_t  *read_func_nowatch[0x101];
//	drive_store_func_t *store_func_nowatch[0x101];

	int sync_factor;
} drivecpud_context_t;

typedef struct drive_context_s {
	int mynumber;         /* init to [0123] */
	CLOCK *clk_ptr;       /* shortcut to drive_clk[mynumber] */

	struct drivecpu_context_s *cpu;
	struct drivecpud_context_s *cpud;
	struct drivefunc_context_s *func;
} drive_context_t;

static int drive_trap_handler(drive_context_t *d)
{
	return -1;
}

typedef struct drivecpu_context_s
{
	int traceflg;
	/* This is non-zero each time a Read-Modify-Write instructions that accesses
	memory is executed.  We can emulate the RMW bug of the 6502 this way.  */
	int rmw_flag; /* init to 0 */

				  /* Interrupt/alarm status.  */
	struct interrupt_cpu_status_s *int_status;

	struct alarm_context_s *alarm_context;

	/* Clk guard.  */
	struct clk_guard_s *clk_guard;

	struct monitor_interface_s *monitor_interface;

	/* Value of clk for the last time mydrive_cpu_execute() was called.  */
	CLOCK last_clk;

	/* Number of cycles in excess we executed last time mydrive_cpu_execute()
	was called.  */
	CLOCK last_exc_cycles;

	CLOCK stop_clk;

	CLOCK cycle_accum;
	BYTE *d_bank_base;
	unsigned int d_bank_start;
	unsigned int d_bank_limit;

	/* Information about the last executed opcode.  */
	unsigned int last_opcode_info;

	/* Address of the last executed opcode. This is used by watchpoints. */
	unsigned int last_opcode_addr;

	/* Public copy of the registers.  */
	mos6510_regs_t cpu_regs;
//	R65C02_regs_t cpu_R65C02_regs;

	BYTE *pageone;        /* init to NULL */

	int monspace;         /* init to e_disk[89]_space */

	char *snap_module_name;

	char *identification_string;
} drivecpu_context_t;

#define LOAD(a)           (drv->cpud->read_func[(a) >> 8](drv, (WORD)(a)))
#define LOAD_ZERO(a)      (drv->cpud->read_func[0](drv, (WORD)(a)))
#define LOAD_ADDR(a)      (LOAD(a) | (LOAD((a) + 1) << 8))
#define LOAD_ZERO_ADDR(a) (LOAD_ZERO(a) | (LOAD_ZERO((a) + 1) << 8))
#define STORE(a, b)       (drv->cpud->store_func[(a) >> 8](drv, (WORD)(a), \
                          (BYTE)(b)))
#define STORE_ZERO(a, b)  (drv->cpud->store_func[0](drv, (WORD)(a), \
                          (BYTE)(b)))

#define JUMP(addr) reg_pc = (unsigned int)(addr); 

#define P_SIGN          0x80
#define P_OVERFLOW      0x40
#define P_UNUSED        0x20
#define P_BREAK         0x10
#define P_DECIMAL       0x08
#define P_INTERRUPT     0x04
#define P_ZERO          0x02
#define P_CARRY         0x01

#define CPU_WDC65C02   0
#define CPU_R65C02     1
#define CPU_65SC02     2

#define CLK (*(drv->clk_ptr))
#define RMW_FLAG (cpu->rmw_flag)
#define PAGE_ONE (cpu->pageone)
#define LAST_OPCODE_INFO (cpu->last_opcode_info)
#define LAST_OPCODE_ADDR (cpu->last_opcode_addr)
#define TRACEFLG (debug.drivecpu_traceflg[drv->mynumber])

#define CPU_INT_STATUS (cpu->int_status)

#define ALARM_CONTEXT (cpu->alarm_context)

#define ROM_TRAP_ALLOWED() 1

#define ROM_TRAP_HANDLER() drive_trap_handler(drv)

#define CALLER (cpu->monspace)

#define DMA_FUNC drive_generic_dma()

#define DMA_ON_RESET

#define cpu_reset() (cpu_reset)(drv)
#define bank_limit (cpu->d_bank_limit)
#define bank_start (cpu->d_bank_start)
#define bank_base (cpu->d_bank_base)

/* WDC_STP() and WDC_WAI() are not used on the R65C02. */
#define WDC_STP()
#define WDC_WAI()

#define GLOBAL_REGS cpu->cpu_R65C02_regs

#define DRIVE_CPU

static void drive_generic_dma(void)
{
}
static void interrupt_trigger_dma(interrupt_cpu_status_t *cs, CLOCK cpu_clk)
{
	cs->global_pending_int = (enum cpu_int)
		(cs->global_pending_int | IK_DMA);
}
static void interrupt_ack_dma(interrupt_cpu_status_t *cs)
{
	cs->global_pending_int = (enum cpu_int)
		(cs->global_pending_int & ~IK_DMA);
}
static void interrupt_do_trap(interrupt_cpu_status_t *cs, WORD address)
{
	cs->global_pending_int &= ~IK_TRAP;
	cs->trap_func(address, cs->trap_data);
}
static void interrupt_ack_reset(interrupt_cpu_status_t *cs)
{
	cs->global_pending_int &= ~IK_RESET;

	if (cs->reset_trap_func)
		cs->reset_trap_func();
}
static void interrupt_ack_nmi(interrupt_cpu_status_t *cs)
{
	cs->global_pending_int =
		(cs->global_pending_int & ~IK_NMI);

	if (cs->nmi_trap_func) {
		cs->nmi_trap_func();
	}
}
static void interrupt_ack_irq(interrupt_cpu_status_t *cs)
{
	cs->global_pending_int =
		(cs->global_pending_int & ~IK_IRQPEND);
	cs->irq_pending_clk = CLOCK_MAX;
}
static int interrupt_check_nmi_delay(interrupt_cpu_status_t *cs,
	CLOCK cpu_clk)
{
	CLOCK nmi_clk = cs->nmi_clk + INTERRUPT_DELAY;

	/* Branch instructions delay IRQs and NMI by one cycle if branch
	is taken with no page boundary crossing.  */
	if (OPINFO_DELAYS_INTERRUPT(*cs->last_opcode_info_ptr))
		nmi_clk++;

	if (cpu_clk >= nmi_clk)
		return 1;

	return 0;
}
static int interrupt_check_irq_delay(interrupt_cpu_status_t *cs,
	CLOCK cpu_clk)
{
	CLOCK irq_clk = cs->irq_clk + INTERRUPT_DELAY;

	/* Branch instructions delay IRQs and NMI by one cycle if branch
	is taken with no page boundary crossing.  */
	if (OPINFO_DELAYS_INTERRUPT(*cs->last_opcode_info_ptr))
		irq_clk++;

	/* If an opcode changes the I flag from 1 to 0, the 6510 needs
	one more opcode before it triggers the IRQ routine.  */
	if (cpu_clk >= irq_clk) {
		if (!OPINFO_ENABLES_IRQ(*cs->last_opcode_info_ptr)) {
			return 1;
		} else {
			cs->global_pending_int |= IK_IRQPEND;
		}
	}
	return 0;
}

struct alarm_context_s {
	CLOCK next_pending_alarm_clk;
};
typedef struct alarm_context_s alarm_context_t;

static void alarm_context_dispatch(alarm_context_t *context, CLOCK cpu_clk)
{
}
static CLOCK alarm_context_next_pending_clk(alarm_context_t *context)
{
	return context->next_pending_alarm_clk;
}

static struct drive_context_s m_4510_context;
static struct drivecpu_context_s m_4510_cpu_context;
static struct drivecpud_context_s m_4510_cpud_context;
static CLOCK m_4510_clk;
static struct alarm_context_s m_4510_alarm_context;
static struct interrupt_cpu_status_s m_4510_interrupt;

static void cpu_4510(void)
{
	int cpu_type = CPU_R65C02;
	drivecpu_context_t *cpu = &m_4510_cpu_context;
	drive_context_t *drv = &m_4510_context;

#define reg_a   (cpu->cpu_regs.a)
#define reg_x   (cpu->cpu_regs.x)
#define reg_y   (cpu->cpu_regs.y)
#define reg_pc  (cpu->cpu_regs.pc)
#define reg_sp  (cpu->cpu_regs.sp)
#define reg_p   (cpu->cpu_regs.p)
#define flag_z  (cpu->cpu_regs.z)
#define flag_n  (cpu->cpu_regs.n)

#include "65c02core.cpp"
}

BYTE read_4510(struct drive_context_s *c, WORD offset)
{
	if (offset >= 32768)
		return c->cpud->drive_ram[offset];
	return 0;
}
void write_4510(struct drive_context_s *c, WORD offset, BYTE v)
{
	if (offset >= 32768)
		return;
	c->cpud->drive_ram[offset] = v;
}

static void init_65c02(void)
{
	drive_context_s *d = &m_4510_context;
	drivecpu_context_s *cpu = &m_4510_cpu_context;
	drivecpud_context_s *cpud = &m_4510_cpud_context;

	d->cpu = cpu;
	d->cpud = cpud;
	d->clk_ptr = &m_4510_clk;

	cpu->rmw_flag = 0;
	cpu->d_bank_limit = 0;
	cpu->d_bank_start = 0;
	cpu->pageone = NULL;
	cpu->alarm_context = &m_4510_alarm_context;
	cpu->int_status = &m_4510_interrupt;

	cpu->int_status->global_pending_int = IK_RESET;

	for(int i = 0; i < 256; i++) {
		cpud->read_func[i] = read_4510;
		cpud->store_func[i] = write_4510;
	}

	struct zfile *zf = read_rom_name(_T("d:\\amiga\\roms\\cdtv-cr-4510.bin"));
	if (zf) {
		zfile_fread(cpud->drive_ram + 32768, 1, 32768, zf);
		zfile_fclose(zf);
	}
}

#endif
