/*
* UAE - The Un*x Amiga Emulator
*
* A2410
*
*/
#include "sysconfig.h"
#include "sysdeps.h"

#include "debug.h"
#include "mameglue.h"
#include "tm34010/tms34010.h"

#include "options.h"
#include "memory.h"
#include "custom.h"
#include "statusline.h"
#include "newcpu.h"
#include "gfxboard.h"
#include "xwin.h"

rectangle tms_rectangle;
static mscreen tms_screen;
static tms340x0_device tms_device;
static address_space tms_space;
mscreen *m_screen;

#define MAX_HEIGHT 2048

struct a2410_struct
{
	int tms_vp, tms_hp;
	int fullrefresh;
	int request_fullrefresh;
	uae_u8 *program_ram;
	int a2410_palette_index;
	uae_u8 a2410_palette[4 * (256 + 4)];
	uae_u32 a2410_palette_32[256 + 4];
	uae_u8 a2410_palette_temp[4];
	uae_u8 a2410_palette_control[4];
	uae_u16 a2410_control;
	int a2410_displaywidth;
	int a2410_displayend;
	int a2410_vertical_start;
	bool a2410_enabled, a2410_active;
	uae_u8 a2410_overlay_mask[2];
	int a2410_overlay_blink_rate_on;
	int a2410_overlay_blink_rate_off;
	int a2410_overlay_blink_cnt;
	uae_u32 tms_configured;
	int a2410_gfxboard;
	int coladdr;

	bool a2410_modechanged;
	int a2410_width, a2410_height;
	bool overlaylinetab[MAX_HEIGHT + 1];
	uae_u32 vramlinetab[MAX_HEIGHT + 1];
	int a2410_vram_start_offset;
	uae_u8 *a2410_surface;
	int a2410_interlace;
	int a2410_interrupt;
	int a2410_hsync_max;
	bool a2410_visible;

	int a2410_activecnt;
	bool a2410_newactive, a2410_preactive;

	addrbank *gfxbank;
};

static struct a2410_struct a2410_data;

extern addrbank tms_bank;

int mscreen::hpos()
{
	if (a2410_data.a2410_displayend) {
		a2410_data.tms_hp++;
		a2410_data.tms_hp %= a2410_data.a2410_displayend;
	} else {
		a2410_data.tms_hp = 0;
	}
	return a2410_data.tms_hp;
}
int mscreen::vpos()
{
	return a2410_data.tms_vp;
}

static void tms_execute_single(void)
{
	tms_device.m_icount = 2;
	tms_device.execute_run();
}

#define A2410_BANK_FRAMEBUFFER 1
#define A2410_BANK_PROGRAM 2
#define A2410_BANK_RAMDAC 3
#define A2410_BANK_CONTROL 4
#define A2410_BANK_TMSIO 5
#define A2410_BANK_DMA 6

static uaecptr makeaddr(UINT32 a, int *bank)
{
	uaecptr addr = 0;
	UINT32 aa = a << 3;
	if ((aa & 0xf0000000) == 0xc0000000) {
		*bank = A2410_BANK_TMSIO;
		addr = (a & 0xff) >> 1;
	} else if ((aa & 0x01900000) == 0x00800000) {
		*bank = A2410_BANK_RAMDAC;
		addr = (a >> 1) & 3;
	} else if ((aa & 0x01900000) == 0x00900000) {
		*bank = A2410_BANK_CONTROL;
	} else if ((aa & 0x30000000) == 0x10000000) {
		addr = a & 0xffffff;
		*bank = A2410_BANK_DMA;
	} else if ((aa & 0x01800000) == 0x01800000) {
		addr = a & 0xfffff;
		*bank = A2410_BANK_PROGRAM;
	} else if ((aa & 0x01800000) == 0x00000000) {
		addr = a & 0xfffff;
		*bank = A2410_BANK_FRAMEBUFFER;
	} else {
		*bank = 0;
		write_log(_T("Unknown BANK %08x PC=%08x\n"), aa, M68K_GETPC);
	}
	return addr;
}

/* CONTROL
 *
 * bit 0 - OSC_SEL - oscillator select
 * bit 1 - _SOG_EN - sync on green
 * bit 2 - SWAP - byte swap enable for DMA
 * bit 3 - SBR - bus request from TMS34010 (Sets bit 7 when bus granted)
 * bit 4 - D_BLANK (read only) - seems to be related to BLANK output of TMS and VSYNC
 * bit 5 - AGBG (read only) - comes straight from Zorro II BCLR signal ?
 * bit 6 - AID = Z3Sense pin when read, HS_INV when write (Horizontal Sync Invert)
 * bit 7 - LGBACK when read, VS_INV when write (Vertical Sync Invert) - LGBACK: A2410 owns the Zorro bus now.
 */

