diff options
Diffstat (limited to 'sim/lm32/dv-lm32uart.c')
-rw-r--r-- | sim/lm32/dv-lm32uart.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/sim/lm32/dv-lm32uart.c b/sim/lm32/dv-lm32uart.c new file mode 100644 index 0000000..f248df9 --- /dev/null +++ b/sim/lm32/dv-lm32uart.c @@ -0,0 +1,317 @@ +/* Lattice Mico32 UART model. + Contributed by Jon Beniston <jon@beniston.com> + + Copyright (C) 2009 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "sim-main.h" +#include "hw-main.h" +#include "sim-assert.h" + +#include <stdio.h> +#include <sys/time.h> + +struct lm32uart +{ + unsigned base; /* Base address of this UART. */ + unsigned limit; /* Limit address of this UART. */ + unsigned char rbr; + unsigned char thr; + unsigned char ier; + unsigned char iir; + unsigned char lcr; + unsigned char mcr; + unsigned char lsr; + unsigned char msr; + unsigned char div; + struct hw_event *event; +}; + +/* UART registers. */ + +#define LM32_UART_RBR 0x0 +#define LM32_UART_THR 0x0 +#define LM32_UART_IER 0x4 +#define LM32_UART_IIR 0x8 +#define LM32_UART_LCR 0xc +#define LM32_UART_MCR 0x10 +#define LM32_UART_LSR 0x14 +#define LM32_UART_MSR 0x18 +#define LM32_UART_DIV 0x1c + +#define LM32_UART_IER_RX_INT 0x1 +#define LM32_UART_IER_TX_INT 0x2 + +#define MICOUART_IIR_TXRDY 0x2 +#define MICOUART_IIR_RXRDY 0x4 + +#define LM32_UART_LSR_RX_RDY 0x01 +#define LM32_UART_LSR_TX_RDY 0x20 + +#define LM32_UART_LCR_WLS_MASK 0x3 +#define LM32_UART_LCR_WLS_5 0x0 +#define LM32_UART_LCR_WLS_6 0x1 +#define LM32_UART_LCR_WLS_7 0x2 +#define LM32_UART_LCR_WLS_8 0x3 + +/* UART ports. */ + +enum +{ + INT_PORT +}; + +static const struct hw_port_descriptor lm32uart_ports[] = { + {"int", INT_PORT, 0, output_port}, + {} +}; + +static void +do_uart_tx_event (struct hw *me, void *data) +{ + struct lm32uart *uart = hw_data (me); + char c; + + /* Generate interrupt when transmission is complete. */ + if (uart->ier & LM32_UART_IER_TX_INT) + { + /* Generate interrupt */ + hw_port_event (me, INT_PORT, 1); + } + + /* Indicate which interrupt has occured. */ + uart->iir = MICOUART_IIR_TXRDY; + + /* Indicate THR is empty. */ + uart->lsr |= LM32_UART_LSR_TX_RDY; + + /* Output the character in the THR. */ + c = (char) uart->thr; + + /* WLS field in LCR register specifies the number of bits to output. */ + switch (uart->lcr & LM32_UART_LCR_WLS_MASK) + { + case LM32_UART_LCR_WLS_5: + c &= 0x1f; + break; + case LM32_UART_LCR_WLS_6: + c &= 0x3f; + break; + case LM32_UART_LCR_WLS_7: + c &= 0x7f; + break; + } + printf ("%c", c); +} + +static unsigned +lm32uart_io_write_buffer (struct hw *me, + const void *source, + int space, unsigned_word base, unsigned nr_bytes) +{ + struct lm32uart *uart = hw_data (me); + int uart_reg; + const unsigned char *source_bytes = source; + int value = 0; + + HW_TRACE ((me, "write to 0x%08lx length %d with 0x%x", (long) base, + (int) nr_bytes, value)); + + if (nr_bytes == 4) + value = (source_bytes[0] << 24) + | (source_bytes[1] << 16) | (source_bytes[2] << 8) | (source_bytes[3]); + else + hw_abort (me, "write of unsupported number of bytes: %d.", nr_bytes); + + uart_reg = base - uart->base; + + switch (uart_reg) + { + case LM32_UART_THR: + /* Buffer the character to output. */ + uart->thr = value; + + /* Indicate the THR is full. */ + uart->lsr &= ~LM32_UART_LSR_TX_RDY; + + /* deassert interrupt when IER is loaded. */ + uart->iir &= ~MICOUART_IIR_TXRDY; + + /* schedule an event to output the character. */ + hw_event_queue_schedule (me, 1, do_uart_tx_event, 0); + + break; + case LM32_UART_IER: + uart->ier = value; + if ((value & LM32_UART_IER_TX_INT) + && (uart->lsr & LM32_UART_LSR_TX_RDY)) + { + /* hw_event_queue_schedule (me, 1, do_uart_tx_event, 0); */ + uart->lsr |= LM32_UART_LSR_TX_RDY; + uart->iir |= MICOUART_IIR_TXRDY; + hw_port_event (me, INT_PORT, 1); + } + else if ((value & LM32_UART_IER_TX_INT) == 0) + { + hw_port_event (me, INT_PORT, 0); + } + break; + case LM32_UART_IIR: + uart->iir = value; + break; + case LM32_UART_LCR: + uart->lcr = value; + break; + case LM32_UART_MCR: + uart->mcr = value; + break; + case LM32_UART_LSR: + uart->lsr = value; + break; + case LM32_UART_MSR: + uart->msr = value; + break; + case LM32_UART_DIV: + uart->div = value; + break; + default: + hw_abort (me, "write to invalid register address: 0x%x.", uart_reg); + } + + return nr_bytes; +} + +static unsigned +lm32uart_io_read_buffer (struct hw *me, + void *dest, + int space, unsigned_word base, unsigned nr_bytes) +{ + struct lm32uart *uart = hw_data (me); + int uart_reg; + int value; + unsigned char *dest_bytes = dest; + fd_set fd; + struct timeval tv; + + HW_TRACE ((me, "read 0x%08lx length %d", (long) base, (int) nr_bytes)); + + uart_reg = base - uart->base; + + switch (uart_reg) + { + case LM32_UART_RBR: + value = getchar (); + uart->lsr &= ~LM32_UART_LSR_RX_RDY; + break; + case LM32_UART_IER: + value = uart->ier; + break; + case LM32_UART_IIR: + value = uart->iir; + break; + case LM32_UART_LCR: + value = uart->lcr; + break; + case LM32_UART_MCR: + value = uart->mcr; + break; + case LM32_UART_LSR: + /* Check to see if any data waiting in stdin. */ + FD_ZERO (&fd); + FD_SET (fileno (stdin), &fd); + tv.tv_sec = 0; + tv.tv_usec = 1; + if (select (fileno (stdin) + 1, &fd, NULL, NULL, &tv)) + uart->lsr |= LM32_UART_LSR_RX_RDY; + value = uart->lsr; + break; + case LM32_UART_MSR: + value = uart->msr; + break; + case LM32_UART_DIV: + value = uart->div; + break; + default: + hw_abort (me, "read from invalid register address: 0x%x.", uart_reg); + } + + if (nr_bytes == 4) + { + dest_bytes[0] = value >> 24; + dest_bytes[1] = value >> 16; + dest_bytes[2] = value >> 8; + dest_bytes[3] = value; + } + else + hw_abort (me, "read of unsupported number of bytes: %d", nr_bytes); + + return nr_bytes; +} + +static void +attach_lm32uart_regs (struct hw *me, struct lm32uart *uart) +{ + unsigned_word attach_address; + int attach_space; + unsigned attach_size; + reg_property_spec reg; + + if (hw_find_property (me, "reg") == NULL) + hw_abort (me, "Missing \"reg\" property"); + if (!hw_find_reg_array_property (me, "reg", 0, ®)) + hw_abort (me, "\"reg\" property must contain three addr/size entries"); + hw_unit_address_to_attach_address (hw_parent (me), + ®.address, + &attach_space, &attach_address, me); + uart->base = attach_address; + hw_unit_size_to_attach_size (hw_parent (me), ®.size, &attach_size, me); + uart->limit = attach_address + (attach_size - 1); + hw_attach_address (hw_parent (me), + 0, attach_space, attach_address, attach_size, me); +} + +static void +lm32uart_finish (struct hw *me) +{ + struct lm32uart *uart; + int i; + + uart = HW_ZALLOC (me, struct lm32uart); + set_hw_data (me, uart); + set_hw_io_read_buffer (me, lm32uart_io_read_buffer); + set_hw_io_write_buffer (me, lm32uart_io_write_buffer); + set_hw_ports (me, lm32uart_ports); + + /* Attach ourself to our parent bus. */ + attach_lm32uart_regs (me, uart); + + /* Initialize the UART. */ + uart->rbr = 0; + uart->thr = 0; + uart->ier = 0; + uart->iir = 0; + uart->lcr = 0; + uart->mcr = 0; + uart->lsr = LM32_UART_LSR_TX_RDY; + uart->msr = 0; + uart->div = 0; /* By setting to zero, characters are output immediately. */ +} + +const struct hw_descriptor dv_lm32uart_descriptor[] = { + {"lm32uart", lm32uart_finish,}, + {NULL}, +}; |