summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/machine/pc16552d.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/machine/pc16552d.c')
-rw-r--r--trunk/src/emu/machine/pc16552d.c439
1 files changed, 439 insertions, 0 deletions
diff --git a/trunk/src/emu/machine/pc16552d.c b/trunk/src/emu/machine/pc16552d.c
new file mode 100644
index 00000000000..4595d48a85f
--- /dev/null
+++ b/trunk/src/emu/machine/pc16552d.c
@@ -0,0 +1,439 @@
+/*
+ National Semiconductor PC16552D
+ Dual Universal Asynchronous Receiver/Transmitter with FIFOs
+
+ Written by Ville Linde
+*/
+
+#include "emu.h"
+#include "pc16552d.h"
+
+#define REG_RECV_BUFFER 0x0 // Read
+#define REG_XMIT_HOLD 0x0 // Write
+#define REG_INT_ENABLE 0x1
+#define REG_FIFO_CTRL 0x2 // Write
+#define REG_LINE_CTRL 0x3
+#define REG_MODEL_CTRL 0x4
+#define REG_LINE_STATUS 0x5
+#define REG_MODEM_STATUS 0x6
+#define REG_SCRATCH 0x7
+#define REG_DIV_LATCH_LSB 0x0 // When DLAB == 1
+#define REG_DIV_LATCH_MSB 0x1 // When DLAB == 1
+#define REG_ALT_FUNCTION 0x2 // When DLAB == 1
+
+#define LINE_CTRL_DLAB 0x80
+
+#define IRQ_RX_LINE_STATUS 0x1
+#define IRQ_RX_DATA_AVAILABLE 0x2
+#define IRQ_CHARACTER_TIMEOUT 0x4
+#define IRQ_TX_HOLDING_REG_EMPTY 0x8
+#define IRQ_MODEM_STATUS 0x10
+
+#define INT_ENABLE_RX_DATA 0x01
+#define INT_ENABLE_TX_EMPTY 0x02
+#define INT_ENABLE_RX_LINE_STATUS 0x04
+#define INT_ENABLE_MODEM_STATUS 0x08
+
+typedef struct
+{
+ UINT16 divisor;
+ UINT8 reg[8];
+ UINT8 rx_fifo[16];
+ UINT8 tx_fifo[16];
+ int pending_interrupt;
+ int rx_fifo_read_ptr;
+ int rx_fifo_write_ptr;
+ int rx_fifo_num;
+ int tx_fifo_read_ptr;
+ int tx_fifo_write_ptr;
+ int tx_fifo_num;
+ emu_timer *tx_fifo_timer;
+} PC16552D_CHANNEL;
+
+typedef struct
+{
+ PC16552D_CHANNEL ch[2];
+ int frequency;
+ void (* irq_handler)(running_machine &machine, int channel, int value);
+ void (* tx_callback)(int channel, int count, UINT8* data);
+} PC16552D_REGS;
+
+#define MAX_PC16552D_CHIPS 4
+
+static PC16552D_REGS duart[MAX_PC16552D_CHIPS];
+
+
+
+static const int rx_trigger_level[4] = { 1, 4, 8, 14 };
+
+
+static void check_interrupts(running_machine &machine, int chip, int channel)
+{
+ PC16552D_CHANNEL *ch = &duart[chip].ch[channel];
+ int signal = 0;
+
+ if (ch->pending_interrupt != 0)
+ {
+ if (((ch->reg[REG_INT_ENABLE] & INT_ENABLE_RX_DATA) && (ch->pending_interrupt & IRQ_RX_DATA_AVAILABLE)) ||
+ ((ch->reg[REG_INT_ENABLE] & INT_ENABLE_TX_EMPTY) && (ch->pending_interrupt & IRQ_TX_HOLDING_REG_EMPTY)) ||
+ ((ch->reg[REG_INT_ENABLE] & INT_ENABLE_RX_LINE_STATUS) && (ch->pending_interrupt & IRQ_RX_LINE_STATUS)) ||
+ ((ch->reg[REG_INT_ENABLE] & INT_ENABLE_MODEM_STATUS) && (ch->pending_interrupt & IRQ_MODEM_STATUS)))
+ {
+ signal = 1;
+ }
+ }
+
+ if (duart[chip].irq_handler != NULL)
+ {
+ duart[chip].irq_handler(machine, channel, signal ? ASSERT_LINE : CLEAR_LINE);
+ }
+}
+
+static void duart_push_rx_fifo(running_machine &machine, int chip, int channel, UINT8 data)
+{
+ PC16552D_CHANNEL *ch = &duart[chip].ch[channel];
+
+ if (ch->rx_fifo_num >= 16)
+ {
+ printf("duart_push_rx_fifo: %d, %d, %02X, FIFO overflow\n", chip, channel, data);
+ return;
+ }
+
+ ch->rx_fifo[ch->rx_fifo_write_ptr++] = data;
+ if (ch->rx_fifo_write_ptr == 16)
+ {
+ ch->rx_fifo_write_ptr = 0;
+ }
+ ch->rx_fifo_num++;
+
+ if (ch->rx_fifo_num == rx_trigger_level[(ch->reg[REG_FIFO_CTRL] >> 6) & 3])
+ {
+ ch->pending_interrupt |= IRQ_RX_DATA_AVAILABLE; // INT ID: received data available
+
+ check_interrupts(machine, chip, channel);
+ }
+}
+
+static UINT8 duart_pop_rx_fifo(running_machine &machine, int chip, int channel)
+{
+ UINT8 r;
+ PC16552D_CHANNEL *ch = &duart[chip].ch[channel];
+
+ if (ch->rx_fifo_num == 0)
+ {
+ printf("duart_pop_rx_fifo: %d, %d, FIFO underflow\n", chip, channel);
+ return 0;
+ }
+
+ r = ch->rx_fifo[ch->rx_fifo_read_ptr++];
+ if (ch->rx_fifo_read_ptr == 16)
+ {
+ ch->rx_fifo_read_ptr = 0;
+ }
+ ch->rx_fifo_num--;
+
+ if (ch->rx_fifo_num < rx_trigger_level[(ch->reg[REG_FIFO_CTRL] >> 6) & 3])
+ {
+ ch->pending_interrupt &= ~IRQ_RX_DATA_AVAILABLE;
+
+ check_interrupts(machine, chip, channel);
+ }
+
+ return r;
+}
+
+static TIMER_CALLBACK( tx_fifo_timer_callback )
+{
+ PC16552D_CHANNEL *ch;
+ int chip = param >> 1;
+ int channel = param & 1;
+
+ ch = &duart[chip].ch[channel];
+
+ if (duart[chip].tx_callback)
+ duart[chip].tx_callback(channel, ch->tx_fifo_num, ch->tx_fifo);
+
+ ch->tx_fifo_num = 0;
+
+ // set transmitter empty interrupt
+ ch->pending_interrupt |= IRQ_TX_HOLDING_REG_EMPTY;
+ check_interrupts(machine, chip, channel);
+
+ duart[chip].ch[channel].tx_fifo_timer->adjust(attotime::never, (chip * 2) + channel);
+}
+
+static void duart_push_tx_fifo(int chip, int channel, UINT8 data)
+{
+ attotime period;
+ PC16552D_CHANNEL *ch = &duart[chip].ch[channel];
+
+ ch->tx_fifo[ch->tx_fifo_num] = data;
+ ch->tx_fifo_num++;
+
+ period = attotime::from_hz(duart[chip].frequency) * (ch->divisor * 16 * 16 * 8);
+
+ duart[chip].ch[channel].tx_fifo_timer->adjust(period, (chip * 2) + channel);
+}
+
+#ifdef UNUSED_FUNCTION
+static UINT8 duart_pop_tx_fifo(int chip, int channel, UINT8 data)
+{
+ return 0;
+}
+#endif
+
+
+static UINT8 duart_r(running_machine &machine, int chip, int reg)
+{
+ int channel = (reg >> 3) & 1;
+ PC16552D_CHANNEL *ch = &duart[chip].ch[channel];
+ reg &= 7;
+
+// printf("duart_r: chip %d, ch %d, reg %d\n", chip, channel, reg);
+
+ switch (reg)
+ {
+ case 0:
+ {
+ if (ch->reg[REG_LINE_CTRL] & LINE_CTRL_DLAB)
+ {
+ // Divisor Latch (LSB)
+ return ch->divisor & 0xff;
+ }
+ else
+ {
+ // Receiver Buffer
+ ch->pending_interrupt &= ~IRQ_RX_DATA_AVAILABLE;
+
+ check_interrupts(machine, chip, channel);
+
+ return duart_pop_rx_fifo(machine, chip, channel);
+ }
+ }
+ case 1:
+ {
+ if (ch->reg[REG_LINE_CTRL] & LINE_CTRL_DLAB)
+ {
+ // Divisor Latch (MSB)
+ return (ch->divisor >> 8) & 0xff;
+ }
+ else
+ {
+
+ }
+ break;
+ }
+
+ case 2:
+ {
+ if (ch->reg[REG_LINE_CTRL] & LINE_CTRL_DLAB)
+ {
+ // Alternate Function
+ }
+ else
+ {
+ // Interrupt Identification Register
+ int i;
+ UINT8 r = 0x01;
+
+ for (i=0; i < 5; i++)
+ {
+ if (ch->pending_interrupt & (1 << i))
+ {
+ switch (i)
+ {
+ case 0: r = 0x06; break; // Receiver Line Status
+ case 1: r = 0x04; break; // Received Data Available
+ case 2: r = 0x0c; break; // Character Timeout Indication
+ case 3: r = 0x02; break; // Transmitter Holding Register Empty
+ case 4: r = 0x00; break; // MODEM Status
+ }
+ break;
+ }
+ }
+
+ if (ch->reg[REG_FIFO_CTRL] & 1)
+ {
+ r |= 0xc0;
+ }
+
+ return r;
+ }
+ break;
+ }
+
+ case 5: // Line Status Register
+ {
+ UINT8 r = 0;
+
+ // set Data Ready flag
+ if (ch->rx_fifo_num > 0)
+ {
+ r |= 0x1;
+ }
+
+ // set Transmitter Holding Register Empty flag
+ if (ch->tx_fifo_num == 0)
+ {
+ r |= 0x20;
+ }
+
+ // set Transmitter Empty flag
+ if (ch->tx_fifo_num == 0)
+ {
+ r |= 0x40;
+ }
+
+ return r;
+ }
+ }
+
+ return ch->reg[reg];
+}
+
+static void duart_w(running_machine &machine, int chip, int reg, UINT8 data)
+{
+ int channel = (reg >> 3) & 1;
+ PC16552D_CHANNEL *ch = &duart[chip].ch[channel];
+ reg &= 7;
+
+// printf("duart_w: chip %d, ch %d, reg %d, data %02X\n", chip, channel, reg, data);
+
+ switch (reg)
+ {
+ case 0:
+ {
+ if (ch->reg[REG_LINE_CTRL] & LINE_CTRL_DLAB)
+ {
+ // Divisor Latch (LSB)
+ ch->divisor &= 0xff00;
+ ch->divisor |= data;
+
+ return;
+ }
+ else
+ {
+ // Transmitter Holding Register
+ duart_push_tx_fifo(chip, channel, data);
+
+ ch->pending_interrupt &= ~IRQ_TX_HOLDING_REG_EMPTY;
+ check_interrupts(machine, chip, channel);
+
+ return;
+ }
+ }
+ case 1:
+ {
+ if (ch->reg[REG_LINE_CTRL] & LINE_CTRL_DLAB)
+ {
+ // Divisor Latch (MSB)
+ ch->divisor &= 0x00ff;
+ ch->divisor |= data << 8;
+
+ // printf("DUART %d %d bps\n", chip, duart[chip].frequency / (ch->divisor * 16));
+ return;
+ }
+ else
+ {
+ // Interrupt enable
+ ch->reg[REG_INT_ENABLE] = data;
+
+ check_interrupts(machine, chip, channel);
+ return;
+ }
+ }
+
+ case 2:
+ {
+ if (ch->reg[REG_LINE_CTRL] & LINE_CTRL_DLAB)
+ {
+ // Alternate Function
+
+ return;
+ }
+ else
+ {
+ // FIFO control
+ if (data & 0x02)
+ {
+ ch->rx_fifo_write_ptr = 0;
+ ch->rx_fifo_read_ptr = 0;
+ ch->rx_fifo_num = 0;
+ }
+ if (data & 0x04)
+ {
+ ch->tx_fifo_write_ptr = 0;
+ ch->tx_fifo_read_ptr = 0;
+ ch->tx_fifo_num = 0;
+
+
+ }
+
+ /*if (data & 0x1 && (ch->reg[reg] & 0x1) == 0)
+ {
+ // cause transmitter empty IRQ
+ ch->pending_interrupt |= IRQ_TX_HOLDING_REG_EMPTY;
+
+ check_interrupts(machine, chip, channel);
+ }
+ */
+ }
+ break;
+ }
+ }
+
+ ch->reg[reg] = data;
+}
+
+/*****************************************************************************/
+
+void pc16552d_init(running_machine &machine, int chip, int frequency, void (* irq_handler)(running_machine &machine, int channel, int value), void (* tx_callback)(int channel, int count, UINT8* data))
+{
+ memset(&duart[chip], 0, sizeof(PC16552D_REGS));
+
+ duart[chip].frequency = frequency;
+ duart[chip].irq_handler = irq_handler;
+ duart[chip].tx_callback = tx_callback;
+
+ // clear interrupts
+ duart[chip].ch[0].pending_interrupt = 0;
+ duart[chip].ch[1].pending_interrupt = 0;
+
+ // allocate transmit timers
+ duart[chip].ch[0].tx_fifo_timer = machine.scheduler().timer_alloc(FUNC(tx_fifo_timer_callback));
+ duart[chip].ch[0].tx_fifo_timer->adjust(attotime::never, (chip * 2) + 0);
+
+ duart[chip].ch[1].tx_fifo_timer = machine.scheduler().timer_alloc(FUNC(tx_fifo_timer_callback));
+ duart[chip].ch[1].tx_fifo_timer->adjust(attotime::never, (chip * 2) + 1);
+}
+
+void pc16552d_rx_data(running_machine &machine, int chip, int channel, UINT8 data)
+{
+ if (duart[chip].ch[channel].reg[REG_FIFO_CTRL] & 0x01) // RCVR & XMIT FIFO enable
+ {
+ duart_push_rx_fifo(machine, chip, channel, data);
+ }
+}
+
+/*****************************************************************************/
+/* Read/Write handlers */
+
+READ8_HANDLER(pc16552d_0_r)
+{
+ return duart_r(space->machine(), 0, offset);
+}
+
+WRITE8_HANDLER(pc16552d_0_w)
+{
+ duart_w(space->machine(), 0, offset, data);
+}
+
+READ8_HANDLER(pc16552d_1_r)
+{
+ return duart_r(space->machine(), 1, offset);
+}
+
+WRITE8_HANDLER(pc16552d_1_w)
+{
+ duart_w(space->machine(), 1, offset, data);
+}