aboutsummaryrefslogtreecommitdiff
path: root/riscv/ns16550.cc
diff options
context:
space:
mode:
Diffstat (limited to 'riscv/ns16550.cc')
-rw-r--r--riscv/ns16550.cc315
1 files changed, 315 insertions, 0 deletions
diff --git a/riscv/ns16550.cc b/riscv/ns16550.cc
new file mode 100644
index 0000000..37af568
--- /dev/null
+++ b/riscv/ns16550.cc
@@ -0,0 +1,315 @@
+#include <sys/time.h>
+#include "devices.h"
+#include "processor.h"
+#include "term.h"
+
+#define UART_QUEUE_SIZE 64
+
+#define UART_RX 0 /* In: Receive buffer */
+#define UART_TX 0 /* Out: Transmit buffer */
+
+#define UART_IER 1 /* Out: Interrupt Enable Register */
+#define UART_IER_MSI 0x08 /* Enable Modem status interrupt */
+#define UART_IER_RLSI 0x04 /* Enable receiver line status interrupt */
+#define UART_IER_THRI 0x02 /* Enable Transmitter holding register int. */
+#define UART_IER_RDI 0x01 /* Enable receiver data interrupt */
+
+#define UART_IIR 2 /* In: Interrupt ID Register */
+#define UART_IIR_NO_INT 0x01 /* No interrupts pending */
+#define UART_IIR_ID 0x0e /* Mask for the interrupt ID */
+#define UART_IIR_MSI 0x00 /* Modem status interrupt */
+#define UART_IIR_THRI 0x02 /* Transmitter holding register empty */
+#define UART_IIR_RDI 0x04 /* Receiver data interrupt */
+#define UART_IIR_RLSI 0x06 /* Receiver line status interrupt */
+
+#define UART_IIR_TYPE_BITS 0xc0
+
+#define UART_FCR 2 /* Out: FIFO Control Register */
+#define UART_FCR_ENABLE_FIFO 0x01 /* Enable the FIFO */
+#define UART_FCR_CLEAR_RCVR 0x02 /* Clear the RCVR FIFO */
+#define UART_FCR_CLEAR_XMIT 0x04 /* Clear the XMIT FIFO */
+#define UART_FCR_DMA_SELECT 0x08 /* For DMA applications */
+
+#define UART_LCR 3 /* Out: Line Control Register */
+#define UART_LCR_DLAB 0x80 /* Divisor latch access bit */
+#define UART_LCR_SBC 0x40 /* Set break control */
+#define UART_LCR_SPAR 0x20 /* Stick parity (?) */
+#define UART_LCR_EPAR 0x10 /* Even parity select */
+#define UART_LCR_PARITY 0x08 /* Parity Enable */
+#define UART_LCR_STOP 0x04 /* Stop bits: 0=1 bit, 1=2 bits */
+
+#define UART_MCR 4 /* Out: Modem Control Register */
+#define UART_MCR_LOOP 0x10 /* Enable loopback test mode */
+#define UART_MCR_OUT2 0x08 /* Out2 complement */
+#define UART_MCR_OUT1 0x04 /* Out1 complement */
+#define UART_MCR_RTS 0x02 /* RTS complement */
+#define UART_MCR_DTR 0x01 /* DTR complement */
+
+#define UART_LSR 5 /* In: Line Status Register */
+#define UART_LSR_FIFOE 0x80 /* Fifo error */
+#define UART_LSR_TEMT 0x40 /* Transmitter empty */
+#define UART_LSR_THRE 0x20 /* Transmit-hold-register empty */
+#define UART_LSR_BI 0x10 /* Break interrupt indicator */
+#define UART_LSR_FE 0x08 /* Frame error indicator */
+#define UART_LSR_PE 0x04 /* Parity error indicator */
+#define UART_LSR_OE 0x02 /* Overrun error indicator */
+#define UART_LSR_DR 0x01 /* Receiver data ready */
+#define UART_LSR_BRK_ERROR_BITS 0x1E /* BI, FE, PE, OE bits */
+
+#define UART_MSR 6 /* In: Modem Status Register */
+#define UART_MSR_DCD 0x80 /* Data Carrier Detect */
+#define UART_MSR_RI 0x40 /* Ring Indicator */
+#define UART_MSR_DSR 0x20 /* Data Set Ready */
+#define UART_MSR_CTS 0x10 /* Clear to Send */
+#define UART_MSR_DDCD 0x08 /* Delta DCD */
+#define UART_MSR_TERI 0x04 /* Trailing edge ring indicator */
+#define UART_MSR_DDSR 0x02 /* Delta DSR */
+#define UART_MSR_DCTS 0x01 /* Delta CTS */
+#define UART_MSR_ANY_DELTA 0x0F /* Any of the delta bits! */
+
+#define UART_SCR 7 /* I/O: Scratch Register */
+
+ns16550_t::ns16550_t(class bus_t *bus, abstract_interrupt_controller_t *intctrl,
+ uint32_t interrupt_id, uint32_t reg_shift, uint32_t reg_io_width)
+ : bus(bus), intctrl(intctrl), interrupt_id(interrupt_id), reg_shift(reg_shift), reg_io_width(reg_io_width)
+{
+ ier = 0;
+ iir = UART_IIR_NO_INT;
+ fcr = 0;
+ lcr = 0;
+ lsr = UART_LSR_TEMT | UART_LSR_THRE;
+ msr = UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS;
+ dll = 0x0C;
+ mcr = UART_MCR_OUT2;
+ scr = 0;
+}
+
+void ns16550_t::update_interrupt(void)
+{
+ uint8_t interrupts = 0;
+
+ /* Handle clear rx */
+ if (lcr & UART_FCR_CLEAR_RCVR) {
+ lcr &= ~UART_FCR_CLEAR_RCVR;
+ while (!rx_queue.empty()) {
+ rx_queue.pop();
+ }
+ lsr &= ~UART_LSR_DR;
+ }
+
+ /* Handle clear tx */
+ if (lcr & UART_FCR_CLEAR_XMIT) {
+ lcr &= ~UART_FCR_CLEAR_XMIT;
+ lsr |= UART_LSR_TEMT | UART_LSR_THRE;
+ }
+
+ /* Data ready and rcv interrupt enabled ? */
+ if ((ier & UART_IER_RDI) && (lsr & UART_LSR_DR)) {
+ interrupts |= UART_IIR_RDI;
+ }
+
+ /* Transmitter empty and interrupt enabled ? */
+ if ((ier & UART_IER_THRI) && (lsr & UART_LSR_TEMT)) {
+ interrupts |= UART_IIR_THRI;
+ }
+
+ /* Now update the interrup line, if necessary */
+ if (!interrupts) {
+ iir = UART_IIR_NO_INT;
+ intctrl->set_interrupt_level(interrupt_id, 0);
+ } else {
+ iir = interrupts;
+ intctrl->set_interrupt_level(interrupt_id, 1);
+ }
+
+ /*
+ * If the OS disabled the tx interrupt, we know that there is nothing
+ * more to transmit.
+ */
+ if (!(ier & UART_IER_THRI)) {
+ lsr |= UART_LSR_TEMT | UART_LSR_THRE;
+ }
+}
+
+uint8_t ns16550_t::rx_byte(void)
+{
+ uint8_t ret = 0;
+
+ if (rx_queue.empty()) {
+ lsr &= ~UART_LSR_DR;
+ return 0;
+ }
+
+ /* Break issued ? */
+ if (lsr & UART_LSR_BI) {
+ lsr &= ~UART_LSR_BI;
+ return 0;
+ }
+
+ ret = rx_queue.front();
+ rx_queue.pop();
+ if (rx_queue.empty()) {
+ lsr &= ~UART_LSR_DR;
+ }
+
+ return ret;
+}
+
+void ns16550_t::tx_byte(uint8_t val)
+{
+ lsr |= UART_LSR_TEMT | UART_LSR_THRE;
+ canonical_terminal_t::write(val);
+}
+
+bool ns16550_t::load(reg_t addr, size_t len, uint8_t* bytes)
+{
+ uint8_t val;
+ bool ret = true, update = false;
+
+ if (reg_io_width != len) {
+ return false;
+ }
+ addr >>= reg_shift;
+ addr &= 7;
+
+ switch (addr) {
+ case UART_RX:
+ if (lcr & UART_LCR_DLAB) {
+ val = dll;
+ } else {
+ val = rx_byte();
+ }
+ update = true;
+ break;
+ case UART_IER:
+ if (lcr & UART_LCR_DLAB) {
+ val = dlm;
+ } else {
+ val = ier;
+ }
+ break;
+ case UART_IIR:
+ val = iir | UART_IIR_TYPE_BITS;
+ break;
+ case UART_LCR:
+ val = lcr;
+ break;
+ case UART_MCR:
+ val = mcr;
+ break;
+ case UART_LSR:
+ val = lsr;
+ break;
+ case UART_MSR:
+ val = msr;
+ break;
+ case UART_SCR:
+ val = scr;
+ break;
+ default:
+ ret = false;
+ break;
+ };
+
+ if (ret) {
+ bytes[0] = val;
+ }
+ if (update) {
+ update_interrupt();
+ }
+
+ return ret;
+}
+
+bool ns16550_t::store(reg_t addr, size_t len, const uint8_t* bytes)
+{
+ uint8_t val;
+ bool ret = true, update = false;
+
+ if (reg_io_width != len) {
+ return false;
+ }
+ addr >>= reg_shift;
+ addr &= 7;
+ val = bytes[0];
+
+ switch (addr) {
+ case UART_TX:
+ update = true;
+
+ if (lcr & UART_LCR_DLAB) {
+ dll = val;
+ break;
+ }
+
+ /* Loopback mode */
+ if (mcr & UART_MCR_LOOP) {
+ if (rx_queue.size() < UART_QUEUE_SIZE) {
+ rx_queue.push(val);
+ lsr |= UART_LSR_DR;
+ }
+ break;
+ }
+
+ tx_byte(val);
+ break;
+ case UART_IER:
+ if (!(lcr & UART_LCR_DLAB)) {
+ ier = val & 0x0f;
+ } else {
+ dlm = val;
+ }
+ update = true;
+ break;
+ case UART_FCR:
+ fcr = val;
+ update = true;
+ break;
+ case UART_LCR:
+ lcr = val;
+ update = true;
+ break;
+ case UART_MCR:
+ mcr = val;
+ update = true;
+ break;
+ case UART_LSR:
+ /* Factory test */
+ break;
+ case UART_MSR:
+ /* Not used */
+ break;
+ case UART_SCR:
+ scr = val;
+ break;
+ default:
+ ret = false;
+ break;
+ };
+
+ if (update) {
+ update_interrupt();
+ }
+
+ return ret;
+}
+
+void ns16550_t::tick(void)
+{
+ int rc;
+
+ if (!(fcr & UART_FCR_ENABLE_FIFO) ||
+ (mcr & UART_MCR_LOOP) ||
+ (UART_QUEUE_SIZE <= rx_queue.size())) {
+ return;
+ }
+
+ rc = canonical_terminal_t::read();
+ if (rc < 0) {
+ return;
+ }
+
+ rx_queue.push((uint8_t)rc);
+ lsr |= UART_LSR_DR;
+ update_interrupt();
+}