#include "ibm.h"
#include "io.h"
#include "piix.h"
#include "piix_pm.h"
#include "x86.h"

#define DEVSTS_TRP_STS_DEV13 (1 << 29)

#define DEVCTL_TRP_EN_DEV13 (1 << 25)

#define GLBSTS_APM_STS (1 << 5)

static uint8_t dev13_read(uint16_t addr, void *p) {
	piix_t *piix = (piix_t *)p;

//        pclog("dev13_read: addr=%04x  %08x\n", addr, piix.pm.devctl);
	if (piix->pm.devctl & DEVCTL_TRP_EN_DEV13) {
		piix->pm.devsts |= DEVSTS_TRP_STS_DEV13;
		x86_smi_trigger();
	}
	return 0xff;
}
static void dev13_write(uint16_t addr, uint8_t val, void *p) {
	piix_t *piix = (piix_t *)p;

//        pclog("dev13_write: addr=%04x val=%02x  %08x\n", addr, val, piix.pm.devctl);
	if (piix->pm.devctl & DEVCTL_TRP_EN_DEV13) {
		piix->pm.devsts |= DEVSTS_TRP_STS_DEV13;
		x86_smi_trigger();
	}
}

static void dev13_recalc(piix_t *piix) {
	if (piix->pm.dev13_enable)
		io_removehandler(piix->pm.dev13_base, piix->pm.dev13_size,
				 dev13_read, NULL, NULL,
				 dev13_write, NULL, NULL, piix);
	piix->pm.dev13_base = piix->card_pm[0x70] | (piix->card_pm[0x71] << 8);
	piix->pm.dev13_size = (piix->card_pm[0x72] & 0xf) + 1;
	piix->pm.dev13_enable = piix->card_pm[0x72] & 0x10;
	if (piix->pm.dev13_enable)
		io_sethandler(piix->pm.dev13_base, piix->pm.dev13_size,
			      dev13_read, NULL, NULL,
			      dev13_write, NULL, NULL, piix);
}

