2017-03-24 10:44:04 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "scc.h"
|
|
|
|
#include "m68k.h"
|
2017-09-04 01:25:24 +00:00
|
|
|
#include "hexdump.h"
|
|
|
|
#include "localtalk.h"
|
2017-03-24 10:44:04 +00:00
|
|
|
|
|
|
|
/*
|
2017-09-04 01:25:24 +00:00
|
|
|
Emulation of the Zilog 8530 SCC.
|
|
|
|
|
|
|
|
This is an extremely minimal SCC implementation: it only somewhat implements the interrupt mechanism for the
|
2017-03-24 10:44:04 +00:00
|
|
|
DCD pins because that is what is needed for the mouse to work.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
void sccIrq(int ena);
|
|
|
|
|
2017-09-04 01:25:24 +00:00
|
|
|
#define BUFLEN 8192
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
int dcd;
|
|
|
|
int cts;
|
|
|
|
int wr1;
|
|
|
|
int sdlcaddr;
|
|
|
|
int wr15;
|
|
|
|
int hunting;
|
|
|
|
int txTimer;
|
|
|
|
uint8_t txData[BUFLEN];
|
|
|
|
uint8_t rxData[BUFLEN];
|
|
|
|
int txPos, rxPos, rxLen;
|
|
|
|
int rxDelay;
|
|
|
|
} SccChan;
|
|
|
|
|
2017-03-24 10:44:04 +00:00
|
|
|
typedef struct {
|
|
|
|
int regptr;
|
|
|
|
int intpending;
|
|
|
|
int intpendingOld;
|
2017-09-04 01:25:24 +00:00
|
|
|
SccChan chan[2];
|
2017-03-24 10:44:04 +00:00
|
|
|
} Scc;
|
|
|
|
|
|
|
|
static Scc scc;
|
|
|
|
|
|
|
|
//WR15 is the External/Status Interrupt Control and has the interrupt enable bits.
|
|
|
|
#define SCC_WR15_BREAK (1<<7)
|
|
|
|
#define SCC_WR15_TXU (1<<6)
|
|
|
|
#define SCC_WR15_CTS (1<<5)
|
|
|
|
#define SCC_WR15_SYNC (1<<4)
|
|
|
|
#define SCC_WR15_DCD (1<<3)
|
|
|
|
#define SCC_WR15_ZCOUNT (1<<1)
|
|
|
|
|
|
|
|
//WR3, when read, gives the Interrupt Pending status.
|
|
|
|
//This is reflected in scc.intpending.
|
|
|
|
#define SCC_WR3_CHB_EXT (1<<0)
|
|
|
|
#define SCC_WR3_CHB_TX (1<<1)
|
|
|
|
#define SCC_WR3_CHB_RX (1<<2)
|
|
|
|
#define SCC_WR3_CHA_EXT (1<<3)
|
|
|
|
#define SCC_WR3_CHA_TX (1<<4)
|
|
|
|
#define SCC_WR3_CHA_RX (1<<5)
|
|
|
|
|
2017-09-04 01:25:24 +00:00
|
|
|
|
2017-03-24 10:44:04 +00:00
|
|
|
static void raiseInt(int chan) {
|
2017-09-04 01:25:24 +00:00
|
|
|
if ((scc.chan[chan].wr1&1) && (scc.intpending&(~scc.intpendingOld))) {
|
2017-03-24 10:44:04 +00:00
|
|
|
scc.intpendingOld=scc.intpending;
|
2017-05-29 16:40:17 +00:00
|
|
|
// printf("SCC int, pending %x\n", scc.intpending);
|
2017-03-24 10:44:04 +00:00
|
|
|
sccIrq(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sccSetDcd(int chan, int val) {
|
|
|
|
val=val?1:0;
|
2017-09-04 01:25:24 +00:00
|
|
|
if (scc.chan[chan].dcd!=val) {
|
2017-03-24 10:44:04 +00:00
|
|
|
if (chan==SCC_CHANA) {
|
2017-09-04 01:25:24 +00:00
|
|
|
if (scc.chan[SCC_CHANA].wr15&SCC_WR15_DCD) {
|
|
|
|
scc.intpending|=SCC_WR3_CHA_EXT;
|
|
|
|
raiseInt(SCC_CHANA);
|
|
|
|
}
|
2017-03-24 10:44:04 +00:00
|
|
|
} else {
|
2017-09-04 01:25:24 +00:00
|
|
|
if (scc.chan[SCC_CHANB].wr15&SCC_WR15_DCD) {
|
|
|
|
scc.intpending|=SCC_WR3_CHB_EXT;
|
|
|
|
raiseInt(SCC_CHANB);
|
|
|
|
}
|
2017-03-24 10:44:04 +00:00
|
|
|
}
|
|
|
|
}
|
2017-09-04 01:25:24 +00:00
|
|
|
scc.chan[chan].dcd=val;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sccTxFinished(int chan) {
|
|
|
|
hexdump(scc.chan[chan].txData, scc.chan[chan].txPos);
|
|
|
|
localtalkSend(scc.chan[chan].txData, scc.chan[chan].txPos);
|
|
|
|
scc.chan[chan].txPos=0;
|
|
|
|
scc.chan[chan].hunting=1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sccRecv(int chan, uint8_t *data, int len) {
|
|
|
|
memcpy(scc.chan[chan].rxData, data, len);
|
|
|
|
scc.chan[chan].rxData[len]=0xA5; //crc1
|
|
|
|
scc.chan[chan].rxData[len+1]=0xA5; //crc2
|
|
|
|
scc.chan[chan].rxData[len+2]=0; //abort
|
|
|
|
scc.chan[chan].rxLen=len+3;
|
|
|
|
scc.chan[chan].rxDelay=30;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void triggerRx(int chan) {
|
|
|
|
if (!scc.chan[chan].hunting) return;
|
|
|
|
printf("Receiving:\n");
|
|
|
|
hexdump(scc.chan[chan].rxData, scc.chan[chan].rxLen);
|
|
|
|
if (scc.chan[chan].rxData[0]==0xFF || scc.chan[chan].rxData[0]==scc.chan[chan].sdlcaddr) {
|
|
|
|
scc.chan[chan].rxPos=0;
|
|
|
|
//Sync int
|
|
|
|
if (scc.chan[chan].wr15&SCC_WR15_SYNC) {
|
|
|
|
scc.intpending|=(chan?SCC_WR3_CHA_EXT:SCC_WR3_CHB_EXT);
|
|
|
|
raiseInt(chan);
|
|
|
|
}
|
|
|
|
//RxD int
|
|
|
|
int rxintena=scc.chan[chan].wr1&0x18;
|
|
|
|
if (rxintena==0x10 || rxintena==0x08) {
|
|
|
|
scc.intpending|=(chan?SCC_WR3_CHA_RX:SCC_WR3_CHB_RX);
|
|
|
|
raiseInt(chan);
|
|
|
|
}
|
|
|
|
scc.chan[chan].hunting=0;
|
|
|
|
} else {
|
|
|
|
scc.chan[chan].rxLen=0;
|
|
|
|
}
|
2017-03-24 10:44:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void sccWrite(unsigned int addr, unsigned int val) {
|
|
|
|
int chan, reg;
|
|
|
|
if (addr & (1<<1)) chan=SCC_CHANA; else chan=SCC_CHANB;
|
|
|
|
if (addr & (1<<2)) {
|
|
|
|
//Data
|
|
|
|
reg=8;
|
|
|
|
} else {
|
|
|
|
//Control
|
|
|
|
reg=scc.regptr;
|
|
|
|
scc.regptr=0;
|
|
|
|
}
|
2017-09-04 01:25:24 +00:00
|
|
|
|
2017-03-24 10:44:04 +00:00
|
|
|
if (reg==0) {
|
|
|
|
scc.regptr=val&0x7;
|
|
|
|
if ((val&0x38)==0x8) scc.regptr|=8;
|
|
|
|
if ((val&0x38)==0x10) {
|
|
|
|
scc.intpending=0;
|
|
|
|
scc.intpendingOld=0;
|
|
|
|
}
|
2017-09-04 01:25:24 +00:00
|
|
|
if ((val&0x38)==0x18) {
|
|
|
|
//SCC abort: parse whatever we sent
|
|
|
|
printf("SCC ABORT: Sent data\n");
|
|
|
|
sccTxFinished(chan);
|
|
|
|
}
|
|
|
|
if ((val&0xc0)==0xC0) {
|
|
|
|
//reset tx underrun latch
|
|
|
|
if (scc.chan[chan].txTimer==0) scc.chan[chan].txTimer=-1;
|
|
|
|
}
|
2017-03-24 10:44:04 +00:00
|
|
|
} else if (reg==1) {
|
2017-09-04 01:25:24 +00:00
|
|
|
scc.chan[chan].wr1=val;
|
|
|
|
} else if (reg==3) {
|
|
|
|
//bitsperchar1, bitsperchar0, autoenables, enterhuntmode, rxcrcen, addresssearch, synccharloadinh, rxena
|
|
|
|
//autoenable: cts = tx ena, dcd = rx ena
|
|
|
|
if (val&0x10) scc.chan[chan].hunting=1;
|
|
|
|
} else if (reg==6) {
|
|
|
|
scc.chan[chan].sdlcaddr=val;
|
|
|
|
} else if (reg==8) {
|
|
|
|
scc.chan[chan].txData[scc.chan[chan].txPos++]=val;
|
|
|
|
printf("TX! Pos %d\n", scc.chan[chan].txPos);
|
|
|
|
scc.chan[chan].txTimer+=30;
|
2017-03-24 10:44:04 +00:00
|
|
|
} else if (reg==15) {
|
2017-09-04 01:25:24 +00:00
|
|
|
scc.chan[chan].wr15=val;
|
2017-03-24 10:44:04 +00:00
|
|
|
}
|
2017-09-04 01:25:24 +00:00
|
|
|
printf("SCC: write to addr %x chan %d reg %d val %x\n", addr, chan, reg, val);
|
2017-03-24 10:44:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int sccRead(unsigned int addr) {
|
|
|
|
int chan, reg, val=0xff;
|
|
|
|
if (addr & (1<<1)) chan=SCC_CHANA; else chan=SCC_CHANB;
|
|
|
|
if (addr & (1<<2)) {
|
|
|
|
//Data
|
|
|
|
reg=8;
|
|
|
|
} else {
|
|
|
|
//Control
|
|
|
|
reg=scc.regptr;
|
|
|
|
scc.regptr=0;
|
|
|
|
}
|
|
|
|
if (reg==0) {
|
2017-09-04 01:25:24 +00:00
|
|
|
val=(1<<2); //tx buffer always empty
|
|
|
|
if (scc.chan[chan].rxLen && !scc.chan[chan].rxDelay) val|=(1<<0);
|
|
|
|
if (scc.chan[chan].txTimer==0) val|=(1<<6);
|
|
|
|
if (scc.chan[chan].dcd) val|=(1<<3);
|
|
|
|
if (scc.chan[chan].cts) val|=(1<<5);
|
|
|
|
if (scc.chan[chan].hunting) val|=(1<<4);
|
|
|
|
if (scc.chan[chan].rxPos==scc.chan[chan].rxLen-1) val|=(1<<7); //abort
|
2017-03-24 10:44:04 +00:00
|
|
|
} else if (reg==1) {
|
2017-09-04 01:25:24 +00:00
|
|
|
//EndOfFrame, CRCErr, RXOverrun, ParityErr, Residue0, Residue1, Residue2, AllSent
|
|
|
|
val=0x7; //residue code 011, all sent
|
|
|
|
if (scc.chan[chan].rxPos==scc.chan[chan].rxLen-2) val|=(1<<7); //end of frame
|
2017-03-24 10:44:04 +00:00
|
|
|
} else if (reg==2 && chan==SCC_CHANB) {
|
|
|
|
//We assume this also does an intack.
|
|
|
|
int rsn=0;
|
|
|
|
if (scc.intpending & SCC_WR3_CHB_EXT) {
|
|
|
|
rsn=1;
|
|
|
|
scc.intpending&=~SCC_WR3_CHB_EXT;
|
|
|
|
}
|
|
|
|
if (scc.intpending & SCC_WR3_CHA_EXT) {
|
|
|
|
rsn=5;
|
|
|
|
scc.intpending&=~SCC_WR3_CHA_EXT;
|
|
|
|
}
|
|
|
|
val=rsn<<1;
|
|
|
|
if (scc.intpending&0x38) raiseInt(SCC_CHANA);
|
|
|
|
if (scc.intpending&0x07) raiseInt(SCC_CHANB);
|
|
|
|
} else if (reg==3) {
|
|
|
|
if (chan==SCC_CHANA) val=scc.intpending; else val=0;
|
2017-09-04 01:25:24 +00:00
|
|
|
} else if (reg==8) {
|
|
|
|
//rx buffer
|
|
|
|
if (scc.chan[chan].rxLen && !scc.chan[chan].rxDelay) {
|
|
|
|
val=scc.chan[chan].rxData[scc.chan[chan].rxPos++];
|
|
|
|
if (scc.chan[chan].rxPos==scc.chan[chan].rxLen) {
|
|
|
|
scc.chan[chan].rxLen=0;
|
|
|
|
} else {
|
|
|
|
int rxintena=scc.chan[chan].wr1&0x18;
|
|
|
|
if (rxintena==0x10) {
|
|
|
|
scc.intpending|=(chan?SCC_WR3_CHA_RX:SCC_WR3_CHB_RX);
|
|
|
|
raiseInt(chan);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (scc.chan[chan].rxPos==scc.chan[chan].rxLen-1) scc.chan[chan].hunting=1;
|
|
|
|
if (scc.chan[chan].rxPos==scc.chan[chan].rxLen-1 && scc.chan[chan].wr15&SCC_WR15_BREAK) {
|
|
|
|
scc.intpending|=(chan?SCC_WR3_CHA_EXT:SCC_WR3_CHB_EXT);
|
|
|
|
raiseInt(chan);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
val=0;
|
|
|
|
}
|
|
|
|
} else if (reg==10) {
|
|
|
|
//Misc status (mostly SDLC)
|
|
|
|
val=0;
|
2017-03-24 10:44:04 +00:00
|
|
|
} else if (reg==15) {
|
2017-09-04 01:25:24 +00:00
|
|
|
val=scc.chan[chan].wr15;
|
2017-03-24 10:44:04 +00:00
|
|
|
}
|
2017-09-04 01:25:24 +00:00
|
|
|
printf("SCC: read from chan %d reg %d val %x\n", chan, reg, val);
|
2017-03-24 10:44:04 +00:00
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2017-09-04 01:25:24 +00:00
|
|
|
//Called at about 800KHz
|
|
|
|
void sccTick() {
|
|
|
|
for (int n=0; n<2; n++) {
|
|
|
|
if (scc.chan[n].txTimer>0) {
|
|
|
|
scc.chan[n].txTimer--;
|
|
|
|
if (scc.chan[n].txTimer==0) {
|
|
|
|
printf("Tx buffer empty: Sent data\n");
|
|
|
|
sccTxFinished(n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (scc.chan[n].rxDelay!=0) {
|
|
|
|
scc.chan[n].rxDelay--;
|
|
|
|
if (scc.chan[n].rxDelay==0) {
|
|
|
|
triggerRx(n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sccInit() {
|
|
|
|
sccSetDcd(1, 1);
|
|
|
|
sccSetDcd(2, 1);
|
|
|
|
scc.chan[0].txTimer=-1;
|
|
|
|
scc.chan[1].txTimer=-1;
|
|
|
|
}
|
|
|
|
|