static uae_u8 get_a2410_control(struct a2410_struct *data)
{
	uae_u8 v = (uae_u8)data->a2410_control;
	v &= ~(0x10 | 0x40 | 0x80);
	v |= 0x20 | 0x80;
	if (v & 0x08) // SBR
		v &= ~0x80; // LGBACK (Active low)
	if (currprefs.cs_compatible == CP_A3000 || currprefs.cs_compatible == CP_A3000T ||
		currprefs.cs_compatible == CP_A4000 || currprefs.cs_compatible == CP_A4000T ||
		currprefs.cs_z3autoconfig)
		v |= 0x40; // AID
	return v;
}

UINT32 total_cycles(void)
{
	return (UINT32)(get_cycles() / CYCLE_UNIT);
}

void m_to_shiftreg_cb(address_space space, offs_t offset, UINT16 *shiftreg)
{
	memcpy(shiftreg, &gfxmem_banks[a2410_data.a2410_gfxboard]->baseaddr[TOWORD(offset)], 256 * sizeof(UINT16));
}
void m_from_shiftreg_cb(address_space space, offs_t offset, UINT16 *shiftreg)
{
	memcpy(&gfxmem_banks[a2410_data.a2410_gfxboard]->baseaddr[TOWORD(offset)], shiftreg, 256 * sizeof(UINT16));
}

UINT16 direct_read_data::read_decrypted_word(UINT32 pc)
{
	uae_u16 v = 0;
	int bank;
	uaecptr addr = makeaddr(pc, &bank);
	v = a2410_data.program_ram[addr] << 8;
	v |= a2410_data.program_ram[addr + 1];
	//write_log(_T("TMS instruction word read RAM %08x (%08x) =%04x\n"), pc, addr, v);
	return v;
}
UINT16 direct_read_data::read_raw_word(UINT32 pc)
{
	uae_u16 v = 0;
	int bank;
	uaecptr addr = makeaddr(pc, &bank);
	v = a2410_data.program_ram[addr] << 8;
	v |= a2410_data.program_ram[addr + 1];
	//write_log(_T("TMS instruction word read RAM %08x (%08x) =%04x\n"), pc, addr, v);
	return v;
}

static void mark_overlay(struct a2410_struct *data, int addr)
{
	if (!data->a2410_enabled)
		return;
	addr &= 0x1ffff;
	data->overlaylinetab[addr >> 7] = true;
}

static void a2410_create_palette32(struct a2410_struct *data, int offset)
{
	int idx = data->a2410_palette_index / 4 + offset;
	if (data->a2410_palette[idx * 4 + 0] != data->a2410_palette_temp[0] ||
		data->a2410_palette[idx * 4 + 1] != data->a2410_palette_temp[1] ||
		data->a2410_palette[idx * 4 + 2] != data->a2410_palette_temp[2]) {
		data->request_fullrefresh = 1;
	}
	data->a2410_palette[idx * 4 + 0] = data->a2410_palette_temp[0];
	data->a2410_palette[idx * 4 + 1] = data->a2410_palette_temp[1];
	data->a2410_palette[idx * 4 + 2] = data->a2410_palette_temp[2];
	data->a2410_palette_32[idx] =
		(data->a2410_palette_temp[0] << 16) |
		(data->a2410_palette_temp[1] <<  8) |
		(data->a2410_palette_temp[2] <<  0);
#if 0
	write_log(_T("PAL %d: %02x %02x %02x = %08x\n"),
		idx,
		a2410_palette_temp[0],
		a2410_palette_temp[1],
		a2410_palette_temp[2],
		a2410_palette_32[idx]);
#endif
}


