/*Arculator 2.2 by Sarah Walker
  IOC emulation*/
#include <stdio.h>
#include "arc.h"
#include "cmos.h"
#include "disc.h"
#include "ds2401.h"
#include "config.h"
#include "ioc.h"
#include "keyboard.h"
#include "printer.h"
#include "timer.h"

IOC_t ioc;

int ref8m_period;

extern int irq;
int keyway=0;
uint8_t tempkey,iockey,iockey2;
int keydelay=0,keydelay2;

void ioc_updateirqs()
{
	if (fdctype == FDC_WD1770)
	{
		if (discchange[curdrive])
		{
			ioc.irqb |= IOC_IRQB_DISC_CHANGED;
//                        if (ioc.mskb&0x10) rpclog("Disc change interrupt\n");
		}
		else
			ioc.irqb &= ~IOC_IRQB_DISC_CHANGED;
	}
	if ((ioc.mska&(ioc.irqa|0x80))||(ioc.mskb&ioc.irqb))   irq|=1;
	else                                            irq&=~1;
//        if (irq&1) rpclog("IRQ %02X %02X\n",(ioc.mska&ioc.irqa),(ioc.mskb&ioc.irqb));
}

void dumpiocregs()
{
	printf("IOC regs :\n");
	printf("STAT : %02X %02X %02X  MASK : %02X %02X %02X\n",ioc.irqa,ioc.irqb,ioc.fiq,ioc.mska,ioc.mskb,ioc.mskf);
}

static void load_timer(int timer_nr)
{
	if (ioc.timerl[timer_nr])
		timer_set_delay_u64(&ioc.timers[timer_nr], ((ioc.timerl[timer_nr] + 1) * TIMER_USEC) / 2);
	else
		timer_set_delay_u64(&ioc.timers[timer_nr], ((0x10000 + 1) * TIMER_USEC) / 2);
}

static uint16_t read_timer(int timer_nr)
{
	uint64_t remaining = timer_get_remaining_u64(&ioc.timers[timer_nr]);

	remaining = (remaining * 2) / TIMER_USEC;

	return remaining & 0xffff;
}

//int genpoll,genpol;
#define gencycdif 0
//#define gencycdif ((genpoll-genpol)<<1)
void ioc_write(uint32_t addr, uint32_t v)
{
//        if ((addr & 0x7c) >= 0x10 && (addr & 0x7c) < 0x40) rpclog("ioc_write %04X %02X %07X\n", addr, v, PC);
	switch (addr & 0x7C)
	{
		case 0x00:
		i2c_change(v & 2, v & 1);
		if (fdctype == FDC_82C711)
			ds2401_write(v & 8);
		ioc.ctrl = v & 0xFC;
		return;
		case 0x04:
		ioc_irqbc(IOC_IRQB_KEYBOARD_TX);
		keyboard_write(v);
		return;
		case 0x14:
		ioc.irqa &= ~v;
		ioc_updateirqs();
		return;
		case 0x18:
		ioc.mska = v;
		ioc_updateirqs();
		return;
		case 0x24: /*????*/
		return;
		case 0x28:
		ioc.mskb = v;
		ioc_updateirqs();
		return;
		case 0x34: /*????*/
		return;
		case 0x38:
		ioc.mskf = v;
		ioc_fiq(0);
		return;
		case 0x40:
		ioc.timerl[0] = (ioc.timerl[0] & 0xff00) | v;
		return;
		case 0x44:
		ioc.timerl[0] = (ioc.timerl[0] & 0x00ff) | (v << 8);
		return;
		case 0x48:
		load_timer(0);
//                rpclog("T0 count = %08X %i\n", ioc.timerc[0], ioc.timerl[0]);
		return;
		case 0x4C:
		ioc.timerr[0] = read_timer(0);
//                rpclog("T0 read  = %08X %i\n", ioc.timerc[0], ioc.timerr[0]);
		return;
		case 0x50:
		ioc.timerl[1] = (ioc.timerl[1] & 0xff00) | v;
		return;
		case 0x54:
		ioc.timerl[1] = (ioc.timerl[1] & 0x00ff) | (v << 8);
		return;
		case 0x58:
		load_timer(1);
		return;
		case 0x5C:
		ioc.timerr[1] = read_timer(1);
		return;
		case 0x60:
		ioc.timerl[2] = (ioc.timerl[2] & 0xff00) | v;
		return;
		case 0x64:
		ioc.timerl[2] = (ioc.timerl[2] & 0x00ff) | (v << 8);
		return;
		case 0x68:
		load_timer(2);
		return;
		case 0x6C:
		ioc.timerr[2] = read_timer(2);
		return;
		case 0x70:
		ioc.timerl[3] = (ioc.timerl[3] & 0xff00) | v;
		return;
		case 0x74:
		ioc.timerl[3] = (ioc.timerl[3] & 0x00ff) | (v << 8);
		return;
		case 0x78:
		load_timer(3);
		return;
		case 0x7C:
		ioc.timerr[3] = read_timer(3);
		return;
	}
#ifndef RELEASE_BUILD
	fatal("Bad IOC write %07X %04X %08X\n",addr,v,PC);
#endif
}

