#include <string.h>

#include "ibm.h"
#include "ide.h"
#include "ide_sff8038i.h"
#include "io.h"
#include "mem.h"
#include "pci.h"
#include "x86.h"

#include "vt82c586b.h"

static struct {
	uint8_t pci_isa_bridge_regs[256];
	uint8_t ide_regs[256];
	uint8_t usb_regs[256];
	uint8_t power_regs[256];

	uint8_t sysctrl;

	sff_busmaster_t busmaster[2];

	struct {
		uint16_t io_base;
	} usb;
	struct {
		uint16_t io_base;

		uint16_t pm_status;
		uint16_t pm_enable;
		uint16_t pm_ctrl;

		uint16_t gbl_status;
		uint16_t gbl_enable;
		uint16_t gbl_ctrl;

		uint8_t smi_cmd;
	} power;
} vt82c586b;

#define ACPI_TIMER_FREQ 3579545

#define ACPI_IO_ENABLE   (1 << 7)
#define ACPI_TIMER_32BIT (1 << 3)

#define GBL_SW_SMI_STS (1 << 6)
#define GBL_SW_SMI_EN  (1 << 6)

#define PM_CTRL_SLP_EN (1 << 13)
#define PM_CTRL_SLP_TYP_MASK (7 << 10)

static uint8_t vt82c586b_read(int func, int addr, void *priv) {
	switch (func) {
	case 0:return vt82c586b.pci_isa_bridge_regs[addr];

	case 1:return vt82c586b.ide_regs[addr];

	case 2:return vt82c586b.usb_regs[addr];

	case 3:return vt82c586b.power_regs[addr];

	default:return 0xff;
	}
}

static void vt82c586b_pci_isa_bridge_write(int addr, uint8_t val) {
	/*Read-only addresses*/
	if ((addr < 4) || (addr == 5) || (addr == 6)
		|| (addr >= 8 && addr < 0x40)
		|| (addr == 0x49)
		|| (addr == 0x4b)
		|| (addr >= 0x51 && addr < 0x54)
		|| (addr >= 0x5d && addr < 0x60)
		|| (addr >= 0x68 && addr < 0x6a)
		|| (addr >= 0x71))
		return;

	switch (addr) {
	case 4:vt82c586b.pci_isa_bridge_regs[4] = (val & 8) | 7;
		break;
	case 6:vt82c586b.pci_isa_bridge_regs[6] &= ~(val & 0xb0);
		break;

	case 0x47:
		if ((val & 0x81) == 0x81)
			resetx86();
		vt82c586b.pci_isa_bridge_regs[0x47] = val & 0xfe;
		break;

	case 0x55:
		if (!(val & 0xf0))
			pci_set_irq_routing(PCI_INTD, PCI_IRQ_DISABLED);
		else
			pci_set_irq_routing(PCI_INTD, val >> 4);
		vt82c586b.pci_isa_bridge_regs[0x55] = val;
		break;
	case 0x56:
		if (!(val & 0xf0))
			pci_set_irq_routing(PCI_INTA, PCI_IRQ_DISABLED);
		else
			pci_set_irq_routing(PCI_INTA, val >> 4);
		if (!(val & 0x0f))
			pci_set_irq_routing(PCI_INTB, PCI_IRQ_DISABLED);
		else
			pci_set_irq_routing(PCI_INTB, val & 0xf);
		vt82c586b.pci_isa_bridge_regs[0x56] = val;
		break;
	case 0x57:
		if (!(val & 0xf0))
			pci_set_irq_routing(PCI_INTC, PCI_IRQ_DISABLED);
		else
			pci_set_irq_routing(PCI_INTC, val >> 4);
		vt82c586b.pci_isa_bridge_regs[0x57] = val;
		break;

	default:vt82c586b.pci_isa_bridge_regs[addr] = val;
		break;
	}
}
static void vt82c586b_ide_write(int addr, uint8_t val) {
	/*Read-only addresses*/
	if ((addr < 4) || (addr == 5) || (addr == 8)
		|| (addr >= 0xa && addr < 0x0d)
		|| (addr >= 0x0e && addr < 0x20)
		|| (addr >= 0x22 && addr < 0x3c)
		|| (addr >= 0x3d && addr < 0x40)
		|| (addr >= 0x54 && addr < 0x60)
		|| (addr >= 0x52 && addr < 0x68)
		|| (addr >= 0x62))
		return;

	switch (addr) {
	case 4:vt82c586b.ide_regs[4] = val & 0x85;
		break;
	case 6:vt82c586b.ide_regs[6] &= ~(val & 0xb0);
		break;

	case 9:vt82c586b.ide_regs[9] = (vt82c586b.pci_isa_bridge_regs[9] & ~0x70) | 0x8a;
		break;

	case 0x20:vt82c586b.ide_regs[0x20] = (val & ~0xf) | 1;
		break;

	default:vt82c586b.ide_regs[addr] = val;
		break;
	}

	if (addr == 4 || (addr & ~3) == 0x20) /*Bus master base address*/
	{
		uint16_t base = (vt82c586b.ide_regs[0x20] & 0xf0) | (vt82c586b.ide_regs[0x21] << 8);
		io_removehandler(0, 0x10000, sff_bus_master_read, NULL, NULL, sff_bus_master_write, NULL, NULL, vt82c586b.busmaster);
		if (vt82c586b.ide_regs[0x04] & 1)
			io_sethandler(base, 0x10, sff_bus_master_read, NULL, NULL, sff_bus_master_write, NULL, NULL, vt82c586b.busmaster);
	}
	if (addr == 4 || addr == 0x40) {
		ide_pri_disable();
		ide_sec_disable();
		if (vt82c586b.ide_regs[0x04] & 1) {
			if (vt82c586b.ide_regs[0x40] & 0x02)
				ide_pri_enable();
			if (vt82c586b.ide_regs[0x40] & 0x01)
				ide_sec_enable();
		}
	}
}