static void write_ramdac(struct a2410_struct *data, int addr, uae_u8 v)
{
	int coloridx = data->a2410_palette_index & 3;
	switch (addr)
	{
		case 0:
		data->a2410_palette_index = v * 4;
		break;
		case 1:
		data->a2410_palette_temp[coloridx] = v;
		data->a2410_palette_index++;
		if ((data->a2410_palette_index & 3) == 3) {
			a2410_create_palette32(data, 0);
			data->a2410_palette_index++;
		}
		if (data->a2410_palette_index >= 256 * 4)
			data->a2410_palette_index = 0;
		break;
		case 2:
		data->a2410_palette_index &= ~3;
		if (data->a2410_palette_index >= 4 * 4 && data->a2410_palette_index < 8 * 4) {
			data->a2410_palette_control[data->a2410_palette_index / 4 - 4] = v;
		}
		data->a2410_overlay_mask[0] = 0xff;
		data->a2410_overlay_mask[1] = 0xff;
		if (!(data->a2410_palette_control[6 - 4] & 1))
			data->a2410_overlay_mask[0] = 0;
		if (!(data->a2410_palette_control[6 - 4] & 2))
			data->a2410_overlay_mask[1] = 0;
		switch((data->a2410_palette_control[6 - 4] >> 4) & 3)
		{
			case 0:
			data->a2410_overlay_blink_rate_on = 16;
			data->a2410_overlay_blink_rate_off = 48;
			break;
			case 1:
			data->a2410_overlay_blink_rate_on = 16;
			data->a2410_overlay_blink_rate_off = 16;
			break;
			case 2:
			data->a2410_overlay_blink_rate_on = 32;
			data->a2410_overlay_blink_rate_off = 32;
			break;
			case 3:
			data->a2410_overlay_blink_rate_on = 64;
			data->a2410_overlay_blink_rate_off = 64;
			break;
		}
		break;
		case 3:
		if (data->a2410_palette_index < 4 * 4) {
			data->a2410_palette_temp[coloridx] = v;
			data->a2410_palette_index++;
			if ((data->a2410_palette_index & 3) == 3) {
				a2410_create_palette32(data, 256);
				data->a2410_palette_index++;
			}
			if (data->a2410_palette_index >= 4 * 4)
				data->a2410_palette_index = 0;
		}
		break;
	}
}

static uae_u8 read_ramdac(struct a2410_struct *data, int addr)
{
	uae_u8 v = 0;
	switch (addr)
	{
		case 0:
		v = data->a2410_palette_index / 4;
		break;
		case 1:
		v = data->a2410_palette[data->a2410_palette_index];
		data->a2410_palette_index++;
		if ((data->a2410_palette_index & 3) == 3)
			data->a2410_palette_index++;
		if (data->a2410_palette_index >= 256 * 4)
			data->a2410_palette_index = 0;
		break;
		case 2:
		if (data->a2410_palette_index >= 4 * 4 && data->a2410_palette_index < 8 * 4) {
			v = data->a2410_palette_control[data->a2410_palette_index / 4 - 4];
		}
		break;
		case 3:
		if (data->a2410_palette_index < 4 * 4) {
			v = data->a2410_palette[data->a2410_palette_index + 256 * 4];
			data->a2410_palette_index++;
			if ((data->a2410_palette_index & 3) == 3)
				data->a2410_palette_index = 0;
			if (data->a2410_palette_index >= 4 * 4)
				data->a2410_palette_index = 0;
		}
		break;
	}
	return v;
}


static bool valid_dma(struct a2410_struct *data, uaecptr addr)
{
	// prevent recursive DMA
	return addr < data->tms_configured || addr >= (data->tms_configured + 65536);
}

UINT8 address_space::read_byte(UINT32 a)
{
	struct a2410_struct *data = &a2410_data;
	int bank;
	uae_u8 v = 0;
	UINT32 aa = a << 3;
	uaecptr addr = makeaddr(a, &bank);

	addr ^= 1;
	switch (bank)
	{
		case A2410_BANK_PROGRAM:
		v = data->program_ram[addr];
		//write_log(_T("TMS byte read RAM %08x (%08x) =%02x PC=%08x\n"), aa, addr, v, M68K_GETPC);
		break;
		case A2410_BANK_FRAMEBUFFER:
		v = data->gfxbank->baseaddr[addr];
		//write_log(_T("TMS byte read framebuffer %08x (%08x) = %02x PC=%08x\n"), aa, addr, v, M68K_GETPC);
		break;
		case A2410_BANK_RAMDAC:
		if (addr & 4) {
			v = read_ramdac(data, addr & 3);
		} else {
			write_ramdac(data, addr & 3, 0xff);
		}
		//write_log(_T("RAMDAC READ %08x = %02x PC=%08x\n"), aa, v, M68K_GETPC);
		break;
		case A2410_BANK_CONTROL:
		v = get_a2410_control(data);
		//write_log(_T("CONTROL READ %08x = %02x PC=%08x\n"), aa, v, M68K_GETPC);
		break;
		case A2410_BANK_DMA:
		if (valid_dma(data, addr)) {
			if (!(data->a2410_control & 4))
				addr ^= 1;
			v = get_byte(addr);
		}
		break;
		default:
		write_log(_T("UNKNOWN READ %08x = %02x PC=%08x\n"), aa, v, M68K_GETPC);
		break;

	}
	return v;
}
UINT16 address_space::read_word(UINT32 a)
{
	struct a2410_struct *data = &a2410_data;
	int bank;
	uae_u16 v = 0;
	UINT32 aa = a << 3;
	uaecptr addr = makeaddr(a, &bank);

	switch (bank)
	{
		case A2410_BANK_TMSIO:
		v = tms_device.io_register_r(*this, addr);
		//write_log(_T("TMS IO word read %08x (%08x) = %04x PC=%08x PC=%08x\n"), aa, addr, v, M68K_GETPC);
		break;
		case A2410_BANK_PROGRAM:
		v = data->program_ram[addr] << 8;
		v |= data->program_ram[addr + 1];
		//write_log(_T("TMS program word read RAM %08x (%08x) = %04x PC=%08x\n"), aa, addr, v, M68K_GETPC);
		break;
		case A2410_BANK_FRAMEBUFFER:
		v = data->gfxbank->baseaddr[addr] << 8;
		v |= data->gfxbank->baseaddr[addr + 1];
		//write_log(_T("TMS gfx word read %08x (%08x) = %04x PC=%08x\n"), aa, addr, v, M68K_GETPC);
		break;
		case A2410_BANK_RAMDAC:
		if (addr & 4) {
			v = read_ramdac(data, addr & 3);
		} else {
			write_ramdac(data, addr & 3, 0xff);
		}
		//write_log(_T("RAMDAC READ %08x = %02x PC=%08x\n"), aa, v, M68K_GETPC);
		break;
		case A2410_BANK_CONTROL:
		v = get_a2410_control(data);
		//write_log(_T("CONTROL READ %08x = %02x PC=%08x\n"), aa, v, M68K_GETPC);
		break;
		case A2410_BANK_DMA:
		if (valid_dma(data, addr)) {
			v = get_word(addr);
			if (!(data->a2410_control & 4))
				v = (v >> 8) | (v << 8);
		}
		break;
		default:
		write_log(_T("UNKNOWN READ %08x = %04x PC=%08x\n"), aa, v, M68K_GETPC);
		break;
	}
	return v;
}

