/*PRD format :
        
        word 0 - base address
        word 1 - bits 1 - 15 = byte count, bit 31 = end of transfer
*/
#include <string.h>

#include "ibm.h"
#include "ide.h"
#include "ide_sff8038i.h"
#include "keyboard_at.h"
#include "io.h"
#include "mem.h"
#include "pci.h"
#include "pic.h"
#include "piix.h"
#include "piix_pm.h"

enum {
	TYPE_PIIX = 0,
	TYPE_PIIX3,
	TYPE_PIIX4
};

static piix_t piix;
static uint8_t card_piix[256], card_piix_ide[256];
static sff_busmaster_t piix_busmaster[2];
static uint8_t piix_rc = 0;
static void (*piix_nb_reset)();

#define REG_SMICNTL 0xa0
#define REG_SMIEN 0xa2
#define REG_SMIREQ 0xaa

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

void piix_write(int func, int addr, uint8_t val, void *priv) {
//        pclog("piix_write: func=%d addr=%02x val=%02x %04x:%08x\n", func, addr, val, CS, pc);
	if (func > ((piix.type == TYPE_PIIX4) ? 3 : 1))
		return;

	if (func == 1) /*IDE*/
	{
		switch (addr) {
		case 0x04:card_piix_ide[0x04] = (card_piix_ide[0x04] & ~5) | (val & 5);
			break;
		case 0x07:card_piix_ide[0x07] = (card_piix_ide[0x07] & ~0x38) | (val & 0x38);
			break;
		case 0x0d:card_piix_ide[0x0d] = val;
			break;

		case 0x20:card_piix_ide[0x20] = (val & ~0x0f) | 1;
			break;
		case 0x21:card_piix_ide[0x21] = val;
			break;

		case 0x40:card_piix_ide[0x40] = val;
			break;
		case 0x41:card_piix_ide[0x41] = val;
			break;
		case 0x42:card_piix_ide[0x42] = val;
			break;
		case 0x43:card_piix_ide[0x43] = val;
			break;
		}
		if (addr == 4 || (addr & ~3) == 0x20) /*Bus master base address*/
		{
			uint16_t base = (card_piix_ide[0x20] & 0xf0) | (card_piix_ide[0x21] << 8);
			io_removehandler(0, 0x10000, sff_bus_master_read, NULL, NULL, sff_bus_master_write, NULL, NULL, piix_busmaster);
			if (card_piix_ide[0x04] & 1)
				io_sethandler(base, 0x10, sff_bus_master_read, NULL, NULL, sff_bus_master_write, NULL, NULL, piix_busmaster);
		}
		if (addr == 4 || addr == 0x41 || addr == 0x43) {
			ide_pri_disable();
			ide_sec_disable();
			if (card_piix_ide[0x04] & 1) {
				if (card_piix_ide[0x41] & 0x80)
					ide_pri_enable();
				if (card_piix_ide[0x43] & 0x80)
					ide_sec_enable();
			}
		}
//                pclog("PIIX write %02X %02X\n", addr, val);
	} else if (func == 2) /*USB*/
	{
		/*Not implemented*/
	} else if (func == 3) /*PM*/
	{
		piix_pm_pci_write(addr, val, &piix);
	} else {
		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 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_INTB, PCI_IRQ_DISABLED);
			else
				pci_set_irq_routing(PCI_INTB, val & 0xf);
			break;
		case 0x62:
//                        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 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:
			if (piix.type <= TYPE_PIIX3) {
				card_piix[addr] &= val;
				return;
			}
			break;
		case REG_SMIREQ + 1:
			if (piix.type <= TYPE_PIIX3) {
				card_piix[addr] &= val;
				return;
			}
			break;

		}
		card_piix[addr] = val;
	}
}

uint8_t piix_read(int func, int addr, void *priv) {
//        pclog("piix_read: func=%d addr=%02x %04x:%08x\n", func, addr, CS, pc);
	if (func > ((piix.type == TYPE_PIIX4) ? 3 : 1))
		return 0xff;

	if (func == 1) /*IDE*/
	{
//                pclog("PIIX IDE read %02X %02X\n", addr, card_piix_ide[addr]);                
		return card_piix_ide[addr];
	} else if (func == 2) /*USB*/
		return 0xff; /*Not implemented*/
	else if (func == 3)
		return piix_pm_pci_read(addr, &piix);
	else
		return card_piix[addr];
}

uint8_t piix_rc_read(uint16_t port, void *p) {
	return piix_rc;
}

void piix_rc_write(uint16_t port, uint8_t val, void *p) {
	if (val & 4) {
		if (val & 2) /*Hard reset*/
		{
			piix_nb_reset();
			keyboard_at_reset(); /*Reset keyboard controller to reset system flag*/
			ide_reset_devices();
			piix.pm.timer_offset = tsc;
			resetx86();
			piix.pm.apmc = piix.pm.apms = 0;
		} else
			softresetx86();
	}

	piix_rc = val & ~4;
}

static uint8_t piix_92_read(uint16_t port, void *p) {
	return piix.port_92;
}
static void piix_92_write(uint16_t port, uint8_t val, void *p) {
	if ((mem_a20_alt ^ val) & 2) {
		mem_a20_alt = val & 2;
		mem_a20_recalc();
	}
	if ((~piix.port_92 & val) & 1) {
		softresetx86();
		cpu_set_edx();
	}
	piix.port_92 = val;
}

