linapple-pie/src/6821.cpp

532 lines
12 KiB
C++

// Based on MAME's 6821pia.c
// - by Kyle Kim (Apple in PC)
//
// From mame.txt (http://www.mame.net/readme.html)
//
// VI. Reuse of Source Code
// --------------------------
// This chapter might not apply to specific portions of MAME (e.g. CPU
// emulators) which bear different copyright notices.
// The source code cannot be used in a commercial product without the written
// authorization of the authors. Use in non-commercial products is allowed, and
// indeed encouraged. If you use portions of the MAME source code in your
// program, however, you must make the full source code freely available as
// well.
// Usage of the _information_ contained in the source code is free for any use.
// However, given the amount of time and energy it took to collect this
// information, if you find new information we would appreciate if you made it
// freely available as well.
//
#include "wincompat.h"
#include "6821.h"
// Ctrl-A(B) register bit mask define
/*
0 1
bit0 IRQ1_DISABLED IRQ1_ENABLED
bit1 C1_HIGH_TO_LOW C1_LOW_TO_HIGH
bit2 DDR_SELECTED OUTPUT_SELECTED
bit3 RESET_C2 SET_C2 ( C2_OUTPUT & C2_SETMODE )
bit3 STROBE_C1_RESET STROBE_E_RESET ( C2_OUTPUT & C2_STROBE_MODE )
bit4 C2_STROBE_MODE C2_SETMODE ( C2_OUTPUT )
bit3 IRQ2_DISABLED IRQ2_ENABLED ( C2_INPUT )
bit4 C2_HIGH_TO_LOW C2_HIGH_TO_LOW ( C2_INPUT )
bit5 C2_INPUT C2_OUTPUT
*/
#define PIA_IRQ1 0x80
#define PIA_IRQ2 0x40
#define SET_IRQ1(c) c |= PIA_IRQ1;
#define SET_IRQ2(c) c |= PIA_IRQ2;
#define CLEAR_IRQ1(c) c &= ~PIA_IRQ1;
#define CLEAR_IRQ2(c) c &= ~PIA_IRQ2;
#define IRQ1(c) ( c & PIA_IRQ1 )
#define IRQ2(c) ( c & PIA_IRQ2 )
#define IRQ1_ENABLED(c) ( c & 0x01 )
#define IRQ1_DISABLED(c) !( c & 0x01 )
#define C1_LOW_TO_HIGH(c) ( c & 0x02 )
#define C1_HIGH_TO_LOW(c) !( c & 0x02 )
#define OUTPUT_SELECTED(c) ( c & 0x04 )
#define DDR_SELECTED(c) !( c & 0x04 )
#define IRQ2_ENABLED(c) ( c & 0x08 )
#define IRQ2_DISABLED(c) !( c & 0x08 )
#define STROBE_E_RESET(c) ( c & 0x08 )
#define STROBE_C1_RESET(c) !( c & 0x08 )
#define SET_C2(c) ( c & 0x08 )
#define RESET_C2(c) !( c & 0x08 )
#define C2_LOW_TO_HIGH(c) ( c & 0x10 )
#define C2_HIGH_TO_LOW(c) !( c & 0x10 )
#define C2_SET_MODE(c) ( c & 0x10 )
#define C2_STROBE_MODE(c) !( c & 0x10 )
#define C2_OUTPUT(c) ( c & 0x20 )
#define C2_INPUT(c) !( c & 0x20 )
#define PIA_W_CALLBACK(st, val) \
if ( st.func ) st.func( this, st.objTo, 0, val )
//////////////////////////////////////////////////////////////////////
C6821::C6821()
{
Reset();
m_stOutA.objTo = NULL;
m_stOutA.func = NULL;
m_stOutB.objTo = NULL;
m_stOutB.func = NULL;
m_stOutCA2.objTo = NULL;
m_stOutCA2.func = NULL;
m_stOutCB2.objTo = NULL;
m_stOutCB2.func = NULL;
m_stOutIRQA.objTo = NULL;
m_stOutIRQA.func = NULL;
m_stOutIRQB.objTo = NULL;
m_stOutIRQB.func = NULL;
}
C6821::~C6821()
{
}
void C6821::SetListenerA(void *objTo, mem_write_handler func)
{
m_stOutA.objTo = objTo;
m_stOutA.func = func;
}
void C6821::SetListenerB(void *objTo, mem_write_handler func)
{
m_stOutB.objTo = objTo;
m_stOutB.func = func;
}
void C6821::SetListenerCA2(void *objTo, mem_write_handler func)
{
m_stOutCA2.objTo = objTo;
m_stOutCA2.func = func;
}
void C6821::SetListenerCB2(void *objTo, mem_write_handler func)
{
m_stOutCB2.objTo = objTo;
m_stOutCB2.func = func;
}
BYTE C6821::Read(BYTE byRS)
{
BYTE retval = 0;
byRS &= 3;
switch ( byRS )
{
/******************* port A output/DDR read *******************/
case PIA_DDRA:
// read output register
if ( OUTPUT_SELECTED(m_byCTLA) )
{
// combine input and output values
retval = ( m_byOA & m_byDDRA ) | ( m_byIA & ~m_byDDRA );
// IRQ flags implicitly cleared by a read
CLEAR_IRQ1( m_byCTLA );
CLEAR_IRQ1( m_byCTLB );
UpdateInterrupts();
// CA2 is configured as output and in read strobe mode
if ( C2_OUTPUT(m_byCTLA) && C2_STROBE_MODE(m_byCTLA) )
{
// this will cause a transition low; call the output function if we're currently high
if ( m_byOCA2 )
PIA_W_CALLBACK( m_stOutCA2, 0 );
m_byOCA2 = 0;
// if the CA2 strobe is cleared by the E, reset it right away
if ( STROBE_E_RESET( m_byCTLA ) )
{
PIA_W_CALLBACK( m_stOutCA2, 1 );
m_byOCA2 = 1;
}
}
}
// read DDR register
else
{
retval = m_byDDRA;
}
break;
/******************* port B output/DDR read *******************/
case PIA_DDRB:
// read output register
if ( OUTPUT_SELECTED( m_byCTLB ) )
{
// combine input and output values
retval = ( m_byOB & m_byDDRB ) + ( m_byIB & ~m_byDDRB );
// IRQ flags implicitly cleared by a read
CLEAR_IRQ2( m_byCTLA );
CLEAR_IRQ2( m_byCTLB );
UpdateInterrupts();
}
/* read DDR register */
else
{
retval = m_byDDRB;
}
break;
/******************* port A control read *******************/
case PIA_CTLA:
// read control register
retval = m_byCTLA;
// when CA2 is an output, IRQA2 = 0, and is not affected by CA2 transitions.
if ( C2_OUTPUT( m_byCTLA ) )
retval &= ~PIA_IRQ2;
break;
/******************* port B control read *******************/
case PIA_CTLB:
retval = m_byCTLB;
// when CB2 is an output, IRQB2 = 0, and is not affected by CB2 transitions.
if ( C2_OUTPUT( m_byCTLB ) )
retval &= ~PIA_IRQ2;
break;
}
return retval;
}
void C6821::Write(BYTE byRS, BYTE byData)
{
byRS &= 3;
switch( byRS )
{
/******************* port A output/DDR write *******************/
case PIA_DDRA:
// write output register
if ( OUTPUT_SELECTED( m_byCTLA ) )
{
// update the output value
m_byOA = byData;
// send it to the output function
if ( m_byDDRA )
PIA_W_CALLBACK( m_stOutA, m_byOA & m_byDDRA );
}
// write DDR register
else
{
if ( m_byDDRA != byData )
{
m_byDDRA = byData;
// send it to the output function
if ( m_byDDRA )
PIA_W_CALLBACK( m_stOutA, m_byOA & m_byDDRA );
}
}
break;
/******************* port B output/DDR write *******************/
case PIA_DDRB:
// write output register
if ( OUTPUT_SELECTED( m_byCTLB ) )
{
// update the output value
m_byOB = byData;
// send it to the output function
if ( m_byDDRB )
PIA_W_CALLBACK( m_stOutB, m_byOB & m_byDDRB );
// CB2 is configured as output and in write strobe mode
if ( C2_OUTPUT( m_byCTLB ) && C2_STROBE_MODE( m_byCTLB ) )
{
// this will cause a transition low; call the output function if we're currently high
if ( m_byOCB2 )
PIA_W_CALLBACK( m_stOutCB2, 0 );
m_byOCB2 = 0;
// if the CB2 strobe is cleared by the E, reset it right away
if ( STROBE_E_RESET( m_byCTLB ) )
{
PIA_W_CALLBACK( m_stOutCB2, 1 );
m_byOCB2 = 1;
}
}
}
// write DDR register
else
{
if ( m_byDDRB != byData )
{
m_byDDRB = byData;
// send it to the output function
if ( m_byDDRB )
PIA_W_CALLBACK( m_stOutB, m_byOB & m_byDDRB );
}
}
break;
/******************* port A control write *******************/
case PIA_CTLA:
// Bit 7 and 6 read only
byData &= 0x3f;
// CA2 is configured as output and in set/reset mode
if ( C2_OUTPUT( byData ) )
{
// determine the new value
int temp = SET_C2( byData ) ? 1 : 0;
// if this creates a transition, call the CA2 output function
if ( m_byOCA2 ^ temp)
PIA_W_CALLBACK( m_stOutCA2, temp );
// set the new value
m_byOCA2 = temp;
}
// update the control register
m_byCTLA = ( m_byCTLA & ~0x3F ) | byData;
// update externals
UpdateInterrupts();
break;
/******************* port B control write *******************/
case PIA_CTLB:
/* Bit 7 and 6 read only - PD 16/01/00 */
byData &= 0x3f;
// CB2 is configured as output and in set/reset mode
if ( C2_OUTPUT( byData ) )
{
// determine the new value
int temp = SET_C2( byData ) ? 1 : 0;
// if this creates a transition, call the CA2 output function
if ( m_byOCB2 ^ temp)
PIA_W_CALLBACK( m_stOutCB2, temp );
// set the new value
m_byOCB2 = temp;
}
// update the control register
m_byCTLB = ( m_byCTLB & ~0x3F ) | byData;
// update externals
UpdateInterrupts();
break;
}
}
void C6821::Reset()
{
m_byIA = 0;
m_byCA1 = 0;
m_byICA2 = 0;
m_byOA = 0;
m_byOCA2 = 0;
m_byDDRA = 0;
m_byCTLA = 0;
m_byIRQAState = 0;
m_byIB = 0;
m_byCB1 = 0;
m_byICB2 = 0;
m_byOB = 0;
m_byOCB2 = 0;
m_byDDRB = 0;
m_byCTLB = 0;
m_byIRQBState = 0;
}
void C6821::UpdateInterrupts()
{
BYTE byNewState;
// start with IRQ A
byNewState = 0;
if ( ( IRQ1( m_byCTLA ) && IRQ1_ENABLED( m_byCTLA ) ) ||
( IRQ2( m_byCTLA ) && IRQ2_ENABLED( m_byCTLA ) ) )
byNewState = 1;
if ( byNewState != m_byIRQAState )
{
m_byIRQAState = byNewState;
PIA_W_CALLBACK( m_stOutIRQA, m_byIRQAState );
}
/* then do IRQ B */
byNewState = 0;
if ( ( IRQ1( m_byCTLB ) && IRQ1_ENABLED( m_byCTLB ) ) ||
( IRQ2( m_byCTLB ) && IRQ2_ENABLED( m_byCTLB ) ) )
byNewState = 1;
if ( byNewState != m_byIRQBState )
{
m_byIRQBState = byNewState;
PIA_W_CALLBACK( m_stOutIRQB, m_byIRQBState );
}
}
void C6821::SetCA1(BYTE byData)
{
byData = byData ? 1 : 0;
// the new state has caused a transition
if ( m_byCA1 ^ byData )
{
// handle the active transition
if ( ( byData && C1_LOW_TO_HIGH( m_byCTLA ) ) ||
( !byData && C1_HIGH_TO_LOW( m_byCTLA ) ) )
{
// mark the IRQ
SET_IRQ1(m_byCTLA);
// update externals
UpdateInterrupts();
// CA2 is configured as output and in read strobe mode and cleared by a CA1 transition
if ( C2_OUTPUT( m_byCTLA ) && C2_STROBE_MODE( m_byCTLA ) && STROBE_C1_RESET( m_byCTLA ) )
{
// call the CA2 output function
if ( !m_byOCA2 )
PIA_W_CALLBACK( m_stOutCA2, 1 );
// clear CA2
m_byOCA2 = 1;
}
}
}
// set the new value for CA1
m_byCA1 = byData;
}
void C6821::SetCA2(BYTE byData)
{
byData = byData ? 1 : 0;
// CA2 is in input mode
if ( C2_INPUT( m_byCTLA ) )
{
// the new state has caused a transition
if ( m_byICA2 ^ byData )
{
// handle the active transition
if ( ( byData && C2_LOW_TO_HIGH( m_byCTLA ) ) ||
( !byData && C2_HIGH_TO_LOW( m_byCTLA ) ) )
{
// mark the IRQ
SET_IRQ2( m_byCTLA );
// update externals
UpdateInterrupts();
}
}
}
// set the new value for CA2
m_byICA2 = byData;
}
void C6821::SetCB1(BYTE byData)
{
byData = byData ? 1 : 0;
// the new state has caused a transition
if ( m_byCB1 ^ byData )
{
// handle the active transition
if ( ( byData && C1_LOW_TO_HIGH( m_byCTLB ) ) ||
( !byData && C1_HIGH_TO_LOW( m_byCTLB ) ) )
{
// mark the IRQ
SET_IRQ1( m_byCTLB );
// update externals
UpdateInterrupts();
// CB2 is configured as output and in read strobe mode and cleared by a CA1 transition
if ( C2_OUTPUT( m_byCTLB ) && C2_STROBE_MODE( m_byCTLB ) && STROBE_C1_RESET( m_byCTLB ) )
{
// the IRQ1 flag must have also been cleared
if ( !IRQ1( m_byCTLB ) )
{
// call the CB2 output function
if ( !m_byOCB2 )
PIA_W_CALLBACK( m_stOutCB2, 1 );
// clear CB2
m_byOCB2 = 1;
}
}
}
}
// set the new value for CA1
m_byCB1 = byData;
}
void C6821::SetCB2(BYTE byData)
{
byData = byData ? 1 : 0;
// CA2 is in input mode
if ( C2_INPUT( m_byCTLB ) )
{
// the new state has caused a transition
if ( m_byICB2 ^ byData )
{
// handle the active transition
if ( ( byData && C2_LOW_TO_HIGH( m_byCTLB ) ) ||
( !byData && C2_HIGH_TO_LOW( m_byCTLB ) ) )
{
// mark the IRQ
SET_IRQ2( m_byCTLB );
// update externals
UpdateInterrupts();
}
}
}
// set the new value for CA2
m_byICB2 = byData;
}
void C6821::SetPA(BYTE byData)
{
m_byIA = byData;
}
void C6821::SetPB(BYTE byData)
{
m_byIB = byData;
}
BYTE C6821::GetPA()
{
return m_byOA & m_byDDRA;
}
BYTE C6821::GetPB()
{
return m_byOB & m_byDDRB;
}