/*
 * 6522 module
 *
 * 12/02/94 jkb  creation
 *
 * TODO: add T2 code
 */

#include <stdio.h>
#include <unistd.h>
#include <errno.h>

#include "6522.h"
#include "module_inc.h"
#include "memory.h"
#include "6502P.h"
#include "timer.h"

/*
 * ---------------------------------------------------------------------------
 * Macros
 * ---------------------------------------------------------------------------
 */

/* #define DEBUG_VIA */

#define update_ifr() \
{\
    if (ifr & 0x7f)\
	ifr |= 0x80;\
    else\
	ifr = 0;\
}

#define update_ifr_set()\
{\
    if (ifr & 0x7f)\
	ifr |= 0x80;\
}

#define update_ifr_clr()\
{\
    if (ifr & 0x7f == 0)\
	ifr = 0;\
}

/*
 * ---------------------------------------------------------------------------
 * Static globals
 * ---------------------------------------------------------------------------
 */


static byte read_via(word addr);
static byte write_via(word addr, byte val);

extern int scan_key();
extern int debug;


t_6522 i_6522_system = {
    init, update,
    read_via, write_via
};

extern int do_irq;
/*
 * Setting ier == 0xc0 by default disables clearing memory on powerup.
 * This speeds up debugging and is only temporary.
 */
static byte ora, orb, ddra, ddrb, sr, acr, pcr, ifr=0, ier = 0x80;
static int t1_timer = 0;
static int t2_timer = 0;

static int pb_latch[8];


/*
 * ---------------------------------------------------------------------------
 * Functions
 * ---------------------------------------------------------------------------
 */
void update_sysvia_t() {
#ifdef DEBUG_VIA
    puts("sysvia_t enter");
#endif
 
    /* set IFR T1 time-out */
    ifr |= IFR_SET | IFR_T1;
    
    /* generate interrupt */
    do_irq = 1;

#ifdef DEBUG_VIA
    puts("sysvia exit");
#endif

}

void update_sysvia_k() {
#ifdef DEBUG_VIA
    puts("sysvia_t enter");
#endif
 
    if (scan_key()) {
	ifr |= IFR_SET | IFR_CA2;
	if (ier & IFR_CA2)
	    do_irq = 1;
#ifdef DEBUG_VIA
	printf("keyp => %02x\n", ifr);
#endif
    }

#ifdef DEBUG_VIA
    puts("sysvia_k exit");
#endif
}

/* ---------------------------------- READ --------------------------------- */

