#include <sys/param.h>
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "uart_backend.h"
#include "uart_emul.h"
#define UART_FIFO_SIZE 16
#define UARTDR 0x00
#define UARTDR_RSR_SHIFT 8
#define UARTRSR 0x01
#define UARTRSR_OE (1 << 3)
#define UARTFR 0x06
#define UARTFR_TXFE (1 << 7)
#define UARTFR_RXFF (1 << 6)
#define UARTFR_TXFF (1 << 5)
#define UARTFR_RXFE (1 << 4)
#define UARTRTINTR (1 << 6)
#define UARTTXINTR (1 << 5)
#define UARTRXINTR (1 << 4)
#define UARTIBRD 0x09
#define UARTFBRD 0x0a
#define UARTFBRD_MASK 0x003f
#define UARTLCR_H 0x0b
#define UARTLCR_H_MASK 0x00ff
#define UARTLCR_H_FEN (1 << 4)
#define UARTCR 0x0c
#define UARTCR_MASK 0xffc7
#define UARTCR_LBE (1 << 7)
#define UARTIFLS 0x0d
#define UARTIFLS_MASK 0x003f
#define UARTIFLS_RXIFLSEL(x) (((x) >> 3) & 0x7)
#define UARTIFLS_TXIFLSEL(x) (((x) >> 0) & 0x7)
#define UARTIMSC 0x0e
#define UARTIMSC_MASK 0x07ff
#define UARTRIS 0x0f
#define UARTMIS 0x10
#define UARTICR 0x11
#define UARTPeriphID 0x00241011
#define UARTPeriphID0 0x3f8
#define UARTPeriphID0_VAL (((UARTPeriphID) >> 0) & 0xff)
#define UARTPeriphID1 0x3f9
#define UARTPeriphID1_VAL (((UARTPeriphID) >> 8) & 0xff)
#define UARTPeriphID2 0x3fa
#define UARTPeriphID2_VAL (((UARTPeriphID) >> 16) & 0xff)
#define UARTPeriphID3 0x3fb
#define UARTPeriphID3_VAL (((UARTPeriphID) >> 24) & 0xff)
#define UARTPCellID 0xb105f00d
#define UARTPCellID0 0x3fc
#define UARTPCellID0_VAL (((UARTPCellID) >> 0) & 0xff)
#define UARTPCellID1 0x3fd
#define UARTPCellID1_VAL (((UARTPCellID) >> 8) & 0xff)
#define UARTPCellID2 0x3fe
#define UARTPCellID2_VAL (((UARTPCellID) >> 16) & 0xff)
#define UARTPCellID3 0x3ff
#define UARTPCellID3_VAL (((UARTPCellID) >> 24) & 0xff)
struct uart_pl011_softc {
struct uart_softc *backend;
uint16_t irq_state;
uint16_t rsr;
uint16_t cr;
uint16_t ifls;
uint16_t imsc;
uint16_t lcr_h;
uint16_t ibrd;
uint16_t fbrd;
void *arg;
uart_intr_func_t intr_assert;
uart_intr_func_t intr_deassert;
};
static void
uart_reset(struct uart_pl011_softc *sc)
{
sc->ifls = 0x12;
uart_rxfifo_reset(sc->backend, 1);
}
static int
uart_rx_trigger_level(struct uart_pl011_softc *sc)
{
if ((sc->lcr_h & UARTLCR_H_FEN) != 0)
return (1);
switch (UARTIFLS_RXIFLSEL(sc->ifls)) {
case 0:
return (UART_FIFO_SIZE / 8);
case 1:
return (UART_FIFO_SIZE / 4);
case 2:
return (UART_FIFO_SIZE / 2);
case 3:
return (UART_FIFO_SIZE * 3 / 4);
case 4:
return (UART_FIFO_SIZE * 7 / 8);
default:
return (UART_FIFO_SIZE);
}
}
static void
uart_toggle_intr(struct uart_pl011_softc *sc)
{
if ((sc->irq_state & sc->imsc) == 0)
(*sc->intr_deassert)(sc->arg);
else
(*sc->intr_assert)(sc->arg);
}
static void
uart_drain(int fd __unused, enum ev_type ev, void *arg)
{
struct uart_pl011_softc *sc;
int old_size, trig_lvl;
bool loopback;
sc = arg;
assert(ev == EVF_READ);
uart_softc_lock(sc->backend);
old_size = uart_rxfifo_numchars(sc->backend);
loopback = (sc->cr & UARTCR_LBE) != 0;
uart_rxfifo_drain(sc->backend, loopback);
trig_lvl = uart_rx_trigger_level(sc);
if (old_size < trig_lvl &&
uart_rxfifo_numchars(sc->backend) >= trig_lvl)
sc->irq_state |= UARTRXINTR;
if (uart_rxfifo_numchars(sc->backend) > 0)
sc->irq_state |= UARTRTINTR;
if (!loopback)
uart_toggle_intr(sc);
uart_softc_unlock(sc->backend);
}
void
uart_pl011_write(struct uart_pl011_softc *sc, int offset, uint32_t value)
{
bool loopback;
uart_softc_lock(sc->backend);
switch (offset) {
case UARTDR:
loopback = (sc->cr & UARTCR_LBE) != 0;
if (uart_rxfifo_putchar(sc->backend, value & 0xff, loopback))
sc->rsr |= UARTRSR_OE;
sc->irq_state |= UARTTXINTR;
break;
case UARTRSR:
sc->rsr = 0;
break;
case UARTFR:
break;
case UARTIBRD:
sc->ibrd = value;
break;
case UARTFBRD:
sc->fbrd = value & UARTFBRD_MASK;
break;
case UARTLCR_H:
if (((sc->lcr_h ^ value) & UARTLCR_H_FEN) != 0) {
if ((value & UARTLCR_H_FEN) != 0) {
uart_rxfifo_reset(sc->backend, UART_FIFO_SIZE);
} else {
uart_rxfifo_reset(sc->backend, 1);
}
}
sc->lcr_h = value & UARTLCR_H_MASK;
break;
case UARTCR:
sc->cr = value & UARTCR_MASK;
break;
case UARTIFLS:
sc->ifls = value & UARTCR_MASK;
break;
case UARTIMSC:
sc->imsc = value & UARTIMSC_MASK;
break;
case UARTRIS:
case UARTMIS:
break;
case UARTICR:
sc->irq_state &= ~value;
break;
default:
break;
}
uart_toggle_intr(sc);
uart_softc_unlock(sc->backend);
}
uint32_t
uart_pl011_read(struct uart_pl011_softc *sc, int offset)
{
uint32_t reg;
int fifo_sz;
reg = 0;
uart_softc_lock(sc->backend);
switch (offset) {
case UARTDR:
reg = uart_rxfifo_getchar(sc->backend);
fifo_sz = uart_rxfifo_numchars(sc->backend);
if (fifo_sz < uart_rx_trigger_level(sc))
sc->irq_state &= ~UARTRXINTR;
if (fifo_sz == 0)
sc->irq_state &= ~UARTRTINTR;
reg |= sc->rsr << UARTDR_RSR_SHIFT;
sc->rsr &= UARTRSR_OE;
break;
case UARTRSR:
reg = sc->rsr;
break;
case UARTFR:
reg = UARTFR_TXFE;
fifo_sz = uart_rxfifo_numchars(sc->backend);
if (fifo_sz == UART_FIFO_SIZE)
reg |= UARTFR_RXFF;
else if (fifo_sz == 0)
reg |= UARTFR_RXFE;
break;
case UARTIBRD:
reg = sc->ibrd;
break;
case UARTFBRD:
reg = sc->fbrd;
break;
case UARTLCR_H:
reg = sc->lcr_h;
break;
case UARTCR:
reg = sc->cr;
break;
case UARTIMSC:
reg = sc->imsc;
break;
case UARTRIS:
reg = sc->irq_state;
break;
case UARTMIS:
reg = sc->irq_state & sc->imsc;
break;
case UARTICR:
reg = 0;
break;
case UARTPeriphID0:
reg = UARTPeriphID0_VAL;
break;
case UARTPeriphID1:
reg =UARTPeriphID1_VAL;
break;
case UARTPeriphID2:
reg = UARTPeriphID2_VAL;
break;
case UARTPeriphID3:
reg = UARTPeriphID3_VAL;
break;
case UARTPCellID0:
reg = UARTPCellID0_VAL;
break;
case UARTPCellID1:
reg = UARTPCellID1_VAL;
break;
case UARTPCellID2:
reg = UARTPCellID2_VAL;
break;
case UARTPCellID3:
reg = UARTPCellID3_VAL;
break;
default:
reg = 0;
break;
}
uart_toggle_intr(sc);
uart_softc_unlock(sc->backend);
return (reg);
}
struct uart_pl011_softc *
uart_pl011_init(uart_intr_func_t intr_assert, uart_intr_func_t intr_deassert,
void *arg)
{
struct uart_pl011_softc *sc;
sc = calloc(1, sizeof(struct uart_pl011_softc));
sc->arg = arg;
sc->intr_assert = intr_assert;
sc->intr_deassert = intr_deassert;
sc->backend = uart_init();
uart_reset(sc);
return (sc);
}
int
uart_pl011_tty_open(struct uart_pl011_softc *sc, const char *device)
{
return (uart_tty_open(sc->backend, device, uart_drain, sc));
}