#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);
}
