/*

ctc.c - Z80 CTC

See http://www.z80.info/zip/z80ctc.pdf

The most recent changes here are to respect interrupt priority.
And in addition, to clear interrupts not just at RETI.

*/

/*...sincludes:0:*/
#include "types.h"
#include "diag.h"
#include "common.h"
#include "ctc.h"

/*...vtypes\46\h:0:*/
/*...vdiag\46\h:0:*/
/*...vcommon\46\h:0:*/
/*...vctc\46\h:0:*/
/*...e*/

/*...svars:0:*/
#define	CC_INTERRUPT     0x80	/* Interrupts enabled */
#define	CC_COUNTER_MODE  0x40	/* Counter (not timer) mode */
#define	CC_PRESCALER_256 0x20	/* Timer prescale factor is 256, not 16 */
#define	CC_RISING_EDGE   0x10	/* Rising (not falling edge) */
#define	CC_TIMER_TRIGGER 0x08	/* Timer trigger */
#define	CC_CONSTANT      0x04	/* Load of constant is pending */
#define	CC_RESET         0x02	/* Channel has been reset */
#define	CC_CONTROL       0x01	/* Write control register (not int vector) */

typedef struct
	{
	byte control;
	byte prescaler;
	byte constant;
	byte counter;
	BOOLEAN pending;
	BOOLEAN active; /* Z80 acknowledged, but no RETI yet */
	} CHANNEL;

#define	N_CHANNELS 4

static CHANNEL ctc_channels[N_CHANNELS];
static byte ctc_int_vector; /* bits 7-3 inclusive */
/*...e*/

/*...sctc_init:0:*/
void ctc_init(void)
	{
	int i;
	ctc_int_vector = 0x00;
	for ( i = 0; i < N_CHANNELS; i++ )
		{
		CHANNEL *c = &(ctc_channels[i]);
		c->control   = (CC_RESET|CC_CONTROL);
		c->prescaler = 0;
		c->constant  = 0; /* treated as 0x100 */
		c->counter   = 0;
		c->pending   = FALSE;
		c->active    = FALSE;
		}
	}
/*...e*/
/*...sctc_in:0:*/
byte ctc_in(int channel)
	{
	byte value = ctc_channels[channel].counter;
	diag_message(DIAG_CTC_REGISTERS, "CTC counter register for channel %d returned as 0x%02x", channel, value);
	return value;
	}
/*...e*/
/*...sctc_out:0:*/
void ctc_out(int channel, byte value)
	{
	CHANNEL *c = &(ctc_channels[channel]);
	if ( c->control & CC_CONSTANT )
		{
		c->control &= ~(CC_CONSTANT|CC_RESET);
		c->constant = value;
			/* 0 is treated as 0x100 */
		c->counter = value;
			/* Note that the Z80 CTC documentation says
			   "This constant is is automatically loaded into the
			    down-counter when the counter/time channel is
			    initialized, and subsequently after each zero count",
			   and on the other hand it appears to contradict with
			   "if updated control and time constant words are
			    written during the count operation, the count
			    continues to zero before the time constant is loaded
			    into the counter",
			   I find that the former gives results matching
			   what I see on the real hardware.
			   Without the fix, some games have to count down 256
			   video frames before the Z80 sees the first
			   interrupt, resulting in a 5s startup delay.
			   For many years, this code used to not set the
			   counter, and the only reason the bug wasn't spotted
			   was another bug in the way video interrupts were
			   handled in memu.c which masked the symptom. */
		diag_message(DIAG_CTC_REGISTERS, "CTC constant for channel %d set to 0x%02x", channel, value);
		}
	else if ( value & CC_CONTROL )
		{
		c->control = value;
		diag_message(DIAG_CTC_REGISTERS, "CTC control register for channel %d set to 0x%02x", channel, value);
		if ( value & CC_RESET )
			/* A bit unusual to see this,
			   but there is code out there that doesn't
			   always end interrupt routines with RETI */
			{
			c->pending = FALSE;
			c->active  = FALSE;
			diag_message(DIAG_CTC_REGISTERS, "CTC interrupt cleared by reset on channel %d", channel);
			}
		}
	else
		{
		if ( channel == 0 )
			{
			ctc_int_vector = (value & 0xf8);
			diag_message(DIAG_CTC_REGISTERS, "CTC interrupt vector set to 0x%02x", value);
			if ( c->active )
				/* I have seen code in F1 Simulator
				   that relies of this happening */
				{
				c->pending = FALSE;
				c->active  = FALSE;
				diag_message(DIAG_CTC_REGISTERS, "CTC interrupt cleared by writing interrupt vector");
				}
			}
		else
			diag_message(DIAG_CTC_REGISTERS, "CTC unexpected write of 0x%02x to channel %d !!!", value, channel);
		}
	}
