File:  [HATARI the Atari ST Emulator] / hatari / src / acia.c
Revision 1.1.1.5 (vendor branch): download - view: text, annotated - select for diffs
Tue Apr 9 08:57:48 2019 UTC (7 years, 1 month ago) by root
Branches: hatari, MAIN
CVS tags: hatari02210, hatari02200, hatari02100, HEAD
hatari 2.1.0

/*
  Hatari - acia.c

  Copyright (C) 2012 by Nicolas Pomarède

  This file is distributed under the GNU General Public License, version 2
  or at your option any later version. Read the file gpl.txt for details.

  MC6850 ACIA emulation.
*/

const char ACIA_fileid[] = "Hatari acia.c : " __DATE__ " " __TIME__;


/* 2012/09/28	[NP]	Start of the full rewrite of the MC6850 ACIA emulation, using the official	*/
/*			datasheets for maximum accuracy, as well as bit level serial transfers (start,	*/
/*			stop and parity bits).								*/
/* 2012/12/21	[NP]	Add accurate cycles delays when accessing an ACIA register, taking E Clock	*/
/*			into account.									*/
/* 2013/04/24	[NP]	Remove INTERRUPT_ACIA_MFP used to add a 4 cycle delay when IRQ is set, as this	*/
/*			delay is now correctly handled directly in the MFP since 2013/03/01.		*/




/*
  6850 ACIA (Asynchronous Communications Inferface Apdater)

  References :
   - MC6850 datasheet by Motorola (DS9493R4, 1985)
   - A6850 datasheet by Altera (A-DS-A6850-01, 1996) (nearly identical component)

  Others references :
   - MAME's 6850acia.c for RTS, CTS and DCD behaviour


  Pins :
    Vss
    RX DATA Receive Data
    RX CLK Receive Clock
    TX CLK Transmitter Clock
    RTS Request To Send
    TX DATA Transmitter Data
    IRQ Interrupt Request
    CS 0,1,2 Chip Select
    RS Register Select
    Vcc Voltage
    R/W Read/Write
    E Enable
    D0-D7 Data
    DCD Data Carrier Detect
    CTS Clear To Send

  Registers :
    0xfffc00 Keyboard ACIA Control (write)/Status(read)
    0xfffc02 Keyboard ACIA Data
    0xfffc04 MIDI ACIA Control (write)/Status(read)
    0xfffc06 MIDI ACIA Data

  Control Register (0xfffc00 write) :
    Bits 0,1 - These bits determine by which factor the transmitter and receiver
      clock will be divided. These bits also are joined with a master reset
      function. The 6850 has no separate reset line, so it must be
      accomplished though software.
        0 0    RXCLK/TXCLK without division
        0 1    RXCLK/TXCLK by 16 (MIDI)
        1 0    RXCLK/TXCLK by 64 (Keyboard)
        1 1    Master RESET
    Bits 2,3,4 - These so-called Word Select bits tell whether 7 or 8 data-bits are
      involved; whether 1 or 2 stop-bits are transferred; and the type of parity
    Bits 5,6 - These Transmitter Control bits set the RTS output pin, and allow or prevent
      an interrupt through the ACIA when the send register is emptied. Also, BREAK signals
      can be sent over the serial output by this line. A BREAK signal is nothing more than
      a long seqence of null bits
        0 0    RTS low, transmitter IRQ disabled
        0 1    RTS low, transmitter IRQ enabled
        1 0    RTS high, transmitter IRQ disabled
        1 1    RTS low, transmitter IRQ disabled, BREAK sent
    Bit 7 - The Receiver Interrupt Enable bit determines whether the receiver interrupt
      will be on. An interrupt can be caused by the DCD line chaning from low to high, or
      by the receiver data buffer filling. Besides that, an interrupt can occur from an
      OVERRUN (a received character isn't properly read from the processor).
        0 Interrupt disabled
        1 Interrupt enabled

  Status Register (0xfffc00 read) :
    Bit 0 - When this bit is high, the RX data register is full. The byte must be read
      before a new character is received (otherwise an OVERRUN happens)
    Bit 1 - This bit reflects the status of the TX data buffer. An empty register
      set the bit.
    Bit 2 - A low-high change in pin DCD sets bit 2. If the receiver interrupt is allowable, the IRQ
      is cancelled. The bit is cleared when the status register and the receiver register are
      read. This also cancels the IRQ. Bit 2 register remains highis the signal on the DCD pin
      is still high; Bit 2 register low if DCD becomes low.
    Bit 3 - This line shows the status of CTS. This signal cannot be altered by a mater reset,
      or by ACIA programming.
    Bit 4 - Shows 'Frame Errors'. Frame errors are when no stop-bit is recognized in receiver
      switching. It can be set with every new character.
    Bit 5 - This bit display the previously mentioned OVERRUN condition. Bit 5 is reset when the
      RX buffer is read.
    Bit 6 - This bit recognizes whether the parity of a received character is correct. The bit is
      set on an error.
    Bit 7 - This signals the state of the IRQ pins; this bit make it possible to switch several
      IRQ lines on one interrupt input. In cases where an interrupt is program-generated, bit 7
      can tell which IC cut off the interrupt.

  ST ACIA :
    CTS,DCD and RTS are not connected
    The keyboard ACIA addresses are 0xfffc000 and 0xfffc02.
    The MIDI ACIA addresses are 0xfffc004 and 0xfffc06.
    Default keyboard parameters are : 8-bit word, 1 stopbit, no parity, 7812.5 baud; 500KHz/64 (keyboard clock div)
    Default MIDI parameters are as above but : 31250 baud; 500KHz/16 (MIDI clock div)


  CPU cycles in the ST :
    When accessing an ACIA register, an additional delay will be added to the usual number of
    cycles for this CPU instruction. This delay is made of 2 parts (for a 68000 at 8 MHz) :
	- a fixed delay of 6 cycles.
	- a variable delay of 0 to 8 cycles to synchronise with the E Clock.

    Examples for some common instructions measured on a real 520 STF
    (with a0=$fffffc00 and 'n' the delay for E Clock) :
	move.b  (a0),d2 : 14 cycles = 8 + 6 + n
	move.w  (a0),d2 : 14 cycles = 8 + 6 + n
	move.l  (a0),d2 : 24 cycles = 12 + 6 + 6 + n
	movep.w (a0),d2 : 28 cycles = 16 + 6 + 6 + n
	movep.l (a0),d2 : 48 cycles = 24 + 6 + 6 + 6 + 6 + n
    (on ST, those values might be rounded to the next multiple of 4 cycles)

    When the ACIA's IRQ signal goes low, the resulting bit in the MFP is visible to the CPU only 4 cycles later.
    From the hardware point of view, the ACIA's irq signal is immediately propagated to the MFP,
    but the MFP will then add a 4 cycle delay before generating a 68000 interrupt.

*/

