#include <stdlib.h>
#include "ibm.h"
#include "device.h"
#include "io.h"
#include "mem.h"
#include "pci.h"
#include "pic.h"

#include "sis496.h"

typedef struct sis496_t {
        uint8_t pci_conf[256];
} sis496_t;

void sis496_recalcmapping(sis496_t *sis496) {
        int c;

        for (c = 0; c < 8; c++) {
                uint32_t base = 0xc0000 + (c << 15);
                if (sis496->pci_conf[0x44] & (1 << c)) {
                        switch (sis496->pci_conf[0x45] & 3) {
                        case 0:
                                mem_set_mem_state(base, 0x8000, MEM_READ_EXTERNAL | MEM_WRITE_INTERNAL);
                                break;
                        case 1:
                                mem_set_mem_state(base, 0x8000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL);
                                break;
                        case 2:
                                mem_set_mem_state(base, 0x8000, MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                                break;
                        case 3:
                                mem_set_mem_state(base, 0x8000, MEM_READ_INTERNAL | MEM_WRITE_EXTERNAL);
                                break;
                        }
                } else
                        mem_set_mem_state(base, 0x8000, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL);
        }

        flushmmucache();
}

void sis496_write(int func, int addr, uint8_t val, void *p) {
        sis496_t *sis496 = (sis496_t *)p;
        // pclog("sis496_write : addr=%02x val=%02x\n", addr, val);
        switch (addr) {
        case 0x44: /*Shadow configure*/
                if ((sis496->pci_conf[0x44] & val) ^ 0xf0) {
                        sis496->pci_conf[0x44] = val;
                        sis496_recalcmapping(sis496);
                }
                break;
        case 0x45: /*Shadow configure*/
                if ((sis496->pci_conf[0x45] & val) ^ 0x01) {
                        sis496->pci_conf[0x45] = val;
                        sis496_recalcmapping(sis496);
                }
                break;

        case 0xc0:
                //              pclog("IRQ routing %02x %02x\n", addr, val);
                if (val & 0x80)
                        pci_set_irq_routing(PCI_INTA, val & 0xf);
                else
                        pci_set_irq_routing(PCI_INTA, PCI_IRQ_DISABLED);
                break;
        case 0xc1:
                //                pclog("IRQ routing %02x %02x\n", addr, val);
                if (val & 0x80)
                        pci_set_irq_routing(PCI_INTB, val & 0xf);
                else
                        pci_set_irq_routing(PCI_INTB, PCI_IRQ_DISABLED);
                break;
        case 0xc2:
                //                pclog("IRQ routing %02x %02x\n", addr, val);
                if (val & 0x80)
                        pci_set_irq_routing(PCI_INTC, val & 0xf);
                else
                        pci_set_irq_routing(PCI_INTC, PCI_IRQ_DISABLED);
                break;
        case 0xc3:
                //                pclog("IRQ routing %02x %02x\n", addr, val);
                if (val & 0x80)
                        pci_set_irq_routing(PCI_INTD, val & 0xf);
                else
                        pci_set_irq_routing(PCI_INTD, PCI_IRQ_DISABLED);
                break;
        }

        if ((addr >= 4 && addr < 8) || addr >= 0x40)
                sis496->pci_conf[addr] = val;
}

uint8_t sis496_read(int func, int addr, void *p) {
        sis496_t *sis496 = (sis496_t *)p;

        return sis496->pci_conf[addr];
}

void *sis496_init() {
        sis496_t *sis496 = malloc(sizeof(sis496_t));
        memset(sis496, 0, sizeof(sis496_t));

        pci_add_specific(5, sis496_read, sis496_write, sis496);

        sis496->pci_conf[0x00] = 0x39; /*SiS*/
        sis496->pci_conf[0x01] = 0x10;
        sis496->pci_conf[0x02] = 0x96; /*496/497*/
        sis496->pci_conf[0x03] = 0x04;

        sis496->pci_conf[0x04] = 7;
        sis496->pci_conf[0x05] = 0;

        sis496->pci_conf[0x06] = 0x80;
        sis496->pci_conf[0x07] = 0x02;

        sis496->pci_conf[0x08] = 2; /*Device revision*/

        sis496->pci_conf[0x09] = 0x00; /*Device class (PCI bridge)*/
        sis496->pci_conf[0x0a] = 0x00;
        sis496->pci_conf[0x0b] = 0x06;

        sis496->pci_conf[0x0e] = 0x00; /*Single function device*/

        pci_set_card_routing(15, PCI_INTA);
        pci_set_card_routing(13, PCI_INTD);
        pci_set_card_routing(11, PCI_INTC);

        pic_init_elcrx();

        return sis496;
}

void sis496_close(void *p) {
        sis496_t *sis496 = (sis496_t *)p;

        free(sis496);
}

device_t sis496_device = {"SiS 496/497", 0, sis496_init, sis496_close, NULL, NULL, NULL, NULL};
