#include "ibm.h"
#include "cpu.h"
#include "model.h"
#include "io.h"
#include "fdd.h"
#include "mouse.h"
#include "mem.h"
#include "device.h"
#include "t3100e.h"
#include "vid_t3100e.h"

/* The Toshiba 3100e is a 286-based portable.
 *
 * To bring up the BIOS setup screen hold down the 'Fn' key on booting
 *
 * Memory management
 * ~~~~~~~~~~~~~~~~~
 * 
 *      Motherboard memory is divided into:
 *      - Conventional memory: Either 512k or 640k
 *      - Upper memory:        Either 512k or 384k, depending on amount of 
 *                             conventional memory. Upper memory can be 
 *                             used either as EMS or XMS.
 *      - High memory:         0-4Mb, depending on RAM installed. The BIOS
 *                             setup screen allows some or all of this to be
 *                             used as EMS; the remainder is XMS.
 * 
 *      Additional memory (either EMS or XMS) can also be provided by ISA 
 *      expansion cards.
 *
 *      Under test in PCEM, the BIOS will boot with up to 65368Kb of memory in 
 *      total (16Mb less 16k). However it will give an error with RAM sizes 
 *      above 8Mb, if any of the high memory is allocated as EMS, because the
 *      builtin EMS page registers can only access up to 8Mb.
 *
 *      Memory is controlled by writes to I/O port 8084h:
 *        Bit 7: Always 0  }
 *        Bit 6: Always 1  } These bits select which motherboard function to
 *        Bit 5: Always 0  } access.
 *        Bit 4: Set to treat upper RAM as XMS
 *        Bit 3: Enable external RAM boards? 
 *        Bit 2: Set for 640k conventional memory, clear for 512k
 *        Bit 1: Enable RAM beyond 1Mb.
 *        Bit 0: Enable EMS.
 *
 *      The last value written to this port is saved at 0040:0093h, and in 
 *      CMOS memory at offset 0x37. If the top bit of the CMOS byte is set,
 *      then high memory is being provided by an add-on card rather than the 
 *      mainboard; accordingly, the BIOS will not allow high memory to be 
 *      used as EMS.
 *
 *      EMS is controlled by 16 page registers:
 *
 *      Page mapped at		0xD000	0xD400	0xD800	0xDC00
 *	------------------------------------------------------
 * 	Pages 0x00-0x7F		 0x208	0x4208	0x8208	0xc208
 *	Pages 0x80-0xFF		 0x218	0x4218	0x8218	0xc218
 *	Pages 0x100-0x17F	 0x258	0x4258	0x8258	0xc258
 *	Pages 0x180-0x1FF	 0x268	0x4268	0x8268	0xc268
 *
 *	The value written has bit 7 set to enable EMS, reset to disable it.
 *
 *	So: OUT 0x208,  0x80  will page in the first 16k page at 0xD0000.
 *	    OUT 0x208,  0x00  will page out EMS, leaving nothing at 0xD0000.
 *	    OUT 0x4208, 0x80  will page in the first 16k page at 0xD4000.
 *	    OUT 0x218,  0x80  will page in the 129th 16k page at 0xD0000.
 *
 *  etc.
 * 
 * To use EMS from DOS, you will need the Toshiba EMS driver (TOSHEMM.ZIP). 
 * This supports the above system, plus further ranges of ports at 
 * 0x_2A8, 0x_2B8, 0x_2C8.
 *
 */

static const int t3100e_log = 0;

extern uint8_t *ram;                /* Physical RAM */