/*...e*/
/*...sctc_reload:0:*/
void ctc_reload(int channel)
	{
	CHANNEL *c = &(ctc_channels[channel]);
	c->counter = c->constant;
	c->prescaler = ( c->control & CC_PRESCALER_256 ) ? 0 : 16; /* Reload prescaler */
	}
/*...e*/
/*...sctc_trigger:0:*/
void ctc_trigger(int channel)
	{
	CHANNEL *c = &(ctc_channels[channel]);
	if ( c->control & CC_COUNTER_MODE )
		if ( (c->control & (CC_CONSTANT|CC_RESET)) == 0 )
			if ( --(c->counter) == 0 )
				{
				c->counter = c->constant; /* Reload */
				if ( c->control & CC_INTERRUPT )
					{
					c->pending = TRUE;
					diag_message(DIAG_CTC_PENDING, "CTC interrupt pending on channel %d (counter)", channel);
					}
				}
	}
/*...e*/
/*...sctc_advance:0:*/
/* Important: This advances in terms of the system clock, as fed to
   the whole of the CTC, not in terms of the input to the channel. */

void ctc_advance(int channel, int clks)
	{
	CHANNEL *c = &(ctc_channels[channel]);
	if ( (c->control & CC_COUNTER_MODE) == 0 )
		if ( (c->control & (CC_CONSTANT|CC_RESET)) == 0 )
			while ( clks > 0 )
				{
				int prescaler = c->prescaler == 0 ? 0x100 : c->prescaler;
				if ( clks < prescaler )
					{
					c->prescaler -= (byte) clks;
					break;
					}
				else
					{
					clks -= prescaler;
					c->prescaler = ( c->control & CC_PRESCALER_256 ) ? 0 : 16; /* Reload prescaler */
					if ( --(c->counter) == 0 )
						{
						c->counter = c->constant; /* Reload */
						if ( c->control & CC_INTERRUPT )
							{
							c->pending = TRUE;
							diag_message(DIAG_CTC_PENDING, "CTC interrupt pending on channel %d (timer)", channel);
							}
						}
					}
				}
	}
/*...e*/
/*...sctc_int_pending:0:*/
int ctc_int_pending(void)
	{
	int i;
	for ( i = 0; i < N_CHANNELS; i++ )
		{
		CHANNEL *c = &(ctc_channels[i]);
		if ( c->active )
			return -1; /* Interrupt already happening, so don't look at any lower priority ones */
		if ( c->pending )
			{
			diag_message(DIAG_CTC_INTERRUPT, "CTC interrupt noticed on channel %d", i);
			return ctc_int_vector|(i<<1);
			}
		}
	return -1;
	}
/*...e*/
/*...sctc_get_int_vector:0:*/
/* This is provided to enable to PANEL hack to work. */
byte ctc_get_int_vector(void)
	{
	return ctc_int_vector;
	}
/*...e*/
/*...sctc_int_ack:0:*/
BOOLEAN ctc_int_ack(void)
	{
	int i;
	for ( i = 0; i < N_CHANNELS; i++ )
		{
		CHANNEL *c = &(ctc_channels[i]);
		if ( c->pending )
			{
			c->active = TRUE;
			diag_message(DIAG_CTC_INTERRUPT, "CTC interrupt acknowledged by CPU on channel %d", i);
			return TRUE;
			}
		}
	return FALSE;
	}
/*...e*/
/*...sctc_reti:0:*/
BOOLEAN ctc_reti(void)
	{
	int i;
	for ( i = 0; i < N_CHANNELS; i++ )
		{
		CHANNEL *c = &(ctc_channels[i]);
		if ( c->active )
			{
			c->pending = FALSE;
			c->active  = FALSE;
			diag_message(DIAG_CTC_INTERRUPT, "CTC interrupt cleared by RETI on channel %d", i);
			return TRUE;
			}
		}
	return FALSE;
	}
/*...e*/
