// license:BSD-3-Clause
// copyright-holders:R. Belmont
/*********************************************************************
8530scc.c
Zilog 8530 SCC (Serial Control Chip) code
*********************************************************************/
#include "emu.h"
#include "8530scc.h"
const device_type SCC8530 = device_creator<scc8530_t>;
/***************************************************************************
PARAMETERS
***************************************************************************/
#define LOG_SCC (0)
/***************************************************************************
IMPLEMENTATION
***************************************************************************/
scc8530_t::scc8530_t(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, SCC8530, "Zilog 8530 SCC (Legacy)", tag, owner, clock, "scc8530l", __FILE__), mode(0), reg(0), status(0), IRQV(0), MasterIRQEnable(0), lastIRQStat(0), IRQType(),
intrq_cb(*this)
{
}
/*-------------------------------------------------
scc_updateirqs
-------------------------------------------------*/
void scc8530_t::updateirqs()
{
int irqstat;
irqstat = 0;
if (MasterIRQEnable)
{
if ((channel[0].txIRQEnable) && (channel[0].txIRQPending))
{
IRQType = IRQ_B_TX;
irqstat = 1;
}
else if ((channel[1].txIRQEnable) && (channel[1].txIRQPending))
{
IRQType = IRQ_A_TX;
irqstat = 1;
}
else if ((channel[0].extIRQEnable) && (channel[0].extIRQPending))
{
IRQType = IRQ_B_EXT;
irqstat = 1;
}
else if ((channel[1].extIRQEnable) && (channel[1].extIRQPending))
{
IRQType = IRQ_A_EXT;
irqstat = 1;
}
}
else
{
IRQType = IRQ_NONE;
}
// printf("SCC: irqstat %d, last %d\n", irqstat, lastIRQStat);
// printf("ch0: en %d pd %d ch1: en %d pd %d\n", channel[0].txIRQEnable, channel[0].txIRQPending, channel[1].txIRQEnable, channel[1].txIRQPending);
// don't spam the driver with unnecessary transitions
if (irqstat != lastIRQStat)
{
lastIRQStat = irqstat;
// tell the driver the new IRQ line status if possible
#if LOG_SCC
printf("SCC8530 IRQ status => %d\n", irqstat);
#endif
if(!intrq_cb.isnull())
intrq_cb(irqstat);
}
}
/*-------------------------------------------------
scc_initchannel
-------------------------------------------------*/
void scc8530_t::initchannel(int ch)
{
channel[ch].syncHunt = 1;
}
/*-------------------------------------------------
scc_resetchannel
-------------------------------------------------*/
void scc8530_t::resetchannel(int ch)
{
emu_timer *timersave = channel[ch].baudtimer;
memset(&channel[ch], 0, sizeof(Chan));
channel[ch].txUnderrun = 1;
channel[ch].baudtimer = timersave;
channel[ch].baudtimer->adjust(attotime::never, ch);
}
/*-------------------------------------------------
scc8530_baud_expire - baud rate timer expiry
-------------------------------------------------*/
void scc8530_t::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
Chan *pChan = &channel[id];
int brconst = pChan->reg_val[13]<<8 | pChan->reg_val[14];
int rate;
if (brconst)
{
rate = clock() / brconst;
}
else
{
rate = 0;
}
// is baud counter IRQ enabled on this channel?
// always flag pending in case it's enabled after this
pChan->baudIRQPending = 1;
if (pChan->baudIRQEnable)
{
if (pChan->extIRQEnable)
{
pChan->extIRQPending = 1;
pChan->baudIRQPending = 0;
updateirqs();
}
}
// reset timer according to current register values
if (rate)
{
attotime attorate = attotime::from_hz(rate);
timer.adjust(attorate, 0, attorate);
}
else
{
timer.adjust(attotime::never, 0, attotime::never);
}
}
/*-------------------------------------------------
device_start - device-specific startup
-------------------------------------------------*/
void scc8530_t::device_start()
{
intrq_cb.resolve();
memset(channel, 0, sizeof(channel));
mode = 0;
reg = 0;
status = 0;
IRQV = 0;
MasterIRQEnable = 0;
lastIRQStat = 0;
IRQType = IRQ_NONE;
channel[0].baudtimer = timer_alloc(0);
channel[1].baudtimer = timer_alloc(1);
}
/*-------------------------------------------------
device_reset - device-specific reset
-------------------------------------------------*/
void scc8530_t::device_reset()
{
IRQType = IRQ_NONE;
MasterIRQEnable = 0;
IRQV = 0;
initchannel(0);
initchannel(1);
resetchannel(0);
resetchannel(1);
}
/*-------------------------------------------------
scc_set_status
-------------------------------------------------*/
void scc8530_t::set_status(int _status)
{
status = _status;
}
/*-------------------------------------------------
scc_acknowledge
-------------------------------------------------*/
void scc8530_t::acknowledge()
{
if(!intrq_cb.isnull())
intrq_cb(0);
}
/*-------------------------------------------------
scc_getareg
-------------------------------------------------*/
uint8_t scc8530_t::getareg()
{
/* Not yet implemented */
#if LOG_SCC
printf("SCC: port A reg %d read 0x%02x\n", reg, channel[0].reg_val[reg]);
#endif
if (reg == 0)
{
uint8_t rv = 0;
Chan *ourCh = &channel[0];
rv |= (ourCh->txUnderrun) ? 0x40 : 0;
rv |= (ourCh->syncHunt) ? 0x10 : 0;
rv |= channel[0].reg_val[0] & 0x0D; // pick up TXBE, RXBF, DCD bits
return rv;
}
else if (reg == 10)
{
return 0;
}
return channel[0].reg_val[reg];
}
/*-------------------------------------------------
scc_getareg
-------------------------------------------------*/
uint8_t scc8530_t::getbreg()
{
#if LOG_SCC
printf("SCC: port B reg %i read 0x%02x\n", reg, channel[1].reg_val[reg]);
#endif
if (reg == 0)
{
uint8_t rv = 0;
Chan *ourCh = &channel[1];
rv |= (ourCh->txUnderrun) ? 0x40 : 0;
rv |= (ourCh->syncHunt) ? 0x10 : 0;
rv |= channel[1].reg_val[0] & 0x0D; // pick up TXBE, RXBF, DCD bits
return rv;
}
else if (reg == 2)
{
/* HACK! but lets the Mac Plus mouse move again. Needs further investigation. */
acknowledge();
return status;
}
else if (reg == 10)
{
return 0;
}
return channel[1].reg_val[reg];
}
/*-------------------------------------------------
scc_putreg
-------------------------------------------------*/
void scc8530_t::putreg(int ch, uint8_t data)
{
Chan *pChan = &channel[ch];
channel[ch].reg_val[reg] = data;
#if LOG_SCC
printf("SCC: port %c reg %d write 0x%02x\n", 'A'+ch, reg, data);
#endif
switch (reg)
{
case 0: // command register
switch ((data >> 3) & 7)
{
case 1: // select high registers (handled elsewhere)
break;
case 2: // reset external and status IRQs
pChan->syncHunt = 0;
break;
case 5: // ack Tx IRQ
pChan->txIRQPending = 0;
updateirqs();
break;
case 0: // nothing
case 3: // send SDLC abort
case 4: // enable IRQ on next Rx byte
case 6: // reset errors
case 7: // reset highest IUS
// we don't handle these yet
break;
}
break;
case 1: // Tx/Rx IRQ and data transfer mode defintion
pChan->extIRQEnable = (data & 1);
pChan->txIRQEnable = (data & 2) ? 1 : 0;
pChan->rxIRQEnable = (data >> 3) & 3;
updateirqs();
break;
case 2: // IRQ vector
IRQV = data;
break;
case 3: // Rx parameters and controls
pChan->rxEnable = (data & 1);
pChan->syncHunt = (data & 0x10) ? 1 : 0;
break;
case 5: // Tx parameters and controls
// printf("ch %d TxEnable = %d [%02x]\n", ch, data & 8, data);
pChan->txEnable = data & 8;
if (pChan->txEnable)
{
pChan->reg_val[0] |= 0x04; // Tx empty
}
break;
case 4: // Tx/Rx misc parameters and modes
case 6: // sync chars/SDLC address field
case 7: // sync char/SDLC flag
break;
case 9: // master IRQ control
MasterIRQEnable = (data & 8) ? 1 : 0;
updateirqs();
// channel reset command
switch ((data>>6) & 3)
{
case 0: // do nothing
break;
case 1: // reset channel B
resetchannel(0);
break;
case 2: // reset channel A
resetchannel(1);
break;
case 3: // force h/w reset (entire chip)
IRQType = IRQ_NONE;
MasterIRQEnable = 0;
IRQV = 0;
initchannel(0);
initchannel(1);
resetchannel(0);
resetchannel(1);
// make sure we stop yanking the IRQ line if we were
updateirqs();
break;
}
break;
case 10: // misc transmitter/receiver control bits
case 11: // clock mode control
case 12: // lower byte of baud rate gen
case 13: // upper byte of baud rate gen
break;
case 14: // misc control bits
if (data & 0x01) // baud rate generator enable?
{
int brconst = pChan->reg_val[13]<<8 | pChan->reg_val[14];
int rate = clock() / brconst;
pChan->baudtimer->adjust(attotime::from_hz(rate), 0, attotime::from_hz(rate));
}
break;
case 15: // external/status interrupt control
pChan->baudIRQEnable = (data & 2) ? 1 : 0;
pChan->DCDEnable = (data & 8) ? 1 : 0;
pChan->CTSEnable = (data & 0x20) ? 1 : 0;
pChan->txUnderrunEnable = (data & 0x40) ? 1 : 0;
break;
}
}
/*-------------------------------------------------
scc8530_get_reg_a
-------------------------------------------------*/
uint8_t scc8530_t::get_reg_a(int reg)
{
return channel[0].reg_val[reg];
}
/*-------------------------------------------------
scc8530_get_reg_b
-------------------------------------------------*/
uint8_t scc8530_t::get_reg_b(int reg)
{
return channel[1].reg_val[reg];
}
/*-------------------------------------------------
scc8530_set_reg_a
-------------------------------------------------*/
void scc8530_t::set_reg_a(int reg, uint8_t data)
{
channel[0].reg_val[reg] = data;
}
/*-------------------------------------------------
scc8530_set_reg_b
-------------------------------------------------*/
void scc8530_t::set_reg_b(int reg, uint8_t data)
{
channel[1].reg_val[reg] = data;
}
//-------------------------------------------------
// reg_r - read handler, trampolines into normal
// getter
//-------------------------------------------------
READ8_MEMBER(scc8530_t::reg_r)
{
return read_reg(offset & 3);
}
//-------------------------------------------------
// read_reg - reads either the control or data
// port for either SCC channel.
//-------------------------------------------------
uint8_t scc8530_t::read_reg(int offset)
{
uint8_t result = 0;
switch(offset)
{
case 0: /* Channel B (Printer Port) Control */
case 1: /* Channel A (Modem Port) Control */
if (mode == 1)
mode = 0;
else
reg = 0;
result = (offset == 0) ? getbreg() : getareg();
break;
case 2: /* Channel B (Printer Port) Data */
case 3:/* Channel A (Modem Port) Data */
result = channel[offset == 2 ? 1 : 0].rxData;
break;
}
return result;
}
//-------------------------------------------------
// reg_w - write handler, trampolines into normal
// setter
//-------------------------------------------------
WRITE8_MEMBER( scc8530_t::reg_w )
{
write_reg(offset & 3, data);
}
//-------------------------------------------------
// write_reg - writes either the control or data
// port for either SCC channel.
//-------------------------------------------------
void scc8530_t::write_reg(int offset, uint8_t data)
{
//offset & 3;
// printf(" mode %d data %x offset %d \n", mode, data, offset);
//Chan *pChan;
switch(offset)
{
case 0: /* Channel B (Printer Port) Control */
case 1: /* Channel A (Modem Port) Control */
{
int chan = ((offset == 0) ? 1 : 0);
if (mode == 0)
{
if((data & 0xf0) == 0) // not a reset command
{
mode = 1;
reg = data & 0x0f;
// putbreg(data & 0xf0);
}
else if (data == 0x10)
{
// clear ext. interrupts
channel[chan].extIRQPending = 0;
channel[chan].baudIRQPending = 0;
updateirqs();
}
}
else
{
mode = 0;
putreg(chan, data);
}
break;
}
case 2: /* Channel B (Printer Port) Data */
case 3: /* Channel A (Modem Port) Data */
{
int chan = ((offset == 2) ? 1 : 0);
if (channel[chan].txEnable)
{
channel[chan].txData = data;
// local loopback?
if (channel[chan].reg_val[14] & 0x10)
{
channel[chan].rxData = data;
channel[chan].reg_val[0] |= 0x01; // Rx character available
}
channel[chan].reg_val[1] |= 0x01; // All sent
channel[chan].reg_val[0] |= 0x04; // Tx empty
channel[chan].txUnderrun = 1;
channel[chan].txIRQPending = 1;
updateirqs();
}
break;
}
}
}
/*
AppleTalk check:
SCC: port B reg 9 write 0x40 Channel Reset B
SCC: port B reg 4 write 0x20 SDLC mode
SCC: port B reg 10 write 0xe0 CRC preset + FM0
SCC: port B reg 6 write 0x00 SDLC address
SCC: port B reg 7 write 0x7e SDLC flag
SCC: port B reg 12 write 0x06 baud rate low
SCC: port B reg 13 write 0x00 baud rate high
SCC: port B reg 14 write 0xc0 Set FM mode
SCC: port B reg 3 write 0xdd Rx 8 bits, enter hunt mode, CRC enable, address search mode, Rx enable
SCC: port B reg 2 write 0x00 interrupt vector 0
SCC: port B reg 15 write 0x08 DCD interrupt enable
SCC: port B reg 1 write 0x09 Rx IRQ on first char or special, ext int enable
SCC: port B reg 9 write 0x0a Master IRQ enable, no-vector mode
SCC: port B reg 11 write 0x70 Rx clock = DPLL output, Tx clock = BR generator
SCC: port B reg 14 write 0x21 Enter search mode, BR generator enable
SCC: port B reg 5 write 0x60 Tx 8 bits/char
SCC: port B reg 6 write 0x2a SDLC address
SCC: port B reg 0 read 0x00
SCC: port B reg 15 write 0x88 DCD interrupt enable, break/abort interrupt enable
(repeats)
SCC: port B reg 1 read 0x09
SCC: port B reg 3 write 0xd0
SCC: port B reg 3 write 0xdd Rx 8 bits, enter hunt mode, CRC enable, address search mode, Rx enable
SCC: port B reg 15 write 0x08 DCD interrupt enable
SCC: port B reg 0 read 0x00
SCC: port B reg 15 write 0x88
System 7:
SCC: port B reg 9 write 0x40 Channel Reset B
SCC: port B reg 4 write 0x20 SDLC mode
SCC: port B reg 10 write 0xe0 CRC preset + FM0
SCC: port B reg 6 write 0x00 SDLC address
SCC: port B reg 7 write 0x7e SDLC flag
SCC: port B reg 12 write 0x06 baud rate low
SCC: port B reg 13 write 0x00 baud rate high
SCC: port B reg 14 write 0xc0 Set FM mode
SCC: port B reg 3 write 0xdd Rx 8 bits, enter hunt mode, CRC enable, address search mode, Rx enable
SCC: port B reg 2 write 0x00 interrupt vector 0
SCC: port B reg 15 write 0x08 DCD interrupt enable
SCC: port B reg 1 write 0x09 Rx IRQ on first char or special, ext int enable
SCC: port B reg 9 write 0x0a Master IRQ enable, no-vector mode
SCC: port B reg 11 write 0x70 Rx clock = DPLL output, Tx clock = BR generator
SCC: port B reg 14 write 0x21 Enter search mode, BR generator enable
SCC: port B reg 5 write 0x60 Tx 8 bits/char
SCC: port B reg 6 write 0x01 SDLC address
SCC: port B reg 3 write 0xdd Rx 8 bits, enter hunt mode, CRC enable, address search mode, Rx enable
(repeats)
SCC: port B reg 0 read 0x00
SCC: port B reg 15 write 0x88 DCD interrupt enable, break/abort interrupt enable
SCC: port B reg 15 write 0x08 DCD interrupt enable
SCC: port B reg 1 read 0x09 Rx IRQ on first char or special, ext int enable
SCC: port B reg 3 write 0xdd Rx 8 bits, enter hunt mode, CRC enable, address search mode, Rx enable
*/