void address_space::write_byte(UINT32 a, UINT8 b)
{
	struct a2410_struct *data = &a2410_data;
	int bank;
	UINT32 aa = a << 3;
	uaecptr addr = makeaddr(a, &bank);
	addr ^= 1;
	switch (bank)
	{
		case A2410_BANK_PROGRAM:
		data->program_ram[addr] = b;
		if (addr < 0x40000)
			mark_overlay(data, addr);
		//write_log(_T("TMS program byte write %08x (%08x) = %02x PC=%08x\n"), aa, addr, b, M68K_GETPC);
		break;
		case A2410_BANK_FRAMEBUFFER:
		data->gfxbank->baseaddr[addr] = b;
		//write_log(_T("TMS gfx byte write %08x (%08x) = %02x PC=%08x\n"), aa, addr, b, M68K_GETPC);
		break;
		case A2410_BANK_RAMDAC:
		//write_log(_T("RAMDAC WRITE %08x = %08x = %02x PC=%08x\n"), addr, aa, b, M68K_GETPC);
		if (!(addr & 4)) {
			write_ramdac(data, addr & 3, b);
		}
		break;
		case A2410_BANK_CONTROL:
		//write_log(_T("CONTROL WRITE %08x = %02x PC=%08x\n"), aa, b, M68K_GETPC);
		data->a2410_control = b;
		break;
		case A2410_BANK_DMA:
		if (valid_dma(data, addr)) {
			if (!(data->a2410_control & 4))
				addr ^= 1;
			put_byte(addr, b);
		}
		break;
		default:
		write_log(_T("UNKNOWN WRITE %08x = %02x PC=%08x\n"), aa, b, M68K_GETPC);
		break;
	}
}

void address_space::write_word(UINT32 a, UINT16 b)
{
	struct a2410_struct *data = &a2410_data;
	int bank;
	UINT32 aa = a << 3;
	uaecptr addr = makeaddr(a, &bank);
	switch (bank)
	{
		case A2410_BANK_TMSIO:
		tms_device.io_register_w(*this, addr, b);
		//write_log(_T("TMS IO word write %08x (%08x) = %04x PC=%08x\n"), aa, addr, b, M68K_GETPC);
		break;
		case A2410_BANK_PROGRAM:
		data->program_ram[addr] = b >> 8;
		data->program_ram[addr + 1] = b & 0xff;
		if (addr < 0x40000) {
			mark_overlay(data, addr);
		}
		//write_log(_T("TMS program word write RAM %08x (%08x) = %04x PC=%08x\n"), aa, addr, b, M68K_GETPC);
		break;
		case A2410_BANK_FRAMEBUFFER:
		data->gfxbank->baseaddr[addr] = b >> 8;
		data->gfxbank->baseaddr[addr + 1] = b & 0xff;
		//write_log(_T("TMS gfx word write %08x (%08x) = %04x PC=%08x\n"), aa, addr, b, M68K_GETPC);
		break;
		case A2410_BANK_RAMDAC:
		//write_log(_T("RAMDAC WRITE %08x = %08x = %04x (%d,%d) PC=%08x\n"), addr, aa, b, data->a2410_palette_index / 4, data->a2410_palette_index & 3, M68K_GETPC);
		if (!(addr & 4)) {
			write_ramdac(data, addr & 3, (uae_u8)b);
		}
		break;
		case A2410_BANK_CONTROL:
		//write_log(_T("CONTROL WRITE %08x = %04x PC=%08x\n"), aa, b, M68K_GETPC);
		data->a2410_control = b;
		break;
		case A2410_BANK_DMA:
		if (valid_dma(data, addr)) {
			if (!(data->a2410_control & 4))
				b = (b >> 8) | (b << 8);
			put_word(addr, b);
		}
		break;
		default:
		write_log(_T("UNKNOWN WRITE %08x = %04x PC=%08x\n"), aa, b, M68K_GETPC);
		break;
	}
}