static byte read_via(word addr) {
    addr &= 0x0f;

#ifdef DEBUG_VIA
    printf("R  %x : %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
	   addr,
	   orb, ora, ddrb, ddra,
	   query_timer_expire(t1_timer) & 0x00ff,
	   (query_timer_expire(t1_timer) & 0xff00) >> 8,
	   query_timer_interval(t1_timer) & 0x00ff,
	   (query_timer_interval(t1_timer) & 0xff00) >> 8,
	   0, 0,
	   sr, acr, pcr, ifr, ier, ora);
#endif

    switch (addr) {
    case 0: /* output reg B */
	ifr &= ~IFR_CB1;
	if (pcr & 0xa0 != 0x20)
	    ifr &= ~IFR_CB2;
	update_ifr_clr();

	/*
	 * FIXME:
	 * With DDRB set to 1 (output), should be reading the levels
	 * stored on the output latch? Makes no difference for us - I
	 * hope! Input latching (see the acr) has an effect here.
	 */
	if (acr & 0x02)
	    return orb | 0xf0;
	else
	    return (orb | 0xf0) /* & ~ddrb */; /* FIXME */

    case 0x1: /* output reg A */
	ifr &= ~IFR_CA1;
	if (pcr & 0x0a != 0x02)
	    ifr &= ~IFR_CA2;
	update_ifr_clr();

	/* NOTE: flow through to case 0xf: */

    case 0xf: /* output reg A, no handshake */

	/*
	 * FIXME:
	 * With DDRA set to 1 (output), should be reading the levels
	 * stored on the output latch? Makes no difference for us - I
	 * hope! Input latching (see the acr) has an effect here.
	 */
	if (acr & 0x01)
	    return ora;
	else
	    return ora /* & ~ddrb */; /* FIXME */

    case 0x2: /* DDRB */
	return ddrb;

    case 0x3: /* DDRA */
	return ddra;

    case 0x4: /* T1 low order counter */
	ifr &= ~IFR_T1;
	update_ifr_clr();

	return query_timer_expire(t1_timer) & 0x00ff;

    case 0x5: /* T1 high order counter */
	/*
	 * FIXME: pages 394 && 401 of AUG conflict on this - check.
	 */
	ifr &= ~IFR_T1;
	update_ifr_clr();

	return (query_timer_expire(t1_timer) & 0xff00) >> 8;

    case 0x6: /* T1 low order latch */
	return query_timer_interval(t1_timer) & 0x00ff;

    case 0x7: /* T1 high order latch */
	return (query_timer_interval(t1_timer) & 0xff00) >> 8;

    case 0x8: /* T2 low order counter/latch */
	ifr &= ~IFR_T2;
	update_ifr_clr();

	/*
	 * FIXME: add t2_timer.
	 *
	 * return query_timer_expire(t2_timer) & 0x00ff;
	 */
	return 0;

    case 0x9: /* T2 high order counter */
	/*
	 * FIXME: add t2_timer.
	 *
	 * return (query_timer_expire(t2_timer) & 0xff00) >> 8;
	 */
	return 0;

    case 0xa: /* Shift reg */
	ifr &= ~IFR_SR;
	update_ifr_clr();

	return sr;

    case 0xb: /* Auxiliary control reg */
	return acr;

    case 0xc: /* Peripheral control reg */
	return pcr;

    case 0xd: /* interrupt flag reg */
	/*
	 * NOTE: we can rely on the top bit being correct.
	 */
	return ifr;

    case 0xe: /* interrupt enable reg */
	return ier | 0x80; /* should be set anyway, but just in case */
    }

    return 0x00;
}

/* --------------------------------- WRITE --------------------------------- */