/*-----------------------------------------------------------------------*/


#include "main.h"
#include "log.h"
#include "memorySnapShot.h"
#include "configuration.h"
#include "acia.h"
#include "m68000.h"
#include "cycInt.h"
#include "ioMem.h"
#include "clocks_timings.h"
#include "mfp.h"
#include "screen.h"
#include "video.h"


#define	ACIA_SR_BIT_RDRF			0x01		/* Receive Data Register Full */
#define	ACIA_SR_BIT_TDRE			0x02		/* Transmit Data Register Empty */
#define	ACIA_SR_BIT_DCD				0x04		/* Data Carrier Detect */
#define	ACIA_SR_BIT_CTS				0x08		/* Clear To Send */
#define	ACIA_SR_BIT_FE				0x10		/* Framing Error */
#define	ACIA_SR_BIT_OVRN			0x20		/* Receiver Overrun */
#define	ACIA_SR_BIT_PE				0x40		/* Parity Error */
#define	ACIA_SR_BIT_IRQ				0x80		/* IRQ */

#define	ACIA_CR_COUNTER_DIVIDE( CR )		( CR & 0x03 )		/* CR1 + CR0 : 0x03 causes a master reset */
#define	ACIA_CR_WORD_SELECT( CR )		( ( CR >> 2 ) & 0x07 )	/* CR4 + CR3 + CR2 : size, parity, stop bits */
#define	ACIA_CR_TRANSMITTER_CONTROL( CR )	( ( CR >> 5 ) & 0x03 )	/* CR6 + CR5 : RTS + IRQ on send */
#define	ACIA_CR_RECEIVE_INTERRUPT_ENABLE( CR )	( ( CR >> 7 ) & 0x01 )	/* CR7 : Receive interrupt enable */


static const int ACIA_Counter_Divide[3] = { 1 , 16 , 64 };	/* Used to divide txclock/rxclock to get the correct baud rate */


/* Data size, parity and stop bits used for the transfer depending on CR_WORD_SELECT */
enum
{
	ACIA_PARITY_NONE ,
	ACIA_PARITY_EVEN ,
	ACIA_PARITY_ODD
};


static struct {
	int	DataBits;						/* 7 or 8 */
	int	Parity;							/* EVEN or ODD or NONE */
	int	StopBits;						/* 1 or 2 */
} ACIA_Serial_Params [ 8 ] = {
  { 7 , ACIA_PARITY_EVEN , 2 },
  { 7 , ACIA_PARITY_ODD ,  2 },
  { 7 , ACIA_PARITY_EVEN , 1 },
  { 7 , ACIA_PARITY_ODD ,  1 },
  { 8 , ACIA_PARITY_NONE , 2 },
  { 8 , ACIA_PARITY_NONE , 1 },
  { 8 , ACIA_PARITY_EVEN , 1 },
  { 8 , ACIA_PARITY_ODD ,  1 }
};



/* Possible states when handling TX/RX interrupts */
enum
{
	ACIA_STATE_IDLE = 0,
	ACIA_STATE_DATA_BIT,
	ACIA_STATE_PARITY_BIT,
	ACIA_STATE_STOP_BIT
};


ACIA_STRUCT		ACIA_Array[ ACIA_MAX_NB ];
ACIA_STRUCT		*pACIA_IKBD;
ACIA_STRUCT		*pACIA_MIDI;




/*--------------------------------------------------------------*/
/* Local functions prototypes					*/
/*--------------------------------------------------------------*/

static void		ACIA_Init_Pointers ( ACIA_STRUCT *pAllACIA );

static void		ACIA_Set_Line_IRQ_MFP ( int bit );
static Uint8 		ACIA_Get_Line_CTS_Dummy ( void );
static Uint8 		ACIA_Get_Line_DCD_Dummy ( void );
static void		ACIA_Set_Line_RTS_Dummy ( int bit );

static void		ACIA_Set_Timers_IKBD ( void *pACIA );
static void		ACIA_Start_InterruptHandler_IKBD ( ACIA_STRUCT *pACIA , int InternalCycleOffset );

static Uint8		ACIA_MasterReset ( ACIA_STRUCT *pACIA , Uint8 CR );

static void		ACIA_UpdateIRQ ( ACIA_STRUCT *pACIA );

static Uint8		ACIA_Read_SR ( ACIA_STRUCT *pACIA );
static void		ACIA_Write_CR ( ACIA_STRUCT *pACIA , Uint8 CR );
static Uint8		ACIA_Read_RDR ( ACIA_STRUCT *pACIA );
static void		ACIA_Write_TDR ( ACIA_STRUCT *pACIA , Uint8 TDR );