static uae_u32 REGPARAM2 tms_bget(uaecptr addr)
{
	struct a2410_struct *data = &a2410_data;
	uae_u32 v = 0xff;
	addr &= 65535;
	uae_u16 vv = tms_device.host_r(tms_space, addr >> 1);
	if (!(addr & 1))
		vv >>= 8;
	v = (uae_u8)vv;
	//write_log(_T("tms_bget %08x = %02x PC=%08x\n"), addr, v & 0xff, M68K_GETPC);
	tms_execute_single();
	return v;
}

static uae_u32 REGPARAM2 tms_wget(uaecptr addr)
{
	struct a2410_struct *data = &a2410_data;
	uae_u16 v;
	addr &= 65535;
	v = tms_device.host_r(tms_space, addr >> 1);
	//write_log(_T("tms_wget %08x = %04x PC=%08x\n"), addr, v & 0xffff, M68K_GETPC);
	tms_execute_single();
	return v;
}

static void REGPARAM2 tms_wput(uaecptr addr, uae_u32 w)
{
	struct a2410_struct *data = &a2410_data;
	addr &= 65535;
	tms_device.host_w(tms_space, addr >> 1, w);
	tms_execute_single();
}

static void REGPARAM2 tms_bput(uaecptr addr, uae_u32 b)
{
	struct a2410_struct *data = &a2410_data;
	b &= 0xff;
	addr &= 65535;
	//write_log(_T("tms_bput %08x=%02x PC=%08x\n"), addr, b, M68K_GETPC);
	tms_device.host_w(tms_space, addr >> 1, (b << 8) | b);
	tms_execute_single();
}

static uae_u32 REGPARAM2 tms_lget(uaecptr addr)
{
	uae_u32 v;
	addr &= 65535;
	v = tms_wget(addr) << 16;
	v |= tms_wget(addr + 2);
	return v;
}
static void REGPARAM2 tms_lput(uaecptr addr, uae_u32 l)
{
	addr &= 65535;
	tms_wput(addr, l >> 16);
	tms_wput(addr + 2, l);
}

static addrbank tms_bank = {
	tms_lget, tms_wget, tms_bget,
	tms_lput, tms_wput, tms_bput,
	default_xlate, default_check, NULL, NULL, _T("A2410"),
	tms_lget, tms_wget,
	ABFLAG_IO, S_READ, S_WRITE
};

static void tms_reset(void *userdata)
{
	struct a2410_struct *data = (struct a2410_struct*)userdata;
	int monid = currprefs.rtgboards[data->a2410_gfxboard].monitor_id;

	if (data->a2410_surface)
		gfx_unlock_picasso(monid, true);
	data->a2410_surface = NULL;

	data->a2410_modechanged = false;
	data->a2410_interlace = 0;
	data->a2410_interrupt = 0;
	data->a2410_hsync_max = 2;
	data->a2410_visible = false;
	data->a2410_enabled = false;
	data->a2410_active = false;
	data->a2410_newactive = false;
	data->a2410_preactive = false;
	data->a2410_control = 0;
	data->tms_vp = data->tms_hp = 0;
	data->a2410_width = 0;
	data->a2410_height = 0;

	tms_screen.width_v = 0;
	tms_screen.height_v = 0;

	if (data->program_ram)
		tms_device.device_reset();

	data->tms_configured = 0;
}

static void tms_configured(void *userdata, uae_u32 address)
{
	struct a2410_struct *data = (struct a2410_struct*)userdata;
	data->tms_configured = address;
}

static void tms_free(void *userdata)
{
	struct a2410_struct *data = (struct a2410_struct*)userdata;
	int monid = currprefs.rtgboards[data->a2410_gfxboard].monitor_id;

	if (data->a2410_surface)
		gfx_unlock_picasso(monid, true);
	data->a2410_surface = NULL;
	if (data->a2410_gfxboard >= 0) {
		gfxboard_free_vram(data->a2410_gfxboard);
	}
	data->a2410_gfxboard = -1;
	xfree(data->program_ram);
	data->program_ram = NULL;
	data->gfxbank = NULL;
}

