aboutsummaryrefslogtreecommitdiff
path: root/hw/misc
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2020-03-12 17:34:34 +0000
committerPeter Maydell <peter.maydell@linaro.org>2020-03-12 17:34:34 +0000
commitd4f7d56759f7c75270c13d5f3f5f736a9558929c (patch)
tree71d7cfda9c4a204a5ab13dd4d19c7c980e1a3877 /hw/misc
parent49780a582d8bcedf098237f8997214c8424124be (diff)
parentaca53be34ac3e7cac5f39396a51a338860a5a837 (diff)
Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20200312' into staging
target-arm queue: * Fix various bugs that might result in an assert() due to incorrect hflags for M-profile CPUs * Fix Aspeed SMC Controller user-mode select handling * Report correct (with-tag) address in fault address register when TBI is enabled * cubieboard: make sure SOC object isn't leaked * fsl-imx25: Wire up eSDHC controllers * fsl-imx25: Wire up USB controllers * New board model: orangepi-pc (OrangePi PC) * ARM/KVM: if user doesn't select GIC version and the host kernel can only provide GICv3, use that, rather than defaulting to "fail because GICv2 isn't possible" * kvm: Only do KVM_SET_VCPU_EVENTS at the last stage of sync # gpg: Signature made Thu 12 Mar 2020 16:43:46 GMT # gpg: using RSA key E1A5C593CD419DE28E8315CF3C2525ED14360CDE # gpg: issuer "peter.maydell@linaro.org" # gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@gmail.com>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" [ultimate] # Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE * remotes/pmaydell/tags/pull-target-arm-20200312: (36 commits) target/arm: kvm: Inject events at the last stage of sync hw/arm/virt: kvm: allow gicv3 by default if v2 cannot work hw/arm/virt: kvm: Restructure finalize_gic_version() target/arm/kvm: Let kvm_arm_vgic_probe() return a bitmap hw/arm/virt: Introduce finalize_gic_version() hw/arm/virt: Introduce VirtGICType enum type hw/arm/virt: Document 'max' value in gic-version property description docs: add Orange Pi PC document tests/boot_linux_console: Test booting NetBSD via U-Boot on OrangePi PC tests/boot_linux_console: Add a SLOW test booting Ubuntu on OrangePi PC tests/boot_linux_console: Add a SD card test for the OrangePi PC board tests/boot_linux_console: Add initrd test for the Orange Pi PC board tests/boot_linux_console: Add a quick test for the OrangePi PC board hw/arm/allwinner: add RTC device support hw/arm/allwinner-h3: add SDRAM controller device hw/arm/allwinner-h3: add Boot ROM support hw/arm/allwinner-h3: add EMAC ethernet device hw/arm/allwinner: add SD/MMC host controller hw/arm/allwinner: add Security Identifier device hw/arm/allwinner: add CPU Configuration module ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw/misc')
-rw-r--r--hw/misc/Makefile.objs5
-rw-r--r--hw/misc/allwinner-cpucfg.c282
-rw-r--r--hw/misc/allwinner-h3-ccu.c242
-rw-r--r--hw/misc/allwinner-h3-dramc.c358
-rw-r--r--hw/misc/allwinner-h3-sysctrl.c140
-rw-r--r--hw/misc/allwinner-sid.c168
-rw-r--r--hw/misc/trace-events19
7 files changed, 1214 insertions, 0 deletions
diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs
index da993f45b7..68aae2eabb 100644
--- a/hw/misc/Makefile.objs
+++ b/hw/misc/Makefile.objs
@@ -28,6 +28,11 @@ common-obj-$(CONFIG_MACIO) += macio/
common-obj-$(CONFIG_IVSHMEM_DEVICE) += ivshmem.o
+common-obj-$(CONFIG_ALLWINNER_H3) += allwinner-h3-ccu.o
+obj-$(CONFIG_ALLWINNER_H3) += allwinner-cpucfg.o
+common-obj-$(CONFIG_ALLWINNER_H3) += allwinner-h3-dramc.o
+common-obj-$(CONFIG_ALLWINNER_H3) += allwinner-h3-sysctrl.o
+common-obj-$(CONFIG_ALLWINNER_H3) += allwinner-sid.o
common-obj-$(CONFIG_REALVIEW) += arm_sysctl.o
common-obj-$(CONFIG_NSERIES) += cbus.o
common-obj-$(CONFIG_ECCMEMCTL) += eccmemctl.o
diff --git a/hw/misc/allwinner-cpucfg.c b/hw/misc/allwinner-cpucfg.c
new file mode 100644
index 0000000000..bbd33a7dac
--- /dev/null
+++ b/hw/misc/allwinner-cpucfg.c
@@ -0,0 +1,282 @@
+/*
+ * Allwinner CPU Configuration Module emulation
+ *
+ * Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/error-report.h"
+#include "qemu/timer.h"
+#include "hw/core/cpu.h"
+#include "target/arm/arm-powerctl.h"
+#include "target/arm/cpu.h"
+#include "hw/misc/allwinner-cpucfg.h"
+#include "trace.h"
+
+/* CPUCFG register offsets */
+enum {
+ REG_CPUS_RST_CTRL = 0x0000, /* CPUs Reset Control */
+ REG_CPU0_RST_CTRL = 0x0040, /* CPU#0 Reset Control */
+ REG_CPU0_CTRL = 0x0044, /* CPU#0 Control */
+ REG_CPU0_STATUS = 0x0048, /* CPU#0 Status */
+ REG_CPU1_RST_CTRL = 0x0080, /* CPU#1 Reset Control */
+ REG_CPU1_CTRL = 0x0084, /* CPU#1 Control */
+ REG_CPU1_STATUS = 0x0088, /* CPU#1 Status */
+ REG_CPU2_RST_CTRL = 0x00C0, /* CPU#2 Reset Control */
+ REG_CPU2_CTRL = 0x00C4, /* CPU#2 Control */
+ REG_CPU2_STATUS = 0x00C8, /* CPU#2 Status */
+ REG_CPU3_RST_CTRL = 0x0100, /* CPU#3 Reset Control */
+ REG_CPU3_CTRL = 0x0104, /* CPU#3 Control */
+ REG_CPU3_STATUS = 0x0108, /* CPU#3 Status */
+ REG_CPU_SYS_RST = 0x0140, /* CPU System Reset */
+ REG_CLK_GATING = 0x0144, /* CPU Clock Gating */
+ REG_GEN_CTRL = 0x0184, /* General Control */
+ REG_SUPER_STANDBY = 0x01A0, /* Super Standby Flag */
+ REG_ENTRY_ADDR = 0x01A4, /* Reset Entry Address */
+ REG_DBG_EXTERN = 0x01E4, /* Debug External */
+ REG_CNT64_CTRL = 0x0280, /* 64-bit Counter Control */
+ REG_CNT64_LOW = 0x0284, /* 64-bit Counter Low */
+ REG_CNT64_HIGH = 0x0288, /* 64-bit Counter High */
+};
+
+/* CPUCFG register flags */
+enum {
+ CPUX_RESET_RELEASED = ((1 << 1) | (1 << 0)),
+ CPUX_STATUS_SMP = (1 << 0),
+ CPU_SYS_RESET_RELEASED = (1 << 0),
+ CLK_GATING_ENABLE = ((1 << 8) | 0xF),
+};
+
+/* CPUCFG register reset values */
+enum {
+ REG_CLK_GATING_RST = 0x0000010F,
+ REG_GEN_CTRL_RST = 0x00000020,
+ REG_SUPER_STANDBY_RST = 0x0,
+ REG_CNT64_CTRL_RST = 0x0,
+};
+
+/* CPUCFG constants */
+enum {
+ CPU_EXCEPTION_LEVEL_ON_RESET = 3, /* EL3 */
+};
+
+static void allwinner_cpucfg_cpu_reset(AwCpuCfgState *s, uint8_t cpu_id)
+{
+ int ret;
+
+ trace_allwinner_cpucfg_cpu_reset(cpu_id, s->entry_addr);
+
+ ARMCPU *target_cpu = ARM_CPU(arm_get_cpu_by_id(cpu_id));
+ if (!target_cpu) {
+ /*
+ * Called with a bogus value for cpu_id. Guest error will
+ * already have been logged, we can simply return here.
+ */
+ return;
+ }
+ bool target_aa64 = arm_feature(&target_cpu->env, ARM_FEATURE_AARCH64);
+
+ ret = arm_set_cpu_on(cpu_id, s->entry_addr, 0,
+ CPU_EXCEPTION_LEVEL_ON_RESET, target_aa64);
+ if (ret != QEMU_ARM_POWERCTL_RET_SUCCESS) {
+ error_report("%s: failed to bring up CPU %d: err %d",
+ __func__, cpu_id, ret);
+ return;
+ }
+}
+
+static uint64_t allwinner_cpucfg_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwCpuCfgState *s = AW_CPUCFG(opaque);
+ uint64_t val = 0;
+
+ switch (offset) {
+ case REG_CPUS_RST_CTRL: /* CPUs Reset Control */
+ case REG_CPU_SYS_RST: /* CPU System Reset */
+ val = CPU_SYS_RESET_RELEASED;
+ break;
+ case REG_CPU0_RST_CTRL: /* CPU#0 Reset Control */
+ case REG_CPU1_RST_CTRL: /* CPU#1 Reset Control */
+ case REG_CPU2_RST_CTRL: /* CPU#2 Reset Control */
+ case REG_CPU3_RST_CTRL: /* CPU#3 Reset Control */
+ val = CPUX_RESET_RELEASED;
+ break;
+ case REG_CPU0_CTRL: /* CPU#0 Control */
+ case REG_CPU1_CTRL: /* CPU#1 Control */
+ case REG_CPU2_CTRL: /* CPU#2 Control */
+ case REG_CPU3_CTRL: /* CPU#3 Control */
+ val = 0;
+ break;
+ case REG_CPU0_STATUS: /* CPU#0 Status */
+ case REG_CPU1_STATUS: /* CPU#1 Status */
+ case REG_CPU2_STATUS: /* CPU#2 Status */
+ case REG_CPU3_STATUS: /* CPU#3 Status */
+ val = CPUX_STATUS_SMP;
+ break;
+ case REG_CLK_GATING: /* CPU Clock Gating */
+ val = CLK_GATING_ENABLE;
+ break;
+ case REG_GEN_CTRL: /* General Control */
+ val = s->gen_ctrl;
+ break;
+ case REG_SUPER_STANDBY: /* Super Standby Flag */
+ val = s->super_standby;
+ break;
+ case REG_ENTRY_ADDR: /* Reset Entry Address */
+ val = s->entry_addr;
+ break;
+ case REG_DBG_EXTERN: /* Debug External */
+ case REG_CNT64_CTRL: /* 64-bit Counter Control */
+ case REG_CNT64_LOW: /* 64-bit Counter Low */
+ case REG_CNT64_HIGH: /* 64-bit Counter High */
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented register at 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ }
+
+ trace_allwinner_cpucfg_read(offset, val, size);
+
+ return val;
+}
+
+static void allwinner_cpucfg_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwCpuCfgState *s = AW_CPUCFG(opaque);
+
+ trace_allwinner_cpucfg_write(offset, val, size);
+
+ switch (offset) {
+ case REG_CPUS_RST_CTRL: /* CPUs Reset Control */
+ case REG_CPU_SYS_RST: /* CPU System Reset */
+ break;
+ case REG_CPU0_RST_CTRL: /* CPU#0 Reset Control */
+ case REG_CPU1_RST_CTRL: /* CPU#1 Reset Control */
+ case REG_CPU2_RST_CTRL: /* CPU#2 Reset Control */
+ case REG_CPU3_RST_CTRL: /* CPU#3 Reset Control */
+ if (val) {
+ allwinner_cpucfg_cpu_reset(s, (offset - REG_CPU0_RST_CTRL) >> 6);
+ }
+ break;
+ case REG_CPU0_CTRL: /* CPU#0 Control */
+ case REG_CPU1_CTRL: /* CPU#1 Control */
+ case REG_CPU2_CTRL: /* CPU#2 Control */
+ case REG_CPU3_CTRL: /* CPU#3 Control */
+ case REG_CPU0_STATUS: /* CPU#0 Status */
+ case REG_CPU1_STATUS: /* CPU#1 Status */
+ case REG_CPU2_STATUS: /* CPU#2 Status */
+ case REG_CPU3_STATUS: /* CPU#3 Status */
+ case REG_CLK_GATING: /* CPU Clock Gating */
+ break;
+ case REG_GEN_CTRL: /* General Control */
+ s->gen_ctrl = val;
+ break;
+ case REG_SUPER_STANDBY: /* Super Standby Flag */
+ s->super_standby = val;
+ break;
+ case REG_ENTRY_ADDR: /* Reset Entry Address */
+ s->entry_addr = val;
+ break;
+ case REG_DBG_EXTERN: /* Debug External */
+ case REG_CNT64_CTRL: /* 64-bit Counter Control */
+ case REG_CNT64_LOW: /* 64-bit Counter Low */
+ case REG_CNT64_HIGH: /* 64-bit Counter High */
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented register at 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps allwinner_cpucfg_ops = {
+ .read = allwinner_cpucfg_read,
+ .write = allwinner_cpucfg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static void allwinner_cpucfg_reset(DeviceState *dev)
+{
+ AwCpuCfgState *s = AW_CPUCFG(dev);
+
+ /* Set default values for registers */
+ s->gen_ctrl = REG_GEN_CTRL_RST;
+ s->super_standby = REG_SUPER_STANDBY_RST;
+ s->entry_addr = 0;
+}
+
+static void allwinner_cpucfg_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwCpuCfgState *s = AW_CPUCFG(obj);
+
+ /* Memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_cpucfg_ops, s,
+ TYPE_AW_CPUCFG, 1 * KiB);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription allwinner_cpucfg_vmstate = {
+ .name = "allwinner-cpucfg",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(gen_ctrl, AwCpuCfgState),
+ VMSTATE_UINT32(super_standby, AwCpuCfgState),
+ VMSTATE_UINT32(entry_addr, AwCpuCfgState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void allwinner_cpucfg_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = allwinner_cpucfg_reset;
+ dc->vmsd = &allwinner_cpucfg_vmstate;
+}
+
+static const TypeInfo allwinner_cpucfg_info = {
+ .name = TYPE_AW_CPUCFG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_cpucfg_init,
+ .instance_size = sizeof(AwCpuCfgState),
+ .class_init = allwinner_cpucfg_class_init,
+};
+
+static void allwinner_cpucfg_register(void)
+{
+ type_register_static(&allwinner_cpucfg_info);
+}
+
+type_init(allwinner_cpucfg_register)
diff --git a/hw/misc/allwinner-h3-ccu.c b/hw/misc/allwinner-h3-ccu.c
new file mode 100644
index 0000000000..18d1074545
--- /dev/null
+++ b/hw/misc/allwinner-h3-ccu.c
@@ -0,0 +1,242 @@
+/*
+ * Allwinner H3 Clock Control Unit emulation
+ *
+ * Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/misc/allwinner-h3-ccu.h"
+
+/* CCU register offsets */
+enum {
+ REG_PLL_CPUX = 0x0000, /* PLL CPUX Control */
+ REG_PLL_AUDIO = 0x0008, /* PLL Audio Control */
+ REG_PLL_VIDEO = 0x0010, /* PLL Video Control */
+ REG_PLL_VE = 0x0018, /* PLL VE Control */
+ REG_PLL_DDR = 0x0020, /* PLL DDR Control */
+ REG_PLL_PERIPH0 = 0x0028, /* PLL Peripherals 0 Control */
+ REG_PLL_GPU = 0x0038, /* PLL GPU Control */
+ REG_PLL_PERIPH1 = 0x0044, /* PLL Peripherals 1 Control */
+ REG_PLL_DE = 0x0048, /* PLL Display Engine Control */
+ REG_CPUX_AXI = 0x0050, /* CPUX/AXI Configuration */
+ REG_APB1 = 0x0054, /* ARM Peripheral Bus 1 Config */
+ REG_APB2 = 0x0058, /* ARM Peripheral Bus 2 Config */
+ REG_DRAM_CFG = 0x00F4, /* DRAM Configuration */
+ REG_MBUS = 0x00FC, /* MBUS Reset */
+ REG_PLL_TIME0 = 0x0200, /* PLL Stable Time 0 */
+ REG_PLL_TIME1 = 0x0204, /* PLL Stable Time 1 */
+ REG_PLL_CPUX_BIAS = 0x0220, /* PLL CPUX Bias */
+ REG_PLL_AUDIO_BIAS = 0x0224, /* PLL Audio Bias */
+ REG_PLL_VIDEO_BIAS = 0x0228, /* PLL Video Bias */
+ REG_PLL_VE_BIAS = 0x022C, /* PLL VE Bias */
+ REG_PLL_DDR_BIAS = 0x0230, /* PLL DDR Bias */
+ REG_PLL_PERIPH0_BIAS = 0x0234, /* PLL Peripherals 0 Bias */
+ REG_PLL_GPU_BIAS = 0x023C, /* PLL GPU Bias */
+ REG_PLL_PERIPH1_BIAS = 0x0244, /* PLL Peripherals 1 Bias */
+ REG_PLL_DE_BIAS = 0x0248, /* PLL Display Engine Bias */
+ REG_PLL_CPUX_TUNING = 0x0250, /* PLL CPUX Tuning */
+ REG_PLL_DDR_TUNING = 0x0260, /* PLL DDR Tuning */
+};
+
+#define REG_INDEX(offset) (offset / sizeof(uint32_t))
+
+/* CCU register flags */
+enum {
+ REG_DRAM_CFG_UPDATE = (1 << 16),
+};
+
+enum {
+ REG_PLL_ENABLE = (1 << 31),
+ REG_PLL_LOCK = (1 << 28),
+};
+
+
+/* CCU register reset values */
+enum {
+ REG_PLL_CPUX_RST = 0x00001000,
+ REG_PLL_AUDIO_RST = 0x00035514,
+ REG_PLL_VIDEO_RST = 0x03006207,
+ REG_PLL_VE_RST = 0x03006207,
+ REG_PLL_DDR_RST = 0x00001000,
+ REG_PLL_PERIPH0_RST = 0x00041811,
+ REG_PLL_GPU_RST = 0x03006207,
+ REG_PLL_PERIPH1_RST = 0x00041811,
+ REG_PLL_DE_RST = 0x03006207,
+ REG_CPUX_AXI_RST = 0x00010000,
+ REG_APB1_RST = 0x00001010,
+ REG_APB2_RST = 0x01000000,
+ REG_DRAM_CFG_RST = 0x00000000,
+ REG_MBUS_RST = 0x80000000,
+ REG_PLL_TIME0_RST = 0x000000FF,
+ REG_PLL_TIME1_RST = 0x000000FF,
+ REG_PLL_CPUX_BIAS_RST = 0x08100200,
+ REG_PLL_AUDIO_BIAS_RST = 0x10100000,
+ REG_PLL_VIDEO_BIAS_RST = 0x10100000,
+ REG_PLL_VE_BIAS_RST = 0x10100000,
+ REG_PLL_DDR_BIAS_RST = 0x81104000,
+ REG_PLL_PERIPH0_BIAS_RST = 0x10100010,
+ REG_PLL_GPU_BIAS_RST = 0x10100000,
+ REG_PLL_PERIPH1_BIAS_RST = 0x10100010,
+ REG_PLL_DE_BIAS_RST = 0x10100000,
+ REG_PLL_CPUX_TUNING_RST = 0x0A101000,
+ REG_PLL_DDR_TUNING_RST = 0x14880000,
+};
+
+static uint64_t allwinner_h3_ccu_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwH3ClockCtlState *s = AW_H3_CCU(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ switch (offset) {
+ case 0x308 ... AW_H3_CCU_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ return s->regs[idx];
+}
+
+static void allwinner_h3_ccu_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwH3ClockCtlState *s = AW_H3_CCU(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ switch (offset) {
+ case REG_DRAM_CFG: /* DRAM Configuration */
+ val &= ~REG_DRAM_CFG_UPDATE;
+ break;
+ case REG_PLL_CPUX: /* PLL CPUX Control */
+ case REG_PLL_AUDIO: /* PLL Audio Control */
+ case REG_PLL_VIDEO: /* PLL Video Control */
+ case REG_PLL_VE: /* PLL VE Control */
+ case REG_PLL_DDR: /* PLL DDR Control */
+ case REG_PLL_PERIPH0: /* PLL Peripherals 0 Control */
+ case REG_PLL_GPU: /* PLL GPU Control */
+ case REG_PLL_PERIPH1: /* PLL Peripherals 1 Control */
+ case REG_PLL_DE: /* PLL Display Engine Control */
+ if (val & REG_PLL_ENABLE) {
+ val |= REG_PLL_LOCK;
+ }
+ break;
+ case 0x308 ... AW_H3_CCU_IOSIZE:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented write offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ }
+
+ s->regs[idx] = (uint32_t) val;
+}
+
+static const MemoryRegionOps allwinner_h3_ccu_ops = {
+ .read = allwinner_h3_ccu_read,
+ .write = allwinner_h3_ccu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static void allwinner_h3_ccu_reset(DeviceState *dev)
+{
+ AwH3ClockCtlState *s = AW_H3_CCU(dev);
+
+ /* Set default values for registers */
+ s->regs[REG_INDEX(REG_PLL_CPUX)] = REG_PLL_CPUX_RST;
+ s->regs[REG_INDEX(REG_PLL_AUDIO)] = REG_PLL_AUDIO_RST;
+ s->regs[REG_INDEX(REG_PLL_VIDEO)] = REG_PLL_VIDEO_RST;
+ s->regs[REG_INDEX(REG_PLL_VE)] = REG_PLL_VE_RST;
+ s->regs[REG_INDEX(REG_PLL_DDR)] = REG_PLL_DDR_RST;
+ s->regs[REG_INDEX(REG_PLL_PERIPH0)] = REG_PLL_PERIPH0_RST;
+ s->regs[REG_INDEX(REG_PLL_GPU)] = REG_PLL_GPU_RST;
+ s->regs[REG_INDEX(REG_PLL_PERIPH1)] = REG_PLL_PERIPH1_RST;
+ s->regs[REG_INDEX(REG_PLL_DE)] = REG_PLL_DE_RST;
+ s->regs[REG_INDEX(REG_CPUX_AXI)] = REG_CPUX_AXI_RST;
+ s->regs[REG_INDEX(REG_APB1)] = REG_APB1_RST;
+ s->regs[REG_INDEX(REG_APB2)] = REG_APB2_RST;
+ s->regs[REG_INDEX(REG_DRAM_CFG)] = REG_DRAM_CFG_RST;
+ s->regs[REG_INDEX(REG_MBUS)] = REG_MBUS_RST;
+ s->regs[REG_INDEX(REG_PLL_TIME0)] = REG_PLL_TIME0_RST;
+ s->regs[REG_INDEX(REG_PLL_TIME1)] = REG_PLL_TIME1_RST;
+ s->regs[REG_INDEX(REG_PLL_CPUX_BIAS)] = REG_PLL_CPUX_BIAS_RST;
+ s->regs[REG_INDEX(REG_PLL_AUDIO_BIAS)] = REG_PLL_AUDIO_BIAS_RST;
+ s->regs[REG_INDEX(REG_PLL_VIDEO_BIAS)] = REG_PLL_VIDEO_BIAS_RST;
+ s->regs[REG_INDEX(REG_PLL_VE_BIAS)] = REG_PLL_VE_BIAS_RST;
+ s->regs[REG_INDEX(REG_PLL_DDR_BIAS)] = REG_PLL_DDR_BIAS_RST;
+ s->regs[REG_INDEX(REG_PLL_PERIPH0_BIAS)] = REG_PLL_PERIPH0_BIAS_RST;
+ s->regs[REG_INDEX(REG_PLL_GPU_BIAS)] = REG_PLL_GPU_BIAS_RST;
+ s->regs[REG_INDEX(REG_PLL_PERIPH1_BIAS)] = REG_PLL_PERIPH1_BIAS_RST;
+ s->regs[REG_INDEX(REG_PLL_DE_BIAS)] = REG_PLL_DE_BIAS_RST;
+ s->regs[REG_INDEX(REG_PLL_CPUX_TUNING)] = REG_PLL_CPUX_TUNING_RST;
+ s->regs[REG_INDEX(REG_PLL_DDR_TUNING)] = REG_PLL_DDR_TUNING_RST;
+}
+
+static void allwinner_h3_ccu_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwH3ClockCtlState *s = AW_H3_CCU(obj);
+
+ /* Memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_h3_ccu_ops, s,
+ TYPE_AW_H3_CCU, AW_H3_CCU_IOSIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription allwinner_h3_ccu_vmstate = {
+ .name = "allwinner-h3-ccu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AwH3ClockCtlState, AW_H3_CCU_REGS_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void allwinner_h3_ccu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = allwinner_h3_ccu_reset;
+ dc->vmsd = &allwinner_h3_ccu_vmstate;
+}
+
+static const TypeInfo allwinner_h3_ccu_info = {
+ .name = TYPE_AW_H3_CCU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_h3_ccu_init,
+ .instance_size = sizeof(AwH3ClockCtlState),
+ .class_init = allwinner_h3_ccu_class_init,
+};
+
+static void allwinner_h3_ccu_register(void)
+{
+ type_register_static(&allwinner_h3_ccu_info);
+}
+
+type_init(allwinner_h3_ccu_register)
diff --git a/hw/misc/allwinner-h3-dramc.c b/hw/misc/allwinner-h3-dramc.c
new file mode 100644
index 0000000000..2b5260260e
--- /dev/null
+++ b/hw/misc/allwinner-h3-dramc.c
@@ -0,0 +1,358 @@
+/*
+ * Allwinner H3 SDRAM Controller emulation
+ *
+ * Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu/error-report.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/address-spaces.h"
+#include "hw/qdev-properties.h"
+#include "qapi/error.h"
+#include "hw/misc/allwinner-h3-dramc.h"
+#include "trace.h"
+
+#define REG_INDEX(offset) (offset / sizeof(uint32_t))
+
+/* DRAMCOM register offsets */
+enum {
+ REG_DRAMCOM_CR = 0x0000, /* Control Register */
+};
+
+/* DRAMCTL register offsets */
+enum {
+ REG_DRAMCTL_PIR = 0x0000, /* PHY Initialization Register */
+ REG_DRAMCTL_PGSR = 0x0010, /* PHY General Status Register */
+ REG_DRAMCTL_STATR = 0x0018, /* Status Register */
+};
+
+/* DRAMCTL register flags */
+enum {
+ REG_DRAMCTL_PGSR_INITDONE = (1 << 0),
+};
+
+enum {
+ REG_DRAMCTL_STATR_ACTIVE = (1 << 0),
+};
+
+static void allwinner_h3_dramc_map_rows(AwH3DramCtlState *s, uint8_t row_bits,
+ uint8_t bank_bits, uint16_t page_size)
+{
+ /*
+ * This function simulates row addressing behavior when bootloader
+ * software attempts to detect the amount of available SDRAM. In U-Boot
+ * the controller is configured with the widest row addressing available.
+ * Then a pattern is written to RAM at an offset on the row boundary size.
+ * If the value read back equals the value read back from the
+ * start of RAM, the bootloader knows the amount of row bits.
+ *
+ * This function inserts a mirrored memory region when the configured row
+ * bits are not matching the actual emulated memory, to simulate the
+ * same behavior on hardware as expected by the bootloader.
+ */
+ uint8_t row_bits_actual = 0;
+
+ /* Calculate the actual row bits using the ram_size property */
+ for (uint8_t i = 8; i < 12; i++) {
+ if (1 << i == s->ram_size) {
+ row_bits_actual = i + 3;
+ break;
+ }
+ }
+
+ if (s->ram_size == (1 << (row_bits - 3))) {
+ /* When row bits is the expected value, remove the mirror */
+ memory_region_set_enabled(&s->row_mirror_alias, false);
+ trace_allwinner_h3_dramc_rowmirror_disable();
+
+ } else if (row_bits_actual) {
+ /* Row bits not matching ram_size, install the rows mirror */
+ hwaddr row_mirror = s->ram_addr + ((1 << (row_bits_actual +
+ bank_bits)) * page_size);
+
+ memory_region_set_enabled(&s->row_mirror_alias, true);
+ memory_region_set_address(&s->row_mirror_alias, row_mirror);
+
+ trace_allwinner_h3_dramc_rowmirror_enable(row_mirror);
+ }
+}
+
+static uint64_t allwinner_h3_dramcom_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ if (idx >= AW_H3_DRAMCOM_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ trace_allwinner_h3_dramcom_read(offset, s->dramcom[idx], size);
+
+ return s->dramcom[idx];
+}
+
+static void allwinner_h3_dramcom_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ trace_allwinner_h3_dramcom_write(offset, val, size);
+
+ if (idx >= AW_H3_DRAMCOM_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return;
+ }
+
+ switch (offset) {
+ case REG_DRAMCOM_CR: /* Control Register */
+ allwinner_h3_dramc_map_rows(s, ((val >> 4) & 0xf) + 1,
+ ((val >> 2) & 0x1) + 2,
+ 1 << (((val >> 8) & 0xf) + 3));
+ break;
+ default:
+ break;
+ };
+
+ s->dramcom[idx] = (uint32_t) val;
+}
+
+static uint64_t allwinner_h3_dramctl_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ if (idx >= AW_H3_DRAMCTL_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ trace_allwinner_h3_dramctl_read(offset, s->dramctl[idx], size);
+
+ return s->dramctl[idx];
+}
+
+static void allwinner_h3_dramctl_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ trace_allwinner_h3_dramctl_write(offset, val, size);
+
+ if (idx >= AW_H3_DRAMCTL_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return;
+ }
+
+ switch (offset) {
+ case REG_DRAMCTL_PIR: /* PHY Initialization Register */
+ s->dramctl[REG_INDEX(REG_DRAMCTL_PGSR)] |= REG_DRAMCTL_PGSR_INITDONE;
+ s->dramctl[REG_INDEX(REG_DRAMCTL_STATR)] |= REG_DRAMCTL_STATR_ACTIVE;
+ break;
+ default:
+ break;
+ }
+
+ s->dramctl[idx] = (uint32_t) val;
+}
+
+static uint64_t allwinner_h3_dramphy_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ if (idx >= AW_H3_DRAMPHY_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ trace_allwinner_h3_dramphy_read(offset, s->dramphy[idx], size);
+
+ return s->dramphy[idx];
+}
+
+static void allwinner_h3_dramphy_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ trace_allwinner_h3_dramphy_write(offset, val, size);
+
+ if (idx >= AW_H3_DRAMPHY_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return;
+ }
+
+ s->dramphy[idx] = (uint32_t) val;
+}
+
+static const MemoryRegionOps allwinner_h3_dramcom_ops = {
+ .read = allwinner_h3_dramcom_read,
+ .write = allwinner_h3_dramcom_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static const MemoryRegionOps allwinner_h3_dramctl_ops = {
+ .read = allwinner_h3_dramctl_read,
+ .write = allwinner_h3_dramctl_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static const MemoryRegionOps allwinner_h3_dramphy_ops = {
+ .read = allwinner_h3_dramphy_read,
+ .write = allwinner_h3_dramphy_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static void allwinner_h3_dramc_reset(DeviceState *dev)
+{
+ AwH3DramCtlState *s = AW_H3_DRAMC(dev);
+
+ /* Set default values for registers */
+ memset(&s->dramcom, 0, sizeof(s->dramcom));
+ memset(&s->dramctl, 0, sizeof(s->dramctl));
+ memset(&s->dramphy, 0, sizeof(s->dramphy));
+}
+
+static void allwinner_h3_dramc_realize(DeviceState *dev, Error **errp)
+{
+ AwH3DramCtlState *s = AW_H3_DRAMC(dev);
+
+ /* Only power of 2 RAM sizes from 256MiB up to 2048MiB are supported */
+ for (uint8_t i = 8; i < 13; i++) {
+ if (1 << i == s->ram_size) {
+ break;
+ } else if (i == 12) {
+ error_report("%s: ram-size %u MiB is not supported",
+ __func__, s->ram_size);
+ exit(1);
+ }
+ }
+
+ /* Setup row mirror mappings */
+ memory_region_init_ram(&s->row_mirror, OBJECT(s),
+ "allwinner-h3-dramc.row-mirror",
+ 4 * KiB, &error_abort);
+ memory_region_add_subregion_overlap(get_system_memory(), s->ram_addr,
+ &s->row_mirror, 10);
+
+ memory_region_init_alias(&s->row_mirror_alias, OBJECT(s),
+ "allwinner-h3-dramc.row-mirror-alias",
+ &s->row_mirror, 0, 4 * KiB);
+ memory_region_add_subregion_overlap(get_system_memory(),
+ s->ram_addr + 1 * MiB,
+ &s->row_mirror_alias, 10);
+ memory_region_set_enabled(&s->row_mirror_alias, false);
+}
+
+static void allwinner_h3_dramc_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwH3DramCtlState *s = AW_H3_DRAMC(obj);
+
+ /* DRAMCOM registers */
+ memory_region_init_io(&s->dramcom_iomem, OBJECT(s),
+ &allwinner_h3_dramcom_ops, s,
+ TYPE_AW_H3_DRAMC, 4 * KiB);
+ sysbus_init_mmio(sbd, &s->dramcom_iomem);
+
+ /* DRAMCTL registers */
+ memory_region_init_io(&s->dramctl_iomem, OBJECT(s),
+ &allwinner_h3_dramctl_ops, s,
+ TYPE_AW_H3_DRAMC, 4 * KiB);
+ sysbus_init_mmio(sbd, &s->dramctl_iomem);
+
+ /* DRAMPHY registers */
+ memory_region_init_io(&s->dramphy_iomem, OBJECT(s),
+ &allwinner_h3_dramphy_ops, s,
+ TYPE_AW_H3_DRAMC, 4 * KiB);
+ sysbus_init_mmio(sbd, &s->dramphy_iomem);
+}
+
+static Property allwinner_h3_dramc_properties[] = {
+ DEFINE_PROP_UINT64("ram-addr", AwH3DramCtlState, ram_addr, 0x0),
+ DEFINE_PROP_UINT32("ram-size", AwH3DramCtlState, ram_size, 256 * MiB),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static const VMStateDescription allwinner_h3_dramc_vmstate = {
+ .name = "allwinner-h3-dramc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(dramcom, AwH3DramCtlState, AW_H3_DRAMCOM_REGS_NUM),
+ VMSTATE_UINT32_ARRAY(dramctl, AwH3DramCtlState, AW_H3_DRAMCTL_REGS_NUM),
+ VMSTATE_UINT32_ARRAY(dramphy, AwH3DramCtlState, AW_H3_DRAMPHY_REGS_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void allwinner_h3_dramc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = allwinner_h3_dramc_reset;
+ dc->vmsd = &allwinner_h3_dramc_vmstate;
+ dc->realize = allwinner_h3_dramc_realize;
+ device_class_set_props(dc, allwinner_h3_dramc_properties);
+}
+
+static const TypeInfo allwinner_h3_dramc_info = {
+ .name = TYPE_AW_H3_DRAMC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_h3_dramc_init,
+ .instance_size = sizeof(AwH3DramCtlState),
+ .class_init = allwinner_h3_dramc_class_init,
+};
+
+static void allwinner_h3_dramc_register(void)
+{
+ type_register_static(&allwinner_h3_dramc_info);
+}
+
+type_init(allwinner_h3_dramc_register)
diff --git a/hw/misc/allwinner-h3-sysctrl.c b/hw/misc/allwinner-h3-sysctrl.c
new file mode 100644
index 0000000000..1d07efa880
--- /dev/null
+++ b/hw/misc/allwinner-h3-sysctrl.c
@@ -0,0 +1,140 @@
+/*
+ * Allwinner H3 System Control emulation
+ *
+ * Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/misc/allwinner-h3-sysctrl.h"
+
+/* System Control register offsets */
+enum {
+ REG_VER = 0x24, /* Version */
+ REG_EMAC_PHY_CLK = 0x30, /* EMAC PHY Clock */
+};
+
+#define REG_INDEX(offset) (offset / sizeof(uint32_t))
+
+/* System Control register reset values */
+enum {
+ REG_VER_RST = 0x0,
+ REG_EMAC_PHY_CLK_RST = 0x58000,
+};
+
+static uint64_t allwinner_h3_sysctrl_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwH3SysCtrlState *s = AW_H3_SYSCTRL(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ if (idx >= AW_H3_SYSCTRL_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ return s->regs[idx];
+}
+
+static void allwinner_h3_sysctrl_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwH3SysCtrlState *s = AW_H3_SYSCTRL(opaque);
+ const uint32_t idx = REG_INDEX(offset);
+
+ if (idx >= AW_H3_SYSCTRL_REGS_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return;
+ }
+
+ switch (offset) {
+ case REG_VER: /* Version */
+ break;
+ default:
+ s->regs[idx] = (uint32_t) val;
+ break;
+ }
+}
+
+static const MemoryRegionOps allwinner_h3_sysctrl_ops = {
+ .read = allwinner_h3_sysctrl_read,
+ .write = allwinner_h3_sysctrl_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static void allwinner_h3_sysctrl_reset(DeviceState *dev)
+{
+ AwH3SysCtrlState *s = AW_H3_SYSCTRL(dev);
+
+ /* Set default values for registers */
+ s->regs[REG_INDEX(REG_VER)] = REG_VER_RST;
+ s->regs[REG_INDEX(REG_EMAC_PHY_CLK)] = REG_EMAC_PHY_CLK_RST;
+}
+
+static void allwinner_h3_sysctrl_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwH3SysCtrlState *s = AW_H3_SYSCTRL(obj);
+
+ /* Memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_h3_sysctrl_ops, s,
+ TYPE_AW_H3_SYSCTRL, 4 * KiB);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription allwinner_h3_sysctrl_vmstate = {
+ .name = "allwinner-h3-sysctrl",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AwH3SysCtrlState, AW_H3_SYSCTRL_REGS_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void allwinner_h3_sysctrl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = allwinner_h3_sysctrl_reset;
+ dc->vmsd = &allwinner_h3_sysctrl_vmstate;
+}
+
+static const TypeInfo allwinner_h3_sysctrl_info = {
+ .name = TYPE_AW_H3_SYSCTRL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_h3_sysctrl_init,
+ .instance_size = sizeof(AwH3SysCtrlState),
+ .class_init = allwinner_h3_sysctrl_class_init,
+};
+
+static void allwinner_h3_sysctrl_register(void)
+{
+ type_register_static(&allwinner_h3_sysctrl_info);
+}
+
+type_init(allwinner_h3_sysctrl_register)
diff --git a/hw/misc/allwinner-sid.c b/hw/misc/allwinner-sid.c
new file mode 100644
index 0000000000..196380c33a
--- /dev/null
+++ b/hw/misc/allwinner-sid.c
@@ -0,0 +1,168 @@
+/*
+ * Allwinner Security ID emulation
+ *
+ * Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/guest-random.h"
+#include "qapi/error.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/allwinner-sid.h"
+#include "trace.h"
+
+/* SID register offsets */
+enum {
+ REG_PRCTL = 0x40, /* Control */
+ REG_RDKEY = 0x60, /* Read Key */
+};
+
+/* SID register flags */
+enum {
+ REG_PRCTL_WRITE = 0x0002, /* Unknown write flag */
+ REG_PRCTL_OP_LOCK = 0xAC00, /* Lock operation */
+};
+
+static uint64_t allwinner_sid_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const AwSidState *s = AW_SID(opaque);
+ uint64_t val = 0;
+
+ switch (offset) {
+ case REG_PRCTL: /* Control */
+ val = s->control;
+ break;
+ case REG_RDKEY: /* Read Key */
+ val = s->rdkey;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+ }
+
+ trace_allwinner_sid_read(offset, val, size);
+
+ return val;
+}
+
+static void allwinner_sid_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ AwSidState *s = AW_SID(opaque);
+
+ trace_allwinner_sid_write(offset, val, size);
+
+ switch (offset) {
+ case REG_PRCTL: /* Control */
+ s->control = val;
+
+ if ((s->control & REG_PRCTL_OP_LOCK) &&
+ (s->control & REG_PRCTL_WRITE)) {
+ uint32_t id = s->control >> 16;
+
+ if (id <= sizeof(QemuUUID) - sizeof(s->rdkey)) {
+ s->rdkey = ldl_be_p(&s->identifier.data[id]);
+ }
+ }
+ s->control &= ~REG_PRCTL_WRITE;
+ break;
+ case REG_RDKEY: /* Read Key */
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps allwinner_sid_ops = {
+ .read = allwinner_sid_read,
+ .write = allwinner_sid_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl.min_access_size = 4,
+};
+
+static void allwinner_sid_reset(DeviceState *dev)
+{
+ AwSidState *s = AW_SID(dev);
+
+ /* Set default values for registers */
+ s->control = 0;
+ s->rdkey = 0;
+}
+
+static void allwinner_sid_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ AwSidState *s = AW_SID(obj);
+
+ /* Memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_sid_ops, s,
+ TYPE_AW_SID, 1 * KiB);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static Property allwinner_sid_properties[] = {
+ DEFINE_PROP_UUID_NODEFAULT("identifier", AwSidState, identifier),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static const VMStateDescription allwinner_sid_vmstate = {
+ .name = "allwinner-sid",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(control, AwSidState),
+ VMSTATE_UINT32(rdkey, AwSidState),
+ VMSTATE_UINT8_ARRAY_V(identifier.data, AwSidState, sizeof(QemuUUID), 1),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void allwinner_sid_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = allwinner_sid_reset;
+ dc->vmsd = &allwinner_sid_vmstate;
+ device_class_set_props(dc, allwinner_sid_properties);
+}
+
+static const TypeInfo allwinner_sid_info = {
+ .name = TYPE_AW_SID,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = allwinner_sid_init,
+ .instance_size = sizeof(AwSidState),
+ .class_init = allwinner_sid_class_init,
+};
+
+static void allwinner_sid_register(void)
+{
+ type_register_static(&allwinner_sid_info);
+}
+
+type_init(allwinner_sid_register)
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 7f0f5dff3a..a5862b2bed 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -1,5 +1,24 @@
# See docs/devel/tracing.txt for syntax documentation.
+# allwinner-cpucfg.c
+allwinner_cpucfg_cpu_reset(uint8_t cpu_id, uint32_t reset_addr) "id %u, reset_addr 0x%" PRIu32
+allwinner_cpucfg_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_cpucfg_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+
+# allwinner-h3-dramc.c
+allwinner_h3_dramc_rowmirror_disable(void) "Disable row mirror"
+allwinner_h3_dramc_rowmirror_enable(uint64_t addr) "Enable row mirror: addr 0x%" PRIx64
+allwinner_h3_dramcom_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_h3_dramcom_write(uint64_t offset, uint64_t data, unsigned size) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_h3_dramctl_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_h3_dramctl_write(uint64_t offset, uint64_t data, unsigned size) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_h3_dramphy_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_h3_dramphy_write(uint64_t offset, uint64_t data, unsigned size) "write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+
+# allwinner-sid.c
+allwinner_sid_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+allwinner_sid_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
+
# eccmemctl.c
ecc_mem_writel_mer(uint32_t val) "Write memory enable 0x%08x"
ecc_mem_writel_mdr(uint32_t val) "Write memory delay 0x%08x"