static void		ACIA_Prepare_TX ( ACIA_STRUCT *pACIA );
static void		ACIA_Prepare_RX ( ACIA_STRUCT *pACIA );
static void		ACIA_Clock_TX ( ACIA_STRUCT *pACIA );
static void		ACIA_Clock_RX ( ACIA_STRUCT *pACIA );





/*-----------------------------------------------------------------------*/
/**
 * Init the 2 ACIAs in an Atari ST.
 * Both ACIAs have a 500 MHZ TX/RX clock.
 * This is called only once, when the emulator starts.
 * NOTE : when testing EmuTos on real hardware, it seems the tx/rx is working
 * after a cold reset (ST switched on), even if Clock_Divider was not initialized yet.
 * The default behaviour is not described in the ACIA's ref doc, but bits
 * seem to be transmitted (maybe with errors ?). So we default
 * to 9600 bauds to avoid a lock if a program uses tx/rx after a reset.
 */
void	ACIA_Init ( ACIA_STRUCT *pAllACIA , Uint32 TX_Clock , Uint32 RX_Clock )
{
	int	i;


	LOG_TRACE ( TRACE_ACIA, "acia init tx_clock=%d rx_clock=%d\n" , TX_Clock , RX_Clock );

	for ( i=0 ; i<ACIA_MAX_NB ; i++ )
	{
		memset ( (void *)&(pAllACIA[ i ]) , 0 , sizeof ( ACIA_STRUCT) );

		pAllACIA[ i ].TX_Clock = TX_Clock;
		pAllACIA[ i ].RX_Clock = RX_Clock;
		pAllACIA[ i ].Clock_Divider = 0;		/* Divider not initialized yet */
		pAllACIA[ i ].FirstMasterReset = 1;
	}

	/* Set the default common callback functions + other pointers */
	ACIA_Init_Pointers ( pAllACIA );
}



/*-----------------------------------------------------------------------*/
/**
 * Init some functions/memory pointers for each ACIA.
 * This is called at Init and when restoring a memory snapshot.
 */
static void	ACIA_Init_Pointers ( ACIA_STRUCT *pAllACIA )
{
	int	i;


	for ( i=0 ; i<ACIA_MAX_NB ; i++ )
	{
		/* Set the default common callback functions */
		pAllACIA[ i ].Set_Line_IRQ = ACIA_Set_Line_IRQ_MFP;
		pAllACIA[ i ].Get_Line_CTS = ACIA_Get_Line_CTS_Dummy;
		pAllACIA[ i ].Get_Line_DCD = ACIA_Get_Line_DCD_Dummy;
		pAllACIA[ i ].Set_Line_RTS = ACIA_Set_Line_RTS_Dummy;
	}

	strcpy ( pAllACIA[ 0 ].ACIA_Name , "ikbd" );
	strcpy ( pAllACIA[ 1 ].ACIA_Name , "midi" );

	pACIA_IKBD = &(pAllACIA[ 0 ]);
	pACIA_MIDI = &(pAllACIA[ 1 ]);

	pACIA_IKBD->Set_Timers = ACIA_Set_Timers_IKBD;
//	pACIA_MIDI->Set_Timers = ACIA_Set_Timers_MIDI;			/* Not used for now */
}




/*-----------------------------------------------------------------------*/
/**
 * There's no real hardware reset on the ACIA, but as the Reset_ST()
 * functions turns off all internal interrupts, we must restart the ACIA's
 * interrupt after a reset.
 */
void	ACIA_Reset ( ACIA_STRUCT *pAllACIA )
{
	int	i;


	LOG_TRACE ( TRACE_ACIA, "acia reset\n" );

	for ( i=0 ; i<ACIA_MAX_NB ; i++ )
	{
		if ( pAllACIA[ i ].Clock_Divider > 0 )				/* Divider already initialized */
			pAllACIA[ i ].Set_Timers ( &(pAllACIA[ i ]) );		/* Restart the timer */
	}
}



/*-----------------------------------------------------------------------*/
/**
 * Save/Restore snapshot of local variables ('MemorySnapShot_Store' handles type)
 */
void	ACIA_MemorySnapShot_Capture ( bool bSave )
{
	MemorySnapShot_Store(&ACIA_Array, sizeof(ACIA_Array));

	if ( !bSave )						/* If restoring */
		ACIA_Init_Pointers ( ACIA_Array );		/* Restore pointers */
}




/*-----------------------------------------------------------------------*/
/**
 * Set or reset the ACIA's IRQ signal.
 * IRQ signal is inverted (0/low sets irq, 1/high resets irq)
 * In the ST, the 2 ACIA's IRQ pins are connected to the same MFP input,
 * so they share the same IRQ bit in GPIP4.
 */