static uint8_t piix_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 piix_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 (card_piix[REG_SMIEN] & SMIEN_APMC) {
			card_piix[REG_SMIREQ] |= SMIREQ_RAPMC;
//                        pclog("APMC write causes SMI\n");
			if (card_piix[REG_SMICNTL] & SMICNTL_CSMIGATE)
				x86_smi_trigger();
		}
		break;

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

static void piix_common_init(int card, int pci_a, int pci_b, int pci_c, int pci_d, void (*nb_reset)()) {
	memset(&piix, 0, sizeof(piix_t));

	pci_add_specific(card, piix_read, piix_write, NULL);

	memset(card_piix, 0, 256);
	card_piix[0x00] = 0x86;
	card_piix[0x01] = 0x80; /*Intel*/
	card_piix[0x02] = 0x2e;
	card_piix[0x03] = 0x12; /*82371FB (PIIX)*/
	card_piix[0x04] = 0x07;
	card_piix[0x05] = 0x00;
	card_piix[0x06] = 0x80;
	card_piix[0x07] = 0x02;
	card_piix[0x08] = 0x00; /*A0 stepping*/
	card_piix[0x09] = 0x00;
	card_piix[0x0a] = 0x01;
	card_piix[0x0b] = 0x06;
	card_piix[0x0e] = 0x80; /*Multi-function device*/
	card_piix[0x4c] = 0x4d;
	card_piix[0x4e] = 0x03;
	card_piix[0x60] = card_piix[0x61] = card_piix[0x62] = card_piix[0x63] = 0x80;
	card_piix[0x69] = 0x02;
	card_piix[0x70] = card_piix[0x71] = 0x80;
	card_piix[0x76] = card_piix[0x77] = 0x0c;
	card_piix[0x78] = 0x02;
	card_piix[0x79] = 0x00;
	card_piix[0xa0] = 0x08;
	card_piix[0xa2] = card_piix[0xa3] = 0x00;
	card_piix[0xa4] = card_piix[0xa5] = card_piix[0xa6] = card_piix[0xa7] = 0x00;
	card_piix[0xa8] = 0x0f;
	card_piix[0xaa] = card_piix[0xab] = 0x00;
	card_piix[0xac] = 0x00;
	card_piix[0xae] = 0x00;

	card_piix_ide[0x00] = 0x86;
	card_piix_ide[0x01] = 0x80; /*Intel*/
	card_piix_ide[0x02] = 0x30;
	card_piix_ide[0x03] = 0x12; /*82371FB (PIIX)*/
	card_piix_ide[0x04] = 0x02;
	card_piix_ide[0x05] = 0x00;
	card_piix_ide[0x06] = 0x80;
	card_piix_ide[0x07] = 0x02;
	card_piix_ide[0x08] = 0x00;
	card_piix_ide[0x09] = 0x80;
	card_piix_ide[0x0a] = 0x01;
	card_piix_ide[0x0b] = 0x01;
	card_piix_ide[0x0d] = 0x00;
	card_piix_ide[0x0e] = 0x00;
	card_piix_ide[0x20] = 0x01;
	card_piix_ide[0x21] = card_piix_ide[0x22] = card_piix_ide[0x23] = 0x00; /*Bus master interface base address*/
	card_piix_ide[0x40] = card_piix_ide[0x41] = 0x00;
	card_piix_ide[0x42] = card_piix_ide[0x43] = 0x00;

	sff_bus_master_init(piix_busmaster);

	pci_set_card_routing(pci_a, PCI_INTA);
	pci_set_card_routing(pci_b, PCI_INTB);
	pci_set_card_routing(pci_c, PCI_INTC);
	pci_set_card_routing(pci_d, PCI_INTD);

	ide_pri_disable();
	ide_sec_disable();

	pic_init_elcrx();

	io_sethandler(0x0cf9, 0x0001, piix_rc_read, NULL, NULL, piix_rc_write, NULL, NULL, NULL);
	piix_nb_reset = nb_reset;

	piix.type = TYPE_PIIX3;
}

void piix_init(int card, int pci_a, int pci_b, int pci_c, int pci_d, void (*nb_reset)()) {
	piix_common_init(card, pci_a, pci_b, pci_c, pci_d, nb_reset);

	io_sethandler(0x00b2, 0x0002, piix_apm_read, NULL, NULL, piix_apm_write, NULL, NULL, &piix);
}

void piix4_init(int card, int pci_a, int pci_b, int pci_c, int pci_d, void (*nb_reset)()) {
	piix_init(card, pci_a, pci_b, pci_c, pci_d, nb_reset);

	piix.type = TYPE_PIIX4;

	card_piix[0x02] = 0x10;
	card_piix[0x03] = 0x71; /*82371EB (PIIX4)*/

	card_piix_ide[0x02] = 0x11;
	card_piix_ide[0x03] = 0x71; /*82371EB (PIIX4)*/

	io_sethandler(0x0092, 0x0001, piix_92_read, NULL, NULL, piix_92_write, NULL, NULL, NULL);
	piix_pm_init(&piix);
}
