240 lines
5.7 KiB
C
240 lines
5.7 KiB
C
#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)
|
|
|
|
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[16];
|
|
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
|
|
|
|
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 & 0xf;
|
|
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);
|
|
}
|
|
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:
|
|
s->ufcon = val;
|
|
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_NATIVE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 4,
|
|
},
|
|
};
|
|
|
|
static int s5l8950x_uart_can_receive(void *opaque)
|
|
{
|
|
S5L8950XUartState *s = S5L8950X_UART(opaque);
|
|
|
|
return sizeof(s->rx_fifo) - 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 < sizeof(s->rx_fifo); 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)
|