/* Features not implemented:
 * > Four video fonts.
 * > BIOS-controlled mapping of serial ports to IRQs.
 * > Custom keyboard controller. This has a number of extra commands in the
 *   0xB0-0xBC range, for such things as turbo on/off, and switching the 
 *   keyboard between AT and PS/2 modes. Currently I have only implemented
 *   command 0xBB, so that self-test completes successfully. Commands include:
 *
 *   0xB0:   Turbo on
 *   0xB1:   Turbo off
 *   0xB2:   Internal display on?
 *   0xB3:   Internal display off?
 *   0xB5:   Get settings byte (bottom bit is colour / mono setting)
 *   0xB6:   Set settings byte
 *   0xB7:   Behave as 101-key PS/2 keyboard
 *   0xB8:   Behave as 84-key AT keyboard
 *   0xBB:   Return a byte, bit 2 is Fn key state, other bits unknown.
 *
 * The other main I/O port needed to POST is:
 *      0x8084: System control.
 *              Top 3 bits give command, bottom 5 bits give parameters.
 *               000 => set serial port IRQ / addresses
 *                      bit 4:    IRQ5 serial port base: 1 => 0x338, 0 => 0x3E8
 *                      bits 3, 2, 0 specify serial IRQs for COM1, COM2, COM3:
 *			    00 0 => 4, 3, 5
 *                          00 1 => 4, 5, 3
 *                          01 0 => 3, 4, 5
 *                          01 1 => 3, 5, 4
 *                          10 0 => 4, -, 3
 *                          10 1 => 3, -, 4 
 *               010 => set memory mappings
 *                         bit 4 set if upper RAM is XMS
 *			   bit 3 enable add-on memory boards beyond 5Mb?
 *                         bit 2 set for 640k sysram, clear for 512k sysram
 *                         bit 1 enable mainboard XMS
 *                         bit 0 enable mainboard EMS
 *               100 => set parallel mode / LCD settings
 *                         bit 4 set for bidirectional parallel port
 *                         bit 3 set to disable internal CGA
 *                         bit 2 set for single-pixel LCD font
 *                         bits 0,1 for display font
 */

void at_init();

/* The T3100e motherboard can (and does) dynamically reassign RAM between
 * conventional, XMS and EMS. This translates to monkeying with the mappings.
 */

extern mem_mapping_t base_mapping;

extern mem_mapping_t ram_low_mapping;        /* This is to switch conventional RAM
					 * between 512k and 640k */

extern mem_mapping_t ram_mid_mapping;        /* This will not be used */

extern mem_mapping_t ram_high_mapping;        /* This is RAM beyond 1Mb if any */

extern mem_mapping_t ram_remapped_mapping; /* This will not be used */

extern uint8_t *ram;

static unsigned t3100e_ems_page_reg[] =
	{
		0x208, 0x4208, 0x8208, 0xc208,        /* The first four map the first 2Mb */
		/* of RAM into the page frame */
		0x218, 0x4218, 0x8218, 0xc218,        /* The next four map the next 2Mb */
		/* of RAM */
		0x258, 0x4258, 0x8258, 0xc258,        /* and so on. */
		0x268, 0x4268, 0x8268, 0xc268,
	};

struct t3100e_ems_regs {
	uint8_t page[16];
	mem_mapping_t mapping[4];
	uint32_t page_exec[4];        /* Physical location of memory pages */
	uint32_t upper_base;        /* Start of upper RAM */
	uint8_t upper_pages;        /* Pages of EMS available from upper RAM */
	uint8_t upper_is_ems;        /* Upper RAM is EMS? */
	mem_mapping_t upper_mapping;
	uint8_t notify;                /* Notification from keyboard controller */
	uint8_t turbo;                /* 0 for 6MHz, else full speed */
	uint8_t mono;                /* Emulates PC/AT 'mono' motherboard switch */
	/* Bit 0 is 0 for colour, 1 for mono */
} t3100e_ems;

void t3100e_ems_out(uint16_t addr, uint8_t val, void *p);

/* Given a memory address (which ought to be in the page frame at 0xD0000),
 * which page does it relate to? */
static int addr_to_page(uint32_t addr) {
	if ((addr & 0xF0000) == 0xD0000) {
		return ((addr >> 14) & 3);
	}
	return -1;
}

/* And vice versa: Given a page slot, which memory address does it 
 * correspond to? */
static uint32_t page_to_addr(int pg) {
	return 0xD0000 + ((pg & 3) * 16384);
}