uint8_t ioc_read(uint32_t addr)
{
	uint8_t temp;
//        if ((addr & 0x7c) >= 0x10 && (addr & 0x7c) < 0x40) rpclog("ioc_read %08X %07X\n", addr, PC);
	switch (addr & 0x7C)
	{
		case 0x00:
		temp = ((i2c_clock) ? 2 : 0) | ((i2c_data) ? 1 : 0) | flyback;
		if (fdctype == FDC_82C711)
		{
			if ((ioc.ctrl & 8) && ds2401_read())
				temp |= 8;
		}
		else if (fdctype == FDC_WD1793_A500)
		{
			if (!discchange[curdrive])
				temp |= 0x10;
			else
				temp &= ~0x10;
		}
		else
		{
			if (!fdc_ready)
				temp |= 4;
			if (printer_get_ack())
				temp |= 0x40;
		}
		return temp;
		case 0x04:
		ioc_irqbc(IOC_IRQB_KEYBOARD_RX);
		temp = keyboard_read();
		LOG_KB_MOUSE("keyboard_read %02X\n", temp);
		return temp;
		case 0x10:
		return ioc.irqa | 0x80;
		case 0x14:
		return (ioc.irqa | 0x80) & ioc.mska;
		case 0x18:
		return ioc.mska;
		case 0x20:
		return ioc.irqb;
		case 0x24:
		return ioc.irqb & ioc.mskb;
		case 0x28:
		return ioc.mskb;
		case 0x30:
		return ioc.fiq;
		case 0x34:
		return ioc.fiq & ioc.mskf;
		case 0x38:
		return ioc.mskf;
		case 0x40:
		return ioc.timerr[0];
		case 0x44:
		return ioc.timerr[0] >> 8;
		case 0x50:
		return ioc.timerr[1];
		case 0x54:
		return ioc.timerr[1] >> 8;
		case 0x60:
		return ioc.timerr[2];
		case 0x64:
		return ioc.timerr[2] >> 8;
		case 0x70:
		return ioc.timerr[3];
		case 0x74:
		return ioc.timerr[3] >> 8;
	}
	return 0;
}

void ioc_fiq(uint8_t v)
{
	ioc.fiq |= v;
	if ((ioc.fiq | 0x80) & ioc.mskf) irq |=  2;
	else                             irq &= ~2;
//        rpclog("FIQ set %02X %02X %i\n", ioc.fiq, ioc.mskf, irq);
}

void ioc_fiqc(uint8_t v)
{
	ioc.fiq &= ~v;
	if ((ioc.fiq | 0x80) & ioc.mskf) irq |=  2;
	else                             irq &= ~2;
//        rpclog("FIQ clear %02X %02X %i\n", ioc.fiq, ioc.mskf, irq);
}

void ioc_irqa(uint8_t v)
{
	ioc.irqa |= v;
	ioc_updateirqs();
}

void ioc_irqac(uint8_t v)
{
	ioc.irqa &= ~v;
	ioc_updateirqs();
}

void ioc_irqb(uint8_t v)
{
	ioc.irqb |= v;
	ioc_updateirqs();
}

void ioc_irqbc(uint8_t v)
{
	ioc.irqb &= ~v;
	ioc_updateirqs();
}

static void ioc_timer_callback(void *p)
{
	int timer_nr = (intptr_t)p;

	if (timer_nr < 2)
		ioc_irqa(timer_nr ? IOC_IRQA_TIMER_1 : IOC_IRQA_TIMER_0);

	if (ioc.timerl[timer_nr])
		timer_advance_u64(&ioc.timers[timer_nr], ((ioc.timerl[timer_nr] + 1) * TIMER_USEC) / 2);
	else
		timer_advance_u64(&ioc.timers[timer_nr], ((0x10000 + 1) * TIMER_USEC) / 2);
}

void ioc_reset()
{
	ioc.irqa = ioc.mska = 0;//x10;
	ioc.irqb = ioc.mskb = 0;
	ioc.fiq  = ioc.mskf = 0;
	ioc.irqa=0x10;
	ioc.irqb = 2;
	timer_add(&ioc.timers[0], ioc_timer_callback, (void *)0, 1);
	timer_add(&ioc.timers[1], ioc_timer_callback, (void *)1, 1);
	timer_add(&ioc.timers[2], ioc_timer_callback, (void *)2, 1);
	timer_add(&ioc.timers[3], ioc_timer_callback, (void *)3, 1);
}

void ioc_discchange(int drive)
{
	discchange[drive] = 1;
	ioc_updateirqs();
	rpclog("ioc_discchange on %i - %02X\n", drive, ioc.irqb);
}
void ioc_discchange_clear(int drive)
{
	discchange[drive] = 0;
	ioc_updateirqs();
	rpclog("ioc_discchange_clear on %i - %02X\n", drive, ioc.irqb);
}

void keycallback()
{
	keyboard_write(iockey2);
	ioc_irqb(IOC_IRQB_KEYBOARD_TX);
}

void keycallback2()
{
	if (ioc.irqb&0x80)
	{
		keydelay2+=256;
		return;
	}
	iockey=tempkey;
	ioc_irqb(IOC_IRQB_KEYBOARD_RX);
}

void ioc_debug_print(char *s)
{
	sprintf(s, "IOC registers:\n"
		   "IRQ status A=%02x  IRQ status B=%02x  FIQ status=%02x\n"
		   "IRQ mask A  =%02x  IRQ mask B  =%02x  FIQ mask  =%02x\n"
		   "T0 count=%04x  T1 count=%04x  T2 count=%04x  T3 count=%04x\n"
		   "T0 latch=%04x  T1 latch=%04x  T2 latch=%04x  T2 latch=%04x\n"
		   "Control=%02x\n",
		   ioc.irqa, ioc.irqb, ioc.fiq,
		   ioc.mska, ioc.mskb, ioc.mskf,
		   read_timer(0), read_timer(1), read_timer(2), read_timer(3),
		   ioc.timerl[0], ioc.timerl[1], ioc.timerl[2], ioc.timerl[3],
		   ioc.ctrl);
}