static void	ACIA_Set_Line_IRQ_MFP ( int bit )
{
	LOG_TRACE ( TRACE_ACIA, "acia set irq line val=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL );

	if ( bit == 0 )
	{
		/* There's a small delay on a real ST between the point in time
		* the irq bit is set and the MFP interrupt is triggered - for example
		* the "V8 music system" demo depends on this behaviour.
		* This 4 cycle delay is handled in mfp.c */
		MFP_GPIP_Set_Line_Input ( MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_LOW );
	}
	else
	{
		MFP_GPIP_Set_Line_Input ( MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_HIGH );
	}
}




/*-----------------------------------------------------------------------*/
/**
 * Read the Clear To Send (CTS) pin
 * When CTS is high, TDRE should always be set to 0
 * Note : this is not connected on an ST, so we always return 0.
 */
static Uint8 	ACIA_Get_Line_CTS_Dummy ( void )
{
	Uint8		bit;

	bit = 0;
	LOG_TRACE ( TRACE_ACIA, "acia get cts=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL );
	return bit;
}

/*-----------------------------------------------------------------------*/
/**
 * Read the Data Carrier Detect (DCD) pin
 * Note : this is not connected on an ST, so we always return 0.
 */
static Uint8 	ACIA_Get_Line_DCD_Dummy ( void )
{
	Uint8		bit;

	bit = 0;
	LOG_TRACE ( TRACE_ACIA, "acia get dcd=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL );
	return bit;
}

/*-----------------------------------------------------------------------*/
/**
 * Set the Request To Send (RTS) pin.
 * Note : this is not connected on an ST, so we ignore it.
 */
static void	ACIA_Set_Line_RTS_Dummy ( int bit )
{
	LOG_TRACE ( TRACE_ACIA, "acia set rts val=%d VBL=%d HBL=%d\n" , bit , nVBLs , nHBL );
}




/*-----------------------------------------------------------------------*/
/**
 * Set the required timers to handle RX / TX, depending on the CR_DIVIDE
 * value.
 * When CR is changed with a new CR_DIVIDE value, we restart the timers.
 */
static void	ACIA_Set_Timers_IKBD ( void *pACIA )
{
	ACIA_Start_InterruptHandler_IKBD ( (ACIA_STRUCT *)pACIA , 0 );
}




/*-----------------------------------------------------------------------*/
/**
 * Set a timer to handle the RX / TX bits at the expected baud rate.
 * NOTE : on ST, TX_Clock and RX_Clock are the same, so the timer's freq will be
 * TX_Clock / Divider and we only need one timer interrupt to handle both RX and TX.
 * This freq should be converted to CPU_CYCLE : 1 ACIA cycle = 16 CPU cycles
 * (with cpu running at 8 MHz)
 * InternalCycleOffset allows to compensate for a != 0 value in PendingInterruptCount
 * to keep a constant baud rate.
 * TODO : we use a fixed 8 MHz clock to convert cycles for our internal timers
 * in cycInt.c. This should be replaced some days by using MachineClocks.CPU_Freq.
 */
static void	ACIA_Start_InterruptHandler_IKBD ( ACIA_STRUCT *pACIA , int InternalCycleOffset )
{
	int		Cycles;


//	Cycles = MachineClocks.CPU_Freq / pACIA->TX_Clock;		/* Convert ACIA cycles in CPU cycles */
	Cycles = 8021247 / pACIA->TX_Clock;				/* Convert ACIA cycles in CPU cycles, for a 8 MHz STF reference */
	Cycles *= pACIA->Clock_Divider;

	LOG_TRACE ( TRACE_ACIA, "acia %s start timer divider=%d cpu_cycles=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name ,
		pACIA->Clock_Divider , Cycles , nVBLs , nHBL );

	CycInt_AddRelativeInterruptWithOffset ( Cycles, INT_CPU8_CYCLE, INTERRUPT_ACIA_IKBD , InternalCycleOffset );
}




/*-----------------------------------------------------------------------*/
/**
 * Interrupt called each time a new bit must be sent / received with the IKBD.
 * This interrupt will be called at freq ( 500 MHz / ACIA_CR_COUNTER_DIVIDE )
 * On ST, RX_Clock = TX_Clock = 500 MHz.
 * We continuously restart the interrupt, taking into account PendingCyclesOver.
 */
void	ACIA_InterruptHandler_IKBD ( void )
{
	int	PendingCyclesOver;


	/* Number of internal cycles we went over for this timer ( <= 0 ) */
	/* Used to restart the next timer and keep a constant baud rate */
	PendingCyclesOver = -PendingInterruptCount;			/* >= 0 */

	LOG_TRACE ( TRACE_ACIA, "acia ikbd interrupt handler pending_cyc=%d VBL=%d HBL=%d\n" , PendingCyclesOver , nVBLs , nHBL );

	/* Remove this interrupt from list and re-order */
	CycInt_AcknowledgeInterrupt();

	ACIA_Clock_TX ( pACIA_IKBD );
	ACIA_Clock_RX ( pACIA_IKBD );

	ACIA_Start_InterruptHandler_IKBD ( pACIA_IKBD , -PendingCyclesOver );	/* Compensate for a != 0 value of PendingCyclesOver */
}




/*-----------------------------------------------------------------------*/
/**
 * Interrupt called each time a new bit must be sent / received with the MIDI.
 * This interrupt will be called at freq ( 500 MHz / ACIA_CR_COUNTER_DIVIDE )
 * On ST, RX_Clock = TX_Clock = 500 MHz.
 */
void	ACIA_InterruptHandler_MIDI ( void )
{
	ACIA_Clock_TX ( pACIA_MIDI );
	ACIA_Clock_RX ( pACIA_MIDI );
}




/*-----------------------------------------------------------------------*/
/**
 * - For each access to an ACIA register, a 6 cycles delay is added to the
 *   normal 68000 timing for the current CPU instruction. If the instruction
 *   accesses several registers at once, the delays are cumulated.
 * - An additional delay will also be added to ensure the 68000 clock and
 *   the E clock are synchronised ; this delay can add between 0 and 8 cycles
 *   to reach the next multiple of 10 cycles. This delay is added only once
 *   per CPU instruction.
 * These delays are measured for an 8 MHz 68000 CPU.
 */
void	ACIA_AddWaitCycles ( void )
{
	int	cycles;

	/* Add a default of 6 cycles for each access */
	cycles = 6;

	/* Wait for E clock only if this is the first ACIA access for this instruction */
	/* (NOTE : in UAE, movep behaves like several bytes access with different IoAccessBaseAddress, */
	/* so only the first movep's access should wait for E Clock) */
	if ( ( ( IoAccessInstrCount == 0 ) && ( IoAccessBaseAddress == IoAccessCurrentAddress ) )
	  || ( IoAccessInstrCount == 1 ) )				/* First access of a movep */
		cycles += M68000_WaitEClock ();
	
	M68000_WaitState ( cycles );
}



/*-----------------------------------------------------------------------*/
/**
 * Return SR for the IKBD's ACIA (0xfffc00)
 */
void	ACIA_IKBD_Read_SR ( void )
{
	ACIA_AddWaitCycles ();						/* Additional cycles when accessing the ACIA */

	IoMem[0xfffc00] = ACIA_Read_SR ( pACIA_IKBD );

	if (LOG_TRACE_LEVEL(TRACE_ACIA))
	{
		int FrameCycles, HblCounterVideo, LineCycles;
		Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
		LOG_TRACE_PRINT("acia %s read fffc00 sr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name ,
		                IoMem[0xfffc00], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
	}
}




/*-----------------------------------------------------------------------*/
/**
 * Return RDR for the IKBD's ACIA (0xfffc02) : receive a byte from the IKBD
 */
void	ACIA_IKBD_Read_RDR ( void )
{
	ACIA_AddWaitCycles ();						/* Additional cycles when accessing the ACIA */

	IoMem[0xfffc02] = ACIA_Read_RDR ( pACIA_IKBD );

	if (LOG_TRACE_LEVEL(TRACE_IKBD_ACIA))
	{
		int FrameCycles, HblCounterVideo, LineCycles;
		Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
		LOG_TRACE_PRINT("acia %s read fffc02 rdr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name ,
				IoMem[0xfffc02], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);
	}
}




/*-----------------------------------------------------------------------*/
/**
 * Write to CR in the IKBD's ACIA (0xfffc00)
 */
void	ACIA_IKBD_Write_CR ( void )
{
	int FrameCycles, HblCounterVideo, LineCycles;

	ACIA_AddWaitCycles ();						/* Additional cycles when accessing the ACIA */

	Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
	LOG_TRACE(TRACE_IKBD_ACIA, "acia %s write fffc00 cr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name ,
				IoMem[0xfffc00], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);

	ACIA_Write_CR ( pACIA_IKBD , IoMem[0xfffc00] );
}




/*-----------------------------------------------------------------------*/
/**
 * Write to TDR in the IKBD's ACIA (0xfffc02) : send a byte to the IKBD
 */
void	ACIA_IKBD_Write_TDR ( void )
{
	int FrameCycles, HblCounterVideo, LineCycles;

	ACIA_AddWaitCycles ();						/* Additional cycles when accessing the ACIA */

	Video_GetPosition ( &FrameCycles , &HblCounterVideo , &LineCycles );
	LOG_TRACE(TRACE_IKBD_ACIA, "acia %s write fffc02 tdr=0x%02x video_cyc=%d %d@%d pc=%x instr_cycle %d\n", pACIA_IKBD->ACIA_Name ,
				IoMem[0xfffc02], FrameCycles, LineCycles, HblCounterVideo, M68000_GetPC(), CurrentInstrCycles);

	ACIA_Write_TDR ( pACIA_IKBD , IoMem[0xfffc02] );
}




/*----------------------------------------------------------------------*/
/* The part below is the real core of the 6850's emulation.		*/
/*									*/
/* This core is not correlated to any specific machine. All the specific*/
/* code between the 6850 and the rest of Hatari is called through some	*/
/* callback functions (see above).					*/
/*----------------------------------------------------------------------*/



/*-----------------------------------------------------------------------*/
/**
 * Reset an ACIA.
 * There's no RESET pin on the MC6850, so the only way to reset the ACIA
 * is to set bit 0 an 1 to 0x03 in the CR to force a master reset.
 * This will clear SR (except CTS and DCD) and halt/initialize both the
 * receiver and transmitter.
 * This also returns the new state of the RTS bit, that must be updated
 * in ACIA_Write_CR.
 */
static Uint8	ACIA_MasterReset ( ACIA_STRUCT *pACIA , Uint8 CR )
{
	Uint8		dcd_bit;
	Uint8		cts_bit;
	Uint8		rts_bit;


	LOG_TRACE ( TRACE_ACIA, "acia %s master reset VBL=%d HBL=%d\n" , pACIA->ACIA_Name , nVBLs , nHBL );
	
	dcd_bit = pACIA->Get_Line_DCD ();
	cts_bit = pACIA->Get_Line_CTS ();

	pACIA->SR = ACIA_SR_BIT_TDRE | ( dcd_bit << 2 ) | ( cts_bit << 3 );

	pACIA->TX_State = ACIA_STATE_IDLE;
	pACIA->TSR = 0;
	pACIA->TX_Size = 0;
	pACIA->TX_SendBrk = 0;

	pACIA->RX_State = ACIA_STATE_IDLE;
	pACIA->RSR = 0;
	pACIA->RX_Size = 0;
	pACIA->RX_Overrun = 0;

	/* On Master Reset, IRQ line is high */
	/* If it's the 1st reset, RTS should be high, else RTS depends on CR bit 5 and 6 */
	pACIA->Set_Line_IRQ ( 1 );					/* IRQ line goes high */
	if ( pACIA->FirstMasterReset == 1 )
	{
		pACIA->FirstMasterReset = 0;
		rts_bit = 1;						/* RTS line goes high */
	}
	else
		rts_bit = ( ACIA_CR_TRANSMITTER_CONTROL ( CR ) == 0x02 ) ? 1 : 0;

	return rts_bit;
}




/*-----------------------------------------------------------------------*/
/**
 * Check if the IRQ must be changed in SR.
 * When there's a change, we must change the IRQ line too.
 */
static void	ACIA_UpdateIRQ ( ACIA_STRUCT *pACIA )
{
	Uint8		irq_bit_new;

	irq_bit_new = 0;

	if ( ACIA_CR_RECEIVE_INTERRUPT_ENABLE ( pACIA->CR ) 		/* Check for RX causes of interrupt */
	  && ( ( pACIA->SR & ( ACIA_SR_BIT_RDRF | ACIA_SR_BIT_DCD ) )
	    || ( pACIA->RX_Overrun ) ) )
	  irq_bit_new = ACIA_SR_BIT_IRQ;
//fprintf(stderr , "acia irq %x %x %x %d\n" , pACIA->CR , pACIA->SR , pACIA->RX_Overrun , irq_bit_new);

	if ( pACIA->TX_EnableInt					/* Check for TX causes of interrupt */
	  && ( pACIA->SR & ACIA_SR_BIT_TDRE )
	  && ( ( pACIA->SR & ACIA_SR_BIT_CTS ) == 0 ) )
	  irq_bit_new = ACIA_SR_BIT_IRQ;
	
	/* Update SR and IRQ line if a change happened */
	if ( ( pACIA->SR & ACIA_SR_BIT_IRQ ) != irq_bit_new )
	{
		LOG_TRACE ( TRACE_ACIA, "acia %s update irq irq_new=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , irq_bit_new?1:0 , nVBLs , nHBL );

		if ( irq_bit_new )
		{
			pACIA->SR |= ACIA_SR_BIT_IRQ;			/* Set IRQ bit */
			pACIA->Set_Line_IRQ ( 0 );			/* IRQ line goes low */
		}
		else
		{
			pACIA->SR &= ~ACIA_SR_BIT_IRQ;			/* Clear IRQ bit */
			pACIA->Set_Line_IRQ ( 1 );			/* IRQ line goes high */
		}
	}
}



/*-----------------------------------------------------------------------*/
/**
 * Read SR.
 * Also update CTS ; when CTS is high, TDRE should always be masked to 0.
 */
static Uint8	ACIA_Read_SR ( ACIA_STRUCT *pACIA )
{
	Uint8	SR;


	if ( pACIA->Get_Line_CTS() == 1 )
		pACIA->SR |= ACIA_SR_BIT_CTS;
	else
		pACIA->SR &= ~ACIA_SR_BIT_CTS;

	SR = pACIA->SR;
	pACIA->SR_Read = 1;						/* Used in ACIA_Read_RDR to clear Overrun and DCD IRQ */

	if ( SR & ACIA_SR_BIT_CTS )
		SR &= ~ACIA_SR_BIT_TDRE;				/* Inhibit TDRE when CTS is set */

	LOG_TRACE ( TRACE_ACIA, "acia %s read sr data=0x%02x VBL=%d HBL=%d\n" , pACIA->ACIA_Name , SR , nVBLs , nHBL );

	return SR;
}




/*-----------------------------------------------------------------------*/
/**
 * Write to CR.
 */
static void	ACIA_Write_CR ( ACIA_STRUCT *pACIA , Uint8 CR )
{
	int	Divide;
	int	Force_rts_bit;
	Uint8	rts_bit=0;

	LOG_TRACE ( TRACE_ACIA, "acia %s write cr data=0x%02x VBL=%d HBL=%d\n" , pACIA->ACIA_Name , CR , nVBLs , nHBL );

	/* Bit 0 and 1 : Counter Divide */
	Divide = ACIA_CR_COUNTER_DIVIDE ( CR );
	if ( Divide == 0x03 )
	{
		Force_rts_bit = ACIA_MasterReset ( pACIA , CR );	/* Special behaviour for RTS after a master reset */
	}
	else
	{
		if ( ACIA_CR_COUNTER_DIVIDE ( CR ) != ACIA_CR_COUNTER_DIVIDE ( pACIA->CR ) )
		{
			pACIA->Clock_Divider = ACIA_Counter_Divide[ Divide ];
			pACIA->Set_Timers ( pACIA );			/* Set a timer at the baud rate computed from Clock_Divider */
		}
		Force_rts_bit = -1;					/* Don't force RTS bit, use bit 5/6 in CR */
	}

	/* Bits 2, 3 and 4 : word select */
	/* Don't do anything here, see ACIA_Prepare_TX and ACIA_Prepare_RX */

	/* Bits 5 and 6 : transmitter control */
	pACIA->TX_EnableInt = 0;
	pACIA->TX_SendBrk = 0;
	switch ( ACIA_CR_TRANSMITTER_CONTROL ( CR ) )
	{
	  case 0x00 :
		rts_bit = 0;
		break;
	  case 0x01 :
		rts_bit = 0;
		pACIA->TX_EnableInt = 1;
		break;
	  case 0x02 :
		rts_bit = 1;
		break;
	  case 0x03 :
		rts_bit = 0;
		pACIA->TX_SendBrk = 1;					/* We will send break bit until CR is changed */
		break;
	}

	if ( Force_rts_bit >= 0 )
		rts_bit = Force_rts_bit;				/* Use the value from ACIA_MasterReset */
	pACIA->Set_Line_RTS ( rts_bit );

	/* Bits 7 : receive interrupt enable, see ACIA_UpdateIRQ */

	pACIA->CR = CR;

	ACIA_UpdateIRQ ( pACIA );
}




/*-----------------------------------------------------------------------*/
/**
 * Read RDR. This will clear RDRF and PE.
 * - OVRN / DCD bits are cleared if SR was read before reading RDR.
 * - OVRN bit is set only when reading RDR, not when the actual overrun happened
 *   during ACIA_Clock_RX.
 * - IRQ bit should be updated depending on the new values of BIT_RDRF,
 *   BIT_DCD and BIT_OVRN.
 */
static Uint8	ACIA_Read_RDR ( ACIA_STRUCT *pACIA )
{
	pACIA->SR &= ~( ACIA_SR_BIT_RDRF | ACIA_SR_BIT_PE );

	/* If we read RDR after reading SR, we clear OVRN / DCD bits */
	if ( pACIA->SR_Read == 1 )
	{
		pACIA->SR_Read = 0;
		pACIA->SR &= ~( ACIA_SR_BIT_DCD | ACIA_SR_BIT_OVRN );
		if ( pACIA->Get_Line_DCD () == 1 )
			pACIA->SR |= ACIA_SR_BIT_DCD;
	}

	if ( pACIA->RX_Overrun )
	{  
		pACIA->SR |= ACIA_SR_BIT_OVRN;
		pACIA->RX_Overrun = 0;
	}

	ACIA_UpdateIRQ ( pACIA );

	LOG_TRACE ( TRACE_ACIA, "acia %s read rdr data=0x%02x new sr=0x%02x overrun=%s VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->RDR ,
		pACIA->SR , ( pACIA->SR & ACIA_SR_BIT_OVRN ) ? "yes" : "no" , nVBLs , nHBL );

	return pACIA->RDR;
}




/*-----------------------------------------------------------------------*/
/**
 * Write to TDR.
 * If the TX process is idle, we should not prepare a new transfer
 * immediately, to ensure that BIT_TDRE remains clear until the next bit
 * is sent (BIT_TDRE will be set again in ACIA_Clock_TX).
 */
static void	ACIA_Write_TDR ( ACIA_STRUCT *pACIA , Uint8 TDR )
{
	LOG_TRACE ( TRACE_ACIA, "acia %s write tdr data=0x%02x overwrite=%s tx_state=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , TDR ,
		( pACIA->SR & ACIA_SR_BIT_TDRE ) ? "no" : "yes" , pACIA->TX_State , nVBLs , nHBL );

	pACIA->TDR = TDR;
	pACIA->SR &= ~ACIA_SR_BIT_TDRE;					/* TDR is not empty anymore */

	ACIA_UpdateIRQ ( pACIA );
}




/*-----------------------------------------------------------------------*/
/**
 * Prepare a new transfer. Copy TDR to TSR and initialize parity, data size
 * and stop bits.
 * Transfer will then start at the next call of ACIA_Clock_TX
 */
static void	ACIA_Prepare_TX ( ACIA_STRUCT *pACIA )
{
	pACIA->TSR = pACIA->TDR;
	pACIA->TX_Parity = 0;
	pACIA->TX_Size = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].DataBits;
	pACIA->TX_StopBits = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].StopBits;

	pACIA->SR |= ACIA_SR_BIT_TDRE;					/* TDR was copied to TSR. TDR is now empty */

	LOG_TRACE ( TRACE_ACIA, "acia %s prepare tx tsr=0x%02x size=%d stop=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->TSR ,
		pACIA->TX_Size , pACIA->TX_StopBits , nVBLs , nHBL );
}




/*-----------------------------------------------------------------------*/
/**
 * Prepare a new reception. Initialize parity, data size and stop bits.
 */
static void	ACIA_Prepare_RX ( ACIA_STRUCT *pACIA )
{
	pACIA->RSR = 0;
	pACIA->RX_Parity = 0;
	pACIA->RX_Size = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].DataBits;
	pACIA->RX_StopBits = ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].StopBits;

	LOG_TRACE ( TRACE_ACIA, "acia %s prepare rx size=%d stop=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name ,
		pACIA->RX_Size , pACIA->RX_StopBits , nVBLs , nHBL );
}




/*-----------------------------------------------------------------------*/
/**
 * Write a new bit on the TX line each time the TX clock expires.
 * This will send TDR over the serial line, using TSR, with additional
 * parity and start/stop bits.
 * We send bit 0 of TSR, then TSR is shifted to the right.
 */
static void	ACIA_Clock_TX ( ACIA_STRUCT *pACIA )
{
	int	StateNext;
	Uint8	tx_bit;


	LOG_TRACE ( TRACE_ACIA, "acia %s clock_tx tx_state=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->TX_State , nVBLs , nHBL );

	StateNext = -1;
	switch ( pACIA->TX_State )
	{
	  case ACIA_STATE_IDLE :
		if ( pACIA->TX_SendBrk )
		{
			pACIA->Set_Line_TX ( 0 );			/* Send 1 break bit */
			break;
		}

		/* If TDR is not empty when we are in idle state, */
		/* this means we have a new byte to send */
		if ( ( pACIA->SR & ACIA_SR_BIT_TDRE ) == 0 )
			ACIA_Prepare_TX ( pACIA );

		if ( pACIA->TX_Size == 0 )				/* TSR is empty */
			pACIA->Set_Line_TX ( 1 );			/* Send stop bits when idle */

		else							/* TSR has some new bits to transfer */
		{
			pACIA->Set_Line_TX ( 0 );			/* Send 1 start bit */
			StateNext = ACIA_STATE_DATA_BIT;
		}
		break;

	  case ACIA_STATE_DATA_BIT :
		tx_bit = pACIA->TSR & 1;				/* New bit to send */
		pACIA->Set_Line_TX ( tx_bit );
		pACIA->TX_Parity ^= tx_bit;
		pACIA->TSR >>= 1;
		pACIA->TX_Size--;

		if ( pACIA->TX_Size == 0 )
		{
			if ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity != ACIA_PARITY_NONE )
				StateNext = ACIA_STATE_PARITY_BIT;
			else
				StateNext = ACIA_STATE_STOP_BIT;	/* No parity */
		}
		break;

	  case ACIA_STATE_PARITY_BIT :
		if ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity == ACIA_PARITY_EVEN )
			pACIA->Set_Line_TX ( pACIA->TX_Parity );
		else
			pACIA->Set_Line_TX ( ( ~pACIA->TX_Parity ) & 1 );

		StateNext = ACIA_STATE_STOP_BIT;
		break;

	  case ACIA_STATE_STOP_BIT :
		pACIA->Set_Line_TX ( 1 );				/* Send 1 stop bit */
		pACIA->TX_StopBits--;

		if ( pACIA->TX_StopBits == 0 )				/* All stop bits were sent : transfer is complete */
		{
			StateNext = ACIA_STATE_IDLE;			/* Go to idle state to see if a new TDR need to be sent */
		}
		break;
	}

	ACIA_UpdateIRQ ( pACIA );

	if ( StateNext >= 0 )
		pACIA->TX_State = StateNext;				/* Go to a new state */
}