/* Given an EMS page ID, return its physical address in RAM. */
uint32_t t3100e_ems_execaddr(struct t3100e_ems_regs *regs,
			     int pg, uint16_t val) {
	uint32_t addr;

	if (!(val & 0x80))
		return 0;        /* Bit 7 reset => not mapped */

	val &= 0x7F;
	val += (0x80 * (pg >> 2));      /* The high bits of the register bank */
	/* are used to extend val to allow up */
	/* to 8Mb of EMS to be accessed */

	/* Is it in the upper memory range? */
	if (regs->upper_is_ems) {
		if (val < regs->upper_pages) {
			addr = regs->upper_base + 0x4000 * val;
			return addr;
		}
		val -= regs->upper_pages;
	}
	/* Otherwise work down from the top of high RAM (so, the more EMS,
	 * the less XMS) */
	if ((val * 0x4000) + 0x100000 >= (mem_size * 1024)) {
		return 0;        /* Not enough high RAM for this page */
	}
	/* High RAM found */
	addr = (mem_size * 1024) - 0x4000 * (val + 1);

	return addr;
}

/* The registers governing the EMS ports are in rather a nonintuitive order */
static int port_to_page(uint16_t addr) {
	switch (addr) {
	case 0x208: return 0;
	case 0x4208: return 1;
	case 0x8208: return 2;
	case 0xC208: return 3;
	case 0x218: return 4;
	case 0x4218: return 5;
	case 0x8218: return 6;
	case 0xC218: return 7;
	case 0x258: return 8;
	case 0x4258: return 9;
	case 0x8258: return 10;
	case 0xC258: return 11;
	case 0x268: return 12;
	case 0x4268: return 13;
	case 0x8268: return 14;
	case 0xC268: return 15;
	}
	return -1;
}

/* Used to dump the memory mapping table, for debugging
void dump_mappings()
{
	mem_mapping_t *mm = base_mapping.next;

	if (!t3100e_log) return;
	while (mm)
	{
		const char *name = "";
		uint32_t offset = (uint32_t)(mm->exec - ram);

		if (mm == &ram_low_mapping ) name = "LOW ";
		if (mm == &ram_mid_mapping ) name = "MID ";
		if (mm == &ram_high_mapping) name = "HIGH";
		if (mm == &t3100e_ems.upper_mapping) name = "UPPR";
		if (mm == &t3100e_ems.mapping[0]) 
		{
			name = "EMS0";
			offset = t3100e_ems.page_exec[0];
		}
		if (mm == &t3100e_ems.mapping[1]) 
		{
			name = "EMS1";
			offset = t3100e_ems.page_exec[1];
		}
		if (mm == &t3100e_ems.mapping[2])
		{
			name = "EMS2";
			offset = t3100e_ems.page_exec[2];
		}
		if (mm == &t3100e_ems.mapping[3]) 
		{
			name = "EMS3";
			offset = t3100e_ems.page_exec[3];
		}

		pclog("  %p | base=%05x size=%05x %c @ %06x %s\n", mm, 
			mm->base, mm->size, mm->enable ? 'Y' : 'N', 
			offset, name);

		mm = mm->next;
	}
}*/

