#include "ibm.h"

#include "fdc.h"
#include "io.h"
#include "lpt.h"
#include "serial.h"
#include "pc87307.h"

typedef struct pc87307_t {
        int cur_reg;
        int cur_device;

        uint8_t superio_cfg1, superio_cfg2;

        struct {
                int enable;
                uint16_t addr;
                int irq;
                int dma;
        } dev[9];
} pc87307_t;

static pc87307_t pc87307_global;

#define DEV_KBC 0
#define DEV_MOUSE 1
#define DEV_RTC 2
#define DEV_FDC 3
#define DEV_LPT 4
#define DEV_COM2 5
#define DEV_COM1 6
#define DEV_GPIO 7
#define DEV_POWER 8

#define REG_DEVICE 0x07
#define REG_SID 0x20
#define REG_SUPERIO_CFG1 0x21
#define REG_SUPERIO_CFG2 0x22
#define REG_ENABLE 0x30
#define REG_ADDRHI 0x60
#define REG_ADDRLO 0x61
#define REG_IRQ 0x70
#define REG_DMA 0x74

void pc87307_write(uint16_t port, uint8_t val, void *p) {
        pc87307_t *pc87307 = (pc87307_t *)p;

        if (port == 0x2e)
                pc87307->cur_reg = val;
        else {
                if (pc87307->cur_reg == REG_DEVICE)
                        pc87307->cur_device = val & 0xf;
                else {
                        //                        pclog("Write pc87307 %02x [%02x] %02x\n", pc87307->cur_reg, pc87307->cur_device,
                        //                        val);
                        switch (pc87307->cur_reg) {
                        case REG_ENABLE:
                                if (pc87307->cur_device <= DEV_POWER)
                                        pc87307->dev[pc87307->cur_device].enable = val;
                                break;
                        case REG_ADDRLO:
                                if (pc87307->cur_device <= DEV_POWER)
                                        pc87307->dev[pc87307->cur_device].addr =
                                                (pc87307->dev[pc87307->cur_device].addr & 0xff00) | val;
                                break;
                        case REG_ADDRHI:
                                if (pc87307->cur_device <= DEV_POWER)
                                        pc87307->dev[pc87307->cur_device].addr =
                                                (pc87307->dev[pc87307->cur_device].addr & 0x00ff) | (val << 8);
                                break;
                        case REG_IRQ:
                                if (pc87307->cur_device <= DEV_POWER)
                                        pc87307->dev[pc87307->cur_device].irq = val;
                                break;
                        case REG_DMA:
                                if (pc87307->cur_device <= DEV_POWER)
                                        pc87307->dev[pc87307->cur_device].dma = val;
                                break;
                        }

                        switch (pc87307->cur_device) {
                        case DEV_FDC:
                                fdc_remove();
                                if (pc87307->dev[DEV_FDC].enable & 1)
                                        fdc_add();
                                break;
                        case DEV_COM1:
                                serial1_remove();
                                if (pc87307->dev[DEV_COM1].enable & 1)
                                        serial1_set(pc87307->dev[DEV_COM1].addr, pc87307->dev[DEV_COM1].irq);
                                break;
                        case DEV_COM2:
                                serial2_remove();
                                if (pc87307->dev[DEV_COM2].enable & 1)
                                        serial2_set(pc87307->dev[DEV_COM2].addr, pc87307->dev[DEV_COM2].irq);
                                break;
                        case DEV_LPT:
                                lpt1_remove();
                                lpt2_remove();
                                if (pc87307->dev[DEV_LPT].enable & 1)
                                        lpt1_init(pc87307->dev[DEV_LPT].addr);
                                break;
                        }
                }
        }
}

uint8_t pc87307_read(uint16_t port, void *p) {
        pc87307_t *pc87307 = (pc87307_t *)p;

        //        pclog("Read pc87307 %02x\n", pc87307->cur_reg);

        switch (pc87307->cur_reg) {
        case REG_DEVICE:
                return pc87307->cur_device;
        case REG_SID:
                return 0x20; /*PC87307*/
        case REG_SUPERIO_CFG1:
                return pc87307->superio_cfg1;
        case REG_SUPERIO_CFG2:
                return pc87307->superio_cfg2;
        case REG_ENABLE:
                return pc87307->dev[pc87307->cur_device].enable;
        case REG_ADDRLO:
                return pc87307->dev[pc87307->cur_device].addr & 0xff;
        case REG_ADDRHI:
                return pc87307->dev[pc87307->cur_device].addr >> 8;
        case REG_IRQ:
                return pc87307->dev[pc87307->cur_device].irq;
        case REG_DMA:
                return pc87307->dev[pc87307->cur_device].dma;
        }

        return 0xff;
}

void pc87307_init(uint16_t base) {
        memset(&pc87307_global, 0, sizeof(pc87307_t));

        pc87307_global.superio_cfg1 = 0x04;
        pc87307_global.superio_cfg2 = 0x03;

        io_sethandler(base, 0x0002, pc87307_read, NULL, NULL, pc87307_write, NULL, NULL, &pc87307_global);
}
