summaryrefslogtreecommitdiffstats
path: root/hw/char/exynos4210_uart.c
diff options
context:
space:
mode:
authorGuenter Roeck2020-01-23 16:22:41 +0100
committerPeter Maydell2020-01-23 17:34:15 +0100
commit3a5d3a6f58ddd4027b9bcf6fe58f2a0224712c07 (patch)
treee5bb0d70b4858bd8f715df1bdb3bcbbaec8cf4b6 /hw/char/exynos4210_uart.c
parenthw/char/exynos4210_uart: Implement post_load function (diff)
downloadqemu-3a5d3a6f58ddd4027b9bcf6fe58f2a0224712c07.tar.gz
qemu-3a5d3a6f58ddd4027b9bcf6fe58f2a0224712c07.tar.xz
qemu-3a5d3a6f58ddd4027b9bcf6fe58f2a0224712c07.zip
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>
Diffstat (limited to 'hw/char/exynos4210_uart.c')
-rw-r--r--hw/char/exynos4210_uart.c117
1 files changed, 92 insertions, 25 deletions
diff --git a/hw/char/exynos4210_uart.c b/hw/char/exynos4210_uart.c
index 6ffbaddfff..363393cc75 100644
--- a/hw/char/exynos4210_uart.c
+++ b/hw/char/exynos4210_uart.c
@@ -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);