/*
 *  CIA.h - 6526 emulation
 *
 *  Frodo (C) 1994-1997,2002-2004 Christian Bauer
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef _CIA_H
#define _CIA_H

#include "Prefs.h"


class MOS6510;
class MOS6502_1541;
class MOS6569;
struct MOS6526State;


class MOS6526 {
public:
	MOS6526(MOS6510 *CPU);

	void Reset(void);
	void GetState(MOS6526State *cs);
	void SetState(MOS6526State *cs);
#ifdef FRODO_SC
	void CheckIRQs(void);
	void EmulateCycle(void);
#else
	void EmulateLine(int cycles);
#endif
	void CountTOD(void);
	virtual void TriggerInterrupt(int bit)=0;

protected:
	MOS6510 *the_cpu;	// Pointer to 6510

	uint8 pra, prb, ddra, ddrb;

	uint16 ta, tb, latcha, latchb;

	uint8 tod_10ths, tod_sec, tod_min, tod_hr;
	uint8 alm_10ths, alm_sec, alm_min, alm_hr;

	uint8 sdr, icr, cra, crb;
	uint8 int_mask;

	int tod_divider;	// TOD frequency divider

	bool tod_halt,		// Flag: TOD halted
		 ta_cnt_phi2,	// Flag: Timer A is counting Phi 2
		 tb_cnt_phi2,	// Flag: Timer B is counting Phi 2
	     tb_cnt_ta;		// Flag: Timer B is counting underflows of Timer A

#ifdef FRODO_SC
	bool ta_irq_next_cycle,		// Flag: Trigger TA IRQ in next cycle
		 tb_irq_next_cycle,		// Flag: Trigger TB IRQ in next cycle
		 has_new_cra,			// Flag: New value for CRA pending
		 has_new_crb;			// Flag: New value for CRB pending
	char ta_state, tb_state;	// Timer A/B states
	uint8 new_cra, new_crb;		// New values for CRA/CRB
#endif
};


class MOS6526_1 : public MOS6526 {
public:
	MOS6526_1(MOS6510 *CPU, MOS6569 *VIC);

	void Reset(void);
	uint8 ReadRegister(uint16 adr);
	void WriteRegister(uint16 adr, uint8 byte);
	virtual void TriggerInterrupt(int bit);

	uint8 KeyMatrix[8];	// C64 keyboard matrix, 1 bit/key (0: key down, 1: key up)
	uint8 RevMatrix[8];	// Reversed keyboard matrix

	uint8 Joystick1;	// Joystick 1 AND value
	uint8 Joystick2;	// Joystick 2 AND value

private:
	void check_lp(void);

	MOS6569 *the_vic;

	uint8 prev_lp;		// Previous state of LP line (bit 4)
};


class MOS6526_2 : public MOS6526{
public:
	MOS6526_2(MOS6510 *CPU, MOS6569 *VIC, MOS6502_1541 *CPU1541);

	void Reset(void);
	uint8 ReadRegister(uint16 adr);
	void WriteRegister(uint16 adr, uint8 byte);
	virtual void TriggerInterrupt(int bit);

	uint8 IECLines;		// State of IEC lines (bit 7 - DATA, bit 6 - CLK, bit 4 - ATN)

private:
	MOS6569 *the_vic;
	MOS6502_1541 *the_cpu_1541;
};


// CIA state
struct MOS6526State {
	uint8 pra;
	uint8 ddra;
	uint8 prb;
	uint8 ddrb;
	uint8 ta_lo;
	uint8 ta_hi;
	uint8 tb_lo;
	uint8 tb_hi;
	uint8 tod_10ths;
	uint8 tod_sec;
	uint8 tod_min;
	uint8 tod_hr;
	uint8 sdr;
	uint8 int_data;		// Pending interrupts
	uint8 cra;
	uint8 crb;
						// Additional registers
	uint16 latcha;		// Timer latches
	uint16 latchb;
	uint8 alm_10ths;	// Alarm time
	uint8 alm_sec;
	uint8 alm_min;
	uint8 alm_hr;
	uint8 int_mask;		// Enabled interrupts
};


/*
 *  Emulate CIA for one cycle/raster line
 */

#ifdef FRODO_SC
inline void MOS6526::CheckIRQs(void)
{
	// Trigger pending interrupts
	if (ta_irq_next_cycle) {
		ta_irq_next_cycle = false;
		TriggerInterrupt(1);
	}
	if (tb_irq_next_cycle) {
		tb_irq_next_cycle = false;
		TriggerInterrupt(2);
	}
}
#else
inline void MOS6526::EmulateLine(int cycles)
{
	unsigned long tmp;

	// Timer A
	if (ta_cnt_phi2) {
		ta = tmp = ta - cycles;		// Decrement timer

		if (tmp > 0xffff) {			// Underflow?
			ta = latcha;			// Reload timer

			if (cra & 8) {			// One-shot?
				cra &= 0xfe;
				ta_cnt_phi2 = false;
			}
			TriggerInterrupt(1);
			if (tb_cnt_ta) {		// Timer B counting underflows of Timer A?
				tb = tmp = tb - 1;	// tmp = --tb doesn't work
				if (tmp > 0xffff) goto tb_underflow;
			}
		}
	}

	// Timer B
	if (tb_cnt_phi2) {
		tb = tmp = tb - cycles;		// Decrement timer

		if (tmp > 0xffff) {			// Underflow?
tb_underflow:
			tb = latchb;

			if (crb & 8) {			// One-shot?
				crb &= 0xfe;
				tb_cnt_phi2 = false;
				tb_cnt_ta = false;
			}
			TriggerInterrupt(2);
		}
	}
}
#endif

#endif