static byte write_via(word addr, byte val) {
    addr &= 0x0f;
 
#ifdef DEBUG_VIA
    printf("W  %x : %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x : %02x\n",
	   addr,
	   orb, ora, ddrb, ddra,
	   query_timer_expire(t1_timer) & 0x00ff,
	   (query_timer_expire(t1_timer) & 0xff00) >> 8,
	   query_timer_interval(t1_timer) & 0x00ff,
	   (query_timer_interval(t1_timer) & 0xff00) >> 8,
	   0, 0,
	   sr, acr, pcr, ifr, ier, ora,
	   val);
#endif

    switch (addr) {
    case 0: /* output reg B */
	ifr &= ~IFR_CB1;
	if (pcr & 0xa0 != 0x20)
	    ifr &= ~IFR_CB2;
	update_ifr_clr();

	/*
	 * FIXME: We should handle the output latching I guess.
	 * In this case, writing to an orb with the ddrb bit set to input
	 * does not update the output on the pin, but does update the
	 * internal register. Subsequent ddrb changes will then set the
	 * output pin correctly. Also a read with ddrb set to output will
	 * read this internal register bit.
	 * Currently this is all ignored.
	 */
	orb = (orb & ~ddrb) | (val & ddrb);

	pb_latch[orb & 0x07] = orb & 0x08;

	/* perform the relevant hardware status change */
	switch(orb & 0x07) { /* FIXME: finish this! */
	case 0: /* Write enable to sound generator */
	    break;

	case 1: /* Read select on the speach processor */
	    break;

	case 2: /* Write select on the speach processor */
	    break;

	case 3: /* Keyboard write enable */
	    /* keyboard write enable low implies scan keyboard matrix */
	    if (pb_latch[3] == 0)
		do_key_matrix(ddra, &ora, &ifr, ier);
	    break;

	case 4:
	case 5: /* screen size */
	    break;

	case 6: /* caps lock LED */
	    break;

	case 7: /* shift lock LED */
	    break;
	}

	break;

    case 1: /* output reg A */
	ifr &= ~IFR_CA1;
	if (pcr & 0x0a != 0x02)
	    ifr &= ~IFR_CA2;
	update_ifr_clr();

	/* NOTE: flow through to case 15: */

    case 15: /* output reg A, no handshake */
	/* FIXME: See comment for ORB */
	ora = (ora & ~ddra) | (val & ddra);

	/*
	 * We may have something enabled on the PB latch. Indeed this is the
	 * way usage of these are done by the OS - set PB latch first,
	 * then set ORA and check new ORA.
	 *
	 * So we'll recompute our registers once more.
	 */
	switch(orb & 0x07) { /* FIXME: finish this! */
	case 0: /* Write enable to sound generator */
	    break;

	case 1: /* Read select on the speach processor */
	    break;

	case 2: /* Write select on the speach processor */
	    break;

	case 3: /* Keyboard write enable */
	    /* keyboard write enable low implies scan keyboard matrix */
	    if (pb_latch[3] == 0)
		do_key_matrix(ddra, &ora, &ifr);
	    break;

	case 4:
	case 5: /* screen size */
	    /* b5 b4
	     *  0  0  16K
	     *  0  1  24K
	     *  1  0  22K
	     *  1  1  12K
	     */
	    break;

	case 6: /* caps lock LED */
	    break;

	case 7: /* shift lock LED */
	    break;
	}

	break;
    case 2: /* DDRB */
	/* FIXME: See comment for ORB */
	ddrb = val;
	break;

    case 3: /* DDRA */
	/* FIXME: See comment for ORB */
	ddra = val;
	break;

    case 4: /* T1 low order latch */
    case 6:	
	change_timer(t1_timer, TIMER_INTERVAL,
		     (query_timer_interval(t1_timer) & 0xff00) + val,
		     0, NULL);
	break;

    case 5: /* T1 high order counter */
	/*
	 * Updates T1 counter from both low and high T1 latches.
	 * This also writes through to the T1 high latch.
	 * IFR flag T1 is cleared.
	 */
	change_timer(t1_timer, TIMER_VALUE, 0, 
		     0x100 * val + (query_timer_interval(t1_timer) & 0x00ff),
		     NULL);

	ifr &= ~IFR_T1;
	update_ifr_clr();

	/* NOTE: flow through to case 7 - T1 high latch */	

    case 7: /* T1 high order latch */
	change_timer(t1_timer, TIMER_INTERVAL,
		     (query_timer_interval(t1_timer) & 0x00ff) + 0x100 * val,
		     0, NULL);
	break;

    case 8: /* T2 low order latch */
	/*
	 * FIXME: add t2_timer.
	 * change_timer(t2_timer, TIMER_INTERVAL,
	 *	     (query_timer_interval(t2_timer) & 0xff00) + val,
	 *	     0, NULL);
	 */
	break;

    case 9: /* T2 high order latch */
	/*
	 * Updates T2 counter from both low and high T2 latches.
	 * Unlike T1, there is no latch for T2 high, so this is not set.
	 * IFR flag T1 is cleared.
	 */
	change_timer(t1_timer, TIMER_VALUE, 0, 
		     0x100 * val + (query_timer_interval(t1_timer) & 0x00ff),
		     NULL);

	ifr &= ~IFR_T2;
	update_ifr_clr();

	break;

    case 10: /* Shift reg */
	sr = val;
	ifr &= ~IFR_SR;
	update_ifr_clr();
	break;

    case 11: /* Auxiliary control reg */
	acr = val;
	break;

    case 12: /* Peripheral control reg */
	pcr = val;
	break;

    case 13: /* interrupt flag reg */
	ifr &= ~val; /* clear bits written */
	update_ifr();

	break;
    case 14: /* interrupt enable reg */
	if (val & 0x80) /* enable */
	    ier |= (val & 0x7f);
	else
	    ier &= ~(val & 0x7f);
	break;
    }

    return 0;
}

static void init() {
    extern int matrix[16][16];
    /*
       t1_timer = add_timer(1000000/HZ_6502, 1000000/HZ_6502, sig_alarm);
       printf("t1_timer = %d\n", t1_timer);
    */

    /* start up in mode 1 - FIXME: doens't work */
    matrix[9][9] = 1; /* dip switch bit 0 */
    matrix[8][8] = 0; /* dip switch bit 1 */
    matrix[7][7] = 0; /* dip switch bit 2 */
}

static time_t update(time_t inc) {
    static time_t time = 0;

/*    printf("Updating 6522 by %d ticks\n", inc); */
    return time += inc;
}