static uint8_t usb_reg_read(uint16_t addr, void *p) {
//        pclog("usb_reg_read: addr=%04x\n", addr);
	switch (addr & 0x1f) {
	case 0x10:
	case 0x11:
	case 0x12:
	case 0x13: /*Port status*/
		return 0;
	}
	return 0xff;
}
static void usb_reg_write(uint16_t addr, uint8_t val, void *p) {
//        pclog("usb_reg_write: addr=%04x val=%02x\n", addr, val);
}
static void usb_update_io_mapping() {
	io_removehandler(vt82c586b.usb.io_base, 0x20, usb_reg_read, NULL, NULL, usb_reg_write, NULL, NULL, NULL);
	vt82c586b.usb.io_base = (vt82c586b.usb_regs[0x20] & ~0x1f) | (vt82c586b.usb_regs[0x21] << 8);
	if (vt82c586b.usb_regs[PCI_REG_COMMAND] & PCI_COMMAND_IO)
		io_sethandler(vt82c586b.usb.io_base, 0x20, usb_reg_read, NULL, NULL, usb_reg_write, NULL, NULL, NULL);
}
static void vt82c586b_usb_write(int addr, uint8_t val) {
	/*Read-only addresses*/
	if ((addr < 4) || (addr == 5) || (addr == 6)
		|| (addr >= 8 && addr < 0xd)
		|| (addr >= 0xe && addr < 0x20)
		|| (addr >= 0x22 && addr < 0x3c)
		|| (addr >= 0x3e && addr < 0x40)
		|| (addr >= 0x42 && addr < 0x44)
		|| (addr >= 0x46 && addr < 0xc0)
		|| (addr >= 0xc2))
		return;

	switch (addr) {
	case 4:vt82c586b.usb_regs[4] = val & 0x97;
		usb_update_io_mapping();
		break;
	case 7:vt82c586b.usb_regs[7] = val & 0x7f;
		break;

	case 0x20:vt82c586b.usb_regs[0x20] = (val & ~0x1f) | 1;
		usb_update_io_mapping();
		break;
	case 0x21:vt82c586b.usb_regs[0x21] = val;
		usb_update_io_mapping();
		break;

	default:vt82c586b.usb_regs[addr] = val;
		break;
	}
//        pclog("  USB write %02x %02x\n", addr, val);
}