void t3100e_map_ram(uint8_t val) {
	int n;
	int32_t upper_len;

	if (t3100e_log) {
		pclog("OUT 0x8084, %02x [ set memory mapping :", val | 0x40);
		if (val & 1)
			pclog("ENABLE_EMS ");
		if (val & 2)
			pclog("ENABLE_XMS ");
		if (val & 4)
			pclog("640K ");
		if (val & 8)
			pclog("X8X ");
		if (val & 16)
			pclog("UPPER_IS_XMS ");
		pclog("\n");
	}
	/* Bit 2 controls size of conventional memory */
	if (val & 4) {
		t3100e_ems.upper_base = 0xA0000;
		t3100e_ems.upper_pages = 24;
	} else {
		t3100e_ems.upper_base = 0x80000;
		t3100e_ems.upper_pages = 32;
	}
	upper_len = t3100e_ems.upper_pages * 16384;

	mem_mapping_set_addr(&ram_low_mapping, 0, t3100e_ems.upper_base);
	/* Bit 0 set if upper RAM is EMS */
	t3100e_ems.upper_is_ems = (val & 1);

	/* Bit 1 set if high RAM is enabled */
	if (val & 2) {
		mem_mapping_enable(&ram_high_mapping);
	} else {
		mem_mapping_disable(&ram_high_mapping);
	}

	/* Bit 4 set if upper RAM is mapped to high memory 
         * (and bit 1 set if XMS enabled) */
	if ((val & 0x12) == 0x12) {
		mem_mapping_set_addr(&t3100e_ems.upper_mapping,
				     mem_size * 1024,
				     upper_len);
		mem_mapping_enable(&t3100e_ems.upper_mapping);
		mem_mapping_set_exec(&t3100e_ems.upper_mapping, ram + t3100e_ems.upper_base);
	} else {
		mem_mapping_disable(&t3100e_ems.upper_mapping);
	}
	/* Recalculate EMS mappings */
	for (n = 0; n < 4; n++) {
		t3100e_ems_out(t3100e_ems_page_reg[n], t3100e_ems.page[n],
			       &t3100e_ems);
	}

	//dump_mappings();
}

void t3100e_notify_set(uint8_t value) {
	t3100e_ems.notify = value;
}

void t3100e_mono_set(uint8_t value) {
	t3100e_ems.mono = value;
}

uint8_t t3100e_mono_get(void) {
	return t3100e_ems.mono;
}

void t3100e_turbo_set(uint8_t value) {
	t3100e_ems.turbo = value;
	if (!value)
		cpu_set_turbo(0);
	else
		cpu_set_turbo(1);
}

uint8_t t3100e_sys_in(uint16_t addr, void *p) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)p;

	/* The low 4 bits always seem to be 0x0C. The high 4 are a 
	 * notification sent by the keyboard controller when it detects
	 * an [Fn] key combination */
	if (t3100e_log)
		pclog("IN 0x8084\n");
	return 0x0C | (regs->notify << 4);
}

/* Handle writes to the T3100e system control port at 0x8084 */
void t3100e_sys_out(uint16_t addr, uint8_t val, void *p) {
//	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)p;

	switch (val & 0xE0) {
	case 0x00: /* Set serial port IRQs. Not implemented */
		if (t3100e_log)
			pclog("OUT 0x8084, %02x [ set serial port IRQs]\n", val);
		break;
	case 0x40: /* Set RAM mappings. */
		t3100e_map_ram(val & 0x1F);
		break;

	case 0x80: /* Set video options. */
		t3100e_video_options_set(val & 0x1F);
		break;

		/* Other options not implemented. */
	default:
		if (t3100e_log)
			pclog("OUT 0x8084, %02x\n", val);
		break;
	}
}

