#include "ibm.h"
#include "82091aa.h"
#include "fdc.h"
#include "io.h"
#include "lpt.h"
#include "serial.h"

#define AIP_AIPID 0x00
#define AIP_AIPREV 0x01
#define AIP_AIPCFG1 0x02
#define AIP_AIPCFG2 0x03
#define AIP_FCFG1 0x10
#define AIP_FCFG2 0x11
#define AIP_PCFG1 0x20
#define AIP_PCFG2 0x21
#define AIP_SACFG1 0x30
#define AIP_SACFG2 0x31
#define AIP_SBCFG1 0x40
#define AIP_SBCFG2 0x41
#define AIP_IDECFG 0x50

#define FCFG1_FDC_ENA (1 << 0)
#define FCFG1_FDC_ADDR (1 << 1)

#define PCFG1_PP_ENA (1 << 0)
#define PCFG1_PP_ADDR (3 << 1)
#define PCFG1_PP_ADDR_378 (0 << 1)
#define PCFG1_PP_ADDR_278 (1 << 1)
#define PCFG1_PP_ADDR_3BC (2 << 1)

#define SxCFG1_ENA (1 << 0)
#define SxCFG1_ADDR (7 << 1)
#define SxCFG1_ADDR_3F8 (0 << 1)
#define SxCFG1_ADDR_2F8 (1 << 1)
#define SxCFG1_ADDR_220 (2 << 1)
#define SxCFG1_ADDR_228 (3 << 1)
#define SxCFG1_ADDR_238 (4 << 1)
#define SxCFG1_ADDR_2E8 (5 << 1)
#define SxCFG1_ADDR_338 (6 << 1)
#define SxCFG1_ADDR_3E8 (7 << 1)
#define SxCFG1_IRQ (1 << 4)
#define SxCFG1_IRQ_3 (0 << 4)
#define SxCFG1_IRQ_4 (1 << 4)

static struct {
        int idx;
        uint8_t regs[256];
} aip;

static uint8_t aip_read(uint16_t addr, void *p) {
        uint8_t temp;

        if (!(addr & 1))
                temp = aip.idx;
        else
                temp = aip.regs[aip.idx];

        //        pclog("aip_read: addr=%04x val=%02x\n", addr, temp);
        return temp;
}

