aboutsummaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/Kconfig1
-rw-r--r--hw/intc/Kconfig15
-rw-r--r--hw/intc/loongarch_extioi.c312
-rw-r--r--hw/intc/loongarch_ipi.c242
-rw-r--r--hw/intc/loongarch_pch_msi.c73
-rw-r--r--hw/intc/loongarch_pch_pic.c431
-rw-r--r--hw/intc/meson.build4
-rw-r--r--hw/intc/trace-events22
-rw-r--r--hw/loongarch/Kconfig16
-rw-r--r--hw/loongarch/loongson3.c382
-rw-r--r--hw/loongarch/meson.build4
-rw-r--r--hw/meson.build1
-rw-r--r--hw/rtc/Kconfig3
-rw-r--r--hw/rtc/ls7a_rtc.c528
-rw-r--r--hw/rtc/meson.build1
15 files changed, 2035 insertions, 0 deletions
diff --git a/hw/Kconfig b/hw/Kconfig
index 50e0952889..38233bbb0f 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -50,6 +50,7 @@ source avr/Kconfig
source cris/Kconfig
source hppa/Kconfig
source i386/Kconfig
+source loongarch/Kconfig
source m68k/Kconfig
source microblaze/Kconfig
source mips/Kconfig
diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
index eded1b557e..ecd2883ceb 100644
--- a/hw/intc/Kconfig
+++ b/hw/intc/Kconfig
@@ -87,3 +87,18 @@ config M68K_IRQC
config NIOS2_VIC
bool
+
+config LOONGARCH_IPI
+ bool
+
+config LOONGARCH_PCH_PIC
+ bool
+ select UNIMP
+
+config LOONGARCH_PCH_MSI
+ select MSI_NONBROKEN
+ bool
+ select UNIMP
+
+config LOONGARCH_EXTIOI
+ bool
diff --git a/hw/intc/loongarch_extioi.c b/hw/intc/loongarch_extioi.c
new file mode 100644
index 0000000000..22803969bc
--- /dev/null
+++ b/hw/intc/loongarch_extioi.c
@@ -0,0 +1,312 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Loongson 3A5000 ext interrupt controller emulation
+ *
+ * Copyright (C) 2021 Loongson Technology Corporation Limited
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+#include "hw/loongarch/virt.h"
+#include "hw/qdev-properties.h"
+#include "exec/address-spaces.h"
+#include "hw/intc/loongarch_extioi.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+
+static void extioi_update_irq(LoongArchExtIOI *s, int irq, int level)
+{
+ int ipnum, cpu, found, irq_index, irq_mask;
+
+ ipnum = s->sw_ipmap[irq / 32];
+ cpu = s->sw_coremap[irq];
+ irq_index = irq / 32;
+ irq_mask = 1 << (irq & 0x1f);
+
+ if (level) {
+ /* if not enable return false */
+ if (((s->enable[irq_index]) & irq_mask) == 0) {
+ return;
+ }
+ s->coreisr[cpu][irq_index] |= irq_mask;
+ found = find_first_bit(s->sw_isr[cpu][ipnum], EXTIOI_IRQS);
+ set_bit(irq, s->sw_isr[cpu][ipnum]);
+ if (found < EXTIOI_IRQS) {
+ /* other irq is handling, need not update parent irq level */
+ return;
+ }
+ } else {
+ s->coreisr[cpu][irq_index] &= ~irq_mask;
+ clear_bit(irq, s->sw_isr[cpu][ipnum]);
+ found = find_first_bit(s->sw_isr[cpu][ipnum], EXTIOI_IRQS);
+ if (found < EXTIOI_IRQS) {
+ /* other irq is handling, need not update parent irq level */
+ return;
+ }
+ }
+ qemu_set_irq(s->parent_irq[cpu][ipnum], level);
+}
+
+static void extioi_setirq(void *opaque, int irq, int level)
+{
+ LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+ trace_loongarch_extioi_setirq(irq, level);
+ if (level) {
+ /*
+ * s->isr should be used in vmstate structure,
+ * but it not support 'unsigned long',
+ * so we have to switch it.
+ */
+ set_bit(irq, (unsigned long *)s->isr);
+ } else {
+ clear_bit(irq, (unsigned long *)s->isr);
+ }
+ extioi_update_irq(s, irq, level);
+}
+
+static uint64_t extioi_readw(void *opaque, hwaddr addr, unsigned size)
+{
+ LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+ unsigned long offset = addr & 0xffff;
+ uint32_t index, cpu, ret = 0;
+
+ switch (offset) {
+ case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END - 1:
+ index = (offset - EXTIOI_NODETYPE_START) >> 2;
+ ret = s->nodetype[index];
+ break;
+ case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END - 1:
+ index = (offset - EXTIOI_IPMAP_START) >> 2;
+ ret = s->ipmap[index];
+ break;
+ case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END - 1:
+ index = (offset - EXTIOI_ENABLE_START) >> 2;
+ ret = s->enable[index];
+ break;
+ case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END - 1:
+ index = (offset - EXTIOI_BOUNCE_START) >> 2;
+ ret = s->bounce[index];
+ break;
+ case EXTIOI_COREISR_START ... EXTIOI_COREISR_END - 1:
+ index = ((offset - EXTIOI_COREISR_START) & 0x1f) >> 2;
+ cpu = ((offset - EXTIOI_COREISR_START) >> 8) & 0x3;
+ ret = s->coreisr[cpu][index];
+ break;
+ case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END - 1:
+ index = (offset - EXTIOI_COREMAP_START) >> 2;
+ ret = s->coremap[index];
+ break;
+ default:
+ break;
+ }
+
+ trace_loongarch_extioi_readw(addr, ret);
+ return ret;
+}
+
+static inline void extioi_enable_irq(LoongArchExtIOI *s, int index,\
+ uint32_t mask, int level)
+{
+ uint32_t val;
+ int irq;
+
+ val = mask & s->isr[index];
+ irq = ctz32(val);
+ while (irq != 32) {
+ /*
+ * enable bit change from 0 to 1,
+ * need to update irq by pending bits
+ */
+ extioi_update_irq(s, irq + index * 32, level);
+ val &= ~(1 << irq);
+ irq = ctz32(val);
+ }
+}
+
+static void extioi_writew(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
+ int i, cpu, index, old_data, irq;
+ uint32_t offset;
+
+ trace_loongarch_extioi_writew(addr, val);
+ offset = addr & 0xffff;
+
+ switch (offset) {
+ case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END - 1:
+ index = (offset - EXTIOI_NODETYPE_START) >> 2;
+ s->nodetype[index] = val;
+ break;
+ case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END - 1:
+ /*
+ * ipmap cannot be set at runtime, can be set only at the beginning
+ * of intr driver, need not update upper irq level
+ */
+ index = (offset - EXTIOI_IPMAP_START) >> 2;
+ s->ipmap[index] = val;
+ /*
+ * loongarch only support little endian,
+ * so we paresd the value with little endian.
+ */
+ val = cpu_to_le64(val);
+ for (i = 0; i < 4; i++) {
+ uint8_t ipnum;
+ ipnum = val & 0xff;
+ ipnum = ctz32(ipnum);
+ ipnum = (ipnum >= 4) ? 0 : ipnum;
+ s->sw_ipmap[index * 4 + i] = ipnum;
+ val = val >> 8;
+ }
+
+ break;
+ case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END - 1:
+ index = (offset - EXTIOI_ENABLE_START) >> 2;
+ old_data = s->enable[index];
+ s->enable[index] = val;
+
+ /* unmask irq */
+ val = s->enable[index] & ~old_data;
+ extioi_enable_irq(s, index, val, 1);
+
+ /* mask irq */
+ val = ~s->enable[index] & old_data;
+ extioi_enable_irq(s, index, val, 0);
+ break;
+ case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END - 1:
+ /* do not emulate hw bounced irq routing */
+ index = (offset - EXTIOI_BOUNCE_START) >> 2;
+ s->bounce[index] = val;
+ break;
+ case EXTIOI_COREISR_START ... EXTIOI_COREISR_END - 1:
+ index = ((offset - EXTIOI_COREISR_START) & 0x1f) >> 2;
+ cpu = ((offset - EXTIOI_COREISR_START) >> 8) & 0x3;
+ old_data = s->coreisr[cpu][index];
+ s->coreisr[cpu][index] = old_data & ~val;
+ /* write 1 to clear interrrupt */
+ old_data &= val;
+ irq = ctz32(old_data);
+ while (irq != 32) {
+ extioi_update_irq(s, irq + index * 32, 0);
+ old_data &= ~(1 << irq);
+ irq = ctz32(old_data);
+ }
+ break;
+ case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END - 1:
+ irq = offset - EXTIOI_COREMAP_START;
+ index = irq / 4;
+ s->coremap[index] = val;
+ /*
+ * loongarch only support little endian,
+ * so we paresd the value with little endian.
+ */
+ val = cpu_to_le64(val);
+
+ for (i = 0; i < 4; i++) {
+ cpu = val & 0xff;
+ cpu = ctz32(cpu);
+ cpu = (cpu >= 4) ? 0 : cpu;
+ val = val >> 8;
+
+ if (s->sw_coremap[irq + i] == cpu) {
+ continue;
+ }
+
+ if (test_bit(irq, (unsigned long *)s->isr)) {
+ /*
+ * lower irq at old cpu and raise irq at new cpu
+ */
+ extioi_update_irq(s, irq + i, 0);
+ s->sw_coremap[irq + i] = cpu;
+ extioi_update_irq(s, irq + i, 1);
+ } else {
+ s->sw_coremap[irq + i] = cpu;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps extioi_ops = {
+ .read = extioi_readw,
+ .write = extioi_writew,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 8,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_loongarch_extioi = {
+ .name = TYPE_LOONGARCH_EXTIOI,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(bounce, LoongArchExtIOI, EXTIOI_IRQS_GROUP_COUNT),
+ VMSTATE_UINT32_2DARRAY(coreisr, LoongArchExtIOI, LOONGARCH_MAX_VCPUS,
+ EXTIOI_IRQS_GROUP_COUNT),
+ VMSTATE_UINT32_ARRAY(nodetype, LoongArchExtIOI,
+ EXTIOI_IRQS_NODETYPE_COUNT / 2),
+ VMSTATE_UINT32_ARRAY(enable, LoongArchExtIOI, EXTIOI_IRQS / 32),
+ VMSTATE_UINT32_ARRAY(isr, LoongArchExtIOI, EXTIOI_IRQS / 32),
+ VMSTATE_UINT32_ARRAY(ipmap, LoongArchExtIOI, EXTIOI_IRQS_IPMAP_SIZE / 4),
+ VMSTATE_UINT32_ARRAY(coremap, LoongArchExtIOI, EXTIOI_IRQS / 4),
+ VMSTATE_UINT8_ARRAY(sw_ipmap, LoongArchExtIOI, EXTIOI_IRQS_IPMAP_SIZE),
+ VMSTATE_UINT8_ARRAY(sw_coremap, LoongArchExtIOI, EXTIOI_IRQS),
+
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void loongarch_extioi_instance_init(Object *obj)
+{
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+ LoongArchExtIOI *s = LOONGARCH_EXTIOI(obj);
+ int i, cpu, pin;
+
+ for (i = 0; i < EXTIOI_IRQS; i++) {
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq[i]);
+ }
+
+ qdev_init_gpio_in(DEVICE(obj), extioi_setirq, EXTIOI_IRQS);
+
+ for (cpu = 0; cpu < LOONGARCH_MAX_VCPUS; cpu++) {
+ memory_region_init_io(&s->extioi_iocsr_mem[cpu], OBJECT(s), &extioi_ops,
+ s, "extioi_iocsr", 0x900);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->extioi_iocsr_mem[cpu]);
+ for (pin = 0; pin < LS3A_INTC_IP; pin++) {
+ qdev_init_gpio_out(DEVICE(obj), &s->parent_irq[cpu][pin], 1);
+ }
+ }
+ memory_region_init_io(&s->extioi_system_mem, OBJECT(s), &extioi_ops,
+ s, "extioi_system_mem", 0x900);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->extioi_system_mem);
+}
+
+static void loongarch_extioi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_loongarch_extioi;
+}
+
+static const TypeInfo loongarch_extioi_info = {
+ .name = TYPE_LOONGARCH_EXTIOI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = loongarch_extioi_instance_init,
+ .instance_size = sizeof(struct LoongArchExtIOI),
+ .class_init = loongarch_extioi_class_init,
+};
+
+static void loongarch_extioi_register_types(void)
+{
+ type_register_static(&loongarch_extioi_info);
+}
+
+type_init(loongarch_extioi_register_types)
diff --git a/hw/intc/loongarch_ipi.c b/hw/intc/loongarch_ipi.c
new file mode 100644
index 0000000000..66bee93675
--- /dev/null
+++ b/hw/intc/loongarch_ipi.c
@@ -0,0 +1,242 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * LoongArch ipi interrupt support
+ *
+ * Copyright (C) 2021 Loongson Technology Corporation Limited
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/intc/loongarch_ipi.h"
+#include "hw/irq.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "exec/address-spaces.h"
+#include "hw/loongarch/virt.h"
+#include "migration/vmstate.h"
+#include "target/loongarch/internals.h"
+#include "trace.h"
+
+static uint64_t loongarch_ipi_readl(void *opaque, hwaddr addr, unsigned size)
+{
+ IPICore *s = opaque;
+ uint64_t ret = 0;
+ int index = 0;
+
+ addr &= 0xff;
+ switch (addr) {
+ case CORE_STATUS_OFF:
+ ret = s->status;
+ break;
+ case CORE_EN_OFF:
+ ret = s->en;
+ break;
+ case CORE_SET_OFF:
+ ret = 0;
+ break;
+ case CORE_CLEAR_OFF:
+ ret = 0;
+ break;
+ case CORE_BUF_20 ... CORE_BUF_38 + 4:
+ index = (addr - CORE_BUF_20) >> 2;
+ ret = s->buf[index];
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "invalid read: %x", (uint32_t)addr);
+ break;
+ }
+
+ trace_loongarch_ipi_read(size, (uint64_t)addr, ret);
+ return ret;
+}
+
+static int get_ipi_data(target_ulong val)
+{
+ int i, mask, data;
+
+ data = val >> 32;
+ mask = (val >> 27) & 0xf;
+
+ for (i = 0; i < 4; i++) {
+ if ((mask >> i) & 1) {
+ data &= ~(0xff << (i * 8));
+ }
+ }
+ return data;
+}
+
+static void ipi_send(uint64_t val)
+{
+ int cpuid, data;
+ CPULoongArchState *env;
+
+ cpuid = (val >> 16) & 0x3ff;
+ /* IPI status vector */
+ data = 1 << (val & 0x1f);
+ qemu_mutex_lock_iothread();
+ CPUState *cs = qemu_get_cpu(cpuid);
+ LoongArchCPU *cpu = LOONGARCH_CPU(cs);
+ env = &cpu->env;
+ loongarch_cpu_set_irq(cpu, IRQ_IPI, 1);
+ qemu_mutex_unlock_iothread();
+ address_space_stl(&env->address_space_iocsr, 0x1008,
+ data, MEMTXATTRS_UNSPECIFIED, NULL);
+
+}
+
+static void mail_send(uint64_t val)
+{
+ int cpuid, data;
+ hwaddr addr;
+ CPULoongArchState *env;
+
+ cpuid = (val >> 16) & 0x3ff;
+ addr = 0x1020 + (val & 0x1c);
+ CPUState *cs = qemu_get_cpu(cpuid);
+ LoongArchCPU *cpu = LOONGARCH_CPU(cs);
+ env = &cpu->env;
+ data = get_ipi_data(val);
+ address_space_stl(&env->address_space_iocsr, addr,
+ data, MEMTXATTRS_UNSPECIFIED, NULL);
+}
+
+static void any_send(uint64_t val)
+{
+ int cpuid, data;
+ hwaddr addr;
+ CPULoongArchState *env;
+
+ cpuid = (val >> 16) & 0x3ff;
+ addr = val & 0xffff;
+ CPUState *cs = qemu_get_cpu(cpuid);
+ LoongArchCPU *cpu = LOONGARCH_CPU(cs);
+ env = &cpu->env;
+ data = get_ipi_data(val);
+ address_space_stl(&env->address_space_iocsr, addr,
+ data, MEMTXATTRS_UNSPECIFIED, NULL);
+}
+
+static void loongarch_ipi_writel(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ IPICore *s = opaque;
+ int index = 0;
+
+ addr &= 0xff;
+ trace_loongarch_ipi_write(size, (uint64_t)addr, val);
+ switch (addr) {
+ case CORE_STATUS_OFF:
+ qemu_log_mask(LOG_GUEST_ERROR, "can not be written");
+ break;
+ case CORE_EN_OFF:
+ s->en = val;
+ break;
+ case CORE_SET_OFF:
+ s->status |= val;
+ if (s->status != 0 && (s->status & s->en) != 0) {
+ qemu_irq_raise(s->irq);
+ }
+ break;
+ case CORE_CLEAR_OFF:
+ s->status &= ~val;
+ if (s->status == 0 && s->en != 0) {
+ qemu_irq_lower(s->irq);
+ }
+ break;
+ case CORE_BUF_20 ... CORE_BUF_38 + 4:
+ index = (addr - CORE_BUF_20) >> 2;
+ s->buf[index] = val;
+ break;
+ case IOCSR_IPI_SEND:
+ ipi_send(val);
+ break;
+ case IOCSR_MAIL_SEND:
+ mail_send(val);
+ break;
+ case IOCSR_ANY_SEND:
+ any_send(val);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "invalid write: %x", (uint32_t)addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps loongarch_ipi_ops = {
+ .read = loongarch_ipi_readl,
+ .write = loongarch_ipi_writel,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 8,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void loongarch_ipi_init(Object *obj)
+{
+ int cpu;
+ LoongArchMachineState *lams;
+ LoongArchIPI *s = LOONGARCH_IPI(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ Object *machine = qdev_get_machine();
+ ObjectClass *mc = object_get_class(machine);
+ /* 'lams' should be initialized */
+ if (!strcmp(MACHINE_CLASS(mc)->name, "none")) {
+ return;
+ }
+ lams = LOONGARCH_MACHINE(machine);
+ for (cpu = 0; cpu < MAX_IPI_CORE_NUM; cpu++) {
+ memory_region_init_io(&s->ipi_iocsr_mem[cpu], obj, &loongarch_ipi_ops,
+ &lams->ipi_core[cpu], "loongarch_ipi_iocsr", 0x100);
+ sysbus_init_mmio(sbd, &s->ipi_iocsr_mem[cpu]);
+ qdev_init_gpio_out(DEVICE(obj), &lams->ipi_core[cpu].irq, 1);
+ }
+}
+
+static const VMStateDescription vmstate_ipi_core = {
+ .name = "ipi-single",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(status, IPICore),
+ VMSTATE_UINT32(en, IPICore),
+ VMSTATE_UINT32(set, IPICore),
+ VMSTATE_UINT32(clear, IPICore),
+ VMSTATE_UINT32_ARRAY(buf, IPICore, MAX_IPI_MBX_NUM * 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_loongarch_ipi = {
+ .name = TYPE_LOONGARCH_IPI,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(ipi_core, LoongArchMachineState,
+ MAX_IPI_CORE_NUM, 0,
+ vmstate_ipi_core, IPICore),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void loongarch_ipi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_loongarch_ipi;
+}
+
+static const TypeInfo loongarch_ipi_info = {
+ .name = TYPE_LOONGARCH_IPI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LoongArchIPI),
+ .instance_init = loongarch_ipi_init,
+ .class_init = loongarch_ipi_class_init,
+};
+
+static void loongarch_ipi_register_types(void)
+{
+ type_register_static(&loongarch_ipi_info);
+}
+
+type_init(loongarch_ipi_register_types)
diff --git a/hw/intc/loongarch_pch_msi.c b/hw/intc/loongarch_pch_msi.c
new file mode 100644
index 0000000000..74bcdbdb48
--- /dev/null
+++ b/hw/intc/loongarch_pch_msi.c
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QEMU Loongson 7A1000 msi interrupt controller.
+ *
+ * Copyright (C) 2021 Loongson Technology Corporation Limited
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+#include "hw/intc/loongarch_pch_msi.h"
+#include "hw/intc/loongarch_pch_pic.h"
+#include "hw/pci/msi.h"
+#include "hw/misc/unimp.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+static uint64_t loongarch_msi_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return 0;
+}
+
+static void loongarch_msi_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ LoongArchPCHMSI *s = LOONGARCH_PCH_MSI(opaque);
+ int irq_num = val & 0xff;
+
+ trace_loongarch_msi_set_irq(irq_num);
+ assert(irq_num < PCH_MSI_IRQ_NUM);
+ qemu_set_irq(s->pch_msi_irq[irq_num], 1);
+}
+
+static const MemoryRegionOps loongarch_pch_msi_ops = {
+ .read = loongarch_msi_mem_read,
+ .write = loongarch_msi_mem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void pch_msi_irq_handler(void *opaque, int irq, int level)
+{
+ LoongArchPCHMSI *s = LOONGARCH_PCH_MSI(opaque);
+
+ qemu_set_irq(s->pch_msi_irq[irq], level);
+}
+
+static void loongarch_pch_msi_init(Object *obj)
+{
+ LoongArchPCHMSI *s = LOONGARCH_PCH_MSI(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->msi_mmio, obj, &loongarch_pch_msi_ops,
+ s, TYPE_LOONGARCH_PCH_MSI, 0x8);
+ sysbus_init_mmio(sbd, &s->msi_mmio);
+ msi_nonbroken = true;
+
+ qdev_init_gpio_out(DEVICE(obj), s->pch_msi_irq, PCH_MSI_IRQ_NUM);
+ qdev_init_gpio_in(DEVICE(obj), pch_msi_irq_handler, PCH_MSI_IRQ_NUM);
+}
+
+static const TypeInfo loongarch_pch_msi_info = {
+ .name = TYPE_LOONGARCH_PCH_MSI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LoongArchPCHMSI),
+ .instance_init = loongarch_pch_msi_init,
+};
+
+static void loongarch_pch_msi_register_types(void)
+{
+ type_register_static(&loongarch_pch_msi_info);
+}
+
+type_init(loongarch_pch_msi_register_types)
diff --git a/hw/intc/loongarch_pch_pic.c b/hw/intc/loongarch_pch_pic.c
new file mode 100644
index 0000000000..3c9814a3b4
--- /dev/null
+++ b/hw/intc/loongarch_pch_pic.c
@@ -0,0 +1,431 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QEMU Loongson 7A1000 I/O interrupt controller.
+ *
+ * Copyright (C) 2021 Loongson Technology Corporation Limited
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/loongarch/virt.h"
+#include "hw/irq.h"
+#include "hw/intc/loongarch_pch_pic.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+static void pch_pic_update_irq(LoongArchPCHPIC *s, uint64_t mask, int level)
+{
+ unsigned long val;
+ int irq;
+
+ if (level) {
+ val = mask & s->intirr & ~s->int_mask;
+ if (val) {
+ irq = find_first_bit(&val, 64);
+ s->intisr |= 0x1ULL << irq;
+ qemu_set_irq(s->parent_irq[s->htmsi_vector[irq]], 1);
+ }
+ } else {
+ val = mask & s->intisr;
+ if (val) {
+ irq = find_first_bit(&val, 64);
+ s->intisr &= ~(0x1ULL << irq);
+ qemu_set_irq(s->parent_irq[s->htmsi_vector[irq]], 0);
+ }
+ }
+}
+
+static void pch_pic_irq_handler(void *opaque, int irq, int level)
+{
+ LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+ uint64_t mask = 1ULL << irq;
+
+ assert(irq < PCH_PIC_IRQ_NUM);
+ trace_loongarch_pch_pic_irq_handler(irq, level);
+
+ if (s->intedge & mask) {
+ /* Edge triggered */
+ if (level) {
+ if ((s->last_intirr & mask) == 0) {
+ s->intirr |= mask;
+ }
+ s->last_intirr |= mask;
+ } else {
+ s->last_intirr &= ~mask;
+ }
+ } else {
+ /* Level triggered */
+ if (level) {
+ s->intirr |= mask;
+ s->last_intirr |= mask;
+ } else {
+ s->intirr &= ~mask;
+ s->last_intirr &= ~mask;
+ }
+ }
+ pch_pic_update_irq(s, mask, level);
+}
+
+static uint64_t loongarch_pch_pic_low_readw(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+ uint64_t val = 0;
+ uint32_t offset = addr & 0xfff;
+
+ switch (offset) {
+ case PCH_PIC_INT_ID_LO:
+ val = PCH_PIC_INT_ID_VAL;
+ break;
+ case PCH_PIC_INT_ID_HI:
+ val = PCH_PIC_INT_ID_NUM;
+ break;
+ case PCH_PIC_INT_MASK_LO:
+ val = (uint32_t)s->int_mask;
+ break;
+ case PCH_PIC_INT_MASK_HI:
+ val = s->int_mask >> 32;
+ break;
+ case PCH_PIC_INT_EDGE_LO:
+ val = (uint32_t)s->intedge;
+ break;
+ case PCH_PIC_INT_EDGE_HI:
+ val = s->intedge >> 32;
+ break;
+ case PCH_PIC_HTMSI_EN_LO:
+ val = (uint32_t)s->htmsi_en;
+ break;
+ case PCH_PIC_HTMSI_EN_HI:
+ val = s->htmsi_en >> 32;
+ break;
+ case PCH_PIC_AUTO_CTRL0_LO:
+ case PCH_PIC_AUTO_CTRL0_HI:
+ case PCH_PIC_AUTO_CTRL1_LO:
+ case PCH_PIC_AUTO_CTRL1_HI:
+ break;
+ default:
+ break;
+ }
+
+ trace_loongarch_pch_pic_low_readw(size, addr, val);
+ return val;
+}
+
+static uint64_t get_writew_val(uint64_t value, uint32_t target, bool hi)
+{
+ uint64_t mask = 0xffffffff00000000;
+ uint64_t data = target;
+
+ return hi ? (value & ~mask) | (data << 32) : (value & mask) | data;
+}
+
+static void loongarch_pch_pic_low_writew(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+ uint32_t offset, old_valid, data = (uint32_t)value;
+ uint64_t old, int_mask;
+ offset = addr & 0xfff;
+
+ trace_loongarch_pch_pic_low_writew(size, addr, data);
+
+ switch (offset) {
+ case PCH_PIC_INT_MASK_LO:
+ old = s->int_mask;
+ s->int_mask = get_writew_val(old, data, 0);
+ old_valid = (uint32_t)old;
+ if (old_valid & ~data) {
+ pch_pic_update_irq(s, (old_valid & ~data), 1);
+ }
+ if (~old_valid & data) {
+ pch_pic_update_irq(s, (~old_valid & data), 0);
+ }
+ break;
+ case PCH_PIC_INT_MASK_HI:
+ old = s->int_mask;
+ s->int_mask = get_writew_val(old, data, 1);
+ old_valid = (uint32_t)(old >> 32);
+ int_mask = old_valid & ~data;
+ if (int_mask) {
+ pch_pic_update_irq(s, int_mask << 32, 1);
+ }
+ int_mask = ~old_valid & data;
+ if (int_mask) {
+ pch_pic_update_irq(s, int_mask << 32, 0);
+ }
+ break;
+ case PCH_PIC_INT_EDGE_LO:
+ s->intedge = get_writew_val(s->intedge, data, 0);
+ break;
+ case PCH_PIC_INT_EDGE_HI:
+ s->intedge = get_writew_val(s->intedge, data, 1);
+ break;
+ case PCH_PIC_INT_CLEAR_LO:
+ if (s->intedge & data) {
+ s->intirr &= (~data);
+ pch_pic_update_irq(s, data, 0);
+ s->intisr &= (~data);
+ }
+ break;
+ case PCH_PIC_INT_CLEAR_HI:
+ value <<= 32;
+ if (s->intedge & value) {
+ s->intirr &= (~value);
+ pch_pic_update_irq(s, value, 0);
+ s->intisr &= (~value);
+ }
+ break;
+ case PCH_PIC_HTMSI_EN_LO:
+ s->htmsi_en = get_writew_val(s->htmsi_en, data, 0);
+ break;
+ case PCH_PIC_HTMSI_EN_HI:
+ s->htmsi_en = get_writew_val(s->htmsi_en, data, 1);
+ break;
+ case PCH_PIC_AUTO_CTRL0_LO:
+ case PCH_PIC_AUTO_CTRL0_HI:
+ case PCH_PIC_AUTO_CTRL1_LO:
+ case PCH_PIC_AUTO_CTRL1_HI:
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t loongarch_pch_pic_high_readw(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+ uint64_t val = 0;
+ uint32_t offset = addr & 0xfff;
+
+ switch (offset) {
+ case STATUS_LO_START:
+ val = (uint32_t)(s->intisr & (~s->int_mask));
+ break;
+ case STATUS_HI_START:
+ val = (s->intisr & (~s->int_mask)) >> 32;
+ break;
+ case POL_LO_START:
+ val = (uint32_t)s->int_polarity;
+ break;
+ case POL_HI_START:
+ val = s->int_polarity >> 32;
+ break;
+ default:
+ break;
+ }
+
+ trace_loongarch_pch_pic_high_readw(size, addr, val);
+ return val;
+}
+
+static void loongarch_pch_pic_high_writew(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+ uint32_t offset, data = (uint32_t)value;
+ offset = addr & 0xfff;
+
+ trace_loongarch_pch_pic_high_writew(size, addr, data);
+
+ switch (offset) {
+ case STATUS_LO_START:
+ s->intisr = get_writew_val(s->intisr, data, 0);
+ break;
+ case STATUS_HI_START:
+ s->intisr = get_writew_val(s->intisr, data, 1);
+ break;
+ case POL_LO_START:
+ s->int_polarity = get_writew_val(s->int_polarity, data, 0);
+ break;
+ case POL_HI_START:
+ s->int_polarity = get_writew_val(s->int_polarity, data, 1);
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t loongarch_pch_pic_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+ uint64_t val = 0;
+ uint32_t offset = (addr & 0xfff) + PCH_PIC_ROUTE_ENTRY_OFFSET;
+ int64_t offset_tmp;
+
+ switch (offset) {
+ case PCH_PIC_HTMSI_VEC_OFFSET ... PCH_PIC_HTMSI_VEC_END:
+ offset_tmp = offset - PCH_PIC_HTMSI_VEC_OFFSET;
+ if (offset_tmp >= 0 && offset_tmp < 64) {
+ val = s->htmsi_vector[offset_tmp];
+ }
+ break;
+ case PCH_PIC_ROUTE_ENTRY_OFFSET ... PCH_PIC_ROUTE_ENTRY_END:
+ offset_tmp = offset - PCH_PIC_ROUTE_ENTRY_OFFSET;
+ if (offset_tmp >= 0 && offset_tmp < 64) {
+ val = s->route_entry[offset_tmp];
+ }
+ break;
+ default:
+ break;
+ }
+
+ trace_loongarch_pch_pic_readb(size, addr, val);
+ return val;
+}
+
+static void loongarch_pch_pic_writeb(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
+ int32_t offset_tmp;
+ uint32_t offset = (addr & 0xfff) + PCH_PIC_ROUTE_ENTRY_OFFSET;
+
+ trace_loongarch_pch_pic_writeb(size, addr, data);
+
+ switch (offset) {
+ case PCH_PIC_HTMSI_VEC_OFFSET ... PCH_PIC_HTMSI_VEC_END:
+ offset_tmp = offset - PCH_PIC_HTMSI_VEC_OFFSET;
+ if (offset_tmp >= 0 && offset_tmp < 64) {
+ s->htmsi_vector[offset_tmp] = (uint8_t)(data & 0xff);
+ }
+ break;
+ case PCH_PIC_ROUTE_ENTRY_OFFSET ... PCH_PIC_ROUTE_ENTRY_END:
+ offset_tmp = offset - PCH_PIC_ROUTE_ENTRY_OFFSET;
+ if (offset_tmp >= 0 && offset_tmp < 64) {
+ s->route_entry[offset_tmp] = (uint8_t)(data & 0xff);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps loongarch_pch_pic_reg32_low_ops = {
+ .read = loongarch_pch_pic_low_readw,
+ .write = loongarch_pch_pic_low_writew,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps loongarch_pch_pic_reg32_high_ops = {
+ .read = loongarch_pch_pic_high_readw,
+ .write = loongarch_pch_pic_high_writew,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps loongarch_pch_pic_reg8_ops = {
+ .read = loongarch_pch_pic_readb,
+ .write = loongarch_pch_pic_writeb,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void loongarch_pch_pic_reset(DeviceState *d)
+{
+ LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(d);
+ int i;
+
+ s->int_mask = -1;
+ s->htmsi_en = 0x0;
+ s->intedge = 0x0;
+ s->intclr = 0x0;
+ s->auto_crtl0 = 0x0;
+ s->auto_crtl1 = 0x0;
+ for (i = 0; i < 64; i++) {
+ s->route_entry[i] = 0x1;
+ s->htmsi_vector[i] = 0x0;
+ }
+ s->intirr = 0x0;
+ s->intisr = 0x0;
+ s->last_intirr = 0x0;
+ s->int_polarity = 0x0;
+}
+
+static void loongarch_pch_pic_init(Object *obj)
+{
+ LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem32_low, obj,
+ &loongarch_pch_pic_reg32_low_ops,
+ s, PCH_PIC_NAME(.reg32_part1), 0x100);
+ memory_region_init_io(&s->iomem8, obj, &loongarch_pch_pic_reg8_ops,
+ s, PCH_PIC_NAME(.reg8), 0x2a0);
+ memory_region_init_io(&s->iomem32_high, obj,
+ &loongarch_pch_pic_reg32_high_ops,
+ s, PCH_PIC_NAME(.reg32_part2), 0xc60);
+ sysbus_init_mmio(sbd, &s->iomem32_low);
+ sysbus_init_mmio(sbd, &s->iomem8);
+ sysbus_init_mmio(sbd, &s->iomem32_high);
+
+ qdev_init_gpio_out(DEVICE(obj), s->parent_irq, PCH_PIC_IRQ_NUM);
+ qdev_init_gpio_in(DEVICE(obj), pch_pic_irq_handler, PCH_PIC_IRQ_NUM);
+}
+
+static const VMStateDescription vmstate_loongarch_pch_pic = {
+ .name = TYPE_LOONGARCH_PCH_PIC,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(int_mask, LoongArchPCHPIC),
+ VMSTATE_UINT64(htmsi_en, LoongArchPCHPIC),
+ VMSTATE_UINT64(intedge, LoongArchPCHPIC),
+ VMSTATE_UINT64(intclr, LoongArchPCHPIC),
+ VMSTATE_UINT64(auto_crtl0, LoongArchPCHPIC),
+ VMSTATE_UINT64(auto_crtl1, LoongArchPCHPIC),
+ VMSTATE_UINT8_ARRAY(route_entry, LoongArchPCHPIC, 64),
+ VMSTATE_UINT8_ARRAY(htmsi_vector, LoongArchPCHPIC, 64),
+ VMSTATE_UINT64(last_intirr, LoongArchPCHPIC),
+ VMSTATE_UINT64(intirr, LoongArchPCHPIC),
+ VMSTATE_UINT64(intisr, LoongArchPCHPIC),
+ VMSTATE_UINT64(int_polarity, LoongArchPCHPIC),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void loongarch_pch_pic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = loongarch_pch_pic_reset;
+ dc->vmsd = &vmstate_loongarch_pch_pic;
+}
+
+static const TypeInfo loongarch_pch_pic_info = {
+ .name = TYPE_LOONGARCH_PCH_PIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LoongArchPCHPIC),
+ .instance_init = loongarch_pch_pic_init,
+ .class_init = loongarch_pch_pic_class_init,
+};
+
+static void loongarch_pch_pic_register_types(void)
+{
+ type_register_static(&loongarch_pch_pic_info);
+}
+
+type_init(loongarch_pch_pic_register_types)
diff --git a/hw/intc/meson.build b/hw/intc/meson.build
index 8b35139f82..bcbf22ff51 100644
--- a/hw/intc/meson.build
+++ b/hw/intc/meson.build
@@ -63,3 +63,7 @@ specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XIVE'],
specific_ss.add(when: 'CONFIG_GOLDFISH_PIC', if_true: files('goldfish_pic.c'))
specific_ss.add(when: 'CONFIG_M68K_IRQC', if_true: files('m68k_irqc.c'))
specific_ss.add(when: 'CONFIG_NIOS2_VIC', if_true: files('nios2_vic.c'))
+specific_ss.add(when: 'CONFIG_LOONGARCH_IPI', if_true: files('loongarch_ipi.c'))
+specific_ss.add(when: 'CONFIG_LOONGARCH_PCH_PIC', if_true: files('loongarch_pch_pic.c'))
+specific_ss.add(when: 'CONFIG_LOONGARCH_PCH_MSI', if_true: files('loongarch_pch_msi.c'))
+specific_ss.add(when: 'CONFIG_LOONGARCH_EXTIOI', if_true: files('loongarch_extioi.c'))
diff --git a/hw/intc/trace-events b/hw/intc/trace-events
index 5271590304..0a90c1cdec 100644
--- a/hw/intc/trace-events
+++ b/hw/intc/trace-events
@@ -287,3 +287,25 @@ sh_intc_register(const char *s, int id, unsigned short v, int c, int m) "%s %u -
sh_intc_read(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " -> 0x%lx"
sh_intc_write(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " <- 0x%lx"
sh_intc_set(int id, int enable) "setting interrupt group %d to %d"
+
+# loongarch_ipi.c
+loongarch_ipi_read(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%"PRIx64
+loongarch_ipi_write(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%"PRIx64
+
+# loongarch_pch_pic.c
+loongarch_pch_pic_irq_handler(int irq, int level) "irq %d level %d"
+loongarch_pch_pic_low_readw(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%" PRIx64
+loongarch_pch_pic_low_writew(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%" PRIx64
+loongarch_pch_pic_high_readw(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%" PRIx64
+loongarch_pch_pic_high_writew(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%" PRIx64
+loongarch_pch_pic_readb(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%" PRIx64
+loongarch_pch_pic_writeb(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%" PRIx64
+
+# loongarch_pch_msi.c
+loongarch_msi_set_irq(int irq_num) "set msi irq %d"
+
+# loongarch_extioi.c
+loongarch_extioi_setirq(int irq, int level) "set extirq irq %d level %d"
+loongarch_extioi_readw(uint64_t addr, uint32_t val) "addr: 0x%"PRIx64 "val: 0x%x"
+loongarch_extioi_writew(uint64_t addr, uint64_t val) "addr: 0x%"PRIx64 "val: 0x%" PRIx64
+
diff --git a/hw/loongarch/Kconfig b/hw/loongarch/Kconfig
new file mode 100644
index 0000000000..35b6680772
--- /dev/null
+++ b/hw/loongarch/Kconfig
@@ -0,0 +1,16 @@
+config LOONGARCH_VIRT
+ bool
+ select PCI
+ select PCI_EXPRESS_GENERIC_BRIDGE
+ imply VGA_PCI
+ imply VIRTIO_VGA
+ imply PCI_DEVICES
+ select ISA_BUS
+ select SERIAL
+ select SERIAL_ISA
+ select VIRTIO_PCI
+ select LOONGARCH_IPI
+ select LOONGARCH_PCH_PIC
+ select LOONGARCH_PCH_MSI
+ select LOONGARCH_EXTIOI
+ select LS7A_RTC
diff --git a/hw/loongarch/loongson3.c b/hw/loongarch/loongson3.c
new file mode 100644
index 0000000000..bd20ebbb78
--- /dev/null
+++ b/hw/loongarch/loongson3.c
@@ -0,0 +1,382 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QEMU loongson 3a5000 develop board emulation
+ *
+ * Copyright (c) 2021 Loongson Technology Corporation Limited
+ */
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu/datadir.h"
+#include "qapi/error.h"
+#include "hw/boards.h"
+#include "hw/char/serial.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/qtest.h"
+#include "sysemu/runstate.h"
+#include "sysemu/reset.h"
+#include "sysemu/rtc.h"
+#include "hw/loongarch/virt.h"
+#include "exec/address-spaces.h"
+#include "hw/irq.h"
+#include "net/net.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "hw/intc/loongarch_ipi.h"
+#include "hw/intc/loongarch_extioi.h"
+#include "hw/intc/loongarch_pch_pic.h"
+#include "hw/intc/loongarch_pch_msi.h"
+#include "hw/pci-host/ls7a.h"
+#include "hw/pci-host/gpex.h"
+#include "hw/misc/unimp.h"
+
+#include "target/loongarch/cpu.h"
+
+#define PM_BASE 0x10080000
+#define PM_SIZE 0x100
+#define PM_CTRL 0x10
+
+/*
+ * This is a placeholder for missing ACPI,
+ * and will eventually be replaced.
+ */
+static uint64_t loongarch_virt_pm_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return 0;
+}
+
+static void loongarch_virt_pm_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ if (addr != PM_CTRL) {
+ return;
+ }
+
+ switch (val) {
+ case 0x00:
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ return;
+ case 0xff:
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ return;
+ default:
+ return;
+ }
+}
+
+static const MemoryRegionOps loongarch_virt_pm_ops = {
+ .read = loongarch_virt_pm_read,
+ .write = loongarch_virt_pm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1
+ }
+};
+
+static struct _loaderparams {
+ uint64_t ram_size;
+ const char *kernel_filename;
+} loaderparams;
+
+static uint64_t cpu_loongarch_virt_to_phys(void *opaque, uint64_t addr)
+{
+ return addr & 0x1fffffffll;
+}
+
+static int64_t load_kernel_info(void)
+{
+ uint64_t kernel_entry, kernel_low, kernel_high;
+ ssize_t kernel_size;
+
+ kernel_size = load_elf(loaderparams.kernel_filename, NULL,
+ cpu_loongarch_virt_to_phys, NULL,
+ &kernel_entry, &kernel_low,
+ &kernel_high, NULL, 0,
+ EM_LOONGARCH, 1, 0);
+
+ if (kernel_size < 0) {
+ error_report("could not load kernel '%s': %s",
+ loaderparams.kernel_filename,
+ load_elf_strerror(kernel_size));
+ exit(1);
+ }
+ return kernel_entry;
+}
+
+static void loongarch_devices_init(DeviceState *pch_pic)
+{
+ DeviceState *gpex_dev;
+ SysBusDevice *d;
+ PCIBus *pci_bus;
+ MemoryRegion *ecam_alias, *ecam_reg, *pio_alias, *pio_reg;
+ MemoryRegion *mmio_alias, *mmio_reg, *pm_mem;
+ int i;
+
+ gpex_dev = qdev_new(TYPE_GPEX_HOST);
+ d = SYS_BUS_DEVICE(gpex_dev);
+ sysbus_realize_and_unref(d, &error_fatal);
+ pci_bus = PCI_HOST_BRIDGE(gpex_dev)->bus;
+
+ /* Map only part size_ecam bytes of ECAM space */
+ ecam_alias = g_new0(MemoryRegion, 1);
+ ecam_reg = sysbus_mmio_get_region(d, 0);
+ memory_region_init_alias(ecam_alias, OBJECT(gpex_dev), "pcie-ecam",
+ ecam_reg, 0, LS_PCIECFG_SIZE);
+ memory_region_add_subregion(get_system_memory(), LS_PCIECFG_BASE,
+ ecam_alias);
+
+ /* Map PCI mem space */
+ mmio_alias = g_new0(MemoryRegion, 1);
+ mmio_reg = sysbus_mmio_get_region(d, 1);
+ memory_region_init_alias(mmio_alias, OBJECT(gpex_dev), "pcie-mmio",
+ mmio_reg, LS7A_PCI_MEM_BASE, LS7A_PCI_MEM_SIZE);
+ memory_region_add_subregion(get_system_memory(), LS7A_PCI_MEM_BASE,
+ mmio_alias);
+
+ /* Map PCI IO port space. */
+ pio_alias = g_new0(MemoryRegion, 1);
+ pio_reg = sysbus_mmio_get_region(d, 2);
+ memory_region_init_alias(pio_alias, OBJECT(gpex_dev), "pcie-io", pio_reg,
+ LS7A_PCI_IO_OFFSET, LS7A_PCI_IO_SIZE);
+ memory_region_add_subregion(get_system_memory(), LS7A_PCI_IO_BASE,
+ pio_alias);
+
+ for (i = 0; i < GPEX_NUM_IRQS; i++) {
+ sysbus_connect_irq(d, i,
+ qdev_get_gpio_in(pch_pic, 16 + i));
+ gpex_set_irq_num(GPEX_HOST(gpex_dev), i, 16 + i);
+ }
+
+ serial_mm_init(get_system_memory(), LS7A_UART_BASE, 0,
+ qdev_get_gpio_in(pch_pic,
+ LS7A_UART_IRQ - PCH_PIC_IRQ_OFFSET),
+ 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN);
+
+ /* Network init */
+ for (i = 0; i < nb_nics; i++) {
+ NICInfo *nd = &nd_table[i];
+
+ if (!nd->model) {
+ nd->model = g_strdup("virtio");
+ }
+
+ pci_nic_init_nofail(nd, pci_bus, nd->model, NULL);
+ }
+
+ /* VGA setup */
+ pci_vga_init(pci_bus);
+
+ /*
+ * There are some invalid guest memory access.
+ * Create some unimplemented devices to emulate this.
+ */
+ create_unimplemented_device("pci-dma-cfg", 0x1001041c, 0x4);
+ sysbus_create_simple("ls7a_rtc", LS7A_RTC_REG_BASE,
+ qdev_get_gpio_in(pch_pic,
+ LS7A_RTC_IRQ - PCH_PIC_IRQ_OFFSET));
+
+ pm_mem = g_new(MemoryRegion, 1);
+ memory_region_init_io(pm_mem, NULL, &loongarch_virt_pm_ops,
+ NULL, "loongarch_virt_pm", PM_SIZE);
+ memory_region_add_subregion(get_system_memory(), PM_BASE, pm_mem);
+}
+
+static void loongarch_irq_init(LoongArchMachineState *lams)
+{
+ MachineState *ms = MACHINE(lams);
+ DeviceState *pch_pic, *pch_msi, *cpudev;
+ DeviceState *ipi, *extioi;
+ SysBusDevice *d;
+ LoongArchCPU *lacpu;
+ CPULoongArchState *env;
+ CPUState *cpu_state;
+ int cpu, pin, i;
+
+ ipi = qdev_new(TYPE_LOONGARCH_IPI);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(ipi), &error_fatal);
+
+ extioi = qdev_new(TYPE_LOONGARCH_EXTIOI);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(extioi), &error_fatal);
+
+ /*
+ * The connection of interrupts:
+ * +-----+ +---------+ +-------+
+ * | IPI |--> | CPUINTC | <-- | Timer |
+ * +-----+ +---------+ +-------+
+ * ^
+ * |
+ * +---------+
+ * | EIOINTC |
+ * +---------+
+ * ^ ^
+ * | |
+ * +---------+ +---------+
+ * | PCH-PIC | | PCH-MSI |
+ * +---------+ +---------+
+ * ^ ^ ^
+ * | | |
+ * +--------+ +---------+ +---------+
+ * | UARTs | | Devices | | Devices |
+ * +--------+ +---------+ +---------+
+ */
+ for (cpu = 0; cpu < ms->smp.cpus; cpu++) {
+ cpu_state = qemu_get_cpu(cpu);
+ cpudev = DEVICE(cpu_state);
+ lacpu = LOONGARCH_CPU(cpu_state);
+ env = &(lacpu->env);
+
+ /* connect ipi irq to cpu irq */
+ qdev_connect_gpio_out(ipi, cpu, qdev_get_gpio_in(cpudev, IRQ_IPI));
+ /* IPI iocsr memory region */
+ memory_region_add_subregion(&env->system_iocsr, SMP_IPI_MAILBOX,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(ipi),
+ cpu));
+ /* extioi iocsr memory region */
+ memory_region_add_subregion(&env->system_iocsr, APIC_BASE,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(extioi),
+ cpu));
+ }
+
+ /*
+ * connect ext irq to the cpu irq
+ * cpu_pin[9:2] <= intc_pin[7:0]
+ */
+ for (cpu = 0; cpu < ms->smp.cpus; cpu++) {
+ cpudev = DEVICE(qemu_get_cpu(cpu));
+ for (pin = 0; pin < LS3A_INTC_IP; pin++) {
+ qdev_connect_gpio_out(extioi, (cpu * 8 + pin),
+ qdev_get_gpio_in(cpudev, pin + 2));
+ }
+ }
+
+ pch_pic = qdev_new(TYPE_LOONGARCH_PCH_PIC);
+ d = SYS_BUS_DEVICE(pch_pic);
+ sysbus_realize_and_unref(d, &error_fatal);
+ memory_region_add_subregion(get_system_memory(), LS7A_IOAPIC_REG_BASE,
+ sysbus_mmio_get_region(d, 0));
+ memory_region_add_subregion(get_system_memory(),
+ LS7A_IOAPIC_REG_BASE + PCH_PIC_ROUTE_ENTRY_OFFSET,
+ sysbus_mmio_get_region(d, 1));
+ memory_region_add_subregion(get_system_memory(),
+ LS7A_IOAPIC_REG_BASE + PCH_PIC_INT_STATUS_LO,
+ sysbus_mmio_get_region(d, 2));
+
+ /* Connect 64 pch_pic irqs to extioi */
+ for (int i = 0; i < PCH_PIC_IRQ_NUM; i++) {
+ qdev_connect_gpio_out(DEVICE(d), i, qdev_get_gpio_in(extioi, i));
+ }
+
+ pch_msi = qdev_new(TYPE_LOONGARCH_PCH_MSI);
+ d = SYS_BUS_DEVICE(pch_msi);
+ sysbus_realize_and_unref(d, &error_fatal);
+ sysbus_mmio_map(d, 0, LS7A_PCH_MSI_ADDR_LOW);
+ for (i = 0; i < PCH_MSI_IRQ_NUM; i++) {
+ /* Connect 192 pch_msi irqs to extioi */
+ qdev_connect_gpio_out(DEVICE(d), i,
+ qdev_get_gpio_in(extioi, i + PCH_MSI_IRQ_START));
+ }
+
+ loongarch_devices_init(pch_pic);
+}
+
+static void reset_load_elf(void *opaque)
+{
+ LoongArchCPU *cpu = opaque;
+ CPULoongArchState *env = &cpu->env;
+
+ cpu_reset(CPU(cpu));
+ if (env->load_elf) {
+ cpu_set_pc(CPU(cpu), env->elf_address);
+ }
+}
+
+static void loongarch_init(MachineState *machine)
+{
+ const char *cpu_model = machine->cpu_type;
+ const char *kernel_filename = machine->kernel_filename;
+ ram_addr_t offset = 0;
+ ram_addr_t ram_size = machine->ram_size;
+ uint64_t highram_size = 0;
+ MemoryRegion *address_space_mem = get_system_memory();
+ LoongArchMachineState *lams = LOONGARCH_MACHINE(machine);
+ LoongArchCPU *lacpu;
+ int i;
+ int64_t kernel_addr = 0;
+
+ if (!cpu_model) {
+ cpu_model = LOONGARCH_CPU_TYPE_NAME("la464");
+ }
+
+ if (!strstr(cpu_model, "la464")) {
+ error_report("LoongArch/TCG needs cpu type la464");
+ exit(1);
+ }
+
+ if (ram_size < 1 * GiB) {
+ error_report("ram_size must be greater than 1G.");
+ exit(1);
+ }
+
+ /* Init CPUs */
+ for (i = 0; i < machine->smp.cpus; i++) {
+ cpu_create(machine->cpu_type);
+ }
+
+ /* Add memory region */
+ memory_region_init_alias(&lams->lowmem, NULL, "loongarch.lowram",
+ machine->ram, 0, 256 * MiB);
+ memory_region_add_subregion(address_space_mem, offset, &lams->lowmem);
+ offset += 256 * MiB;
+ highram_size = ram_size - 256 * MiB;
+ memory_region_init_alias(&lams->highmem, NULL, "loongarch.highmem",
+ machine->ram, offset, highram_size);
+ memory_region_add_subregion(address_space_mem, 0x90000000, &lams->highmem);
+ /* Add isa io region */
+ memory_region_init_alias(&lams->isa_io, NULL, "isa-io",
+ get_system_io(), 0, LOONGARCH_ISA_IO_SIZE);
+ memory_region_add_subregion(address_space_mem, LOONGARCH_ISA_IO_BASE,
+ &lams->isa_io);
+ if (kernel_filename) {
+ loaderparams.ram_size = ram_size;
+ loaderparams.kernel_filename = kernel_filename;
+ kernel_addr = load_kernel_info();
+ if (!machine->firmware) {
+ for (i = 0; i < machine->smp.cpus; i++) {
+ lacpu = LOONGARCH_CPU(qemu_get_cpu(i));
+ lacpu->env.load_elf = true;
+ lacpu->env.elf_address = kernel_addr;
+ qemu_register_reset(reset_load_elf, lacpu);
+ }
+ }
+ }
+ /* Initialize the IO interrupt subsystem */
+ loongarch_irq_init(lams);
+}
+
+static void loongarch_class_init(ObjectClass *oc, void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->desc = "Loongson-3A5000 LS7A1000 machine";
+ mc->init = loongarch_init;
+ mc->default_ram_size = 1 * GiB;
+ mc->default_cpu_type = LOONGARCH_CPU_TYPE_NAME("la464");
+ mc->default_ram_id = "loongarch.ram";
+ mc->max_cpus = LOONGARCH_MAX_VCPUS;
+ mc->is_default = 1;
+ mc->default_kernel_irqchip_split = false;
+ mc->block_default_type = IF_VIRTIO;
+ mc->default_boot_order = "c";
+ mc->no_cdrom = 1;
+}
+
+static const TypeInfo loongarch_machine_types[] = {
+ {
+ .name = TYPE_LOONGARCH_MACHINE,
+ .parent = TYPE_MACHINE,
+ .instance_size = sizeof(LoongArchMachineState),
+ .class_init = loongarch_class_init,
+ }
+};
+
+DEFINE_TYPES(loongarch_machine_types)
diff --git a/hw/loongarch/meson.build b/hw/loongarch/meson.build
new file mode 100644
index 0000000000..cecb1a5d65
--- /dev/null
+++ b/hw/loongarch/meson.build
@@ -0,0 +1,4 @@
+loongarch_ss = ss.source_set()
+loongarch_ss.add(when: 'CONFIG_LOONGARCH_VIRT', if_true: files('loongson3.c'))
+
+hw_arch += {'loongarch': loongarch_ss}
diff --git a/hw/meson.build b/hw/meson.build
index 9992c5101e..c7ac7d3d75 100644
--- a/hw/meson.build
+++ b/hw/meson.build
@@ -50,6 +50,7 @@ subdir('avr')
subdir('cris')
subdir('hppa')
subdir('i386')
+subdir('loongarch')
subdir('m68k')
subdir('microblaze')
subdir('mips')
diff --git a/hw/rtc/Kconfig b/hw/rtc/Kconfig
index 730c272bc5..d0d8dda084 100644
--- a/hw/rtc/Kconfig
+++ b/hw/rtc/Kconfig
@@ -27,3 +27,6 @@ config SUN4V_RTC
config GOLDFISH_RTC
bool
+
+config LS7A_RTC
+ bool
diff --git a/hw/rtc/ls7a_rtc.c b/hw/rtc/ls7a_rtc.c
new file mode 100644
index 0000000000..fe6710310f
--- /dev/null
+++ b/hw/rtc/ls7a_rtc.c
@@ -0,0 +1,528 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Loongarch LS7A Real Time Clock emulation
+ *
+ * Copyright (C) 2021 Loongson Technology Corporation Limited
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+#include "include/hw/register.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "qemu/cutils.h"
+#include "qemu/log.h"
+#include "migration/vmstate.h"
+#include "hw/misc/unimp.h"
+#include "sysemu/rtc.h"
+#include "hw/registerfields.h"
+
+#define SYS_TOYTRIM 0x20
+#define SYS_TOYWRITE0 0x24
+#define SYS_TOYWRITE1 0x28
+#define SYS_TOYREAD0 0x2C
+#define SYS_TOYREAD1 0x30
+#define SYS_TOYMATCH0 0x34
+#define SYS_TOYMATCH1 0x38
+#define SYS_TOYMATCH2 0x3C
+#define SYS_RTCCTRL 0x40
+#define SYS_RTCTRIM 0x60
+#define SYS_RTCWRTIE0 0x64
+#define SYS_RTCREAD0 0x68
+#define SYS_RTCMATCH0 0x6C
+#define SYS_RTCMATCH1 0x70
+#define SYS_RTCMATCH2 0x74
+
+#define LS7A_RTC_FREQ 32768
+#define TIMER_NUMS 3
+/*
+ * Shift bits and filed mask
+ */
+
+FIELD(TOY, MON, 26, 6)
+FIELD(TOY, DAY, 21, 5)
+FIELD(TOY, HOUR, 16, 5)
+FIELD(TOY, MIN, 10, 6)
+FIELD(TOY, SEC, 4, 6)
+FIELD(TOY, MSEC, 0, 4)
+
+FIELD(TOY_MATCH, YEAR, 26, 6)
+FIELD(TOY_MATCH, MON, 22, 4)
+FIELD(TOY_MATCH, DAY, 17, 5)
+FIELD(TOY_MATCH, HOUR, 12, 5)
+FIELD(TOY_MATCH, MIN, 6, 6)
+FIELD(TOY_MATCH, SEC, 0, 6)
+
+FIELD(RTC_CTRL, RTCEN, 13, 1)
+FIELD(RTC_CTRL, TOYEN, 11, 1)
+FIELD(RTC_CTRL, EO, 8, 1)
+
+#define TYPE_LS7A_RTC "ls7a_rtc"
+OBJECT_DECLARE_SIMPLE_TYPE(LS7ARtcState, LS7A_RTC)
+
+struct LS7ARtcState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ /*
+ * Needed to preserve the tick_count across migration, even if the
+ * absolute value of the rtc_clock is different on the source and
+ * destination.
+ */
+ int64_t offset_toy;
+ int64_t offset_rtc;
+ uint64_t save_toy_mon;
+ uint64_t save_toy_year;
+ uint64_t save_rtc;
+ int64_t data;
+ int tidx;
+ uint32_t toymatch[3];
+ uint32_t toytrim;
+ uint32_t cntrctl;
+ uint32_t rtctrim;
+ uint32_t rtccount;
+ uint32_t rtcmatch[3];
+ QEMUTimer *toy_timer[TIMER_NUMS];
+ QEMUTimer *rtc_timer[TIMER_NUMS];
+ qemu_irq irq;
+};
+
+/* switch nanoseconds time to rtc ticks */
+static inline uint64_t ls7a_rtc_ticks(void)
+{
+ return qemu_clock_get_ns(rtc_clock) * LS7A_RTC_FREQ / NANOSECONDS_PER_SECOND;
+}
+
+/* switch rtc ticks to nanoseconds */
+static inline uint64_t ticks_to_ns(uint64_t ticks)
+{
+ return ticks * NANOSECONDS_PER_SECOND / LS7A_RTC_FREQ;
+}
+
+static inline bool toy_enabled(LS7ARtcState *s)
+{
+ return FIELD_EX32(s->cntrctl, RTC_CTRL, TOYEN) &&
+ FIELD_EX32(s->cntrctl, RTC_CTRL, EO);
+}
+
+static inline bool rtc_enabled(LS7ARtcState *s)
+{
+ return FIELD_EX32(s->cntrctl, RTC_CTRL, RTCEN) &&
+ FIELD_EX32(s->cntrctl, RTC_CTRL, EO);
+}
+
+/* parse toy value to struct tm */
+static inline void toy_val_to_time_mon(uint64_t toy_val, struct tm *tm)
+{
+ tm->tm_sec = FIELD_EX32(toy_val, TOY, SEC);
+ tm->tm_min = FIELD_EX32(toy_val, TOY, MIN);
+ tm->tm_hour = FIELD_EX32(toy_val, TOY, HOUR);
+ tm->tm_mday = FIELD_EX32(toy_val, TOY, DAY);
+ tm->tm_mon = FIELD_EX32(toy_val, TOY, MON) - 1;
+}
+
+static inline void toy_val_to_time_year(uint64_t toy_year, struct tm *tm)
+{
+ tm->tm_year = toy_year;
+}
+
+/* parse struct tm to toy value */
+static inline uint64_t toy_time_to_val_mon(struct tm tm)
+{
+ uint64_t val = 0;
+
+ val = FIELD_DP32(val, TOY, MON, tm.tm_mon + 1);
+ val = FIELD_DP32(val, TOY, DAY, tm.tm_mday);
+ val = FIELD_DP32(val, TOY, HOUR, tm.tm_hour);
+ val = FIELD_DP32(val, TOY, MIN, tm.tm_min);
+ val = FIELD_DP32(val, TOY, SEC, tm.tm_sec);
+ return val;
+}
+
+static inline uint64_t toy_time_to_val_year(struct tm tm)
+{
+ uint64_t year;
+
+ year = tm.tm_year;
+ return year;
+}
+
+static inline void toymatch_val_to_time(uint64_t val, struct tm *tm)
+{
+ tm->tm_sec = FIELD_EX32(val, TOY_MATCH, SEC);
+ tm->tm_min = FIELD_EX32(val, TOY_MATCH, MIN);
+ tm->tm_hour = FIELD_EX32(val, TOY_MATCH, HOUR);
+ tm->tm_mday = FIELD_EX32(val, TOY_MATCH, DAY);
+ tm->tm_mon = FIELD_EX32(val, TOY_MATCH, MON) - 1;
+ tm->tm_year += (FIELD_EX32(val, TOY_MATCH, YEAR) - (tm->tm_year & 0x3f));
+}
+
+static void toymatch_write(LS7ARtcState *s, struct tm *tm, uint64_t val, int num)
+{
+ int64_t now, expire_time;
+
+ /* it do not support write when toy disabled */
+ if (toy_enabled(s)) {
+ s->toymatch[num] = val;
+ /* caculate expire time */
+ now = qemu_clock_get_ms(rtc_clock);
+ toymatch_val_to_time(val, tm);
+ expire_time = now + (qemu_timedate_diff(tm) - s->offset_toy) * 1000;
+ timer_mod(s->toy_timer[num], expire_time);
+ }
+}
+
+static void rtcmatch_write(LS7ARtcState *s, uint64_t val, int num)
+{
+ uint64_t expire_ns;
+
+ /* it do not support write when toy disabled */
+ if (rtc_enabled(s)) {
+ s->rtcmatch[num] = val;
+ /* caculate expire time */
+ expire_ns = ticks_to_ns(val) - ticks_to_ns(s->offset_rtc);
+ timer_mod_ns(s->rtc_timer[num], expire_ns);
+ }
+}
+
+static void ls7a_toy_stop(LS7ARtcState *s)
+{
+ int i;
+ struct tm tm;
+ /*
+ * save time when disabled toy,
+ * because toy time not add counters.
+ */
+ qemu_get_timedate(&tm, s->offset_toy);
+ s->save_toy_mon = toy_time_to_val_mon(tm);
+ s->save_toy_year = toy_time_to_val_year(tm);
+
+ /* delete timers, and when re-enabled, recaculate expire time */
+ for (i = 0; i < TIMER_NUMS; i++) {
+ timer_del(s->toy_timer[i]);
+ }
+}
+
+static void ls7a_rtc_stop(LS7ARtcState *s)
+{
+ int i;
+ uint64_t time;
+
+ /* save rtc time */
+ time = ls7a_rtc_ticks() + s->offset_rtc;
+ s->save_rtc = time;
+
+ /* delete timers, and when re-enabled, recaculate expire time */
+ for (i = 0; i < TIMER_NUMS; i++) {
+ timer_del(s->rtc_timer[i]);
+ }
+}
+
+static void ls7a_toy_start(LS7ARtcState *s)
+{
+ int i;
+ uint64_t expire_time, now;
+ struct tm tm;
+ /*
+ * need to recaculate toy offset
+ * and expire time when enable it.
+ */
+ toy_val_to_time_mon(s->save_toy_mon, &tm);
+ toy_val_to_time_year(s->save_toy_year, &tm);
+
+ s->offset_toy = qemu_timedate_diff(&tm);
+ now = qemu_clock_get_ms(rtc_clock);
+
+ /* recaculate expire time and enable timer */
+ for (i = 0; i < TIMER_NUMS; i++) {
+ toymatch_val_to_time(s->toymatch[i], &tm);
+ expire_time = now + (qemu_timedate_diff(&tm) - s->offset_toy) * 1000;
+ timer_mod(s->toy_timer[i], expire_time);
+ }
+}
+
+static void ls7a_rtc_start(LS7ARtcState *s)
+{
+ int i;
+ uint64_t expire_time, now;
+
+ /*
+ * need to recaculate rtc offset
+ * and expire time when enable it.
+ */
+ now = ls7a_rtc_ticks();
+ s->offset_rtc = s->save_rtc - now;
+
+ /* recaculate expire time and enable timer */
+ for (i = 0; i < TIMER_NUMS; i++) {
+ expire_time = ticks_to_ns(s->rtcmatch[i]) - ticks_to_ns(s->offset_rtc);
+ timer_mod_ns(s->rtc_timer[i], expire_time);
+ }
+}
+
+static uint64_t ls7a_rtc_read(void *opaque, hwaddr addr, unsigned size)
+{
+ LS7ARtcState *s = LS7A_RTC(opaque);
+ struct tm tm;
+ int val = 0;
+
+ switch (addr) {
+ case SYS_TOYREAD0:
+ /* if toy disabled, read save toy time */
+ if (toy_enabled(s)) {
+ qemu_get_timedate(&tm, s->offset_toy);
+ val = toy_time_to_val_mon(tm);
+ } else {
+ /* read save mon val */
+ val = s->save_toy_mon;
+ }
+ break;
+ case SYS_TOYREAD1:
+ /* if toy disabled, read save toy time */
+ if (toy_enabled(s)) {
+ qemu_get_timedate(&tm, s->offset_toy);
+ val = tm.tm_year;
+ } else {
+ /* read save year val */
+ val = s->save_toy_year;
+ }
+ break;
+ case SYS_TOYMATCH0:
+ val = s->toymatch[0];
+ break;
+ case SYS_TOYMATCH1:
+ val = s->toymatch[1];
+ break;
+ case SYS_TOYMATCH2:
+ val = s->toymatch[2];
+ break;
+ case SYS_RTCCTRL:
+ val = s->cntrctl;
+ break;
+ case SYS_RTCREAD0:
+ /* if rtc disabled, read save rtc time */
+ if (rtc_enabled(s)) {
+ val = ls7a_rtc_ticks() + s->offset_rtc;
+ } else {
+ val = s->save_rtc;
+ }
+ break;
+ case SYS_RTCMATCH0:
+ val = s->rtcmatch[0];
+ break;
+ case SYS_RTCMATCH1:
+ val = s->rtcmatch[1];
+ break;
+ case SYS_RTCMATCH2:
+ val = s->rtcmatch[2];
+ break;
+ default:
+ val = 0;
+ break;
+ }
+ return val;
+}
+
+static void ls7a_rtc_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ int old_toyen, old_rtcen, new_toyen, new_rtcen;
+ LS7ARtcState *s = LS7A_RTC(opaque);
+ struct tm tm;
+
+ switch (addr) {
+ case SYS_TOYWRITE0:
+ /* it do not support write when toy disabled */
+ if (toy_enabled(s)) {
+ qemu_get_timedate(&tm, s->offset_toy);
+ tm.tm_sec = FIELD_EX32(val, TOY, SEC);
+ tm.tm_min = FIELD_EX32(val, TOY, MIN);
+ tm.tm_hour = FIELD_EX32(val, TOY, HOUR);
+ tm.tm_mday = FIELD_EX32(val, TOY, DAY);
+ tm.tm_mon = FIELD_EX32(val, TOY, MON) - 1;
+ s->offset_toy = qemu_timedate_diff(&tm);
+ }
+ break;
+ case SYS_TOYWRITE1:
+ if (toy_enabled(s)) {
+ qemu_get_timedate(&tm, s->offset_toy);
+ tm.tm_year = val;
+ s->offset_toy = qemu_timedate_diff(&tm);
+ }
+ break;
+ case SYS_TOYMATCH0:
+ toymatch_write(s, &tm, val, 0);
+ break;
+ case SYS_TOYMATCH1:
+ toymatch_write(s, &tm, val, 1);
+ break;
+ case SYS_TOYMATCH2:
+ toymatch_write(s, &tm, val, 2);
+ break;
+ case SYS_RTCCTRL:
+ /* get old ctrl */
+ old_toyen = toy_enabled(s);
+ old_rtcen = rtc_enabled(s);
+
+ s->cntrctl = val;
+ /* get new ctrl */
+ new_toyen = toy_enabled(s);
+ new_rtcen = rtc_enabled(s);
+
+ /*
+ * we do not consider if EO changed, as it always set at most time.
+ * toy or rtc enabled should start timer. otherwise, stop timer
+ */
+ if (old_toyen != new_toyen) {
+ if (new_toyen) {
+ ls7a_toy_start(s);
+ } else {
+ ls7a_toy_stop(s);
+ }
+ }
+ if (old_rtcen != new_rtcen) {
+ if (new_rtcen) {
+ ls7a_rtc_start(s);
+ } else {
+ ls7a_rtc_stop(s);
+ }
+ }
+ break;
+ case SYS_RTCWRTIE0:
+ if (rtc_enabled(s)) {
+ s->offset_rtc = val - ls7a_rtc_ticks();
+ }
+ break;
+ case SYS_RTCMATCH0:
+ rtcmatch_write(s, val, 0);
+ break;
+ case SYS_RTCMATCH1:
+ rtcmatch_write(s, val, 1);
+ break;
+ case SYS_RTCMATCH2:
+ rtcmatch_write(s, val, 2);
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps ls7a_rtc_ops = {
+ .read = ls7a_rtc_read,
+ .write = ls7a_rtc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void toy_timer_cb(void *opaque)
+{
+ LS7ARtcState *s = opaque;
+
+ if (toy_enabled(s)) {
+ qemu_irq_pulse(s->irq);
+ }
+}
+
+static void rtc_timer_cb(void *opaque)
+{
+ LS7ARtcState *s = opaque;
+
+ if (rtc_enabled(s)) {
+ qemu_irq_pulse(s->irq);
+ }
+}
+
+static void ls7a_rtc_realize(DeviceState *dev, Error **errp)
+{
+ int i;
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ LS7ARtcState *d = LS7A_RTC(sbd);
+ memory_region_init_io(&d->iomem, NULL, &ls7a_rtc_ops,
+ (void *)d, "ls7a_rtc", 0x100);
+
+ sysbus_init_irq(sbd, &d->irq);
+
+ sysbus_init_mmio(sbd, &d->iomem);
+ for (i = 0; i < TIMER_NUMS; i++) {
+ d->toymatch[i] = 0;
+ d->rtcmatch[i] = 0;
+ d->toy_timer[i] = timer_new_ms(rtc_clock, toy_timer_cb, d);
+ d->rtc_timer[i] = timer_new_ms(rtc_clock, rtc_timer_cb, d);
+ }
+ d->offset_toy = 0;
+ d->offset_rtc = 0;
+ d->save_toy_mon = 0;
+ d->save_toy_year = 0;
+ d->save_rtc = 0;
+
+ create_unimplemented_device("mmio fallback 1", 0x10013ffc, 0x4);
+}
+
+static int ls7a_rtc_pre_save(void *opaque)
+{
+ LS7ARtcState *s = LS7A_RTC(opaque);
+
+ ls7a_toy_stop(s);
+ ls7a_rtc_stop(s);
+
+ return 0;
+}
+
+static int ls7a_rtc_post_load(void *opaque, int version_id)
+{
+ LS7ARtcState *s = LS7A_RTC(opaque);
+ if (toy_enabled(s)) {
+ ls7a_toy_start(s);
+ }
+
+ if (rtc_enabled(s)) {
+ ls7a_rtc_start(s);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_ls7a_rtc = {
+ .name = "ls7a_rtc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = ls7a_rtc_pre_save,
+ .post_load = ls7a_rtc_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT64(offset_toy, LS7ARtcState),
+ VMSTATE_INT64(offset_rtc, LS7ARtcState),
+ VMSTATE_UINT64(save_toy_mon, LS7ARtcState),
+ VMSTATE_UINT64(save_toy_year, LS7ARtcState),
+ VMSTATE_UINT64(save_rtc, LS7ARtcState),
+ VMSTATE_UINT32_ARRAY(toymatch, LS7ARtcState, TIMER_NUMS),
+ VMSTATE_UINT32_ARRAY(rtcmatch, LS7ARtcState, TIMER_NUMS),
+ VMSTATE_UINT32(cntrctl, LS7ARtcState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ls7a_rtc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->vmsd = &vmstate_ls7a_rtc;
+ dc->realize = ls7a_rtc_realize;
+ dc->desc = "ls7a rtc";
+}
+
+static const TypeInfo ls7a_rtc_info = {
+ .name = TYPE_LS7A_RTC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LS7ARtcState),
+ .class_init = ls7a_rtc_class_init,
+};
+
+static void ls7a_rtc_register_types(void)
+{
+ type_register_static(&ls7a_rtc_info);
+}
+
+type_init(ls7a_rtc_register_types)
diff --git a/hw/rtc/meson.build b/hw/rtc/meson.build
index 7cecdee5dd..dc33973384 100644
--- a/hw/rtc/meson.build
+++ b/hw/rtc/meson.build
@@ -11,6 +11,7 @@ softmmu_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_rtc.c'))
softmmu_ss.add(when: 'CONFIG_SUN4V_RTC', if_true: files('sun4v-rtc.c'))
softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_rtc.c'))
softmmu_ss.add(when: 'CONFIG_GOLDFISH_RTC', if_true: files('goldfish_rtc.c'))
+softmmu_ss.add(when: 'CONFIG_LS7A_RTC', if_true: files('ls7a_rtc.c'))
softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-rtc.c'))
specific_ss.add(when: 'CONFIG_MC146818RTC', if_true: files('mc146818rtc.c'))