static void piix_pm_write(uint16_t port, uint8_t val, void *p) {
	piix_t *piix = (piix_t *)p;

//        pclog("piix_pm_write: port=%04x val=%02x\n", port, val);
	switch (port & 0x3f) {
	case 0x00:piix->pm.pmsts &= ~(val & 0x31);
		break;
	case 0x01:piix->pm.pmsts &= ~((val & 0x8d) << 8);
		break;

	case 0x02:piix->pm.pmen = (piix->pm.pmen & ~0xff) | (val & 0x21);
		break;
	case 0x03:piix->pm.pmen = (piix->pm.pmen & ~0xff00) | ((val & 0x05) << 8);
		break;

	case 0x04:piix->pm.pmcntrl = (piix->pm.pmcntrl & ~0xff) | (val & 0x03);
		break;
	case 0x05:pclog("PM reg 5 write %02x\n", val);
		if (val & 0x20) {
			pclog("PIIX transition to power state %i\n", (val >> 2) & 7);
			if (val == 0x20) {
				pclog("Power off\n");
				stop_emulation_now();
			}
		}
		piix->pm.pmcntrl = (piix->pm.pmcntrl & ~0xff00) | ((val & 0x1c) << 8);
		break;

	case 0x0c:
	case 0x0d:piix->pm.gpsts &= ~(val << ((port & 1) * 8));
		break;

	case 0x0e:piix->pm.pmen = (piix->pm.pmen & ~0xff) | (val & 0x01);
		break;
	case 0x0f:piix->pm.pmen = (piix->pm.pmen & ~0xff00) | ((val & 0x0f) << 8);
		break;

	case 0x10:piix->pm.pcntrl = (piix->pm.pcntrl & ~0xff) | (val & 0x1e);
		break;
	case 0x11:piix->pm.pcntrl = (piix->pm.pcntrl & ~0xff00) | ((val & 0x3e) << 8);
		break;

	case 0x18:piix->pm.glbsts &= ~(val & 0x25);
		break;
	case 0x19:piix->pm.glbsts &= ~((val & 0x0d) << 8);
		break;

	case 0x1c:
	case 0x1d:
	case 0x1e:
	case 0x1f:piix->pm.devsts &= ~(val << ((port & 3) * 8));
		break;

	case 0x20:piix->pm.glben = (piix->pm.glben & ~0xff) | (val & 0x01);
		break;
	case 0x21:piix->pm.glben = (piix->pm.glben & ~0xff00) | ((val & 0x0f) << 8);
		break;

	case 0x28:piix->pm.glbctl = (piix->pm.glbctl & ~0xff) | (val & 0x05);
		break;
	case 0x29:piix->pm.glbctl = (piix->pm.glbctl & ~0xff00) | ((val & 0xff) << 8);
		break;
	case 0x2a:piix->pm.glbctl = (piix->pm.glbctl & ~0xff0000) | ((val & 0x01) << 16);
		break;
	case 0x2b:piix->pm.glbctl = (piix->pm.glbctl & ~0xff000000) | ((val & 0x07) << 24);
		break;

	case 0x2c:piix->pm.devctl = (piix->pm.devctl & ~0xff) | val;
		break;
	case 0x2d:piix->pm.devctl = (piix->pm.devctl & ~0xff00) | (val << 8);
		break;
	case 0x2e:piix->pm.devctl = (piix->pm.devctl & ~0xff0000) | (val << 16);
		break;
	case 0x2f:piix->pm.devctl = (piix->pm.devctl & ~0xff000000) | ((val & 0x0f) << 24);
		break;

	case 0x34:piix->pm.gporeg = (piix->pm.gporeg & ~0xff) | val;
		break;
	case 0x35:piix->pm.gporeg = (piix->pm.gporeg & ~0xff00) | (val << 8);
		break;
	case 0x36:piix->pm.gporeg = (piix->pm.gporeg & ~0xff0000) | (val << 16);
		break;
	case 0x37:piix->pm.gporeg = (piix->pm.gporeg & ~0xff000000) | (val << 24);
		break;
	}
}
static uint8_t piix_pm_read(uint16_t port, void *p) {
	piix_t *piix = (piix_t *)p;
	uint8_t ret = 0xff;

	switch (port & 0x3f) {
	case 0x00:
	case 0x01:ret = piix->pm.pmsts >> ((port & 1) * 8);
		break;

	case 0x02:
	case 0x03:ret = piix->pm.pmen >> ((port & 1) * 8);
		break;

	case 0x04:
	case 0x05:ret = piix->pm.pmcntrl >> ((port & 1) * 8);
		break;

	case 0x08:
	case 0x09:
	case 0x0a:
	case 0x0b: {
		uint64_t timer = tsc - piix->pm.timer_offset;
		uint32_t pm_timer = (timer * 3579545) / cpu_get_speed();

		ret = pm_timer >> ((port & 3) * 8);
		break;
	}

	case 0x0c:
	case 0x0d:ret = piix->pm.gpsts >> ((port & 1) * 8);
		break;

	case 0x0e:
	case 0x0f:ret = piix->pm.pmen >> ((port & 1) * 8);
		break;

	case 0x10:
	case 0x11:
	case 0x12:
	case 0x13:ret = piix->pm.pcntrl >> ((port & 3) * 8);
		break;

	case 0x14:ret = 0;
		break;
	case 0x15:ret = 0;
		break;

	case 0x18:
	case 0x19:ret = piix->pm.glbsts >> ((port & 1) * 8);
		break;

	case 0x1c:
	case 0x1d:
	case 0x1e:
	case 0x1f:ret = piix->pm.devsts >> ((port & 3) * 8);
		break;

	case 0x20:
	case 0x21:ret = piix->pm.glben >> ((port & 1) * 8);
		break;

	case 0x28:
	case 0x29:
	case 0x2a:
	case 0x2b:ret = piix->pm.glbctl >> ((port & 3) * 8);
		break;

	case 0x2c:
	case 0x2d:
	case 0x2e:
	case 0x2f:ret = piix->pm.devctl >> ((port & 3) * 8);
		break;

	case 0x30:ret = 0x00;
		break;
	case 0x31:ret = 0x00;
		break;
	case 0x32:
		/*GA686BX - bit 1 = low battery*/
		ret = 0x00;
		break;

	case 0x34:
	case 0x35:
	case 0x36:
	case 0x37:ret = piix->pm.gporeg >> ((port & 3) * 8);
		break;

//                default:
//                fatal("piix_pm_read: unknown read %04x\n", port);
	}

//        pclog("piix_pm_read: port=%04x val=%02x\n", port, ret);
	return ret;
}

static void piix_smbus_write(uint16_t port, uint8_t val, void *p) {
	piix_t *piix = (piix_t *)p;
//        pclog("piix_smbus_write: port=%04x val=%02x\n", port, val);

	switch (port & 0xf) {
	case 0x2:piix->smbus.host_ctrl = (val & 0x5f) | (piix->smbus.host_ctrl & 0x40);
		break;
	}
}
static uint8_t piix_smbus_read(uint16_t port, void *p) {
	piix_t *piix = (piix_t *)p;
	uint8_t ret = 0xff;

	switch (port & 0x0f) {
	case 0x0:ret = 0;
		break;

	case 0x2:ret = piix->smbus.host_ctrl;
		break;

	case 0x5:ret = 0;
		break;

//                default:
//                fatal("piix_smbus_read: unknown read %04x\n", port);
	}

	return ret;
}