uint8_t t3100e_config_get(void) {
/* The byte returned:
	Bit 7: Set if internal plasma display enabled
	Bit 6: Set if running at 6MHz, clear at full speed
	Bit 5: Always 1?
	Bit 4: Set if the FD2MB jumper is present (internal floppy is ?tri-mode)
	Bit 3: Clear if the FD2 jumper is present (two internal floppies)
	Bit 2: Set if the internal drive is A:, clear if B:
	Bit 1: Set if the parallel port is configured as a floppy connector
               for the second drive.
	Bit 0: Set if the F2HD jumper is present (internal floppy is 720k)
 */
	uint8_t value = 0x28;        /* Start with bits 5 and 3 set. */

	int type_a = fdd_get_type(0);
	int type_b = fdd_get_type(1);
	int prt_switch;                /* External drive type: 0=> none, 1=>A, 2=>B */

/* Get display setting */
	if (t3100e_display_get())
		value |= 0x80;
	if (!t3100e_ems.turbo)
		value |= 0x40;

/* Try to determine the floppy types.*/

	prt_switch = (type_b ? 2 : 0);
	switch (type_a) {
/* Since a T3100e cannot have an internal 5.25" drive, mark 5.25" A: drive as 
 * being external, and set the internal type based on type_b. */
	case 1:                        /* 360k */
	case 2:                        /* 1.2Mb */
	case 3:                        /* 1.2Mb RPMx2*/
		prt_switch = 1;        /* External drive is A: */
		switch (type_b) {
		case 1:                                /* 360k */
		case 4: value |= 1;
			break;        /* 720k */
		case 6: value |= 0x10;
			break;        /* Tri-mode */
			/* All others will be treated as 1.4M */
		}
		break;
	case 4: value |= 0x01;        /* 720k */
		if (type_a == type_b) {
			value &= (~8);        /* Two internal drives */
			prt_switch = 0;        /* No external drive */
		}
		break;
	case 5:        /* 1.4M */
	case 7:        /* 2.8M */
		if (type_a == type_b) {
			value &= (~8);        /* Two internal drives */
			prt_switch = 0;        /* No external drive */
		}
		break;
	case 6:        /* 3-mode */
		value |= 0x10;
		if (type_a == type_b) {
			value &= (~8);        /* Two internal drives */
			prt_switch = 0;        /* No external drive */
		}
		break;
	}        /* End switch */
	switch (prt_switch) {
	case 0: value |= 4;
		break;        /* No external floppy */
	case 1: value |= 2;
		break;        /* External floppy is A: */
	case 2: value |= 6;
		break;        /* External floppy is B: */
	}
	return value;
}

/* Read EMS page register */
uint8_t t3100e_ems_in(uint16_t addr, void *p) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)p;

	return regs->page[port_to_page(addr)];

}

/* Write EMS page register */
void t3100e_ems_out(uint16_t addr, uint8_t val, void *p) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)p;
	int pg = port_to_page(addr);

	regs->page_exec[pg & 3] = t3100e_ems_execaddr(regs, pg, val);
	if (t3100e_log)
		pclog("EMS: page %d %02x -> %02x [%06x]\n",
		      pg, regs->page[pg], val, regs->page_exec[pg & 3]);
	regs->page[pg] = val;

	pg &= 3;
/* Bit 7 set if page is enabled, reset if page is disabled */
	if (regs->page_exec[pg]) {
		if (t3100e_log)
			pclog("Enabling EMS RAM at %05x\n",
			      page_to_addr(pg));
		mem_mapping_enable(&regs->mapping[pg]);
		mem_mapping_set_exec(&regs->mapping[pg], ram + regs->page_exec[pg]);
	} else {
		if (t3100e_log)
			pclog("Disabling EMS RAM at %05x\n",
			      page_to_addr(pg));
		mem_mapping_disable(&regs->mapping[pg]);
	}
}

/* Read RAM in the EMS page frame */
static uint8_t ems_read_ram(uint32_t addr, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;
	int pg = addr_to_page(addr);

	if (pg < 0)
		return 0xFF;
	addr = regs->page_exec[pg] + (addr & 0x3FFF);
	return ram[addr];
}

static uint16_t ems_read_ramw(uint32_t addr, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;
	int pg = addr_to_page(addr);

	if (pg < 0)
		return 0xFF;
	//pclog("ems_read_ramw addr=%05x ", addr);
	addr = regs->page_exec[pg] + (addr & 0x3FFF);
	//pclog("-> %06x val=%04x\n", addr, *(uint16_t *)&ram[addr]);	
	return *(uint16_t *)&ram[addr];
}

static uint32_t ems_read_raml(uint32_t addr, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;
	int pg = addr_to_page(addr);

	if (pg < 0)
		return 0xFF;
	addr = regs->page_exec[pg] + (addr & 0x3FFF);
	return *(uint32_t *)&ram[addr];
}

/* Write RAM in the EMS page frame */
static void ems_write_ram(uint32_t addr, uint8_t val, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;
	int pg = addr_to_page(addr);

	if (pg < 0)
		return;
	addr = regs->page_exec[pg] + (addr & 0x3FFF);
	ram[addr] = val;
}

