hw/char/exynos4210_uart: Implement Rx FIFO level triggers and timeouts
The driver already implements a receive FIFO, but it does not handle receive FIFO trigger levels and timeout. Implement the missing functionality. Signed-off-by: Guenter Roeck <linux@roeck-us.net> Message-id: 20200123052540.6132-7-linux@roeck-us.net Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
parent
c9d3396d80
commit
3a5d3a6f58
@ -24,6 +24,7 @@
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "chardev/char-fe.h"
|
||||
#include "chardev/char-serial.h"
|
||||
|
||||
@ -118,6 +119,7 @@ static const Exynos4210UartReg exynos4210_uart_regs[] = {
|
||||
#define ULCON_STOP_BIT_SHIFT 1
|
||||
|
||||
/* UART Tx/Rx Status */
|
||||
#define UTRSTAT_Rx_TIMEOUT 0x8
|
||||
#define UTRSTAT_TRANSMITTER_EMPTY 0x4
|
||||
#define UTRSTAT_Tx_BUFFER_EMPTY 0x2
|
||||
#define UTRSTAT_Rx_BUFFER_DATA_READY 0x1
|
||||
@ -147,6 +149,9 @@ typedef struct Exynos4210UartState {
|
||||
Exynos4210UartFIFO rx;
|
||||
Exynos4210UartFIFO tx;
|
||||
|
||||
QEMUTimer *fifo_timeout_timer;
|
||||
uint64_t wordtime; /* word time in ns */
|
||||
|
||||
CharBackend chr;
|
||||
qemu_irq irq;
|
||||
|
||||
@ -209,15 +214,12 @@ static void fifo_reset(Exynos4210UartFIFO *q)
|
||||
q->rp = 0;
|
||||
}
|
||||
|
||||
static uint32_t exynos4210_uart_Tx_FIFO_trigger_level(const Exynos4210UartState *s)
|
||||
static uint32_t exynos4210_uart_FIFO_trigger_level(uint32_t channel,
|
||||
uint32_t reg)
|
||||
{
|
||||
uint32_t level = 0;
|
||||
uint32_t reg;
|
||||
uint32_t level;
|
||||
|
||||
reg = (s->reg[I_(UFCON)] & UFCON_Tx_FIFO_TRIGGER_LEVEL) >>
|
||||
UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT;
|
||||
|
||||
switch (s->channel) {
|
||||
switch (channel) {
|
||||
case 0:
|
||||
level = reg * 32;
|
||||
break;
|
||||
@ -231,12 +233,34 @@ static uint32_t exynos4210_uart_Tx_FIFO_trigger_level(const Exynos4210UartState
|
||||
break;
|
||||
default:
|
||||
level = 0;
|
||||
trace_exynos_uart_channel_error(s->channel);
|
||||
trace_exynos_uart_channel_error(channel);
|
||||
break;
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
exynos4210_uart_Tx_FIFO_trigger_level(const Exynos4210UartState *s)
|
||||
{
|
||||
uint32_t reg;
|
||||
|
||||
reg = (s->reg[I_(UFCON)] & UFCON_Tx_FIFO_TRIGGER_LEVEL) >>
|
||||
UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT;
|
||||
|
||||
return exynos4210_uart_FIFO_trigger_level(s->channel, reg);
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
exynos4210_uart_Rx_FIFO_trigger_level(const Exynos4210UartState *s)
|
||||
{
|
||||
uint32_t reg;
|
||||
|
||||
reg = ((s->reg[I_(UFCON)] & UFCON_Rx_FIFO_TRIGGER_LEVEL) >>
|
||||
UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT) + 1;
|
||||
|
||||
return exynos4210_uart_FIFO_trigger_level(s->channel, reg);
|
||||
}
|
||||
|
||||
static void exynos4210_uart_update_irq(Exynos4210UartState *s)
|
||||
{
|
||||
/*
|
||||
@ -244,13 +268,25 @@ static void exynos4210_uart_update_irq(Exynos4210UartState *s)
|
||||
* transmit FIFO is smaller than the trigger level.
|
||||
*/
|
||||
if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
|
||||
|
||||
uint32_t count = (s->reg[I_(UFSTAT)] & UFSTAT_Tx_FIFO_COUNT) >>
|
||||
UFSTAT_Tx_FIFO_COUNT_SHIFT;
|
||||
|
||||
if (count <= exynos4210_uart_Tx_FIFO_trigger_level(s)) {
|
||||
s->reg[I_(UINTSP)] |= UINTSP_TXD;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rx interrupt if trigger level is reached or if rx timeout
|
||||
* interrupt is disabled and there is data in the receive buffer
|
||||
*/
|
||||
count = fifo_elements_number(&s->rx);
|
||||
if ((count && !(s->reg[I_(UCON)] & 0x80)) ||
|
||||
count >= exynos4210_uart_Rx_FIFO_trigger_level(s)) {
|
||||
s->reg[I_(UINTSP)] |= UINTSP_RXD;
|
||||
timer_del(s->fifo_timeout_timer);
|
||||
}
|
||||
} else if (s->reg[I_(UTRSTAT)] & UTRSTAT_Rx_BUFFER_DATA_READY) {
|
||||
s->reg[I_(UINTSP)] |= UINTSP_RXD;
|
||||
}
|
||||
|
||||
s->reg[I_(UINTP)] = s->reg[I_(UINTSP)] & ~s->reg[I_(UINTM)];
|
||||
@ -264,6 +300,21 @@ static void exynos4210_uart_update_irq(Exynos4210UartState *s)
|
||||
}
|
||||
}
|
||||
|
||||
static void exynos4210_uart_timeout_int(void *opaque)
|
||||
{
|
||||
Exynos4210UartState *s = opaque;
|
||||
|
||||
trace_exynos_uart_rx_timeout(s->channel, s->reg[I_(UTRSTAT)],
|
||||
s->reg[I_(UINTSP)]);
|
||||
|
||||
if ((s->reg[I_(UTRSTAT)] & UTRSTAT_Rx_BUFFER_DATA_READY) ||
|
||||
(s->reg[I_(UCON)] & (1 << 11))) {
|
||||
s->reg[I_(UINTSP)] |= UINTSP_RXD;
|
||||
s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_TIMEOUT;
|
||||
exynos4210_uart_update_irq(s);
|
||||
}
|
||||
}
|
||||
|
||||
static void exynos4210_uart_update_parameters(Exynos4210UartState *s)
|
||||
{
|
||||
int speed, parity, data_bits, stop_bits;
|
||||
@ -302,10 +353,24 @@ static void exynos4210_uart_update_parameters(Exynos4210UartState *s)
|
||||
ssp.data_bits = data_bits;
|
||||
ssp.stop_bits = stop_bits;
|
||||
|
||||
s->wordtime = NANOSECONDS_PER_SECOND * (data_bits + stop_bits + 1) / speed;
|
||||
|
||||
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
|
||||
|
||||
trace_exynos_uart_update_params(
|
||||
s->channel, speed, parity, data_bits, stop_bits);
|
||||
s->channel, speed, parity, data_bits, stop_bits, s->wordtime);
|
||||
}
|
||||
|
||||
static void exynos4210_uart_rx_timeout_set(Exynos4210UartState *s)
|
||||
{
|
||||
if (s->reg[I_(UCON)] & 0x80) {
|
||||
uint32_t timeout = ((s->reg[I_(UCON)] >> 12) & 0x0f) * s->wordtime;
|
||||
|
||||
timer_mod(s->fifo_timeout_timer,
|
||||
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout);
|
||||
} else {
|
||||
timer_del(s->fifo_timeout_timer);
|
||||
}
|
||||
}
|
||||
|
||||
static void exynos4210_uart_write(void *opaque, hwaddr offset,
|
||||
@ -361,6 +426,10 @@ static void exynos4210_uart_write(void *opaque, hwaddr offset,
|
||||
exynos4210_uart_update_irq(s);
|
||||
break;
|
||||
case UTRSTAT:
|
||||
if (val & UTRSTAT_Rx_TIMEOUT) {
|
||||
s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_TIMEOUT;
|
||||
}
|
||||
break;
|
||||
case UERSTAT:
|
||||
case UFSTAT:
|
||||
case UMSTAT:
|
||||
@ -382,6 +451,7 @@ static void exynos4210_uart_write(void *opaque, hwaddr offset,
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t exynos4210_uart_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
@ -461,7 +531,6 @@ static int exynos4210_uart_can_receive(void *opaque)
|
||||
return fifo_empty_elements_number(&s->rx);
|
||||
}
|
||||
|
||||
|
||||
static void exynos4210_uart_receive(void *opaque, const uint8_t *buf, int size)
|
||||
{
|
||||
Exynos4210UartState *s = (Exynos4210UartState *)opaque;
|
||||
@ -469,24 +538,17 @@ static void exynos4210_uart_receive(void *opaque, const uint8_t *buf, int size)
|
||||
|
||||
if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
|
||||
if (fifo_empty_elements_number(&s->rx) < size) {
|
||||
for (i = 0; i < fifo_empty_elements_number(&s->rx); i++) {
|
||||
fifo_store(&s->rx, buf[i]);
|
||||
}
|
||||
size = fifo_empty_elements_number(&s->rx);
|
||||
s->reg[I_(UINTSP)] |= UINTSP_ERROR;
|
||||
s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
|
||||
} else {
|
||||
for (i = 0; i < size; i++) {
|
||||
fifo_store(&s->rx, buf[i]);
|
||||
}
|
||||
s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
|
||||
}
|
||||
/* XXX: Around here we maybe should check Rx trigger level */
|
||||
s->reg[I_(UINTSP)] |= UINTSP_RXD;
|
||||
for (i = 0; i < size; i++) {
|
||||
fifo_store(&s->rx, buf[i]);
|
||||
}
|
||||
exynos4210_uart_rx_timeout_set(s);
|
||||
} else {
|
||||
s->reg[I_(URXH)] = buf[0];
|
||||
s->reg[I_(UINTSP)] |= UINTSP_RXD;
|
||||
s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
|
||||
}
|
||||
s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
|
||||
|
||||
exynos4210_uart_update_irq(s);
|
||||
}
|
||||
@ -527,6 +589,7 @@ static int exynos4210_uart_post_load(void *opaque, int version_id)
|
||||
Exynos4210UartState *s = (Exynos4210UartState *)opaque;
|
||||
|
||||
exynos4210_uart_update_parameters(s);
|
||||
exynos4210_uart_rx_timeout_set(s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -588,6 +651,10 @@ static void exynos4210_uart_init(Object *obj)
|
||||
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
|
||||
Exynos4210UartState *s = EXYNOS4210_UART(dev);
|
||||
|
||||
s->fifo_timeout_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
|
||||
exynos4210_uart_timeout_int, s);
|
||||
s->wordtime = NANOSECONDS_PER_SECOND * 10 / 9600;
|
||||
|
||||
/* memory mapping */
|
||||
memory_region_init_io(&s->iomem, obj, &exynos4210_uart_ops, s,
|
||||
"exynos4210.uart", EXYNOS4210_UART_REGS_MEM_SIZE);
|
||||
|
@ -81,7 +81,7 @@ nrf51_uart_write(uint64_t addr, uint64_t value, unsigned int size) "addr 0x%" PR
|
||||
# exynos4210_uart.c
|
||||
exynos_uart_irq_raised(uint32_t channel, uint32_t reg) "UART%d: IRQ raised: 0x%08"PRIx32
|
||||
exynos_uart_irq_lowered(uint32_t channel) "UART%d: IRQ lowered"
|
||||
exynos_uart_update_params(uint32_t channel, int speed, uint8_t parity, int data, int stop) "UART%d: speed: %d, parity: %c, data bits: %d, stop bits: %d"
|
||||
exynos_uart_update_params(uint32_t channel, int speed, uint8_t parity, int data, int stop, uint64_t wordtime) "UART%d: speed: %d, parity: %c, data bits: %d, stop bits: %d wordtime: %"PRId64"ns"
|
||||
exynos_uart_write(uint32_t channel, uint32_t offset, const char *name, uint64_t val) "UART%d: <0x%04x> %s <- 0x%" PRIx64
|
||||
exynos_uart_read(uint32_t channel, uint32_t offset, const char *name, uint64_t val) "UART%d: <0x%04x> %s -> 0x%" PRIx64
|
||||
exynos_uart_rx_fifo_reset(uint32_t channel) "UART%d: Rx FIFO Reset"
|
||||
@ -94,3 +94,4 @@ exynos_uart_rx_error(uint32_t channel) "UART%d: Rx error"
|
||||
exynos_uart_wo_read(uint32_t channel, const char *name, uint32_t reg) "UART%d: Trying to read from WO register: %s [0x%04"PRIx32"]"
|
||||
exynos_uart_rxsize(uint32_t channel, uint32_t size) "UART%d: Rx FIFO size: %d"
|
||||
exynos_uart_channel_error(uint32_t channel) "Wrong UART channel number: %d"
|
||||
exynos_uart_rx_timeout(uint32_t channel, uint32_t stat, uint32_t intsp) "UART%d: Rx timeout stat=0x%x intsp=0x%x"
|
||||
|
Loading…
Reference in New Issue
Block a user