static uint8_t piix4_apm_read(uint16_t port, void *p) {
	piix_t *piix = (piix_t *)p;
	uint8_t ret = 0xff;

	switch (port) {
	case 0xb2:ret = piix->pm.apmc;
		break;

	case 0xb3:ret = piix->pm.apms;
		break;
	}
//        pclog("piix_apm_read: port=%04x ret=%02x\n", port, ret);
	return ret;
}
static void piix4_apm_write(uint16_t port, uint8_t val, void *p) {
	piix_t *piix = (piix_t *)p;

//        pclog("piix_apm_write: port=%04x val=%02x\n", port, val);

	switch (port) {
	case 0xb2:piix->pm.apmc = val;
		if (piix->card_pm[0x5b] & (1 << 1)) {
			piix->pm.glbsts |= GLBSTS_APM_STS;
//                        pclog("APMC write causes SMI\n");
			x86_smi_trigger();
		}
		break;

	case 0xb3:piix->pm.apms = val;
		break;
	}
}

static void pm_clear_io(piix_t *piix) {
	io_removehandler(piix->pmba, 0x0040, piix_pm_read, NULL, NULL, piix_pm_write, NULL, NULL, piix);
	io_removehandler(piix->smbba, 0x0010, piix_smbus_read, NULL, NULL, piix_smbus_write, NULL, NULL, piix);
}
static void pm_set_io(piix_t *piix) {
//        pclog("pm_set_io: %02x %04x\n", piix.card_pm[0x04], piix.smbba);
	if ((piix->card_pm[0x04] & 0x01) && piix->pmba) {
//                pclog("PMBA=%04x\n", piix.pmba);
		io_sethandler(piix->pmba, 0x0040, piix_pm_read, NULL, NULL, piix_pm_write, NULL, NULL, piix);
	}
	if ((piix->card_pm[0x04] & 0x01) && piix->smbba) {
//                pclog("SMBBA=%04x\n", piix.smbba);
		io_sethandler(piix->smbba, 0x0010, piix_smbus_read, NULL, NULL, piix_smbus_write, NULL, NULL, piix);
	}
}

void piix_pm_pci_write(int addr, uint8_t val, void *p) {
	piix_t *piix = (piix_t *)p;

	switch (addr) {
	case 0x04:pm_clear_io(piix);
		piix->card_pm[0x04] = val & 1;
		pm_set_io(piix);
		break;
	case 0x40:pm_clear_io(piix);
		piix->pmba = (piix->pmba & 0xff00) | (val & 0xc0);
		pm_set_io(piix);
		break;
	case 0x41:pm_clear_io(piix);
		piix->pmba = (piix->pmba & 0xc0) | (val << 8);
		pm_set_io(piix);
		break;
	case 0x58:
	case 0x59:
	case 0x5b:
	case 0x5c:piix->card_pm[addr] = val;
		break;
	case 0x70:
	case 0x71:
	case 0x72:piix->card_pm[addr] = val;
		dev13_recalc(piix);
		break;
	case 0x90:pm_clear_io(piix);
		piix->smbba = (piix->smbba & 0xff00) | (val & 0xf0);
		pm_set_io(piix);
		break;
	case 0x91:pm_clear_io(piix);
		piix->smbba = (piix->smbba & 0xf0) | (val << 8);
		pm_set_io(piix);
		break;
	}
}
uint8_t piix_pm_pci_read(int addr, void *p) {
	piix_t *piix = (piix_t *)p;

	return piix->card_pm[addr];
}

void piix_pm_init(piix_t *piix) {
	memset(&piix->card_pm, 0, sizeof(piix->card_pm));
	piix->card_pm[0x00] = 0x86;
	piix->card_pm[0x01] = 0x80; /*Intel*/
	piix->card_pm[0x02] = 0x13;
	piix->card_pm[0x03] = 0x71; /*82371EB (PIIX4)*/
	piix->card_pm[0x06] = 0x80;
	piix->card_pm[0x07] = 0x02;
	piix->card_pm[0x09] = 0x00;
	piix->card_pm[0x0a] = 0x80;
	piix->card_pm[0x0b] = 0x06; /*Bridge device*/
	piix->card_pm[0x3d] = 0x01; /*IRQA*/
	piix->card_pm[0x40] = 0x01;
	piix->card_pm[0x90] = 0x01;
	piix->pm.timer_offset = 0;
	piix->pm.gporeg = 0x7fffbfff;

	io_sethandler(0x00b2, 0x0002, piix4_apm_read, NULL, NULL, piix4_apm_write, NULL, NULL, piix);
}
