#include "qemu/osdep.h" #include "hw/core/sysbus.h" #include "hw/core/irq.h" #include "hw/core/qdev-properties-system.h" #include "qemu/log.h" #include "qemu/module.h" #include "chardev/char-fe.h" #define TYPE_S5L8950X_UART "s5l8950x-uart" OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XUartState, S5L8950X_UART) #define S5L8950X_UART_RX_FIFO_SIZE 16 struct S5L8950XUartState { SysBusDevice parent_obj; MemoryRegion iomem; CharFrontend chr; uint32_t ulcon; uint32_t ucon; uint32_t ufcon; uint32_t umcon; uint32_t ubrdiv; uint32_t ufracval; uint8_t rx_fifo[S5L8950X_UART_RX_FIFO_SIZE]; int rx_count; qemu_irq irq; }; #define UART_ULCON 0x00 #define UART_UCON 0x04 #define UART_UFCON 0x08 #define UART_UMCON 0x0C #define UART_UTRSTAT 0x10 #define UART_UERSTAT 0x14 #define UART_UFSTAT 0x18 #define UART_UMSTAT 0x1C #define UART_UTXH 0x20 #define UART_URXH 0x24 #define UART_UBRDIV 0x28 #define UART_UFRACVAL 0x2C #define UART_UFCON_RX_FIFO_RESET BIT(1) #define UART_UFCON_TX_FIFO_RESET BIT(2) static void s5l8950x_uart_update_irq(S5L8950XUartState *s) { if (s->rx_count > 0) { qemu_irq_raise(s->irq); } else { qemu_irq_lower(s->irq); } } static uint64_t s5l8950x_uart_read(void *opaque, hwaddr offset, unsigned size) { S5L8950XUartState *s = S5L8950X_UART(opaque); uint32_t val = 0; switch (offset) { case UART_ULCON: val = s->ulcon; break; case UART_UCON: val = s->ucon; break; case UART_UFCON: val = s->ufcon; break; case UART_UMCON: val = s->umcon; break; case UART_UTRSTAT: /* Bit 0: rx ready, Bit 1: tx buf empty, Bit 2: tx empty */ val = 0x6 | (s->rx_count > 0 ? 1 : 0); break; case UART_UERSTAT: val = 0; break; case UART_UFSTAT: /* Return rx count in low bits, no tx fifo used here */ val = s->rx_count; break; case UART_UMSTAT: val = 0; break; case UART_UTXH: break; case UART_URXH: if (s->rx_count > 0) { val = s->rx_fifo[0]; s->rx_count--; memmove(s->rx_fifo, s->rx_fifo + 1, s->rx_count); s5l8950x_uart_update_irq(s); qemu_chr_fe_accept_input(&s->chr); } break; case UART_UBRDIV: val = s->ubrdiv; break; case UART_UFRACVAL: val = s->ufracval; break; default: qemu_log_mask(LOG_UNIMP, "%s: Unimplemented read at offset 0x%" HWADDR_PRIx "\n", __func__, offset); break; } return val; } static void s5l8950x_uart_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) { S5L8950XUartState *s = S5L8950X_UART(opaque); unsigned char c; switch (offset) { case UART_ULCON: s->ulcon = val; break; case UART_UCON: s->ucon = val; break; case UART_UFCON: if (val & UART_UFCON_RX_FIFO_RESET) { s->rx_count = 0; s5l8950x_uart_update_irq(s); qemu_chr_fe_accept_input(&s->chr); } /* FIFO reset bits are self-clearing; TX is unbuffered. */ s->ufcon = val & ~(UART_UFCON_RX_FIFO_RESET | UART_UFCON_TX_FIFO_RESET); break; case UART_UMCON: s->umcon = val; break; case UART_UTRSTAT: case UART_UERSTAT: case UART_UFSTAT: case UART_UMSTAT: /* Read-only registers */ break; case UART_UTXH: c = val & 0xFF; qemu_chr_fe_write_all(&s->chr, &c, 1); break; case UART_URXH: break; case UART_UBRDIV: s->ubrdiv = val; break; case UART_UFRACVAL: s->ufracval = val; break; default: qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write at offset 0x%" HWADDR_PRIx "\n", __func__, offset); break; } } static const MemoryRegionOps s5l8950x_uart_ops = { .read = s5l8950x_uart_read, .write = s5l8950x_uart_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 1, .max_access_size = 4, }, .impl = { .min_access_size = 1, .max_access_size = 4, }, }; static int s5l8950x_uart_can_receive(void *opaque) { S5L8950XUartState *s = S5L8950X_UART(opaque); return S5L8950X_UART_RX_FIFO_SIZE - s->rx_count; } static void s5l8950x_uart_receive(void *opaque, const uint8_t *buf, int size) { S5L8950XUartState *s = S5L8950X_UART(opaque); int i; for (i = 0; i < size && s->rx_count < S5L8950X_UART_RX_FIFO_SIZE; i++) { s->rx_fifo[s->rx_count++] = buf[i]; } s5l8950x_uart_update_irq(s); } static void s5l8950x_uart_reset(DeviceState *dev) { S5L8950XUartState *s = S5L8950X_UART(dev); s->ulcon = 0; s->ucon = 0; s->ufcon = 0; s->umcon = 0; s->ubrdiv = 0; s->ufracval = 0; s->rx_count = 0; s5l8950x_uart_update_irq(s); } static void s5l8950x_uart_realize(DeviceState *dev, Error **errp) { S5L8950XUartState *s = S5L8950X_UART(dev); qemu_chr_fe_set_handlers(&s->chr, s5l8950x_uart_can_receive, s5l8950x_uart_receive, NULL, NULL, s, NULL, true); } static void s5l8950x_uart_init(Object *obj) { SysBusDevice *sbd = SYS_BUS_DEVICE(obj); S5L8950XUartState *s = S5L8950X_UART(obj); memory_region_init_io(&s->iomem, obj, &s5l8950x_uart_ops, s, TYPE_S5L8950X_UART, 0x100); sysbus_init_mmio(sbd, &s->iomem); sysbus_init_irq(sbd, &s->irq); } static const Property s5l8950x_uart_properties[] = { DEFINE_PROP_CHR("chardev", S5L8950XUartState, chr), }; static void s5l8950x_uart_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_legacy_reset(dc, s5l8950x_uart_reset); dc->realize = s5l8950x_uart_realize; device_class_set_props(dc, s5l8950x_uart_properties); } static const TypeInfo s5l8950x_uart_info = { .name = TYPE_S5L8950X_UART, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(S5L8950XUartState), .instance_init = s5l8950x_uart_init, .class_init = s5l8950x_uart_class_init, }; static void s5l8950x_uart_register_types(void) { type_register_static(&s5l8950x_uart_info); } type_init(s5l8950x_uart_register_types)