static bool tms_init(struct autoconfig_info *aci)
{
	struct a2410_struct *data = &a2410_data;

	aci->addrbank = &tms_bank;
	aci->label = _T("A2410");
	if (!aci->doinit) {
		return true;
	}

	data->a2410_gfxboard = aci->prefs->rtgboards[aci->devnum].rtg_index;

	if (data->a2410_gfxboard < 0)
		return false;

	data->gfxbank = gfxmem_banks[data->a2410_gfxboard];

	mapped_free(data->gfxbank);
	xfree(data->program_ram);

	data->gfxbank->label = _T("ram_a8");
	data->gfxbank->reserved_size = 1 * 1024 * 1024;
	gfxboard_get_a8_vram(data->a2410_gfxboard);

	picasso_allocatewritewatch(data->a2410_gfxboard, data->gfxbank->allocated_size);
	data->gfxbank->start = 0xa80000;

	data->program_ram = xcalloc(uae_u8, 1 * 1024 * 1024);

	m_screen = &tms_screen;
	tms_device.device_start();
	tms_reset(data);

	aci->userdata = data; 
	return true;
}

void mscreen::configure(int width, int height, rectangle vis)
{
	struct a2410_struct *data = &a2410_data;

	int ow = data->a2410_width, oh = data->a2410_height;
	data->a2410_width = vis.max_x - vis.min_x + 1;
	data->a2410_height = vis.max_y - vis.min_y + 1;
	data->a2410_interlace = vis.interlace ? 1 : 0;
	if (data->a2410_interlace)
		data->a2410_height *= 2;
	data->a2410_modechanged = true;
	m_screen->height_v = height;
	m_screen->width_v = width;
	tms_rectangle = vis;
	write_log(_T("A2410 %d*%d -> %d*%d\n"), ow, oh, data->a2410_width, data->a2410_height);
	data->a2410_hsync_max = data->a2410_height / 300;
	if (data->a2410_hsync_max <= 0) {
		data->a2410_hsync_max = 1;
	}
}

static void get_a2410_surface(struct a2410_struct *data)
{
	int monid = currprefs.rtgboards[data->a2410_gfxboard].monitor_id;
	struct amigadisplay *ad = &adisplays[monid];

	if (ad->picasso_on) {
		if (data->a2410_surface == NULL) {
			data->a2410_surface = gfx_lock_picasso(monid, false);
		}
	}
}

static bool tms_toggle(void *userdata, int mode)
{
	struct a2410_struct *data = (struct a2410_struct*)userdata;

	if (!data->tms_configured)
		return false;

	if (!mode) {
		data->a2410_enabled = false;
		data->a2410_modechanged = true;
		return true;
	} else {
		data->a2410_enabled = true;
		data->a2410_modechanged = true;
		return true;
	}
	return false;
}

static void tms_vsync_handler2(struct a2410_struct *data, bool internalsync)
{
	int monid = currprefs.rtgboards[data->a2410_gfxboard].monitor_id;
	struct amigadisplay *ad = &adisplays[monid];

	if (!data->tms_configured)
		return;

	tms34010_display_params parms;
	tms_device.get_display_params(&parms);
	bool active = parms.enabled != 0;

	// don't disable display if parms.enabled is inactive less than 1 field
	if (active != data->a2410_newactive) {
		if (data->a2410_activecnt > 0) {
			data->a2410_activecnt = 0;
			data->a2410_newactive = active;
			data->a2410_preactive = active;
		} else if (active) {
			data->a2410_preactive = active;
			data->a2410_newactive = active;
			data->request_fullrefresh = 1;
		} else {
			data->a2410_activecnt = 2;
			data->a2410_preactive = data->a2410_newactive;
			data->a2410_newactive = active;
			active = data->a2410_preactive;
		}
	} else {
		if (data->a2410_activecnt > 0) {
			active = data->a2410_preactive;
			data->a2410_activecnt--;
			if (!data->a2410_activecnt) {
				data->a2410_preactive = data->a2410_newactive;
				active = data->a2410_newactive;
				if (active) {
					data->request_fullrefresh = 1;
				}
			}
		}
	}

	if (active && !data->a2410_visible && !data->a2410_active && !data->a2410_enabled) {
		gfxboard_rtg_enable_initial(monid, data->a2410_gfxboard);
		data->a2410_visible = true;
		tms_toggle(data, 1);
	} else if ((!active || !data->a2410_enabled) && data->a2410_visible) {
		if (gfxboard_switch_away(monid)) {
			tms_toggle(data, 0);
			data->a2410_visible = false;
		}
		if (data->a2410_surface)
			gfx_unlock_picasso(monid, false);
		data->a2410_surface = NULL;
		return;
	} else if (active && data->a2410_enabled && !data->a2410_visible) {
		if (!gfxboard_rtg_enable_initial(monid, data->a2410_gfxboard)) {
			data->a2410_visible = true;
			tms_toggle(data, 1);
		}
	}

	if (data->a2410_visible) {
		if (active != data->a2410_active || data->a2410_modechanged) {
			if (data->a2410_surface)
				gfx_unlock_picasso(monid, false);
			data->a2410_surface = NULL;

			if (active) {
				data->fullrefresh = 2;
			}
			data->a2410_modechanged = false;
			write_log(_T("A2410 MONITOR=%d ACTIVE=%d\n"), monid, data->a2410_enabled);
		}

		if (ad->picasso_on) {
			if (currprefs.leds_on_screen & STATUSLINE_RTG) {
				get_a2410_surface(data);
			}
			if (internalsync) {
				if (data->request_fullrefresh) {
					data->fullrefresh = 2;
					data->request_fullrefresh = 0;
				}
			}
		}
		if (data->a2410_surface) {
			data->a2410_surface = NULL;
			gfx_unlock_picasso(monid, false);
		}
	}
	data->a2410_active = active;

	data->a2410_interlace = -data->a2410_interlace;

	data->a2410_overlay_blink_cnt++;
	if (data->a2410_overlay_blink_cnt == 0 || data->a2410_overlay_blink_cnt == data->a2410_overlay_blink_rate_on) {
		// any blink mode enabled?
		if (data->a2410_palette_control[5 - 4] != 0 || (data->a2410_palette_control[6 - 4] & (4 | 8)))
			data->fullrefresh = 2;
	}
	if (data->a2410_overlay_blink_cnt > data->a2410_overlay_blink_rate_off + data->a2410_overlay_blink_rate_on) {
		data->a2410_overlay_blink_cnt = 0;
	}
}


