#include <string.h>

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

#include "sio.h"

typedef struct sio_t {
        struct {
                uint8_t apmc, apms;
        } pm;
} sio_t;
static sio_t sio;
static uint8_t card_sio[256];
// static int pci_cards_ids[4];

#define REG_SMIEN 0xa2
#define REG_SMIREQ 0xaa

#define SMIEN_APMC (1 << 7)
#define SMIREQ_RAPMC (1 << 7)

void sio_write(int func, int addr, uint8_t val, void *priv) {
        //        pclog("sio_write: func=%d addr=%02x val=%02x %04x:%08x\n", func, addr, val, CS, cpu_state.pc);
        if (func > 0)
                return;

        if (addr >= 0x0f && addr < 0x4c)
                return;

        switch (addr) {
        case 0x00:
        case 0x01:
        case 0x02:
        case 0x03:
        case 0x08:
        case 0x09:
        case 0x0a:
        case 0x0b:
        case 0x0e:
                return;

        case 0x04: /*Command register*/
                val &= 0x08;
                val |= 0x07;
                break;
        case 0x05:
                val = 0;
                break;

        case 0x06: /*Status*/
                val = 0;
                break;
        case 0x07:
                val = 0x02;
                break;

        case 0x60:
                //                pclog("IRQ routing %02x %02x\n", addr, val);
                if (val & 0x80)
                        pci_set_irq_routing(PCI_INTA, PCI_IRQ_DISABLED);
                else
                        pci_set_irq_routing(PCI_INTA, val & 0xf);
                break;
        case 0x61:
                //                pclog("IRQ routing %02x %02x\n", addr, val);
                if (val & 0x80)
                        pci_set_irq_routing(PCI_INTC, PCI_IRQ_DISABLED);
                else
                        pci_set_irq_routing(PCI_INTC, val & 0xf);
                break;
        case 0x62:
                //                pclog("IRQ routing %02x %02x\n", addr, val);
                if (val & 0x80)
                        pci_set_irq_routing(PCI_INTB, PCI_IRQ_DISABLED);
                else
                        pci_set_irq_routing(PCI_INTB, val & 0xf);
                break;
        case 0x63:
                //                pclog("IRQ routing %02x %02x\n", addr, val);
                if (val & 0x80)
                        pci_set_irq_routing(PCI_INTD, PCI_IRQ_DISABLED);
                else
                        pci_set_irq_routing(PCI_INTD, val & 0xf);
                break;

        case REG_SMIREQ:
                card_sio[addr] &= val;
                return;
        case REG_SMIREQ + 1:
                card_sio[addr] &= val;
                return;
        }
        card_sio[addr] = val;
}

uint8_t sio_read(int func, int addr, void *priv) {
        //        pclog("sio_read: func=%d addr=%02x %04x:%08x\n", func, addr, CS, pc);
        if (func > 0)
                return 0xff;

        return card_sio[addr];
}

static uint8_t sio_apm_read(uint16_t port, void *p) {
        sio_t *sio = (sio_t *)p;
        uint8_t ret = 0xff;

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

        case 0xb3:
                ret = sio->pm.apms;
                break;
        }
        //        pclog("sio_apm_read: port=%04x ret=%02x\n", port, ret);
        return ret;
}

static void sio_apm_write(uint16_t port, uint8_t val, void *p) {
        sio_t *sio = (sio_t *)p;

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

        switch (port) {
        case 0xb2:
                sio->pm.apmc = val;
                if (card_sio[REG_SMIEN] & SMIEN_APMC) {
                        card_sio[REG_SMIREQ] |= SMIREQ_RAPMC;
                        //                        pclog("APMC write causes SMI\n");
                        x86_smi_trigger();
                }
                break;

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

void sio_init(int card, int pci_a, int pci_b, int pci_c, int pci_d) {
        memset(&sio, 0, sizeof(sio_t));

        pci_add_specific(card, sio_read, sio_write, NULL);

        memset(card_sio, 0, 256);
        card_sio[0x00] = 0x86;
        card_sio[0x01] = 0x80; /*Intel*/
        card_sio[0x02] = 0x84;
        card_sio[0x03] = 0x04; /*82378IB (SIO)*/
        card_sio[0x04] = 0x07;
        card_sio[0x05] = 0x00;
        card_sio[0x06] = 0x00;
        card_sio[0x07] = 0x02;
        card_sio[0x08] = 0x03; /*A0 stepping*/

        card_sio[0x40] = 0x20;
        card_sio[0x41] = 0x00;
        card_sio[0x42] = 0x04;
        card_sio[0x43] = 0x00;
        card_sio[0x44] = 0x20;
        card_sio[0x45] = 0x10;
        card_sio[0x46] = 0x0f;
        card_sio[0x47] = 0x00;
        card_sio[0x48] = 0x01;
        card_sio[0x49] = 0x10;
        card_sio[0x4a] = 0x10;
        card_sio[0x4b] = 0x0f;
        card_sio[0x4c] = 0x56;
        card_sio[0x4d] = 0x40;
        card_sio[0x4e] = 0x07;
        card_sio[0x4f] = 0x4f;
        card_sio[0x54] = 0x00;
        card_sio[0x55] = 0x00;
        card_sio[0x56] = 0x00;
        card_sio[0x60] = 0x80;
        card_sio[0x61] = 0x80;
        card_sio[0x62] = 0x80;
        card_sio[0x63] = 0x80;
        card_sio[0x80] = 0x78;
        card_sio[0x81] = 0x00;
        card_sio[0xa0] = 0x08;
        card_sio[0xa8] = 0x0f;

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

        io_sethandler(0x00b2, 0x0002, sio_apm_read, NULL, NULL, sio_apm_write, NULL, NULL, &sio);
}