static void ems_write_ramw(uint32_t addr, uint16_t val, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;
	int pg = addr_to_page(addr);

	if (pg < 0)
		return;
	//pclog("ems_write_ramw addr=%05x ", addr);
	addr = regs->page_exec[pg] + (addr & 0x3FFF);
	//pclog("-> %06x val=%04x\n", addr, val);

	*(uint16_t *)&ram[addr] = val;
}

static void ems_write_raml(uint32_t addr, uint32_t val, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;
	int pg = addr_to_page(addr);

	if (pg < 0)
		return;
	addr = regs->page_exec[pg] + (addr & 0x3FFF);
	*(uint32_t *)&ram[addr] = val;
}

/* Read RAM in the upper area. This is basically what the 'remapped'
 * mapping in mem.c does, except that the upper area can move around */
static uint8_t upper_read_ram(uint32_t addr, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;

	addr = (addr - (1024 * mem_size)) + regs->upper_base;
	return ram[addr];
}

static uint16_t upper_read_ramw(uint32_t addr, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;

	addr = (addr - (1024 * mem_size)) + regs->upper_base;
	return *(uint16_t *)&ram[addr];
}

static uint32_t upper_read_raml(uint32_t addr, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;

	addr = (addr - (1024 * mem_size)) + regs->upper_base;
	return *(uint32_t *)&ram[addr];
}

static void upper_write_ram(uint32_t addr, uint8_t val, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;

	addr = (addr - (1024 * mem_size)) + regs->upper_base;
	ram[addr] = val;
}

static void upper_write_ramw(uint32_t addr, uint16_t val, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;

	addr = (addr - (1024 * mem_size)) + regs->upper_base;
	*(uint16_t *)&ram[addr] = val;
}

static void upper_write_raml(uint32_t addr, uint32_t val, void *priv) {
	struct t3100e_ems_regs *regs = (struct t3100e_ems_regs *)priv;

	addr = (addr - (1024 * mem_size)) + regs->upper_base;
	*(uint32_t *)&ram[addr] = val;
}

void at_t3100e_init() {
	int pg;

	memset(&t3100e_ems, 0, sizeof(t3100e_ems));

	at_init();
	/* Hook up system control port */
	io_sethandler(0x8084, 0x0001,
		      t3100e_sys_in, NULL, NULL,
		      t3100e_sys_out, NULL, NULL, &t3100e_ems);

	/* Start monitoring all 16 EMS registers */
	for (pg = 0; pg < 16; pg++) {
		io_sethandler(t3100e_ems_page_reg[pg], 0x0001,
			      t3100e_ems_in, NULL, NULL,
			      t3100e_ems_out, NULL, NULL, &t3100e_ems);
	}

	/* Map the EMS page frame */
	for (pg = 0; pg < 4; pg++) {
		if (t3100e_log)
			pclog("Adding memory map at %x for page %d\n", page_to_addr(pg), pg);
		mem_mapping_add(&t3100e_ems.mapping[pg],
				page_to_addr(pg), 16384,
				ems_read_ram, ems_read_ramw, ems_read_raml,
				ems_write_ram, ems_write_ramw, ems_write_raml,
				NULL, MEM_MAPPING_EXTERNAL,
				&t3100e_ems);
		/* Start them all off disabled */
		mem_mapping_disable(&t3100e_ems.mapping[pg]);
	}
	/* Mapping for upper RAM when in use as XMS*/
	mem_mapping_add(&t3100e_ems.upper_mapping, mem_size * 1024, 384 * 1024,
			upper_read_ram, upper_read_ramw, upper_read_raml,
			upper_write_ram, upper_write_ramw, upper_write_raml,
			NULL, MEM_MAPPING_INTERNAL, &t3100e_ems);
	mem_mapping_disable(&t3100e_ems.upper_mapping);
	mem_mapping_disable(&ram_remapped_mapping);
}