static void a2410_rethink(struct a2410_struct *data)
{
	if (data->a2410_interrupt)
		INTREQ_0(0x8000 | 0x0008);
}

static bool tms_vsync(void *userdata, struct gfxboard_mode *mode)
{
	struct a2410_struct *data = (struct a2410_struct*)userdata;
	int monid = currprefs.rtgboards[data->a2410_gfxboard].monitor_id;

	bool flushed = false;
	if (!data->a2410_enabled)
		tms_vsync_handler2(data, false);

	data->a2410_surface = NULL;
	gfx_unlock_picasso(monid, true);

	if (data->a2410_visible) {
		mode->width = data->a2410_width;
		mode->height = data->a2410_height;
		mode->hlinedbl = 1;
		mode->vlinedbl = 1;
		mode->mode = RGBFB_CLUT;
	}

	return flushed;
}

static void tms_hsync_handler2(struct a2410_struct *data)
{
	int monid = currprefs.rtgboards[data->a2410_gfxboard].monitor_id;
	struct picasso_vidbuf_description *vidinfo = &picasso_vidinfo[monid];
	struct amigadisplay *ad = &adisplays[monid];

	if (!data->tms_configured)
		return;

	tms_device.m_icount = 1000;
	tms_device.execute_run();
	int a2410_vpos = data->tms_vp;

	tms34010_display_params parms;
	tms_device.get_display_params(&parms);

	data->tms_vp = tms_device.scanline_callback(NULL, data->tms_vp, data->a2410_interlace < 0);

	a2410_rethink(data);

	if (!data->a2410_preactive)
		return;

	if (a2410_vpos == 0) {
		tms_vsync_handler2(data, true);
		if (data->a2410_interlace <= 0) {
			picasso_getwritewatch(data->a2410_gfxboard, data->a2410_vram_start_offset, NULL, NULL);
		}
	}

	if (data->a2410_modechanged || !ad->picasso_on)
		return;

	if (a2410_vpos == 0 && data->fullrefresh > 0) {
		data->fullrefresh--;
	}

	data->a2410_displaywidth = parms.hsblnk - parms.heblnk;
	data->a2410_displayend = parms.heblnk;
	data->a2410_vertical_start = parms.veblnk;

	int yoffset = a2410_vpos - data->a2410_vertical_start;

	int coladdr = parms.coladdr & 0x1ff;
	int vramoffset = ((parms.rowaddr << 8) & 0x7ffff);
	int overlayoffset = (parms.rowaddr << 6) & 0x1ffff;
	int overlayline = parms.yoffset;

	if (yoffset < 0 || yoffset >= MAX_HEIGHT || a2410_vpos < 0 || a2410_vpos >= MAX_HEIGHT)
		return;

	if (data->a2410_interlace) {
		yoffset *= 2;
		if (data->a2410_interlace < 0) {
			yoffset++;
		}
	}

	uae_u16 *vram = (uae_u16 *)data->gfxbank->baseaddr + vramoffset;

	get_a2410_surface(data);
	uae_u8 *dst = data->a2410_surface;
	if (!dst)
		return;

	if (yoffset >= data->a2410_height || yoffset >= vidinfo->maxheight)
		return;
	if (overlayline < 0 || overlayline >= MAX_HEIGHT)
		return;

	bool linerefresh = false;

	if (data->vramlinetab[a2410_vpos] != vramoffset) {
		data->vramlinetab[a2410_vpos] = vramoffset;
		linerefresh = true;
	}

	if (coladdr != data->coladdr) {
		data->fullrefresh = 1;
		data->coladdr = coladdr;
	}

	overlayoffset += (coladdr >> 3) << 1;

	if (!data->fullrefresh && !data->overlaylinetab[overlayoffset >> 7] && !linerefresh) {
		if (!picasso_is_vram_dirty(data->a2410_gfxboard, data->gfxbank->start + (vramoffset << 1), data->a2410_displaywidth)) {
			return;
		}
	}

	if (data->a2410_interlace <= 0) {
		data->overlaylinetab[overlayoffset >> 7] = false;
	}

	dst += yoffset * vidinfo->rowbytes;
	uae_u32 *dst32 = (uae_u32*)dst;

	uae_u8 *overlay0 = data->program_ram + overlayoffset;
	uae_u8 *overlay1 = overlay0 + 0x20000;

	bool overlay0color = !(data->a2410_palette_control[6 - 4] & 0x40);
	uae_u16 bitmap_mask = data->a2410_palette_control[4 - 4];

	if (data->a2410_overlay_blink_cnt >= data->a2410_overlay_blink_rate_on) {
		uae_u8 overlay_mask[2] = { data->a2410_overlay_mask[0], data->a2410_overlay_mask[1] };
		if (data->a2410_palette_control[6 - 4] & 4)
			overlay_mask[0] = 0;
		if (data->a2410_palette_control[6 - 4] & 8)
			overlay_mask[1] = 0;
		bitmap_mask &= ~data->a2410_palette_control[5 - 4];
	}

	int xx = 0;
	int overlay_offset = 0;
	int overlay_bitcount = 0;
	uae_u8 opix0 = 0, opix1 = 0;

	for (int x = parms.heblnk; x < parms.hsblnk && xx < vidinfo->maxwidth; x += 2, xx += 2) {

		if (a2410_vpos >= parms.veblnk && a2410_vpos < parms.vsblnk) {

			if (!overlay_bitcount && overlayoffset >= 0) {
				opix0 = overlay0[overlay_offset ^ 1] & data->a2410_overlay_mask[0];
				opix1 = overlay1[overlay_offset ^ 1] & data->a2410_overlay_mask[1];
				overlay_offset++;
			}

			uae_u16 pix = 0;
			if (coladdr < 0x400) {
				pix = vram[coladdr];
				coladdr++;
			}

			if (overlay0color || opix0 || opix1) {

				int pal;
				uae_u8 ov;

				pal = (pix >> 8) & bitmap_mask;
				ov = opix0 & 1;
				ov |= (opix1 & 1) << 1;
				if (ov || overlay0color)
					pal = 256 + ov;
				*dst32++ = data->a2410_palette_32[pal];
				opix0 >>= 1;
				opix1 >>= 1;

				pal = pix & bitmap_mask;
				ov = opix0 & 1;
				ov |= (opix1 & 1) << 1;
				if (ov || overlay0color)
					pal = 256 + ov;
				*dst32++ = data->a2410_palette_32[pal];
				opix0 >>= 1;
				opix1 >>= 1;


			} else {

				*dst32++ = data->a2410_palette_32[pix >> 8];
				*dst32++ = data->a2410_palette_32[pix & 0xff];

				opix0 >>= 2;
				opix1 >>= 2;

			}

			overlay_bitcount += 2;
			overlay_bitcount &= 7;
		}
	}

	while (xx < vidinfo->maxwidth) {
		*dst32++ = 0;
		xx++;
	}

}

static void tms_hsync(void *userdata)
{
	struct a2410_struct *data = (struct a2410_struct*)userdata;

	for (int i = 0; i < data->a2410_hsync_max; i++)
		tms_hsync_handler2(data);
}

void standard_irq_callback(int level)
{
	struct a2410_struct *data = &a2410_data;

	data->a2410_interrupt = level;
	a2410_rethink(data);
}

static void tms_refresh(void *userdata)
{
	struct a2410_struct *data = (struct a2410_struct *)userdata;
	data->request_fullrefresh = 1;
}

struct gfxboard_func a2410_func
{
	tms_init,
	tms_free,
	tms_reset,
	tms_hsync,
	tms_vsync,
	tms_refresh,
	tms_toggle,
	tms_configured
};