/*-----------------------------------------------------------------------*/
/**
 * Handle a new bit on the RX line each time the RX clock expires.
 * This will fill RDR with bits received from the serial line, using RSR.
 * Incoming bits are stored in bit 7 of RSR, then RSR is shifted to the right.
 */
static void	ACIA_Clock_RX ( ACIA_STRUCT *pACIA )
{
	int	StateNext;
	Uint8	rx_bit;


	rx_bit = pACIA->Get_Line_RX();

	LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx rx_state=%d bit=%d VBL=%d HBL=%d\n" , pACIA->ACIA_Name , pACIA->RX_State , rx_bit , nVBLs , nHBL );

	StateNext = -1;
	switch ( pACIA->RX_State )
	{
	  case ACIA_STATE_IDLE :
		if ( rx_bit == 0 )					/* Receive 1 start bit */
		{
			ACIA_Prepare_RX ( pACIA );
			StateNext = ACIA_STATE_DATA_BIT;
		}
		break;							/* If no start bit, we stay in idle state */

	  case ACIA_STATE_DATA_BIT :
		if ( rx_bit )
			pACIA->RSR |= 0x80;
		pACIA->RX_Parity ^= rx_bit;
		pACIA->RX_Size--;

		if ( pACIA->RX_Size > 0 )				/* All bits were not received yet */
		{
			pACIA->RSR >>= 1;
		}
		else
		{
// [NP] : MC6850 doc is not very clear "the overrun condition begins at the midpoint of the last bit
// of the second character received [...]". Is it the last bit of the data word, or the stop bit ?
// It makes more sense to check for overrun after the stop bit, when RSR should be copied to RDR,
// because RDR could be read between the last data bit and the stop bit, so RX_Overrun and
// ACIA_SR_BIT_OVRN would need to be cancelled.
// 			if ( pACIA->SR & ACIA_SR_BIT_RDRF )
// 			{
// 				LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx overrun rsr=0x%02x VBL=%d HBL=%d\n" ,
// 					pACIA->ACIA_Name , pACIA->RSR , nVBLs , nHBL );
// 				pACIA->RX_Overrun = 1;			/* Bit in SR will be set when reading RDR */
// 			}
			if ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity != ACIA_PARITY_NONE )
				StateNext = ACIA_STATE_PARITY_BIT;
			else
				StateNext = ACIA_STATE_STOP_BIT;	/* No parity */
		}
		break;

	  case ACIA_STATE_PARITY_BIT :
		if ( ( ACIA_Serial_Params[ ACIA_CR_WORD_SELECT ( pACIA->CR ) ].Parity == ACIA_PARITY_EVEN )
		    && ( pACIA->RX_Parity != rx_bit ) )
			pACIA->SR |= ACIA_SR_BIT_PE;

		else if ( pACIA->RX_Parity == rx_bit )			/* Odd parity */
			pACIA->SR |= ACIA_SR_BIT_PE;

		if ( pACIA->SR & ACIA_SR_BIT_PE )
			LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx parity error VBL=%d HBL=%d\n" , pACIA->ACIA_Name , nVBLs , nHBL );

		StateNext = ACIA_STATE_STOP_BIT;
		break;

	  case ACIA_STATE_STOP_BIT :
		if ( rx_bit == 1 )					/* Wait for 1 or 2 "1" stop bits */
		{
			pACIA->RX_StopBits--;
			if ( pACIA->RX_StopBits == 0 )			/* All stop bits were received : reception is complete */
			{
				pACIA->SR &= ~ACIA_SR_BIT_FE;
				
				if ( ( pACIA->SR & ACIA_SR_BIT_RDRF ) == 0 )
				{
					pACIA->RDR = pACIA->RSR;
					pACIA->SR |= ACIA_SR_BIT_RDRF;
					LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx received rdr=0x%02x VBL=%d HBL=%d\n" ,
						pACIA->ACIA_Name , pACIA->RDR , nVBLs , nHBL );
				}
				else
				{
					LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx overrun rsr=0x%02x unread rdr=0x%02x VBL=%d HBL=%d\n" ,
						pACIA->ACIA_Name , pACIA->RSR , pACIA->RDR , nVBLs , nHBL );
					pACIA->RX_Overrun = 1;		/* Bit in SR will be set when reading RDR */
				}
				StateNext = ACIA_STATE_IDLE;		/* Go to idle state and wait for start bit */
			}
		}
		else							/* Not a valid stop bit */
		{
			LOG_TRACE ( TRACE_ACIA, "acia %s clock_rx framing error VBL=%d HBL=%d\n" , pACIA->ACIA_Name , nVBLs , nHBL );

			/* According to the A6850 doc, RSR is copied to RDR in case of a framing error */
			/* (Should be the same for the MC6850 ?) */
			pACIA->SR |= ACIA_SR_BIT_FE;
			pACIA->RDR = pACIA->RSR;
			StateNext = ACIA_STATE_IDLE;			/* Go to idle state and wait for start bit */
		}
		break;
	}

	ACIA_UpdateIRQ ( pACIA );

	if ( StateNext >= 0 )
		pACIA->RX_State = StateNext;				/* Go to a new state */
}




unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.