// 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; }