static uint8_t power_reg_read(uint16_t addr, void *p) {
	uint32_t timer;
	uint8_t ret = 0xff;

	switch (addr & 0xff) {
	case 0x00:
	case 0x01:ret = vt82c586b.power.pm_status >> ((addr & 1) * 8);
		break;
	case 0x02:
	case 0x03:ret = vt82c586b.power.pm_enable >> ((addr & 1) * 8);
		break;
	case 0x04:
	case 0x05:ret = vt82c586b.power.pm_ctrl >> ((addr & 1) * 8);
		break;

	case 0x08:
	case 0x09:
	case 0x0a:
	case 0x0b: /*ACPI timer*/
		timer = (tsc * ACPI_TIMER_FREQ) / cpu_get_speed();
		if (!(vt82c586b.power_regs[0x41] & ACPI_TIMER_32BIT))
			timer &= 0x00ffffff;
		return (timer >> (8 * (addr & 3))) & 0xff;

	case 0x28:
	case 0x29:ret = vt82c586b.power.gbl_status >> ((addr & 1) * 8);
		break;
	case 0x2a:
	case 0x2b:ret = vt82c586b.power.gbl_enable >> ((addr & 1) * 8);
		break;
	case 0x2c:
	case 0x2d:ret = vt82c586b.power.gbl_ctrl >> ((addr & 1) * 8);
		break;

	case 0x2f:ret = vt82c586b.power.smi_cmd;
		break;
	}
//        pclog("power_reg_read: addr=%04x ret=%02x\n", addr, ret);
	return ret;
}
static void power_reg_write(uint16_t addr, uint8_t val, void *p) {
//        pclog("power_reg_write: addr=%04x val=%02x\n", addr, val);
	switch (addr & 0xff) {
	case 0x00:vt82c586b.power.pm_status &= ~(val & 0x31);
		break;
	case 0x01:vt82c586b.power.pm_status &= ~((val & 0x8d) << 8);
		break;

	case 0x02:vt82c586b.power.pm_enable = (vt82c586b.power.pm_enable & ~0xff) | (val & 0x21);
		break;
	case 0x03:vt82c586b.power.pm_enable = (vt82c586b.power.pm_enable & ~0xff00) | ((val & 0x05) << 8);
		break;

	case 0x04:vt82c586b.power.pm_ctrl = (vt82c586b.power.pm_ctrl & ~0xff) | (val & 0x07);
		if (val & (1 << 2))
			pclog("VT82C586B set GBL_RLS\n");
		break;
	case 0x05:vt82c586b.power.pm_ctrl = (vt82c586b.power.pm_ctrl & ~0xff00) | ((val & 0x1c) << 8);
		/*Note: PM_CTRL_SLP_EN is write-only, hence not stored in pm_ctrl*/
		if (val & (PM_CTRL_SLP_EN >> 8)) {
			pclog("VT82C586B transition to power state %i\n", (val >> 2) & 7);
			if (!(vt82c586b.power.pm_ctrl & PM_CTRL_SLP_TYP_MASK)) {
				pclog("Power off\n");
				stop_emulation_now();
			}
		}
		break;

	case 0x28:vt82c586b.power.gbl_status &= ~(val & 0x7f);
		break;

	case 0x2a:vt82c586b.power.gbl_enable = (vt82c586b.power.gbl_enable & ~0xff) | (val & 0x7f);
		break;

	case 0x2c:vt82c586b.power.gbl_ctrl = (vt82c586b.power.gbl_ctrl & ~0xff) | (val & 0x17);
		if (val & (1 << 1))
			pclog("VT82C586B set BIOS_RLS\n");
		break;
	case 0x2d:
		if (val & 1)
			vt82c586b.power.gbl_ctrl &= ~0x100;
		break;

	case 0x2f:vt82c586b.power.smi_cmd = val;
		vt82c586b.power_regs[0x47] = val;
		if (vt82c586b.power.gbl_enable & GBL_SW_SMI_EN) {
			vt82c586b.power.gbl_status |= GBL_SW_SMI_STS;
			x86_smi_trigger();
		}
		pclog("SMI_CMD write %02x\n", val);
		break;
	}
}
static void vt82c586b_power_write(int addr, uint8_t val) {
	/*Read-only addresses*/
	if ((addr < 0xd) || (addr >= 0xe && addr < 0x40)
		|| (addr == 0x43)
		|| (addr == 0x48)
		|| (addr >= 0x4a && addr < 0x50)
		|| (addr >= 0x54))
		return;

	switch (addr) {
	case 0x41:io_removehandler(vt82c586b.power.io_base, 0x0100, power_reg_read, NULL, NULL, power_reg_write, NULL, NULL, NULL);
		vt82c586b.power_regs[addr] = val;
		if (vt82c586b.power_regs[0x41] & ACPI_IO_ENABLE)
			io_sethandler(vt82c586b.power.io_base, 0x0100, power_reg_read, NULL, NULL, power_reg_write, NULL, NULL, NULL);
		break;

	case 0x49:io_removehandler(vt82c586b.power.io_base, 0x0100, power_reg_read, NULL, NULL, power_reg_write, NULL, NULL, NULL);
		vt82c586b.power_regs[addr] = val;
		vt82c586b.power.io_base = val << 8;
		if (vt82c586b.power_regs[0x41] & ACPI_IO_ENABLE)
			io_sethandler(vt82c586b.power.io_base, 0x0100, power_reg_read, NULL, NULL, power_reg_write, NULL, NULL, NULL);
		break;

	default:vt82c586b.power_regs[addr] = val;
		break;
	}
}