static void aip_write(uint16_t addr, uint8_t val, void *p) {
        //        pclog("aip_write: addr=%04x val=%02x\n", addr, val);
        if (!(addr & 1))
                aip.idx = val;
        else {
                //                pclog("AIP register write: idx=%02x val=%02x\n", aip.idx, val);
                switch (aip.idx) {
                case AIP_AIPCFG1:
                        aip.regs[AIP_AIPCFG1] = (val & ~0x4e) | (aip.regs[AIP_AIPCFG1] & 0x48);
                        break;
                case AIP_AIPCFG2:
                        aip.regs[AIP_AIPCFG2] = val & 0xf8;
                        break;
                case AIP_FCFG1:
                        aip.regs[AIP_FCFG1] = val & 0x83;
                        fdc_remove();
                        if ((aip.regs[AIP_FCFG1] & FCFG1_FDC_ENA) && !(aip.regs[AIP_FCFG1] & FCFG1_FDC_ADDR))
                                fdc_add();
                        break;
                case AIP_FCFG2:
                        aip.regs[AIP_FCFG2] = (val & 0x0d) | (aip.regs[AIP_FCFG2] & 0x02);
                        break;
                case AIP_PCFG1:
                        aip.regs[AIP_PCFG1] = val & 0xef;
                        lpt1_remove();
                        if (aip.regs[AIP_PCFG1] & PCFG1_PP_ENA) {
                                switch (aip.regs[AIP_PCFG1] & PCFG1_PP_ADDR) {
                                case PCFG1_PP_ADDR_378:
                                        lpt1_init(0x378);
                                        break;
                                case PCFG1_PP_ADDR_278:
                                        lpt1_init(0x278);
                                        break;
                                case PCFG1_PP_ADDR_3BC:
                                        lpt1_init(0x3bc);
                                        break;
                                }
                        }
                        break;
                case AIP_PCFG2:
                        aip.regs[AIP_PCFG2] = (val & 0x0d) | (aip.regs[AIP_PCFG2] & 0x22);
                        break;
                case AIP_SACFG1:
                        aip.regs[AIP_SACFG1] = val & 0x9f;
                        serial1_remove();
                        if (aip.regs[AIP_SACFG1] & SxCFG1_ENA) {
                                uint16_t base = 0;
                                int irq = 0;

                                switch (aip.regs[AIP_SACFG1] & SxCFG1_ADDR) {
                                case SxCFG1_ADDR_3F8:
                                        base = 0x3f8;
                                        break;
                                case SxCFG1_ADDR_2F8:
                                        base = 0x2f8;
                                        break;
                                case SxCFG1_ADDR_220:
                                        base = 0x220;
                                        break;
                                case SxCFG1_ADDR_228:
                                        base = 0x228;
                                        break;
                                case SxCFG1_ADDR_238:
                                        base = 0x238;
                                        break;
                                case SxCFG1_ADDR_2E8:
                                        base = 0x2e8;
                                        break;
                                case SxCFG1_ADDR_338:
                                        base = 0x338;
                                        break;
                                case SxCFG1_ADDR_3E8:
                                        base = 0x3e8;
                                        break;
                                }
                                switch (aip.regs[AIP_SACFG1] & SxCFG1_IRQ) {
                                case SxCFG1_IRQ_3:
                                        irq = 3;
                                        break;
                                case SxCFG1_IRQ_4:
                                        irq = 4;
                                        break;
                                }
                                serial1_set(base, irq);
                        }
                        break;
                case AIP_SACFG2:
                        aip.regs[AIP_SACFG2] = (val & 0x01d) | (aip.regs[AIP_SACFG2] & 0x02);
                        break;
                case AIP_SBCFG1:
                        aip.regs[AIP_SBCFG1] = val & 0x9f;
                        serial2_remove();
                        if (aip.regs[AIP_SBCFG1] & SxCFG1_ENA) {
                                uint16_t base = 0;
                                int irq = 0;

                                switch (aip.regs[AIP_SBCFG1] & SxCFG1_ADDR) {
                                case SxCFG1_ADDR_3F8:
                                        base = 0x3f8;
                                        break;
                                case SxCFG1_ADDR_2F8:
                                        base = 0x2f8;
                                        break;
                                case SxCFG1_ADDR_220:
                                        base = 0x220;
                                        break;
                                case SxCFG1_ADDR_228:
                                        base = 0x228;
                                        break;
                                case SxCFG1_ADDR_238:
                                        base = 0x238;
                                        break;
                                case SxCFG1_ADDR_2E8:
                                        base = 0x2e8;
                                        break;
                                case SxCFG1_ADDR_338:
                                        base = 0x338;
                                        break;
                                case SxCFG1_ADDR_3E8:
                                        base = 0x3e8;
                                        break;
                                }
                                switch (aip.regs[AIP_SBCFG1] & SxCFG1_IRQ) {
                                case SxCFG1_IRQ_3:
                                        irq = 3;
                                        break;
                                case SxCFG1_IRQ_4:
                                        irq = 4;
                                        break;
                                }
                                serial2_set(base, irq);
                        }
                        break;
                case AIP_SBCFG2:
                        aip.regs[AIP_SACFG2] = (val & 0x01d) | (aip.regs[AIP_SACFG2] & 0x02);
                        break;
                case AIP_IDECFG:
                        aip.regs[AIP_IDECFG] = val & 0x07;
                        break;
                }
        }
}

void aip_82091aa_init(uint16_t base) {
        io_sethandler(base, 0x0002, aip_read, NULL, NULL, aip_write, NULL, NULL, NULL);

        memset(&aip.regs, 0xff, sizeof(aip.regs));
        aip.regs[AIP_AIPID] = 0xa0;
        aip.regs[AIP_AIPREV] = 0x00;
        aip.regs[AIP_AIPCFG1] = 0x40;
        aip.regs[AIP_AIPCFG2] = 0x00;
        aip.regs[AIP_FCFG1] = 0x01;
        aip.regs[AIP_FCFG2] = 0x00;
        aip.regs[AIP_PCFG1] = 0x00;
        aip.regs[AIP_PCFG2] = 0x00;
        aip.regs[AIP_SACFG1] = 0x00;
        aip.regs[AIP_SACFG2] = 0x02;
        aip.regs[AIP_SBCFG1] = 0x00;
        aip.regs[AIP_SBCFG2] = 0x02;
        aip.regs[AIP_IDECFG] = 0x01;
}