static void vt82c586b_write(int func, int addr, uint8_t val, void *priv) {
	switch (func) {
	case 0:vt82c586b_pci_isa_bridge_write(addr, val);
		break;

	case 1:vt82c586b_ide_write(addr, val);
		break;

	case 2:vt82c586b_usb_write(addr, val);
		break;

	case 3:vt82c586b_power_write(addr, val);
		break;
	}
}

uint8_t vt82c586b_read_sysctrl(uint16_t port, void *p) {
	return vt82c586b.sysctrl;
}

void vt82c586b_write_sysctrl(uint16_t port, uint8_t val, void *p) {
	if ((mem_a20_alt ^ val) & 2) {
		mem_a20_alt = val & 2;
		mem_a20_recalc();
	}
	if ((~vt82c586b.sysctrl & val) & 1) {
		softresetx86();
		cpu_set_edx();
	}
	vt82c586b.sysctrl = val;
}

void vt82c586b_init(int card, int pci_a, int pci_b, int pci_c, int pci_d) {
	pci_add_specific(card, vt82c586b_read, vt82c586b_write, NULL);
	io_sethandler(0x0092, 0x0001, vt82c586b_read_sysctrl, NULL, NULL, vt82c586b_write_sysctrl, NULL, NULL, NULL);

	memset(&vt82c586b, 0, sizeof(vt82c586b));
	vt82c586b.pci_isa_bridge_regs[0x00] = 0x06;
	vt82c586b.pci_isa_bridge_regs[0x01] = 0x11; /*VIA*/
	vt82c586b.pci_isa_bridge_regs[0x02] = 0x86;
	vt82c586b.pci_isa_bridge_regs[0x03] = 0x05; /*VT82C586B*/
	vt82c586b.pci_isa_bridge_regs[0x04] = 0x0f;
	vt82c586b.pci_isa_bridge_regs[0x05] = 0x00;
	vt82c586b.pci_isa_bridge_regs[0x06] = 0x00;
	vt82c586b.pci_isa_bridge_regs[0x07] = 0x02;
	vt82c586b.pci_isa_bridge_regs[0x0a] = 0x01;
	vt82c586b.pci_isa_bridge_regs[0x0b] = 0x06;
	vt82c586b.pci_isa_bridge_regs[0x0e] = 0x80;

	/*ISA bus control*/
	vt82c586b.pci_isa_bridge_regs[0x48] = 0x01;
	vt82c586b.pci_isa_bridge_regs[0x4a] = 0x04;
	vt82c586b.pci_isa_bridge_regs[0x4f] = 0x03;

	/*Plug and Play control*/
	vt82c586b.pci_isa_bridge_regs[0x50] = 0x24;
	vt82c586b.pci_isa_bridge_regs[0x59] = 0x04;

	vt82c586b.ide_regs[0x00] = 0x06;
	vt82c586b.ide_regs[0x01] = 0x11; /*VIA*/
	vt82c586b.ide_regs[0x02] = 0x71;
	vt82c586b.ide_regs[0x03] = 0x05;
	vt82c586b.ide_regs[0x04] = 0x80;
	vt82c586b.ide_regs[0x05] = 0x00;
	vt82c586b.ide_regs[0x06] = 0x80;
	vt82c586b.ide_regs[0x07] = 0x02;
	vt82c586b.ide_regs[0x09] = 0x85;
	vt82c586b.ide_regs[0x0a] = 0x01;
	vt82c586b.ide_regs[0x0b] = 0x01;

	vt82c586b.ide_regs[0x10] = 0xf1;
	vt82c586b.ide_regs[0x11] = 0x01;
	vt82c586b.ide_regs[0x14] = 0xf5;
	vt82c586b.ide_regs[0x15] = 0x03;
	vt82c586b.ide_regs[0x18] = 0x71;
	vt82c586b.ide_regs[0x19] = 0x01;
	vt82c586b.ide_regs[0x1c] = 0x75;
	vt82c586b.ide_regs[0x1d] = 0x03;
	vt82c586b.ide_regs[0x20] = 0x01;
	vt82c586b.ide_regs[0x21] = 0xcc;
	vt82c586b.ide_regs[0x3c] = 0x0e;

	vt82c586b.ide_regs[0x40] = 0x08;
	vt82c586b.ide_regs[0x41] = 0x02;
	vt82c586b.ide_regs[0x42] = 0x09;
	vt82c586b.ide_regs[0x43] = 0x3a;
	vt82c586b.ide_regs[0x44] = 0x68;
	vt82c586b.ide_regs[0x46] = 0xc0;
	vt82c586b.ide_regs[0x48] = 0xa8;
	vt82c586b.ide_regs[0x49] = 0xa8;
	vt82c586b.ide_regs[0x4a] = 0xa8;
	vt82c586b.ide_regs[0x4b] = 0xa8;
	vt82c586b.ide_regs[0x4c] = 0xff;
	vt82c586b.ide_regs[0x4e] = 0xff;
	vt82c586b.ide_regs[0x4f] = 0xff;
	vt82c586b.ide_regs[0x50] = 0x03;
	vt82c586b.ide_regs[0x51] = 0x03;
	vt82c586b.ide_regs[0x52] = 0x03;
	vt82c586b.ide_regs[0x53] = 0x03;
	vt82c586b.ide_regs[0x61] = 0x02;
	vt82c586b.ide_regs[0x68] = 0x02;

	vt82c586b.usb_regs[0x00] = 0x06;
	vt82c586b.usb_regs[0x01] = 0x11; /*VIA*/
	vt82c586b.usb_regs[0x02] = 0x38;
	vt82c586b.usb_regs[0x03] = 0x30;
	vt82c586b.usb_regs[0x04] = 0x00;
	vt82c586b.usb_regs[0x05] = 0x00;
	vt82c586b.usb_regs[0x06] = 0x00;
	vt82c586b.usb_regs[0x07] = 0x02;
	vt82c586b.usb_regs[0x0a] = 0x03;
	vt82c586b.usb_regs[0x0b] = 0x0c;
	vt82c586b.usb_regs[0x0d] = 0x16;
	vt82c586b.usb_regs[0x20] = 0x01;
	vt82c586b.usb_regs[0x21] = 0x03;
	vt82c586b.usb_regs[0x3d] = 0x04;

	vt82c586b.usb_regs[0x60] = 0x10;
	vt82c586b.usb_regs[0xc1] = 0x20;

	vt82c586b.power_regs[0x00] = 0x06;
	vt82c586b.power_regs[0x01] = 0x11; /*VIA*/
	vt82c586b.power_regs[0x02] = 0x40;
	vt82c586b.power_regs[0x03] = 0x30;
	vt82c586b.power_regs[0x04] = 0x00;
	vt82c586b.power_regs[0x05] = 0x00;
	vt82c586b.power_regs[0x06] = 0x80;
	vt82c586b.power_regs[0x07] = 0x02;
	vt82c586b.power_regs[0x08] = 0x10; /*Production version (3041)*/
	vt82c586b.power_regs[0x48] = 0x01;

	if (pci_a)
		pci_set_card_routing(pci_a, PCI_INTA);
	if (pci_b)
		pci_set_card_routing(pci_b, PCI_INTB);
	if (pci_c)
		pci_set_card_routing(pci_c, PCI_INTC);
	if (pci_d)
		pci_set_card_routing(pci_d, PCI_INTD);

	sff_bus_master_init(vt82c586b.busmaster);

	ide_pri_disable();
	ide_sec_disable();
}
