aboutsummaryrefslogtreecommitdiffstats
path: root/hw/misc
diff options
context:
space:
mode:
Diffstat (limited to 'hw/misc')
-rw-r--r--hw/misc/Kconfig174
-rw-r--r--hw/misc/a9scu.c153
-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.c169
-rw-r--r--hw/misc/applesmc.c372
-rw-r--r--hw/misc/arm11scu.c104
-rw-r--r--hw/misc/arm_integrator_debug.c100
-rw-r--r--hw/misc/arm_l2x0.c203
-rw-r--r--hw/misc/arm_sysctl.c662
-rw-r--r--hw/misc/armsse-cpu-pwrctrl.c149
-rw-r--r--hw/misc/armsse-cpuid.c135
-rw-r--r--hw/misc/armsse-mhu.c200
-rw-r--r--hw/misc/armv7m_ras.c93
-rw-r--r--hw/misc/aspeed_hace.c389
-rw-r--r--hw/misc/aspeed_lpc.c486
-rw-r--r--hw/misc/aspeed_scu.c740
-rw-r--r--hw/misc/aspeed_sdmc.c522
-rw-r--r--hw/misc/aspeed_xdma.c245
-rw-r--r--hw/misc/auxbus.c337
-rw-r--r--hw/misc/avr_power.c113
-rw-r--r--hw/misc/bcm2835_cprman.c813
-rw-r--r--hw/misc/bcm2835_mbox.c340
-rw-r--r--hw/misc/bcm2835_mphi.c191
-rw-r--r--hw/misc/bcm2835_powermgt.c160
-rw-r--r--hw/misc/bcm2835_property.c436
-rw-r--r--hw/misc/bcm2835_rng.c147
-rw-r--r--hw/misc/bcm2835_thermal.c135
-rw-r--r--hw/misc/cbus.c619
-rw-r--r--hw/misc/debugexit.c84
-rw-r--r--hw/misc/eccmemctl.c357
-rw-r--r--hw/misc/edu.c442
-rw-r--r--hw/misc/empty_slot.c109
-rw-r--r--hw/misc/exynos4210_clk.c166
-rw-r--r--hw/misc/exynos4210_pmu.c522
-rw-r--r--hw/misc/exynos4210_rng.c277
-rw-r--r--hw/misc/grlib_ahb_apb_pnp.c301
-rw-r--r--hw/misc/imx25_ccm.c320
-rw-r--r--hw/misc/imx31_ccm.c347
-rw-r--r--hw/misc/imx6_ccm.c783
-rw-r--r--hw/misc/imx6_src.c311
-rw-r--r--hw/misc/imx6ul_ccm.c938
-rw-r--r--hw/misc/imx7_ccm.c287
-rw-r--r--hw/misc/imx7_gpr.c124
-rw-r--r--hw/misc/imx7_snvs.c83
-rw-r--r--hw/misc/imx_ccm.c86
-rw-r--r--hw/misc/imx_rngc.c277
-rw-r--r--hw/misc/iotkit-secctl.c845
-rw-r--r--hw/misc/iotkit-sysctl.c872
-rw-r--r--hw/misc/iotkit-sysinfo.c187
-rw-r--r--hw/misc/ivshmem.c1135
-rw-r--r--hw/misc/led.c161
-rw-r--r--hw/misc/mac_via.c1221
-rw-r--r--hw/misc/macio/Kconfig11
-rw-r--r--hw/misc/macio/cuda.c629
-rw-r--r--hw/misc/macio/gpio.c219
-rw-r--r--hw/misc/macio/mac_dbdma.c940
-rw-r--r--hw/misc/macio/macio.c504
-rw-r--r--hw/misc/macio/meson.build8
-rw-r--r--hw/misc/macio/pmu.c871
-rw-r--r--hw/misc/macio/trace-events42
-rw-r--r--hw/misc/macio/trace.h1
-rw-r--r--hw/misc/mchp_pfsoc_dmc.c215
-rw-r--r--hw/misc/mchp_pfsoc_ioscb.c241
-rw-r--r--hw/misc/mchp_pfsoc_sysreg.c98
-rw-r--r--hw/misc/meson.build128
-rw-r--r--hw/misc/mips_cmgcr.c255
-rw-r--r--hw/misc/mips_cpc.c195
-rw-r--r--hw/misc/mips_itu.c581
-rw-r--r--hw/misc/mos6522.c541
-rw-r--r--hw/misc/mps2-fpgaio.c355
-rw-r--r--hw/misc/mps2-scc.c396
-rw-r--r--hw/misc/msf2-sysreg.c163
-rw-r--r--hw/misc/mst_fpga.c269
-rw-r--r--hw/misc/npcm7xx_clk.c1095
-rw-r--r--hw/misc/npcm7xx_gcr.c269
-rw-r--r--hw/misc/npcm7xx_mft.c540
-rw-r--r--hw/misc/npcm7xx_pwm.c569
-rw-r--r--hw/misc/npcm7xx_rng.c180
-rw-r--r--hw/misc/nrf51_rng.c266
-rw-r--r--hw/misc/omap_clk.c1267
-rw-r--r--hw/misc/omap_gpmc.c898
-rw-r--r--hw/misc/omap_l4.c163
-rw-r--r--hw/misc/omap_sdrc.c168
-rw-r--r--hw/misc/omap_tap.c118
-rw-r--r--hw/misc/pc-testdev.c216
-rw-r--r--hw/misc/pca9552.c437
-rw-r--r--hw/misc/pci-testdev.c361
-rw-r--r--hw/misc/pvpanic-isa.c93
-rw-r--r--hw/misc/pvpanic-pci.c93
-rw-r--r--hw/misc/pvpanic.c70
-rw-r--r--hw/misc/sbsa_ec.c98
-rw-r--r--hw/misc/sga.c71
-rw-r--r--hw/misc/sifive_e_prci.c124
-rw-r--r--hw/misc/sifive_test.c99
-rw-r--r--hw/misc/sifive_u_otp.c301
-rw-r--r--hw/misc/sifive_u_prci.c169
-rw-r--r--hw/misc/slavio_misc.c515
-rw-r--r--hw/misc/stm32f2xx_syscfg.c161
-rw-r--r--hw/misc/stm32f4xx_exti.c188
-rw-r--r--hw/misc/stm32f4xx_syscfg.c171
-rw-r--r--hw/misc/trace-events255
-rw-r--r--hw/misc/trace.h1
-rw-r--r--hw/misc/tz-mpc.c636
-rw-r--r--hw/misc/tz-msc.c312
-rw-r--r--hw/misc/tz-ppc.c352
-rw-r--r--hw/misc/unimp.c99
-rw-r--r--hw/misc/virt_ctrl.c150
-rw-r--r--hw/misc/vmcoreinfo.c108
-rw-r--r--hw/misc/xlnx-versal-xramc.c253
-rw-r--r--hw/misc/zynq_slcr.c637
113 files changed, 37609 insertions, 0 deletions
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
new file mode 100644
index 000000000..507058d8b
--- /dev/null
+++ b/hw/misc/Kconfig
@@ -0,0 +1,174 @@
+config APPLESMC
+ bool
+ depends on ISA_BUS
+
+config ARMSSE_CPUID
+ bool
+
+config ARMSSE_MHU
+ bool
+
+config ARMSSE_CPU_PWRCTRL
+ bool
+
+config ISA_DEBUG
+ bool
+ depends on ISA_BUS
+
+config SGA
+ bool
+ depends on ISA_BUS
+
+config ISA_TESTDEV
+ bool
+ default y if TEST_DEVICES
+ depends on ISA_BUS
+
+config PCI_TESTDEV
+ bool
+ default y if TEST_DEVICES
+ depends on PCI
+
+config EDU
+ bool
+ default y if TEST_DEVICES
+ depends on PCI && MSI_NONBROKEN
+
+config PCA9552
+ bool
+ depends on I2C
+
+config PL310
+ bool
+
+config INTEGRATOR_DEBUG
+ bool
+
+config A9SCU
+ bool
+
+config ARM11SCU
+ bool
+
+config MOS6522
+ bool
+
+config MACIO
+ bool
+ select CUDA
+ select ESCC
+ select IDE_MACIO
+ select MAC_DBDMA
+ select MAC_NVRAM
+ select MOS6522
+
+config IVSHMEM_DEVICE
+ bool
+ default y if PCI_DEVICES
+ depends on PCI && LINUX && IVSHMEM && MSI_NONBROKEN
+
+config ECCMEMCTL
+ bool
+ select ECC
+
+config IMX
+ bool
+ select PTIMER
+ select SSI
+ select USB_EHCI_SYSBUS
+
+config STM32F2XX_SYSCFG
+ bool
+
+config STM32F4XX_SYSCFG
+ bool
+
+config STM32F4XX_EXTI
+ bool
+
+config MIPS_ITU
+ bool
+
+config MPS2_FPGAIO
+ bool
+ select LED
+
+config MPS2_SCC
+ bool
+ select LED
+
+config TZ_MPC
+ bool
+
+config TZ_MSC
+ bool
+
+config TZ_PPC
+ bool
+
+config IOTKIT_SECCTL
+ bool
+
+config IOTKIT_SYSCTL
+ bool
+
+config IOTKIT_SYSINFO
+ bool
+
+config PVPANIC_COMMON
+ bool
+
+config PVPANIC_PCI
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select PVPANIC_COMMON
+
+config PVPANIC_ISA
+ bool
+ depends on ISA_BUS
+ select PVPANIC_COMMON
+
+config AUX
+ bool
+ select I2C
+
+config UNIMP
+ bool
+
+config LED
+ bool
+
+config MAC_VIA
+ bool
+ select MOS6522
+ select ADB
+
+config AVR_POWER
+ bool
+
+config MCHP_PFSOC_DMC
+ bool
+
+config MCHP_PFSOC_IOSCB
+ bool
+
+config MCHP_PFSOC_SYSREG
+ bool
+
+config SIFIVE_TEST
+ bool
+
+config SIFIVE_E_PRCI
+ bool
+
+config SIFIVE_U_OTP
+ bool
+
+config SIFIVE_U_PRCI
+ bool
+
+config VIRT_CTRL
+ bool
+
+source macio/Kconfig
diff --git a/hw/misc/a9scu.c b/hw/misc/a9scu.c
new file mode 100644
index 000000000..a375ebc98
--- /dev/null
+++ b/hw/misc/a9scu.c
@@ -0,0 +1,153 @@
+/*
+ * Cortex-A9MPCore Snoop Control Unit (SCU) emulation.
+ *
+ * Copyright (c) 2009 CodeSourcery.
+ * Copyright (c) 2011 Linaro Limited.
+ * Written by Paul Brook, Peter Maydell.
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/a9scu.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define A9_SCU_CPU_MAX 4
+
+static uint64_t a9_scu_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ A9SCUState *s = (A9SCUState *)opaque;
+ switch (offset) {
+ case 0x00: /* Control */
+ return s->control;
+ case 0x04: /* Configuration */
+ return (((1 << s->num_cpu) - 1) << 4) | (s->num_cpu - 1);
+ case 0x08: /* CPU Power Status */
+ return s->status;
+ case 0x0c: /* Invalidate All Registers In Secure State */
+ return 0;
+ case 0x40: /* Filtering Start Address Register */
+ case 0x44: /* Filtering End Address Register */
+ /* RAZ/WI, like an implementation with only one AXI master */
+ return 0;
+ case 0x50: /* SCU Access Control Register */
+ case 0x54: /* SCU Non-secure Access Control Register */
+ /* unimplemented, fall through */
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: Unsupported offset 0x%"HWADDR_PRIx"\n",
+ __func__, offset);
+ return 0;
+ }
+}
+
+static void a9_scu_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ A9SCUState *s = (A9SCUState *)opaque;
+
+ switch (offset) {
+ case 0x00: /* Control */
+ s->control = value & 1;
+ break;
+ case 0x4: /* Configuration: RO */
+ break;
+ case 0x08: case 0x09: case 0x0A: case 0x0B: /* Power Control */
+ s->status = value;
+ break;
+ case 0x0c: /* Invalidate All Registers In Secure State */
+ /* no-op as we do not implement caches */
+ break;
+ case 0x40: /* Filtering Start Address Register */
+ case 0x44: /* Filtering End Address Register */
+ /* RAZ/WI, like an implementation with only one AXI master */
+ break;
+ case 0x50: /* SCU Access Control Register */
+ case 0x54: /* SCU Non-secure Access Control Register */
+ /* unimplemented, fall through */
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: Unsupported offset 0x%"HWADDR_PRIx
+ " value 0x%"PRIx64"\n",
+ __func__, offset, value);
+ break;
+ }
+}
+
+static const MemoryRegionOps a9_scu_ops = {
+ .read = a9_scu_read,
+ .write = a9_scu_write,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void a9_scu_reset(DeviceState *dev)
+{
+ A9SCUState *s = A9_SCU(dev);
+ s->control = 0;
+}
+
+static void a9_scu_realize(DeviceState *dev, Error **errp)
+{
+ A9SCUState *s = A9_SCU(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ if (!s->num_cpu || s->num_cpu > A9_SCU_CPU_MAX) {
+ error_setg(errp, "Illegal CPU count: %u", s->num_cpu);
+ return;
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &a9_scu_ops, s,
+ "a9-scu", 0x100);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription vmstate_a9_scu = {
+ .name = "a9-scu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(control, A9SCUState),
+ VMSTATE_UINT32(status, A9SCUState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property a9_scu_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", A9SCUState, num_cpu, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void a9_scu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, a9_scu_properties);
+ dc->vmsd = &vmstate_a9_scu;
+ dc->reset = a9_scu_reset;
+ dc->realize = a9_scu_realize;
+}
+
+static const TypeInfo a9_scu_info = {
+ .name = TYPE_A9_SCU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(A9SCUState),
+ .class_init = a9_scu_class_init,
+};
+
+static void a9mp_register_types(void)
+{
+ type_register_static(&a9_scu_info);
+}
+
+type_init(a9mp_register_types)
diff --git a/hw/misc/allwinner-cpucfg.c b/hw/misc/allwinner-cpucfg.c
new file mode 100644
index 000000000..bbd33a7da
--- /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 000000000..18d107454
--- /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 000000000..1d37cf422
--- /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 + ((1ULL << (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 000000000..1d07efa88
--- /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 000000000..6d61f55b1
--- /dev/null
+++ b/hw/misc/allwinner-sid.c
@@ -0,0 +1,169 @@
+/*
+ * 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/qdev-properties-system.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/applesmc.c b/hw/misc/applesmc.c
new file mode 100644
index 000000000..1b9acaf1d
--- /dev/null
+++ b/hw/misc/applesmc.c
@@ -0,0 +1,372 @@
+/*
+ * Apple SMC controller
+ *
+ * Copyright (c) 2007 Alexander Graf
+ *
+ * Authors: Alexander Graf <agraf@suse.de>
+ * Susanne Graf <suse@csgraf.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * *****************************************************************
+ *
+ * In all Intel-based Apple hardware there is an SMC chip to control the
+ * backlight, fans and several other generic device parameters. It also
+ * contains the magic keys used to dongle Mac OS X to the device.
+ *
+ * This driver was mostly created by looking at the Linux AppleSMC driver
+ * implementation and does not support IRQ.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "hw/isa/isa.h"
+#include "hw/qdev-properties.h"
+#include "ui/console.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qom/object.h"
+
+/* #define DEBUG_SMC */
+
+#define APPLESMC_DEFAULT_IOBASE 0x300
+
+enum {
+ APPLESMC_DATA_PORT = 0x00,
+ APPLESMC_CMD_PORT = 0x04,
+ APPLESMC_ERR_PORT = 0x1e,
+ APPLESMC_NUM_PORTS = 0x20,
+};
+
+enum {
+ APPLESMC_READ_CMD = 0x10,
+ APPLESMC_WRITE_CMD = 0x11,
+ APPLESMC_GET_KEY_BY_INDEX_CMD = 0x12,
+ APPLESMC_GET_KEY_TYPE_CMD = 0x13,
+};
+
+enum {
+ APPLESMC_ST_CMD_DONE = 0x00,
+ APPLESMC_ST_DATA_READY = 0x01,
+ APPLESMC_ST_BUSY = 0x02,
+ APPLESMC_ST_ACK = 0x04,
+ APPLESMC_ST_NEW_CMD = 0x08,
+};
+
+enum {
+ APPLESMC_ST_1E_CMD_INTRUPTED = 0x80,
+ APPLESMC_ST_1E_STILL_BAD_CMD = 0x81,
+ APPLESMC_ST_1E_BAD_CMD = 0x82,
+ APPLESMC_ST_1E_NOEXIST = 0x84,
+ APPLESMC_ST_1E_WRITEONLY = 0x85,
+ APPLESMC_ST_1E_READONLY = 0x86,
+ APPLESMC_ST_1E_BAD_INDEX = 0xb8,
+};
+
+#ifdef DEBUG_SMC
+#define smc_debug(...) fprintf(stderr, "AppleSMC: " __VA_ARGS__)
+#else
+#define smc_debug(...) do { } while (0)
+#endif
+
+static char default_osk[64] = "This is a dummy key. Enter the real key "
+ "using the -osk parameter";
+
+struct AppleSMCData {
+ uint8_t len;
+ const char *key;
+ const char *data;
+ QLIST_ENTRY(AppleSMCData) node;
+};
+
+OBJECT_DECLARE_SIMPLE_TYPE(AppleSMCState, APPLE_SMC)
+
+struct AppleSMCState {
+ ISADevice parent_obj;
+
+ MemoryRegion io_data;
+ MemoryRegion io_cmd;
+ MemoryRegion io_err;
+ uint32_t iobase;
+ uint8_t cmd;
+ uint8_t status;
+ uint8_t status_1e;
+ uint8_t last_ret;
+ char key[4];
+ uint8_t read_pos;
+ uint8_t data_len;
+ uint8_t data_pos;
+ uint8_t data[255];
+ char *osk;
+ QLIST_HEAD(, AppleSMCData) data_def;
+};
+
+static void applesmc_io_cmd_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ AppleSMCState *s = opaque;
+ uint8_t status = s->status & 0x0f;
+
+ smc_debug("CMD received: 0x%02x\n", (uint8_t)val);
+ switch (val) {
+ case APPLESMC_READ_CMD:
+ /* did last command run through OK? */
+ if (status == APPLESMC_ST_CMD_DONE || status == APPLESMC_ST_NEW_CMD) {
+ s->cmd = val;
+ s->status = APPLESMC_ST_NEW_CMD | APPLESMC_ST_ACK;
+ } else {
+ smc_debug("ERROR: previous command interrupted!\n");
+ s->status = APPLESMC_ST_NEW_CMD;
+ s->status_1e = APPLESMC_ST_1E_CMD_INTRUPTED;
+ }
+ break;
+ default:
+ smc_debug("UNEXPECTED CMD 0x%02x\n", (uint8_t)val);
+ s->status = APPLESMC_ST_NEW_CMD;
+ s->status_1e = APPLESMC_ST_1E_BAD_CMD;
+ }
+ s->read_pos = 0;
+ s->data_pos = 0;
+}
+
+static struct AppleSMCData *applesmc_find_key(AppleSMCState *s)
+{
+ struct AppleSMCData *d;
+
+ QLIST_FOREACH(d, &s->data_def, node) {
+ if (!memcmp(d->key, s->key, 4)) {
+ return d;
+ }
+ }
+ return NULL;
+}
+
+static void applesmc_io_data_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ AppleSMCState *s = opaque;
+ struct AppleSMCData *d;
+
+ smc_debug("DATA received: 0x%02x\n", (uint8_t)val);
+ switch (s->cmd) {
+ case APPLESMC_READ_CMD:
+ if ((s->status & 0x0f) == APPLESMC_ST_CMD_DONE) {
+ break;
+ }
+ if (s->read_pos < 4) {
+ s->key[s->read_pos] = val;
+ s->status = APPLESMC_ST_ACK;
+ } else if (s->read_pos == 4) {
+ d = applesmc_find_key(s);
+ if (d != NULL) {
+ memcpy(s->data, d->data, d->len);
+ s->data_len = d->len;
+ s->data_pos = 0;
+ s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY;
+ s->status_1e = APPLESMC_ST_CMD_DONE; /* clear on valid key */
+ } else {
+ smc_debug("READ_CMD: key '%c%c%c%c' not found!\n",
+ s->key[0], s->key[1], s->key[2], s->key[3]);
+ s->status = APPLESMC_ST_CMD_DONE;
+ s->status_1e = APPLESMC_ST_1E_NOEXIST;
+ }
+ }
+ s->read_pos++;
+ break;
+ default:
+ s->status = APPLESMC_ST_CMD_DONE;
+ s->status_1e = APPLESMC_ST_1E_STILL_BAD_CMD;
+ }
+}
+
+static void applesmc_io_err_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ smc_debug("ERR_CODE received: 0x%02x, ignoring!\n", (uint8_t)val);
+ /* NOTE: writing to the error port not supported! */
+}
+
+static uint64_t applesmc_io_data_read(void *opaque, hwaddr addr, unsigned size)
+{
+ AppleSMCState *s = opaque;
+
+ switch (s->cmd) {
+ case APPLESMC_READ_CMD:
+ if (!(s->status & APPLESMC_ST_DATA_READY)) {
+ break;
+ }
+ if (s->data_pos < s->data_len) {
+ s->last_ret = s->data[s->data_pos];
+ smc_debug("READ '%c%c%c%c'[%d] = %02x\n",
+ s->key[0], s->key[1], s->key[2], s->key[3],
+ s->data_pos, s->last_ret);
+ s->data_pos++;
+ if (s->data_pos == s->data_len) {
+ s->status = APPLESMC_ST_CMD_DONE;
+ smc_debug("READ '%c%c%c%c' Len=%d complete!\n",
+ s->key[0], s->key[1], s->key[2], s->key[3],
+ s->data_len);
+ } else {
+ s->status = APPLESMC_ST_ACK | APPLESMC_ST_DATA_READY;
+ }
+ }
+ break;
+ default:
+ s->status = APPLESMC_ST_CMD_DONE;
+ s->status_1e = APPLESMC_ST_1E_STILL_BAD_CMD;
+ }
+ smc_debug("DATA sent: 0x%02x\n", s->last_ret);
+
+ return s->last_ret;
+}
+
+static uint64_t applesmc_io_cmd_read(void *opaque, hwaddr addr, unsigned size)
+{
+ AppleSMCState *s = opaque;
+
+ smc_debug("CMD sent: 0x%02x\n", s->status);
+ return s->status;
+}
+
+static uint64_t applesmc_io_err_read(void *opaque, hwaddr addr, unsigned size)
+{
+ AppleSMCState *s = opaque;
+
+ /* NOTE: read does not clear the 1e status */
+ smc_debug("ERR_CODE sent: 0x%02x\n", s->status_1e);
+ return s->status_1e;
+}
+
+static void applesmc_add_key(AppleSMCState *s, const char *key,
+ int len, const char *data)
+{
+ struct AppleSMCData *def;
+
+ def = g_malloc0(sizeof(struct AppleSMCData));
+ def->key = key;
+ def->len = len;
+ def->data = data;
+
+ QLIST_INSERT_HEAD(&s->data_def, def, node);
+}
+
+static void qdev_applesmc_isa_reset(DeviceState *dev)
+{
+ AppleSMCState *s = APPLE_SMC(dev);
+ struct AppleSMCData *d, *next;
+
+ /* Remove existing entries */
+ QLIST_FOREACH_SAFE(d, &s->data_def, node, next) {
+ QLIST_REMOVE(d, node);
+ }
+ s->status = 0x00;
+ s->status_1e = 0x00;
+ s->last_ret = 0x00;
+
+ applesmc_add_key(s, "REV ", 6, "\x01\x13\x0f\x00\x00\x03");
+ applesmc_add_key(s, "OSK0", 32, s->osk);
+ applesmc_add_key(s, "OSK1", 32, s->osk + 32);
+ applesmc_add_key(s, "NATJ", 1, "\0");
+ applesmc_add_key(s, "MSSP", 1, "\0");
+ applesmc_add_key(s, "MSSD", 1, "\0x3");
+}
+
+static const MemoryRegionOps applesmc_data_io_ops = {
+ .write = applesmc_io_data_write,
+ .read = applesmc_io_data_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const MemoryRegionOps applesmc_cmd_io_ops = {
+ .write = applesmc_io_cmd_write,
+ .read = applesmc_io_cmd_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const MemoryRegionOps applesmc_err_io_ops = {
+ .write = applesmc_io_err_write,
+ .read = applesmc_io_err_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void applesmc_isa_realize(DeviceState *dev, Error **errp)
+{
+ AppleSMCState *s = APPLE_SMC(dev);
+
+ memory_region_init_io(&s->io_data, OBJECT(s), &applesmc_data_io_ops, s,
+ "applesmc-data", 1);
+ isa_register_ioport(&s->parent_obj, &s->io_data,
+ s->iobase + APPLESMC_DATA_PORT);
+
+ memory_region_init_io(&s->io_cmd, OBJECT(s), &applesmc_cmd_io_ops, s,
+ "applesmc-cmd", 1);
+ isa_register_ioport(&s->parent_obj, &s->io_cmd,
+ s->iobase + APPLESMC_CMD_PORT);
+
+ memory_region_init_io(&s->io_err, OBJECT(s), &applesmc_err_io_ops, s,
+ "applesmc-err", 1);
+ isa_register_ioport(&s->parent_obj, &s->io_err,
+ s->iobase + APPLESMC_ERR_PORT);
+
+ if (!s->osk || (strlen(s->osk) != 64)) {
+ warn_report("Using AppleSMC with invalid key");
+ s->osk = default_osk;
+ }
+
+ QLIST_INIT(&s->data_def);
+ qdev_applesmc_isa_reset(dev);
+}
+
+static Property applesmc_isa_properties[] = {
+ DEFINE_PROP_UINT32(APPLESMC_PROP_IO_BASE, AppleSMCState, iobase,
+ APPLESMC_DEFAULT_IOBASE),
+ DEFINE_PROP_STRING("osk", AppleSMCState, osk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void qdev_applesmc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = applesmc_isa_realize;
+ dc->reset = qdev_applesmc_isa_reset;
+ device_class_set_props(dc, applesmc_isa_properties);
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo applesmc_isa_info = {
+ .name = TYPE_APPLE_SMC,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(AppleSMCState),
+ .class_init = qdev_applesmc_class_init,
+};
+
+static void applesmc_register_types(void)
+{
+ type_register_static(&applesmc_isa_info);
+}
+
+type_init(applesmc_register_types)
diff --git a/hw/misc/arm11scu.c b/hw/misc/arm11scu.c
new file mode 100644
index 000000000..17c36a054
--- /dev/null
+++ b/hw/misc/arm11scu.c
@@ -0,0 +1,104 @@
+/*
+ * ARM11MPCore Snoop Control Unit (SCU) emulation
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Copyright (c) 2013 SUSE LINUX Products GmbH
+ * Written by Paul Brook and Andreas Färber
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/arm11scu.h"
+#include "hw/qdev-properties.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+static uint64_t mpcore_scu_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ ARM11SCUState *s = (ARM11SCUState *)opaque;
+ int id;
+ /* SCU */
+ switch (offset) {
+ case 0x00: /* Control. */
+ return s->control;
+ case 0x04: /* Configuration. */
+ id = ((1 << s->num_cpu) - 1) << 4;
+ return id | (s->num_cpu - 1);
+ case 0x08: /* CPU status. */
+ return 0;
+ case 0x0c: /* Invalidate all. */
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "mpcore_priv_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void mpcore_scu_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ ARM11SCUState *s = (ARM11SCUState *)opaque;
+ /* SCU */
+ switch (offset) {
+ case 0: /* Control register. */
+ s->control = value & 1;
+ break;
+ case 0x0c: /* Invalidate all. */
+ /* This is a no-op as cache is not emulated. */
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "mpcore_priv_read: Bad offset %x\n", (int)offset);
+ }
+}
+
+static const MemoryRegionOps mpcore_scu_ops = {
+ .read = mpcore_scu_read,
+ .write = mpcore_scu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void arm11_scu_realize(DeviceState *dev, Error **errp)
+{
+}
+
+static void arm11_scu_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ ARM11SCUState *s = ARM11_SCU(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s),
+ &mpcore_scu_ops, s, "mpcore-scu", 0x100);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static Property arm11_scu_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", ARM11SCUState, num_cpu, 1),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void arm11_scu_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = arm11_scu_realize;
+ device_class_set_props(dc, arm11_scu_properties);
+}
+
+static const TypeInfo arm11_scu_type_info = {
+ .name = TYPE_ARM11_SCU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ARM11SCUState),
+ .instance_init = arm11_scu_init,
+ .class_init = arm11_scu_class_init,
+};
+
+static void arm11_scu_register_types(void)
+{
+ type_register_static(&arm11_scu_type_info);
+}
+
+type_init(arm11_scu_register_types)
diff --git a/hw/misc/arm_integrator_debug.c b/hw/misc/arm_integrator_debug.c
new file mode 100644
index 000000000..9a1972782
--- /dev/null
+++ b/hw/misc/arm_integrator_debug.c
@@ -0,0 +1,100 @@
+/*
+ * LED, Switch and Debug control registers for ARM Integrator Boards
+ *
+ * This is currently a stub for this functionality but at least
+ * ensures something other than unassigned_mem_read() handles access
+ * to this area.
+ *
+ * The real h/w is described at:
+ * https://developer.arm.com/documentation/dui0159/b/peripherals-and-interfaces/debug-leds-and-dip-switch-interface
+ *
+ * Copyright (c) 2013 Alex Bennée <alex@bennee.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/misc/arm_integrator_debug.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+OBJECT_DECLARE_SIMPLE_TYPE(IntegratorDebugState, INTEGRATOR_DEBUG)
+
+struct IntegratorDebugState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+};
+
+static uint64_t intdbg_control_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ switch (offset >> 2) {
+ case 0: /* ALPHA */
+ case 1: /* LEDS */
+ case 2: /* SWITCHES */
+ qemu_log_mask(LOG_UNIMP,
+ "%s: returning zero from %" HWADDR_PRIx ":%u\n",
+ __func__, offset, size);
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset %" HWADDR_PRIx,
+ __func__, offset);
+ return 0;
+ }
+}
+
+static void intdbg_control_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ switch (offset >> 2) {
+ case 1: /* ALPHA */
+ case 2: /* LEDS */
+ case 3: /* SWITCHES */
+ /* Nothing interesting implemented yet. */
+ qemu_log_mask(LOG_UNIMP,
+ "%s: ignoring write of %" PRIu64
+ " to %" HWADDR_PRIx ":%u\n",
+ __func__, value, offset, size);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write of %" PRIu64
+ " to bad offset %" HWADDR_PRIx "\n",
+ __func__, value, offset);
+ }
+}
+
+static const MemoryRegionOps intdbg_control_ops = {
+ .read = intdbg_control_read,
+ .write = intdbg_control_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void intdbg_control_init(Object *obj)
+{
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ IntegratorDebugState *s = INTEGRATOR_DEBUG(obj);
+
+ memory_region_init_io(&s->iomem, obj, &intdbg_control_ops,
+ NULL, "dbg-leds", 0x1000000);
+ sysbus_init_mmio(sd, &s->iomem);
+}
+
+static const TypeInfo intdbg_info = {
+ .name = TYPE_INTEGRATOR_DEBUG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IntegratorDebugState),
+ .instance_init = intdbg_control_init,
+};
+
+static void intdbg_register_types(void)
+{
+ type_register_static(&intdbg_info);
+}
+
+type_init(intdbg_register_types)
diff --git a/hw/misc/arm_l2x0.c b/hw/misc/arm_l2x0.c
new file mode 100644
index 000000000..75c3eb898
--- /dev/null
+++ b/hw/misc/arm_l2x0.c
@@ -0,0 +1,203 @@
+/*
+ * ARM dummy L210, L220, PL310 cache controller.
+ *
+ * Copyright (c) 2010-2012 Calxeda
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or any later version, as published by the Free Software
+ * Foundation.
+ *
+ * This program is distributed in the hope 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 "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+/* L2C-310 r3p2 */
+#define CACHE_ID 0x410000c8
+
+#define TYPE_ARM_L2X0 "l2x0"
+OBJECT_DECLARE_SIMPLE_TYPE(L2x0State, ARM_L2X0)
+
+struct L2x0State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t cache_type;
+ uint32_t ctrl;
+ uint32_t aux_ctrl;
+ uint32_t data_ctrl;
+ uint32_t tag_ctrl;
+ uint32_t filter_start;
+ uint32_t filter_end;
+};
+
+static const VMStateDescription vmstate_l2x0 = {
+ .name = "l2x0",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ctrl, L2x0State),
+ VMSTATE_UINT32(aux_ctrl, L2x0State),
+ VMSTATE_UINT32(data_ctrl, L2x0State),
+ VMSTATE_UINT32(tag_ctrl, L2x0State),
+ VMSTATE_UINT32(filter_start, L2x0State),
+ VMSTATE_UINT32(filter_end, L2x0State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+static uint64_t l2x0_priv_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t cache_data;
+ L2x0State *s = (L2x0State *)opaque;
+ offset &= 0xfff;
+ if (offset >= 0x730 && offset < 0x800) {
+ return 0; /* cache ops complete */
+ }
+ switch (offset) {
+ case 0:
+ return CACHE_ID;
+ case 0x4:
+ /* aux_ctrl values affect cache_type values */
+ cache_data = (s->aux_ctrl & (7 << 17)) >> 15;
+ cache_data |= (s->aux_ctrl & (1 << 16)) >> 16;
+ return s->cache_type |= (cache_data << 18) | (cache_data << 6);
+ case 0x100:
+ return s->ctrl;
+ case 0x104:
+ return s->aux_ctrl;
+ case 0x108:
+ return s->tag_ctrl;
+ case 0x10C:
+ return s->data_ctrl;
+ case 0xC00:
+ return s->filter_start;
+ case 0xC04:
+ return s->filter_end;
+ case 0xF40:
+ return 0;
+ case 0xF60:
+ return 0;
+ case 0xF80:
+ return 0;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "l2x0_priv_read: Bad offset %x\n", (int)offset);
+ break;
+ }
+ return 0;
+}
+
+static void l2x0_priv_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ L2x0State *s = (L2x0State *)opaque;
+ offset &= 0xfff;
+ if (offset >= 0x730 && offset < 0x800) {
+ /* ignore */
+ return;
+ }
+ switch (offset) {
+ case 0x100:
+ s->ctrl = value & 1;
+ break;
+ case 0x104:
+ s->aux_ctrl = value;
+ break;
+ case 0x108:
+ s->tag_ctrl = value;
+ break;
+ case 0x10C:
+ s->data_ctrl = value;
+ break;
+ case 0xC00:
+ s->filter_start = value;
+ break;
+ case 0xC04:
+ s->filter_end = value;
+ break;
+ case 0xF40:
+ return;
+ case 0xF60:
+ return;
+ case 0xF80:
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "l2x0_priv_write: Bad offset %x\n", (int)offset);
+ break;
+ }
+}
+
+static void l2x0_priv_reset(DeviceState *dev)
+{
+ L2x0State *s = ARM_L2X0(dev);
+
+ s->ctrl = 0;
+ s->aux_ctrl = 0x02020000;
+ s->tag_ctrl = 0;
+ s->data_ctrl = 0;
+ s->filter_start = 0;
+ s->filter_end = 0;
+}
+
+static const MemoryRegionOps l2x0_mem_ops = {
+ .read = l2x0_priv_read,
+ .write = l2x0_priv_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ };
+
+static void l2x0_priv_init(Object *obj)
+{
+ L2x0State *s = ARM_L2X0(obj);
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &l2x0_mem_ops, s,
+ "l2x0_cc", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+}
+
+static Property l2x0_properties[] = {
+ DEFINE_PROP_UINT32("cache-type", L2x0State, cache_type, 0x1c100100),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void l2x0_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_l2x0;
+ device_class_set_props(dc, l2x0_properties);
+ dc->reset = l2x0_priv_reset;
+}
+
+static const TypeInfo l2x0_info = {
+ .name = TYPE_ARM_L2X0,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(L2x0State),
+ .instance_init = l2x0_priv_init,
+ .class_init = l2x0_class_init,
+};
+
+static void l2x0_register_types(void)
+{
+ type_register_static(&l2x0_info);
+}
+
+type_init(l2x0_register_types)
diff --git a/hw/misc/arm_sysctl.c b/hw/misc/arm_sysctl.c
new file mode 100644
index 000000000..42d469385
--- /dev/null
+++ b/hw/misc/arm_sysctl.c
@@ -0,0 +1,662 @@
+/*
+ * Status and system control registers for ARM RealView/Versatile boards.
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "qemu/timer.h"
+#include "sysemu/runstate.h"
+#include "qemu/bitops.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/arm/primecell.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+#define LOCK_VALUE 0xa05f
+
+#define TYPE_ARM_SYSCTL "realview_sysctl"
+OBJECT_DECLARE_SIMPLE_TYPE(arm_sysctl_state, ARM_SYSCTL)
+
+struct arm_sysctl_state {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq pl110_mux_ctrl;
+
+ uint32_t sys_id;
+ uint32_t leds;
+ uint16_t lockval;
+ uint32_t cfgdata1;
+ uint32_t cfgdata2;
+ uint32_t flags;
+ uint32_t nvflags;
+ uint32_t resetlevel;
+ uint32_t proc_id;
+ uint32_t sys_mci;
+ uint32_t sys_cfgdata;
+ uint32_t sys_cfgctrl;
+ uint32_t sys_cfgstat;
+ uint32_t sys_clcd;
+ uint32_t mb_clock[6];
+ uint32_t *db_clock;
+ uint32_t db_num_vsensors;
+ uint32_t *db_voltage;
+ uint32_t db_num_clocks;
+ uint32_t *db_clock_reset;
+};
+
+static const VMStateDescription vmstate_arm_sysctl = {
+ .name = "realview_sysctl",
+ .version_id = 4,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(leds, arm_sysctl_state),
+ VMSTATE_UINT16(lockval, arm_sysctl_state),
+ VMSTATE_UINT32(cfgdata1, arm_sysctl_state),
+ VMSTATE_UINT32(cfgdata2, arm_sysctl_state),
+ VMSTATE_UINT32(flags, arm_sysctl_state),
+ VMSTATE_UINT32(nvflags, arm_sysctl_state),
+ VMSTATE_UINT32(resetlevel, arm_sysctl_state),
+ VMSTATE_UINT32_V(sys_mci, arm_sysctl_state, 2),
+ VMSTATE_UINT32_V(sys_cfgdata, arm_sysctl_state, 2),
+ VMSTATE_UINT32_V(sys_cfgctrl, arm_sysctl_state, 2),
+ VMSTATE_UINT32_V(sys_cfgstat, arm_sysctl_state, 2),
+ VMSTATE_UINT32_V(sys_clcd, arm_sysctl_state, 3),
+ VMSTATE_UINT32_ARRAY_V(mb_clock, arm_sysctl_state, 6, 4),
+ VMSTATE_VARRAY_UINT32(db_clock, arm_sysctl_state, db_num_clocks,
+ 4, vmstate_info_uint32, uint32_t),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* The PB926 actually uses a different format for
+ * its SYS_ID register. Fortunately the bits which are
+ * board type on later boards are distinct.
+ */
+#define BOARD_ID_PB926 0x100
+#define BOARD_ID_EB 0x140
+#define BOARD_ID_PBA8 0x178
+#define BOARD_ID_PBX 0x182
+#define BOARD_ID_VEXPRESS 0x190
+
+static int board_id(arm_sysctl_state *s)
+{
+ /* Extract the board ID field from the SYS_ID register value */
+ return (s->sys_id >> 16) & 0xfff;
+}
+
+static void arm_sysctl_reset(DeviceState *d)
+{
+ arm_sysctl_state *s = ARM_SYSCTL(d);
+ int i;
+
+ s->leds = 0;
+ s->lockval = 0;
+ s->cfgdata1 = 0;
+ s->cfgdata2 = 0;
+ s->flags = 0;
+ s->resetlevel = 0;
+ /* Motherboard oscillators (in Hz) */
+ s->mb_clock[0] = 50000000; /* Static memory clock: 50MHz */
+ s->mb_clock[1] = 23750000; /* motherboard CLCD clock: 23.75MHz */
+ s->mb_clock[2] = 24000000; /* IO FPGA peripheral clock: 24MHz */
+ s->mb_clock[3] = 24000000; /* IO FPGA reserved clock: 24MHz */
+ s->mb_clock[4] = 24000000; /* System bus global clock: 24MHz */
+ s->mb_clock[5] = 24000000; /* IO FPGA reserved clock: 24MHz */
+ /* Daughterboard oscillators: reset from property values */
+ for (i = 0; i < s->db_num_clocks; i++) {
+ s->db_clock[i] = s->db_clock_reset[i];
+ }
+ if (board_id(s) == BOARD_ID_VEXPRESS) {
+ /* On VExpress this register will RAZ/WI */
+ s->sys_clcd = 0;
+ } else {
+ /* All others: CLCDID 0x1f, indicating VGA */
+ s->sys_clcd = 0x1f00;
+ }
+}
+
+static uint64_t arm_sysctl_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ arm_sysctl_state *s = (arm_sysctl_state *)opaque;
+
+ switch (offset) {
+ case 0x00: /* ID */
+ return s->sys_id;
+ case 0x04: /* SW */
+ /* General purpose hardware switches.
+ We don't have a useful way of exposing these to the user. */
+ return 0;
+ case 0x08: /* LED */
+ return s->leds;
+ case 0x20: /* LOCK */
+ return s->lockval;
+ case 0x0c: /* OSC0 */
+ case 0x10: /* OSC1 */
+ case 0x14: /* OSC2 */
+ case 0x18: /* OSC3 */
+ case 0x1c: /* OSC4 */
+ case 0x24: /* 100HZ */
+ /* ??? Implement these. */
+ return 0;
+ case 0x28: /* CFGDATA1 */
+ return s->cfgdata1;
+ case 0x2c: /* CFGDATA2 */
+ return s->cfgdata2;
+ case 0x30: /* FLAGS */
+ return s->flags;
+ case 0x38: /* NVFLAGS */
+ return s->nvflags;
+ case 0x40: /* RESETCTL */
+ if (board_id(s) == BOARD_ID_VEXPRESS) {
+ /* reserved: RAZ/WI */
+ return 0;
+ }
+ return s->resetlevel;
+ case 0x44: /* PCICTL */
+ return 1;
+ case 0x48: /* MCI */
+ return s->sys_mci;
+ case 0x4c: /* FLASH */
+ return 0;
+ case 0x50: /* CLCD */
+ return s->sys_clcd;
+ case 0x54: /* CLCDSER */
+ return 0;
+ case 0x58: /* BOOTCS */
+ return 0;
+ case 0x5c: /* 24MHz */
+ return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24000000,
+ NANOSECONDS_PER_SECOND);
+ case 0x60: /* MISC */
+ return 0;
+ case 0x84: /* PROCID0 */
+ return s->proc_id;
+ case 0x88: /* PROCID1 */
+ return 0xff000000;
+ case 0x64: /* DMAPSR0 */
+ case 0x68: /* DMAPSR1 */
+ case 0x6c: /* DMAPSR2 */
+ case 0x70: /* IOSEL */
+ case 0x74: /* PLDCTL */
+ case 0x80: /* BUSID */
+ case 0x8c: /* OSCRESET0 */
+ case 0x90: /* OSCRESET1 */
+ case 0x94: /* OSCRESET2 */
+ case 0x98: /* OSCRESET3 */
+ case 0x9c: /* OSCRESET4 */
+ case 0xc0: /* SYS_TEST_OSC0 */
+ case 0xc4: /* SYS_TEST_OSC1 */
+ case 0xc8: /* SYS_TEST_OSC2 */
+ case 0xcc: /* SYS_TEST_OSC3 */
+ case 0xd0: /* SYS_TEST_OSC4 */
+ return 0;
+ case 0xa0: /* SYS_CFGDATA */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ return s->sys_cfgdata;
+ case 0xa4: /* SYS_CFGCTRL */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ return s->sys_cfgctrl;
+ case 0xa8: /* SYS_CFGSTAT */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ return s->sys_cfgstat;
+ default:
+ bad_reg:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "arm_sysctl_read: Bad register offset 0x%x\n",
+ (int)offset);
+ return 0;
+ }
+}
+
+/* SYS_CFGCTRL functions */
+#define SYS_CFG_OSC 1
+#define SYS_CFG_VOLT 2
+#define SYS_CFG_AMP 3
+#define SYS_CFG_TEMP 4
+#define SYS_CFG_RESET 5
+#define SYS_CFG_SCC 6
+#define SYS_CFG_MUXFPGA 7
+#define SYS_CFG_SHUTDOWN 8
+#define SYS_CFG_REBOOT 9
+#define SYS_CFG_DVIMODE 11
+#define SYS_CFG_POWER 12
+#define SYS_CFG_ENERGY 13
+
+/* SYS_CFGCTRL site field values */
+#define SYS_CFG_SITE_MB 0
+#define SYS_CFG_SITE_DB1 1
+#define SYS_CFG_SITE_DB2 2
+
+/**
+ * vexpress_cfgctrl_read:
+ * @s: arm_sysctl_state pointer
+ * @dcc, @function, @site, @position, @device: split out values from
+ * SYS_CFGCTRL register
+ * @val: pointer to where to put the read data on success
+ *
+ * Handle a VExpress SYS_CFGCTRL register read. On success, return true and
+ * write the read value to *val. On failure, return false (and val may
+ * or may not be written to).
+ */
+static bool vexpress_cfgctrl_read(arm_sysctl_state *s, unsigned int dcc,
+ unsigned int function, unsigned int site,
+ unsigned int position, unsigned int device,
+ uint32_t *val)
+{
+ /* We don't support anything other than DCC 0, board stack position 0
+ * or sites other than motherboard/daughterboard:
+ */
+ if (dcc != 0 || position != 0 ||
+ (site != SYS_CFG_SITE_MB && site != SYS_CFG_SITE_DB1)) {
+ goto cfgctrl_unimp;
+ }
+
+ switch (function) {
+ case SYS_CFG_VOLT:
+ if (site == SYS_CFG_SITE_DB1 && device < s->db_num_vsensors) {
+ *val = s->db_voltage[device];
+ return true;
+ }
+ if (site == SYS_CFG_SITE_MB && device == 0) {
+ /* There is only one motherboard voltage sensor:
+ * VIO : 3.3V : bus voltage between mother and daughterboard
+ */
+ *val = 3300000;
+ return true;
+ }
+ break;
+ case SYS_CFG_OSC:
+ if (site == SYS_CFG_SITE_MB && device < ARRAY_SIZE(s->mb_clock)) {
+ /* motherboard clock */
+ *val = s->mb_clock[device];
+ return true;
+ }
+ if (site == SYS_CFG_SITE_DB1 && device < s->db_num_clocks) {
+ /* daughterboard clock */
+ *val = s->db_clock[device];
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+
+cfgctrl_unimp:
+ qemu_log_mask(LOG_UNIMP,
+ "arm_sysctl: Unimplemented SYS_CFGCTRL read of function "
+ "0x%x DCC 0x%x site 0x%x position 0x%x device 0x%x\n",
+ function, dcc, site, position, device);
+ return false;
+}
+
+/**
+ * vexpress_cfgctrl_write:
+ * @s: arm_sysctl_state pointer
+ * @dcc, @function, @site, @position, @device: split out values from
+ * SYS_CFGCTRL register
+ * @val: data to write
+ *
+ * Handle a VExpress SYS_CFGCTRL register write. On success, return true.
+ * On failure, return false.
+ */
+static bool vexpress_cfgctrl_write(arm_sysctl_state *s, unsigned int dcc,
+ unsigned int function, unsigned int site,
+ unsigned int position, unsigned int device,
+ uint32_t val)
+{
+ /* We don't support anything other than DCC 0, board stack position 0
+ * or sites other than motherboard/daughterboard:
+ */
+ if (dcc != 0 || position != 0 ||
+ (site != SYS_CFG_SITE_MB && site != SYS_CFG_SITE_DB1)) {
+ goto cfgctrl_unimp;
+ }
+
+ switch (function) {
+ case SYS_CFG_OSC:
+ if (site == SYS_CFG_SITE_MB && device < ARRAY_SIZE(s->mb_clock)) {
+ /* motherboard clock */
+ s->mb_clock[device] = val;
+ return true;
+ }
+ if (site == SYS_CFG_SITE_DB1 && device < s->db_num_clocks) {
+ /* daughterboard clock */
+ s->db_clock[device] = val;
+ return true;
+ }
+ break;
+ case SYS_CFG_MUXFPGA:
+ if (site == SYS_CFG_SITE_MB && device == 0) {
+ /* Select whether video output comes from motherboard
+ * or daughterboard: log and ignore as QEMU doesn't
+ * support this.
+ */
+ qemu_log_mask(LOG_UNIMP, "arm_sysctl: selection of video output "
+ "not supported, ignoring\n");
+ return true;
+ }
+ break;
+ case SYS_CFG_SHUTDOWN:
+ if (site == SYS_CFG_SITE_MB && device == 0) {
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ return true;
+ }
+ break;
+ case SYS_CFG_REBOOT:
+ if (site == SYS_CFG_SITE_MB && device == 0) {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ return true;
+ }
+ break;
+ case SYS_CFG_DVIMODE:
+ if (site == SYS_CFG_SITE_MB && device == 0) {
+ /* Selecting DVI mode is meaningless for QEMU: we will
+ * always display the output correctly according to the
+ * pixel height/width programmed into the CLCD controller.
+ */
+ return true;
+ }
+ default:
+ break;
+ }
+
+cfgctrl_unimp:
+ qemu_log_mask(LOG_UNIMP,
+ "arm_sysctl: Unimplemented SYS_CFGCTRL write of function "
+ "0x%x DCC 0x%x site 0x%x position 0x%x device 0x%x\n",
+ function, dcc, site, position, device);
+ return false;
+}
+
+static void arm_sysctl_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ arm_sysctl_state *s = (arm_sysctl_state *)opaque;
+
+ switch (offset) {
+ case 0x08: /* LED */
+ s->leds = val;
+ break;
+ case 0x0c: /* OSC0 */
+ case 0x10: /* OSC1 */
+ case 0x14: /* OSC2 */
+ case 0x18: /* OSC3 */
+ case 0x1c: /* OSC4 */
+ /* ??? */
+ break;
+ case 0x20: /* LOCK */
+ if (val == LOCK_VALUE)
+ s->lockval = val;
+ else
+ s->lockval = val & 0x7fff;
+ break;
+ case 0x28: /* CFGDATA1 */
+ /* ??? Need to implement this. */
+ s->cfgdata1 = val;
+ break;
+ case 0x2c: /* CFGDATA2 */
+ /* ??? Need to implement this. */
+ s->cfgdata2 = val;
+ break;
+ case 0x30: /* FLAGSSET */
+ s->flags |= val;
+ break;
+ case 0x34: /* FLAGSCLR */
+ s->flags &= ~val;
+ break;
+ case 0x38: /* NVFLAGSSET */
+ s->nvflags |= val;
+ break;
+ case 0x3c: /* NVFLAGSCLR */
+ s->nvflags &= ~val;
+ break;
+ case 0x40: /* RESETCTL */
+ switch (board_id(s)) {
+ case BOARD_ID_PB926:
+ if (s->lockval == LOCK_VALUE) {
+ s->resetlevel = val;
+ if (val & 0x100) {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ }
+ }
+ break;
+ case BOARD_ID_PBX:
+ case BOARD_ID_PBA8:
+ if (s->lockval == LOCK_VALUE) {
+ s->resetlevel = val;
+ if (val & 0x04) {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ }
+ }
+ break;
+ case BOARD_ID_VEXPRESS:
+ case BOARD_ID_EB:
+ default:
+ /* reserved: RAZ/WI */
+ break;
+ }
+ break;
+ case 0x44: /* PCICTL */
+ /* nothing to do. */
+ break;
+ case 0x4c: /* FLASH */
+ break;
+ case 0x50: /* CLCD */
+ switch (board_id(s)) {
+ case BOARD_ID_PB926:
+ /* On 926 bits 13:8 are R/O, bits 1:0 control
+ * the mux that defines how to interpret the PL110
+ * graphics format, and other bits are r/w but we
+ * don't implement them to do anything.
+ */
+ s->sys_clcd &= 0x3f00;
+ s->sys_clcd |= val & ~0x3f00;
+ qemu_set_irq(s->pl110_mux_ctrl, val & 3);
+ break;
+ case BOARD_ID_EB:
+ /* The EB is the same except that there is no mux since
+ * the EB has a PL111.
+ */
+ s->sys_clcd &= 0x3f00;
+ s->sys_clcd |= val & ~0x3f00;
+ break;
+ case BOARD_ID_PBA8:
+ case BOARD_ID_PBX:
+ /* On PBA8 and PBX bit 7 is r/w and all other bits
+ * are either r/o or RAZ/WI.
+ */
+ s->sys_clcd &= (1 << 7);
+ s->sys_clcd |= val & ~(1 << 7);
+ break;
+ case BOARD_ID_VEXPRESS:
+ default:
+ /* On VExpress this register is unimplemented and will RAZ/WI */
+ break;
+ }
+ break;
+ case 0x54: /* CLCDSER */
+ case 0x64: /* DMAPSR0 */
+ case 0x68: /* DMAPSR1 */
+ case 0x6c: /* DMAPSR2 */
+ case 0x70: /* IOSEL */
+ case 0x74: /* PLDCTL */
+ case 0x80: /* BUSID */
+ case 0x84: /* PROCID0 */
+ case 0x88: /* PROCID1 */
+ case 0x8c: /* OSCRESET0 */
+ case 0x90: /* OSCRESET1 */
+ case 0x94: /* OSCRESET2 */
+ case 0x98: /* OSCRESET3 */
+ case 0x9c: /* OSCRESET4 */
+ break;
+ case 0xa0: /* SYS_CFGDATA */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ s->sys_cfgdata = val;
+ return;
+ case 0xa4: /* SYS_CFGCTRL */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ /* Undefined bits [19:18] are RAZ/WI, and writing to
+ * the start bit just triggers the action; it always reads
+ * as zero.
+ */
+ s->sys_cfgctrl = val & ~((3 << 18) | (1 << 31));
+ if (val & (1 << 31)) {
+ /* Start bit set -- actually do something */
+ unsigned int dcc = extract32(s->sys_cfgctrl, 26, 4);
+ unsigned int function = extract32(s->sys_cfgctrl, 20, 6);
+ unsigned int site = extract32(s->sys_cfgctrl, 16, 2);
+ unsigned int position = extract32(s->sys_cfgctrl, 12, 4);
+ unsigned int device = extract32(s->sys_cfgctrl, 0, 12);
+ s->sys_cfgstat = 1; /* complete */
+ if (s->sys_cfgctrl & (1 << 30)) {
+ if (!vexpress_cfgctrl_write(s, dcc, function, site, position,
+ device, s->sys_cfgdata)) {
+ s->sys_cfgstat |= 2; /* error */
+ }
+ } else {
+ uint32_t val;
+ if (!vexpress_cfgctrl_read(s, dcc, function, site, position,
+ device, &val)) {
+ s->sys_cfgstat |= 2; /* error */
+ } else {
+ s->sys_cfgdata = val;
+ }
+ }
+ }
+ s->sys_cfgctrl &= ~(1 << 31);
+ return;
+ case 0xa8: /* SYS_CFGSTAT */
+ if (board_id(s) != BOARD_ID_VEXPRESS) {
+ goto bad_reg;
+ }
+ s->sys_cfgstat = val & 3;
+ return;
+ default:
+ bad_reg:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "arm_sysctl_write: Bad register offset 0x%x\n",
+ (int)offset);
+ return;
+ }
+}
+
+static const MemoryRegionOps arm_sysctl_ops = {
+ .read = arm_sysctl_read,
+ .write = arm_sysctl_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void arm_sysctl_gpio_set(void *opaque, int line, int level)
+{
+ arm_sysctl_state *s = (arm_sysctl_state *)opaque;
+ switch (line) {
+ case ARM_SYSCTL_GPIO_MMC_WPROT:
+ {
+ /* For PB926 and EB write-protect is bit 2 of SYS_MCI;
+ * for all later boards it is bit 1.
+ */
+ int bit = 2;
+ if ((board_id(s) == BOARD_ID_PB926) || (board_id(s) == BOARD_ID_EB)) {
+ bit = 4;
+ }
+ s->sys_mci &= ~bit;
+ if (level) {
+ s->sys_mci |= bit;
+ }
+ break;
+ }
+ case ARM_SYSCTL_GPIO_MMC_CARDIN:
+ s->sys_mci &= ~1;
+ if (level) {
+ s->sys_mci |= 1;
+ }
+ break;
+ }
+}
+
+static void arm_sysctl_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ arm_sysctl_state *s = ARM_SYSCTL(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &arm_sysctl_ops, s,
+ "arm-sysctl", 0x1000);
+ sysbus_init_mmio(sd, &s->iomem);
+ qdev_init_gpio_in(dev, arm_sysctl_gpio_set, 2);
+ qdev_init_gpio_out(dev, &s->pl110_mux_ctrl, 1);
+}
+
+static void arm_sysctl_realize(DeviceState *d, Error **errp)
+{
+ arm_sysctl_state *s = ARM_SYSCTL(d);
+
+ s->db_clock = g_new0(uint32_t, s->db_num_clocks);
+}
+
+static void arm_sysctl_finalize(Object *obj)
+{
+ arm_sysctl_state *s = ARM_SYSCTL(obj);
+
+ g_free(s->db_voltage);
+ g_free(s->db_clock);
+ g_free(s->db_clock_reset);
+}
+
+static Property arm_sysctl_properties[] = {
+ DEFINE_PROP_UINT32("sys_id", arm_sysctl_state, sys_id, 0),
+ DEFINE_PROP_UINT32("proc_id", arm_sysctl_state, proc_id, 0),
+ /* Daughterboard power supply voltages (as reported via SYS_CFG) */
+ DEFINE_PROP_ARRAY("db-voltage", arm_sysctl_state, db_num_vsensors,
+ db_voltage, qdev_prop_uint32, uint32_t),
+ /* Daughterboard clock reset values (as reported via SYS_CFG) */
+ DEFINE_PROP_ARRAY("db-clock", arm_sysctl_state, db_num_clocks,
+ db_clock_reset, qdev_prop_uint32, uint32_t),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void arm_sysctl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = arm_sysctl_realize;
+ dc->reset = arm_sysctl_reset;
+ dc->vmsd = &vmstate_arm_sysctl;
+ device_class_set_props(dc, arm_sysctl_properties);
+}
+
+static const TypeInfo arm_sysctl_info = {
+ .name = TYPE_ARM_SYSCTL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(arm_sysctl_state),
+ .instance_init = arm_sysctl_init,
+ .instance_finalize = arm_sysctl_finalize,
+ .class_init = arm_sysctl_class_init,
+};
+
+static void arm_sysctl_register_types(void)
+{
+ type_register_static(&arm_sysctl_info);
+}
+
+type_init(arm_sysctl_register_types)
diff --git a/hw/misc/armsse-cpu-pwrctrl.c b/hw/misc/armsse-cpu-pwrctrl.c
new file mode 100644
index 000000000..42fc38879
--- /dev/null
+++ b/hw/misc/armsse-cpu-pwrctrl.c
@@ -0,0 +1,149 @@
+/*
+ * Arm SSE CPU PWRCTRL register block
+ *
+ * Copyright (c) 2021 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+/*
+ * This is a model of the "CPU<N>_PWRCTRL block" which is part of the
+ * Arm Corstone SSE-300 Example Subsystem and documented in
+ * https://developer.arm.com/documentation/101773/0000
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/sysbus.h"
+#include "hw/registerfields.h"
+#include "hw/misc/armsse-cpu-pwrctrl.h"
+
+REG32(CPUPWRCFG, 0x0)
+REG32(PID4, 0xfd0)
+REG32(PID5, 0xfd4)
+REG32(PID6, 0xfd8)
+REG32(PID7, 0xfdc)
+REG32(PID0, 0xfe0)
+REG32(PID1, 0xfe4)
+REG32(PID2, 0xfe8)
+REG32(PID3, 0xfec)
+REG32(CID0, 0xff0)
+REG32(CID1, 0xff4)
+REG32(CID2, 0xff8)
+REG32(CID3, 0xffc)
+
+/* PID/CID values */
+static const int cpu_pwrctrl_id[] = {
+ 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
+ 0x5a, 0xb8, 0x0b, 0x00, /* PID0..PID3 */
+ 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
+};
+
+static uint64_t pwrctrl_read(void *opaque, hwaddr offset, unsigned size)
+{
+ ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(opaque);
+ uint64_t r;
+
+ switch (offset) {
+ case A_CPUPWRCFG:
+ r = s->cpupwrcfg;
+ break;
+ case A_PID4 ... A_CID3:
+ r = cpu_pwrctrl_id[(offset - A_PID4) / 4];
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SSE CPU_PWRCTRL read: bad offset %x\n", (int)offset);
+ r = 0;
+ break;
+ }
+ trace_armsse_cpu_pwrctrl_read(offset, r, size);
+ return r;
+}
+
+static void pwrctrl_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(opaque);
+
+ trace_armsse_cpu_pwrctrl_write(offset, value, size);
+
+ switch (offset) {
+ case A_CPUPWRCFG:
+ qemu_log_mask(LOG_UNIMP,
+ "SSE CPU_PWRCTRL: CPUPWRCFG unimplemented\n");
+ s->cpupwrcfg = value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SSE CPU_PWRCTRL write: bad offset 0x%x\n", (int)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps pwrctrl_ops = {
+ .read = pwrctrl_read,
+ .write = pwrctrl_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static void pwrctrl_reset(DeviceState *dev)
+{
+ ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(dev);
+
+ s->cpupwrcfg = 0;
+}
+
+static const VMStateDescription pwrctrl_vmstate = {
+ .name = "armsse-cpu-pwrctrl",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cpupwrcfg, ARMSSECPUPwrCtrl),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void pwrctrl_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(obj);
+
+ memory_region_init_io(&s->iomem, obj, &pwrctrl_ops,
+ s, "armsse-cpu-pwrctrl", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void pwrctrl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = pwrctrl_reset;
+ dc->vmsd = &pwrctrl_vmstate;
+}
+
+static const TypeInfo pwrctrl_info = {
+ .name = TYPE_ARMSSE_CPU_PWRCTRL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ARMSSECPUPwrCtrl),
+ .instance_init = pwrctrl_init,
+ .class_init = pwrctrl_class_init,
+};
+
+static void pwrctrl_register_types(void)
+{
+ type_register_static(&pwrctrl_info);
+}
+
+type_init(pwrctrl_register_types);
diff --git a/hw/misc/armsse-cpuid.c b/hw/misc/armsse-cpuid.c
new file mode 100644
index 000000000..e785a0905
--- /dev/null
+++ b/hw/misc/armsse-cpuid.c
@@ -0,0 +1,135 @@
+/*
+ * ARM SSE-200 CPU_IDENTITY register block
+ *
+ * Copyright (c) 2019 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+/*
+ * This is a model of the "CPU_IDENTITY" register block which is part of the
+ * Arm SSE-200 and documented in
+ * https://developer.arm.com/documentation/101104/latest/
+ *
+ * It consists of one read-only CPUID register (set by QOM property), plus the
+ * usual ID registers.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/registerfields.h"
+#include "hw/misc/armsse-cpuid.h"
+#include "hw/qdev-properties.h"
+
+REG32(CPUID, 0x0)
+REG32(PID4, 0xfd0)
+REG32(PID5, 0xfd4)
+REG32(PID6, 0xfd8)
+REG32(PID7, 0xfdc)
+REG32(PID0, 0xfe0)
+REG32(PID1, 0xfe4)
+REG32(PID2, 0xfe8)
+REG32(PID3, 0xfec)
+REG32(CID0, 0xff0)
+REG32(CID1, 0xff4)
+REG32(CID2, 0xff8)
+REG32(CID3, 0xffc)
+
+/* PID/CID values */
+static const int sysinfo_id[] = {
+ 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
+ 0x58, 0xb8, 0x0b, 0x00, /* PID0..PID3 */
+ 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
+};
+
+static uint64_t armsse_cpuid_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ ARMSSECPUID *s = ARMSSE_CPUID(opaque);
+ uint64_t r;
+
+ switch (offset) {
+ case A_CPUID:
+ r = s->cpuid;
+ break;
+ case A_PID4 ... A_CID3:
+ r = sysinfo_id[(offset - A_PID4) / 4];
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SSE CPU_IDENTITY read: bad offset 0x%x\n", (int)offset);
+ r = 0;
+ break;
+ }
+ trace_armsse_cpuid_read(offset, r, size);
+ return r;
+}
+
+static void armsse_cpuid_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ trace_armsse_cpuid_write(offset, value, size);
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SSE CPU_IDENTITY: write to RO offset 0x%x\n", (int)offset);
+}
+
+static const MemoryRegionOps armsse_cpuid_ops = {
+ .read = armsse_cpuid_read,
+ .write = armsse_cpuid_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ /* byte/halfword accesses are just zero-padded on reads and writes */
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+};
+
+static Property armsse_cpuid_props[] = {
+ DEFINE_PROP_UINT32("CPUID", ARMSSECPUID, cpuid, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void armsse_cpuid_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ ARMSSECPUID *s = ARMSSE_CPUID(obj);
+
+ memory_region_init_io(&s->iomem, obj, &armsse_cpuid_ops,
+ s, "armsse-cpuid", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void armsse_cpuid_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ /*
+ * This device has no guest-modifiable state and so it
+ * does not need a reset function or VMState.
+ */
+
+ device_class_set_props(dc, armsse_cpuid_props);
+}
+
+static const TypeInfo armsse_cpuid_info = {
+ .name = TYPE_ARMSSE_CPUID,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ARMSSECPUID),
+ .instance_init = armsse_cpuid_init,
+ .class_init = armsse_cpuid_class_init,
+};
+
+static void armsse_cpuid_register_types(void)
+{
+ type_register_static(&armsse_cpuid_info);
+}
+
+type_init(armsse_cpuid_register_types);
diff --git a/hw/misc/armsse-mhu.c b/hw/misc/armsse-mhu.c
new file mode 100644
index 000000000..0be7f0fc8
--- /dev/null
+++ b/hw/misc/armsse-mhu.c
@@ -0,0 +1,200 @@
+/*
+ * ARM SSE-200 Message Handling Unit (MHU)
+ *
+ * Copyright (c) 2019 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+/*
+ * This is a model of the Message Handling Unit (MHU) which is part of the
+ * Arm SSE-200 and documented in
+ * https://developer.arm.com/documentation/101104/latest/
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/registerfields.h"
+#include "hw/irq.h"
+#include "hw/misc/armsse-mhu.h"
+
+REG32(CPU0INTR_STAT, 0x0)
+REG32(CPU0INTR_SET, 0x4)
+REG32(CPU0INTR_CLR, 0x8)
+REG32(CPU1INTR_STAT, 0x10)
+REG32(CPU1INTR_SET, 0x14)
+REG32(CPU1INTR_CLR, 0x18)
+REG32(PID4, 0xfd0)
+REG32(PID5, 0xfd4)
+REG32(PID6, 0xfd8)
+REG32(PID7, 0xfdc)
+REG32(PID0, 0xfe0)
+REG32(PID1, 0xfe4)
+REG32(PID2, 0xfe8)
+REG32(PID3, 0xfec)
+REG32(CID0, 0xff0)
+REG32(CID1, 0xff4)
+REG32(CID2, 0xff8)
+REG32(CID3, 0xffc)
+
+/* Valid bits in the interrupt registers. If any are set the IRQ is raised */
+#define INTR_MASK 0xf
+
+/* PID/CID values */
+static const int armsse_mhu_id[] = {
+ 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
+ 0x56, 0xb8, 0x0b, 0x00, /* PID0..PID3 */
+ 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
+};
+
+static void armsse_mhu_update(ARMSSEMHU *s)
+{
+ qemu_set_irq(s->cpu0irq, s->cpu0intr != 0);
+ qemu_set_irq(s->cpu1irq, s->cpu1intr != 0);
+}
+
+static uint64_t armsse_mhu_read(void *opaque, hwaddr offset, unsigned size)
+{
+ ARMSSEMHU *s = ARMSSE_MHU(opaque);
+ uint64_t r;
+
+ switch (offset) {
+ case A_CPU0INTR_STAT:
+ r = s->cpu0intr;
+ break;
+
+ case A_CPU1INTR_STAT:
+ r = s->cpu1intr;
+ break;
+
+ case A_PID4 ... A_CID3:
+ r = armsse_mhu_id[(offset - A_PID4) / 4];
+ break;
+
+ case A_CPU0INTR_SET:
+ case A_CPU0INTR_CLR:
+ case A_CPU1INTR_SET:
+ case A_CPU1INTR_CLR:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SSE MHU: read of write-only register at offset 0x%x\n",
+ (int)offset);
+ r = 0;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SSE MHU read: bad offset 0x%x\n", (int)offset);
+ r = 0;
+ break;
+ }
+ trace_armsse_mhu_read(offset, r, size);
+ return r;
+}
+
+static void armsse_mhu_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ ARMSSEMHU *s = ARMSSE_MHU(opaque);
+
+ trace_armsse_mhu_write(offset, value, size);
+
+ switch (offset) {
+ case A_CPU0INTR_SET:
+ s->cpu0intr |= (value & INTR_MASK);
+ break;
+ case A_CPU0INTR_CLR:
+ s->cpu0intr &= ~(value & INTR_MASK);
+ break;
+ case A_CPU1INTR_SET:
+ s->cpu1intr |= (value & INTR_MASK);
+ break;
+ case A_CPU1INTR_CLR:
+ s->cpu1intr &= ~(value & INTR_MASK);
+ break;
+
+ case A_CPU0INTR_STAT:
+ case A_CPU1INTR_STAT:
+ case A_PID4 ... A_CID3:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SSE MHU: write to read-only register at offset 0x%x\n",
+ (int)offset);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SSE MHU write: bad offset 0x%x\n", (int)offset);
+ break;
+ }
+
+ armsse_mhu_update(s);
+}
+
+static const MemoryRegionOps armsse_mhu_ops = {
+ .read = armsse_mhu_read,
+ .write = armsse_mhu_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static void armsse_mhu_reset(DeviceState *dev)
+{
+ ARMSSEMHU *s = ARMSSE_MHU(dev);
+
+ s->cpu0intr = 0;
+ s->cpu1intr = 0;
+}
+
+static const VMStateDescription armsse_mhu_vmstate = {
+ .name = "armsse-mhu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cpu0intr, ARMSSEMHU),
+ VMSTATE_UINT32(cpu1intr, ARMSSEMHU),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void armsse_mhu_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ ARMSSEMHU *s = ARMSSE_MHU(obj);
+
+ memory_region_init_io(&s->iomem, obj, &armsse_mhu_ops,
+ s, "armsse-mhu", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->cpu0irq);
+ sysbus_init_irq(sbd, &s->cpu1irq);
+}
+
+static void armsse_mhu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = armsse_mhu_reset;
+ dc->vmsd = &armsse_mhu_vmstate;
+}
+
+static const TypeInfo armsse_mhu_info = {
+ .name = TYPE_ARMSSE_MHU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ARMSSEMHU),
+ .instance_init = armsse_mhu_init,
+ .class_init = armsse_mhu_class_init,
+};
+
+static void armsse_mhu_register_types(void)
+{
+ type_register_static(&armsse_mhu_info);
+}
+
+type_init(armsse_mhu_register_types);
diff --git a/hw/misc/armv7m_ras.c b/hw/misc/armv7m_ras.c
new file mode 100644
index 000000000..de24922c9
--- /dev/null
+++ b/hw/misc/armv7m_ras.c
@@ -0,0 +1,93 @@
+/*
+ * Arm M-profile RAS (Reliability, Availability and Serviceability) block
+ *
+ * Copyright (c) 2021 Linaro Limited
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/armv7m_ras.h"
+#include "qemu/log.h"
+
+static MemTxResult ras_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ if (attrs.user) {
+ return MEMTX_ERROR;
+ }
+
+ switch (addr) {
+ case 0xe10: /* ERRIIDR */
+ /* architect field = Arm; product/variant/revision 0 */
+ *data = 0x43b;
+ break;
+ case 0xfc8: /* ERRDEVID */
+ /* Minimal RAS: we implement 0 error record indexes */
+ *data = 0;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "Read RAS register offset 0x%x\n",
+ (uint32_t)addr);
+ *data = 0;
+ break;
+ }
+ return MEMTX_OK;
+}
+
+static MemTxResult ras_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ if (attrs.user) {
+ return MEMTX_ERROR;
+ }
+
+ switch (addr) {
+ default:
+ qemu_log_mask(LOG_UNIMP, "Write to RAS register offset 0x%x\n",
+ (uint32_t)addr);
+ break;
+ }
+ return MEMTX_OK;
+}
+
+static const MemoryRegionOps ras_ops = {
+ .read_with_attrs = ras_read,
+ .write_with_attrs = ras_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+
+static void armv7m_ras_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ ARMv7MRAS *s = ARMV7M_RAS(obj);
+
+ memory_region_init_io(&s->iomem, obj, &ras_ops,
+ s, "armv7m-ras", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void armv7m_ras_class_init(ObjectClass *klass, void *data)
+{
+ /* This device has no state: no need for vmstate or reset */
+}
+
+static const TypeInfo armv7m_ras_info = {
+ .name = TYPE_ARMV7M_RAS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ARMv7MRAS),
+ .instance_init = armv7m_ras_init,
+ .class_init = armv7m_ras_class_init,
+};
+
+static void armv7m_ras_register_types(void)
+{
+ type_register_static(&armv7m_ras_info);
+}
+
+type_init(armv7m_ras_register_types);
diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c
new file mode 100644
index 000000000..10f00e65f
--- /dev/null
+++ b/hw/misc/aspeed_hace.c
@@ -0,0 +1,389 @@
+/*
+ * ASPEED Hash and Crypto Engine
+ *
+ * Copyright (C) 2021 IBM Corp.
+ *
+ * Joel Stanley <joel@jms.id.au>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/error-report.h"
+#include "hw/misc/aspeed_hace.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "crypto/hash.h"
+#include "hw/qdev-properties.h"
+#include "hw/irq.h"
+
+#define R_CRYPT_CMD (0x10 / 4)
+
+#define R_STATUS (0x1c / 4)
+#define HASH_IRQ BIT(9)
+#define CRYPT_IRQ BIT(12)
+#define TAG_IRQ BIT(15)
+
+#define R_HASH_SRC (0x20 / 4)
+#define R_HASH_DEST (0x24 / 4)
+#define R_HASH_SRC_LEN (0x2c / 4)
+
+#define R_HASH_CMD (0x30 / 4)
+/* Hash algorithm selection */
+#define HASH_ALGO_MASK (BIT(4) | BIT(5) | BIT(6))
+#define HASH_ALGO_MD5 0
+#define HASH_ALGO_SHA1 BIT(5)
+#define HASH_ALGO_SHA224 BIT(6)
+#define HASH_ALGO_SHA256 (BIT(4) | BIT(6))
+#define HASH_ALGO_SHA512_SERIES (BIT(5) | BIT(6))
+/* SHA512 algorithm selection */
+#define SHA512_HASH_ALGO_MASK (BIT(10) | BIT(11) | BIT(12))
+#define HASH_ALGO_SHA512_SHA512 0
+#define HASH_ALGO_SHA512_SHA384 BIT(10)
+#define HASH_ALGO_SHA512_SHA256 BIT(11)
+#define HASH_ALGO_SHA512_SHA224 (BIT(10) | BIT(11))
+/* HMAC modes */
+#define HASH_HMAC_MASK (BIT(7) | BIT(8))
+#define HASH_DIGEST 0
+#define HASH_DIGEST_HMAC BIT(7)
+#define HASH_DIGEST_ACCUM BIT(8)
+#define HASH_HMAC_KEY (BIT(7) | BIT(8))
+/* Cascaded operation modes */
+#define HASH_ONLY 0
+#define HASH_ONLY2 BIT(0)
+#define HASH_CRYPT_THEN_HASH BIT(1)
+#define HASH_HASH_THEN_CRYPT (BIT(0) | BIT(1))
+/* Other cmd bits */
+#define HASH_IRQ_EN BIT(9)
+#define HASH_SG_EN BIT(18)
+/* Scatter-gather data list */
+#define SG_LIST_LEN_SIZE 4
+#define SG_LIST_LEN_MASK 0x0FFFFFFF
+#define SG_LIST_LEN_LAST BIT(31)
+#define SG_LIST_ADDR_SIZE 4
+#define SG_LIST_ADDR_MASK 0x7FFFFFFF
+#define SG_LIST_ENTRY_SIZE (SG_LIST_LEN_SIZE + SG_LIST_ADDR_SIZE)
+#define ASPEED_HACE_MAX_SG 256 /* max number of entries */
+
+static const struct {
+ uint32_t mask;
+ QCryptoHashAlgorithm algo;
+} hash_algo_map[] = {
+ { HASH_ALGO_MD5, QCRYPTO_HASH_ALG_MD5 },
+ { HASH_ALGO_SHA1, QCRYPTO_HASH_ALG_SHA1 },
+ { HASH_ALGO_SHA224, QCRYPTO_HASH_ALG_SHA224 },
+ { HASH_ALGO_SHA256, QCRYPTO_HASH_ALG_SHA256 },
+ { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA512, QCRYPTO_HASH_ALG_SHA512 },
+ { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA384, QCRYPTO_HASH_ALG_SHA384 },
+ { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA256, QCRYPTO_HASH_ALG_SHA256 },
+};
+
+static int hash_algo_lookup(uint32_t reg)
+{
+ int i;
+
+ reg &= HASH_ALGO_MASK | SHA512_HASH_ALGO_MASK;
+
+ for (i = 0; i < ARRAY_SIZE(hash_algo_map); i++) {
+ if (reg == hash_algo_map[i].mask) {
+ return hash_algo_map[i].algo;
+ }
+ }
+
+ return -1;
+}
+
+static void do_hash_operation(AspeedHACEState *s, int algo, bool sg_mode)
+{
+ struct iovec iov[ASPEED_HACE_MAX_SG];
+ g_autofree uint8_t *digest_buf;
+ size_t digest_len = 0;
+ int i;
+
+ if (sg_mode) {
+ uint32_t len = 0;
+
+ for (i = 0; !(len & SG_LIST_LEN_LAST); i++) {
+ uint32_t addr, src;
+ hwaddr plen;
+
+ if (i == ASPEED_HACE_MAX_SG) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "aspeed_hace: guest failed to set end of sg list marker\n");
+ break;
+ }
+
+ src = s->regs[R_HASH_SRC] + (i * SG_LIST_ENTRY_SIZE);
+
+ len = address_space_ldl_le(&s->dram_as, src,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+
+ addr = address_space_ldl_le(&s->dram_as, src + SG_LIST_LEN_SIZE,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ addr &= SG_LIST_ADDR_MASK;
+
+ iov[i].iov_len = len & SG_LIST_LEN_MASK;
+ plen = iov[i].iov_len;
+ iov[i].iov_base = address_space_map(&s->dram_as, addr, &plen, false,
+ MEMTXATTRS_UNSPECIFIED);
+ }
+ } else {
+ hwaddr len = s->regs[R_HASH_SRC_LEN];
+
+ iov[0].iov_len = len;
+ iov[0].iov_base = address_space_map(&s->dram_as, s->regs[R_HASH_SRC],
+ &len, false,
+ MEMTXATTRS_UNSPECIFIED);
+ i = 1;
+ }
+
+ if (qcrypto_hash_bytesv(algo, iov, i, &digest_buf, &digest_len, NULL) < 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto failed\n", __func__);
+ return;
+ }
+
+ if (address_space_write(&s->dram_as, s->regs[R_HASH_DEST],
+ MEMTXATTRS_UNSPECIFIED,
+ digest_buf, digest_len)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "aspeed_hace: address space write failed\n");
+ }
+
+ for (; i > 0; i--) {
+ address_space_unmap(&s->dram_as, iov[i - 1].iov_base,
+ iov[i - 1].iov_len, false,
+ iov[i - 1].iov_len);
+ }
+
+ /*
+ * Set status bits to indicate completion. Testing shows hardware sets
+ * these irrespective of HASH_IRQ_EN.
+ */
+ s->regs[R_STATUS] |= HASH_IRQ;
+}
+
+static uint64_t aspeed_hace_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ AspeedHACEState *s = ASPEED_HACE(opaque);
+
+ addr >>= 2;
+
+ if (addr >= ASPEED_HACE_NR_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
+ __func__, addr << 2);
+ return 0;
+ }
+
+ return s->regs[addr];
+}
+
+static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ AspeedHACEState *s = ASPEED_HACE(opaque);
+ AspeedHACEClass *ahc = ASPEED_HACE_GET_CLASS(s);
+
+ addr >>= 2;
+
+ if (addr >= ASPEED_HACE_NR_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
+ __func__, addr << 2);
+ return;
+ }
+
+ switch (addr) {
+ case R_STATUS:
+ if (data & HASH_IRQ) {
+ data &= ~HASH_IRQ;
+
+ if (s->regs[addr] & HASH_IRQ) {
+ qemu_irq_lower(s->irq);
+ }
+ }
+ break;
+ case R_HASH_SRC:
+ data &= ahc->src_mask;
+ break;
+ case R_HASH_DEST:
+ data &= ahc->dest_mask;
+ break;
+ case R_HASH_SRC_LEN:
+ data &= 0x0FFFFFFF;
+ break;
+ case R_HASH_CMD: {
+ int algo;
+ data &= ahc->hash_mask;
+
+ if ((data & HASH_HMAC_MASK)) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: HMAC engine command mode %"PRIx64" not implemented",
+ __func__, (data & HASH_HMAC_MASK) >> 8);
+ }
+ if (data & BIT(1)) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Cascaded mode not implemented",
+ __func__);
+ }
+ algo = hash_algo_lookup(data);
+ if (algo < 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Invalid hash algorithm selection 0x%"PRIx64"\n",
+ __func__, data & ahc->hash_mask);
+ break;
+ }
+ do_hash_operation(s, algo, data & HASH_SG_EN);
+
+ if (data & HASH_IRQ_EN) {
+ qemu_irq_raise(s->irq);
+ }
+ break;
+ }
+ case R_CRYPT_CMD:
+ qemu_log_mask(LOG_UNIMP, "%s: Crypt commands not implemented\n",
+ __func__);
+ break;
+ default:
+ break;
+ }
+
+ s->regs[addr] = data;
+}
+
+static const MemoryRegionOps aspeed_hace_ops = {
+ .read = aspeed_hace_read,
+ .write = aspeed_hace_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static void aspeed_hace_reset(DeviceState *dev)
+{
+ struct AspeedHACEState *s = ASPEED_HACE(dev);
+
+ memset(s->regs, 0, sizeof(s->regs));
+}
+
+static void aspeed_hace_realize(DeviceState *dev, Error **errp)
+{
+ AspeedHACEState *s = ASPEED_HACE(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_hace_ops, s,
+ TYPE_ASPEED_HACE, 0x1000);
+
+ if (!s->dram_mr) {
+ error_setg(errp, TYPE_ASPEED_HACE ": 'dram' link not set");
+ return;
+ }
+
+ address_space_init(&s->dram_as, s->dram_mr, "dram");
+
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static Property aspeed_hace_properties[] = {
+ DEFINE_PROP_LINK("dram", AspeedHACEState, dram_mr,
+ TYPE_MEMORY_REGION, MemoryRegion *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+
+static const VMStateDescription vmstate_aspeed_hace = {
+ .name = TYPE_ASPEED_HACE,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AspeedHACEState, ASPEED_HACE_NR_REGS),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static void aspeed_hace_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = aspeed_hace_realize;
+ dc->reset = aspeed_hace_reset;
+ device_class_set_props(dc, aspeed_hace_properties);
+ dc->vmsd = &vmstate_aspeed_hace;
+}
+
+static const TypeInfo aspeed_hace_info = {
+ .name = TYPE_ASPEED_HACE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AspeedHACEState),
+ .class_init = aspeed_hace_class_init,
+ .class_size = sizeof(AspeedHACEClass)
+};
+
+static void aspeed_ast2400_hace_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedHACEClass *ahc = ASPEED_HACE_CLASS(klass);
+
+ dc->desc = "AST2400 Hash and Crypto Engine";
+
+ ahc->src_mask = 0x0FFFFFFF;
+ ahc->dest_mask = 0x0FFFFFF8;
+ ahc->hash_mask = 0x000003ff; /* No SG or SHA512 modes */
+}
+
+static const TypeInfo aspeed_ast2400_hace_info = {
+ .name = TYPE_ASPEED_AST2400_HACE,
+ .parent = TYPE_ASPEED_HACE,
+ .class_init = aspeed_ast2400_hace_class_init,
+};
+
+static void aspeed_ast2500_hace_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedHACEClass *ahc = ASPEED_HACE_CLASS(klass);
+
+ dc->desc = "AST2500 Hash and Crypto Engine";
+
+ ahc->src_mask = 0x3fffffff;
+ ahc->dest_mask = 0x3ffffff8;
+ ahc->hash_mask = 0x000003ff; /* No SG or SHA512 modes */
+}
+
+static const TypeInfo aspeed_ast2500_hace_info = {
+ .name = TYPE_ASPEED_AST2500_HACE,
+ .parent = TYPE_ASPEED_HACE,
+ .class_init = aspeed_ast2500_hace_class_init,
+};
+
+static void aspeed_ast2600_hace_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedHACEClass *ahc = ASPEED_HACE_CLASS(klass);
+
+ dc->desc = "AST2600 Hash and Crypto Engine";
+
+ ahc->src_mask = 0x7FFFFFFF;
+ ahc->dest_mask = 0x7FFFFFF8;
+ ahc->hash_mask = 0x00147FFF;
+}
+
+static const TypeInfo aspeed_ast2600_hace_info = {
+ .name = TYPE_ASPEED_AST2600_HACE,
+ .parent = TYPE_ASPEED_HACE,
+ .class_init = aspeed_ast2600_hace_class_init,
+};
+
+static void aspeed_hace_register_types(void)
+{
+ type_register_static(&aspeed_ast2400_hace_info);
+ type_register_static(&aspeed_ast2500_hace_info);
+ type_register_static(&aspeed_ast2600_hace_info);
+ type_register_static(&aspeed_hace_info);
+}
+
+type_init(aspeed_hace_register_types);
diff --git a/hw/misc/aspeed_lpc.c b/hw/misc/aspeed_lpc.c
new file mode 100644
index 000000000..2dddb27c3
--- /dev/null
+++ b/hw/misc/aspeed_lpc.c
@@ -0,0 +1,486 @@
+/*
+ * ASPEED LPC Controller
+ *
+ * Copyright (C) 2017-2018 IBM Corp.
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/error-report.h"
+#include "hw/misc/aspeed_lpc.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+
+#define TO_REG(offset) ((offset) >> 2)
+
+#define HICR0 TO_REG(0x00)
+#define HICR0_LPC3E BIT(7)
+#define HICR0_LPC2E BIT(6)
+#define HICR0_LPC1E BIT(5)
+#define HICR1 TO_REG(0x04)
+#define HICR2 TO_REG(0x08)
+#define HICR2_IBFIE3 BIT(3)
+#define HICR2_IBFIE2 BIT(2)
+#define HICR2_IBFIE1 BIT(1)
+#define HICR3 TO_REG(0x0C)
+#define HICR4 TO_REG(0x10)
+#define HICR4_KCSENBL BIT(2)
+#define IDR1 TO_REG(0x24)
+#define IDR2 TO_REG(0x28)
+#define IDR3 TO_REG(0x2C)
+#define ODR1 TO_REG(0x30)
+#define ODR2 TO_REG(0x34)
+#define ODR3 TO_REG(0x38)
+#define STR1 TO_REG(0x3C)
+#define STR_OBF BIT(0)
+#define STR_IBF BIT(1)
+#define STR_CMD_DATA BIT(3)
+#define STR2 TO_REG(0x40)
+#define STR3 TO_REG(0x44)
+#define HICR5 TO_REG(0x80)
+#define HICR6 TO_REG(0x84)
+#define HICR7 TO_REG(0x88)
+#define HICR8 TO_REG(0x8C)
+#define HICRB TO_REG(0x100)
+#define HICRB_IBFIE4 BIT(1)
+#define HICRB_LPC4E BIT(0)
+#define IDR4 TO_REG(0x114)
+#define ODR4 TO_REG(0x118)
+#define STR4 TO_REG(0x11C)
+
+enum aspeed_kcs_channel_id {
+ kcs_channel_1 = 0,
+ kcs_channel_2,
+ kcs_channel_3,
+ kcs_channel_4,
+};
+
+static const enum aspeed_lpc_subdevice aspeed_kcs_subdevice_map[] = {
+ [kcs_channel_1] = aspeed_lpc_kcs_1,
+ [kcs_channel_2] = aspeed_lpc_kcs_2,
+ [kcs_channel_3] = aspeed_lpc_kcs_3,
+ [kcs_channel_4] = aspeed_lpc_kcs_4,
+};
+
+struct aspeed_kcs_channel {
+ enum aspeed_kcs_channel_id id;
+
+ int idr;
+ int odr;
+ int str;
+};
+
+static const struct aspeed_kcs_channel aspeed_kcs_channel_map[] = {
+ [kcs_channel_1] = {
+ .id = kcs_channel_1,
+ .idr = IDR1,
+ .odr = ODR1,
+ .str = STR1
+ },
+
+ [kcs_channel_2] = {
+ .id = kcs_channel_2,
+ .idr = IDR2,
+ .odr = ODR2,
+ .str = STR2
+ },
+
+ [kcs_channel_3] = {
+ .id = kcs_channel_3,
+ .idr = IDR3,
+ .odr = ODR3,
+ .str = STR3
+ },
+
+ [kcs_channel_4] = {
+ .id = kcs_channel_4,
+ .idr = IDR4,
+ .odr = ODR4,
+ .str = STR4
+ },
+};
+
+struct aspeed_kcs_register_data {
+ const char *name;
+ int reg;
+ const struct aspeed_kcs_channel *chan;
+};
+
+static const struct aspeed_kcs_register_data aspeed_kcs_registers[] = {
+ {
+ .name = "idr1",
+ .reg = IDR1,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_1],
+ },
+ {
+ .name = "odr1",
+ .reg = ODR1,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_1],
+ },
+ {
+ .name = "str1",
+ .reg = STR1,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_1],
+ },
+ {
+ .name = "idr2",
+ .reg = IDR2,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_2],
+ },
+ {
+ .name = "odr2",
+ .reg = ODR2,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_2],
+ },
+ {
+ .name = "str2",
+ .reg = STR2,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_2],
+ },
+ {
+ .name = "idr3",
+ .reg = IDR3,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_3],
+ },
+ {
+ .name = "odr3",
+ .reg = ODR3,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_3],
+ },
+ {
+ .name = "str3",
+ .reg = STR3,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_3],
+ },
+ {
+ .name = "idr4",
+ .reg = IDR4,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_4],
+ },
+ {
+ .name = "odr4",
+ .reg = ODR4,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_4],
+ },
+ {
+ .name = "str4",
+ .reg = STR4,
+ .chan = &aspeed_kcs_channel_map[kcs_channel_4],
+ },
+ { },
+};
+
+static const struct aspeed_kcs_register_data *
+aspeed_kcs_get_register_data_by_name(const char *name)
+{
+ const struct aspeed_kcs_register_data *pos = aspeed_kcs_registers;
+
+ while (pos->name) {
+ if (!strcmp(pos->name, name)) {
+ return pos;
+ }
+ pos++;
+ }
+
+ return NULL;
+}
+
+static const struct aspeed_kcs_channel *
+aspeed_kcs_get_channel_by_register(int reg)
+{
+ const struct aspeed_kcs_register_data *pos = aspeed_kcs_registers;
+
+ while (pos->name) {
+ if (pos->reg == reg) {
+ return pos->chan;
+ }
+ pos++;
+ }
+
+ return NULL;
+}
+
+static void aspeed_kcs_get_register_property(Object *obj,
+ Visitor *v,
+ const char *name,
+ void *opaque,
+ Error **errp)
+{
+ const struct aspeed_kcs_register_data *data;
+ AspeedLPCState *s = ASPEED_LPC(obj);
+ uint32_t val;
+
+ data = aspeed_kcs_get_register_data_by_name(name);
+ if (!data) {
+ return;
+ }
+
+ if (!strncmp("odr", name, 3)) {
+ s->regs[data->chan->str] &= ~STR_OBF;
+ }
+
+ val = s->regs[data->reg];
+
+ visit_type_uint32(v, name, &val, errp);
+}
+
+static bool aspeed_kcs_channel_enabled(AspeedLPCState *s,
+ const struct aspeed_kcs_channel *channel)
+{
+ switch (channel->id) {
+ case kcs_channel_1: return s->regs[HICR0] & HICR0_LPC1E;
+ case kcs_channel_2: return s->regs[HICR0] & HICR0_LPC2E;
+ case kcs_channel_3:
+ return (s->regs[HICR0] & HICR0_LPC3E) &&
+ (s->regs[HICR4] & HICR4_KCSENBL);
+ case kcs_channel_4: return s->regs[HICRB] & HICRB_LPC4E;
+ default: return false;
+ }
+}
+
+static bool
+aspeed_kcs_channel_ibf_irq_enabled(AspeedLPCState *s,
+ const struct aspeed_kcs_channel *channel)
+{
+ if (!aspeed_kcs_channel_enabled(s, channel)) {
+ return false;
+ }
+
+ switch (channel->id) {
+ case kcs_channel_1: return s->regs[HICR2] & HICR2_IBFIE1;
+ case kcs_channel_2: return s->regs[HICR2] & HICR2_IBFIE2;
+ case kcs_channel_3: return s->regs[HICR2] & HICR2_IBFIE3;
+ case kcs_channel_4: return s->regs[HICRB] & HICRB_IBFIE4;
+ default: return false;
+ }
+}
+
+static void aspeed_kcs_set_register_property(Object *obj,
+ Visitor *v,
+ const char *name,
+ void *opaque,
+ Error **errp)
+{
+ const struct aspeed_kcs_register_data *data;
+ AspeedLPCState *s = ASPEED_LPC(obj);
+ uint32_t val;
+
+ data = aspeed_kcs_get_register_data_by_name(name);
+ if (!data) {
+ return;
+ }
+
+ if (!visit_type_uint32(v, name, &val, errp)) {
+ return;
+ }
+
+ if (strncmp("str", name, 3)) {
+ s->regs[data->reg] = val;
+ }
+
+ if (!strncmp("idr", name, 3)) {
+ s->regs[data->chan->str] |= STR_IBF;
+ if (aspeed_kcs_channel_ibf_irq_enabled(s, data->chan)) {
+ enum aspeed_lpc_subdevice subdev;
+
+ subdev = aspeed_kcs_subdevice_map[data->chan->id];
+ qemu_irq_raise(s->subdevice_irqs[subdev]);
+ }
+ }
+}
+
+static void aspeed_lpc_set_irq(void *opaque, int irq, int level)
+{
+ AspeedLPCState *s = (AspeedLPCState *)opaque;
+
+ if (level) {
+ s->subdevice_irqs_pending |= BIT(irq);
+ } else {
+ s->subdevice_irqs_pending &= ~BIT(irq);
+ }
+
+ qemu_set_irq(s->irq, !!s->subdevice_irqs_pending);
+}
+
+static uint64_t aspeed_lpc_read(void *opaque, hwaddr offset, unsigned size)
+{
+ AspeedLPCState *s = ASPEED_LPC(opaque);
+ int reg = TO_REG(offset);
+
+ if (reg >= ARRAY_SIZE(s->regs)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return 0;
+ }
+
+ switch (reg) {
+ case IDR1:
+ case IDR2:
+ case IDR3:
+ case IDR4:
+ {
+ const struct aspeed_kcs_channel *channel;
+
+ channel = aspeed_kcs_get_channel_by_register(reg);
+ if (s->regs[channel->str] & STR_IBF) {
+ enum aspeed_lpc_subdevice subdev;
+
+ subdev = aspeed_kcs_subdevice_map[channel->id];
+ qemu_irq_lower(s->subdevice_irqs[subdev]);
+ }
+
+ s->regs[channel->str] &= ~STR_IBF;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return s->regs[reg];
+}
+
+static void aspeed_lpc_write(void *opaque, hwaddr offset, uint64_t data,
+ unsigned int size)
+{
+ AspeedLPCState *s = ASPEED_LPC(opaque);
+ int reg = TO_REG(offset);
+
+ if (reg >= ARRAY_SIZE(s->regs)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return;
+ }
+
+
+ switch (reg) {
+ case ODR1:
+ case ODR2:
+ case ODR3:
+ case ODR4:
+ s->regs[aspeed_kcs_get_channel_by_register(reg)->str] |= STR_OBF;
+ break;
+ default:
+ break;
+ }
+
+ s->regs[reg] = data;
+}
+
+static const MemoryRegionOps aspeed_lpc_ops = {
+ .read = aspeed_lpc_read,
+ .write = aspeed_lpc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static void aspeed_lpc_reset(DeviceState *dev)
+{
+ struct AspeedLPCState *s = ASPEED_LPC(dev);
+
+ s->subdevice_irqs_pending = 0;
+
+ memset(s->regs, 0, sizeof(s->regs));
+
+ s->regs[HICR7] = s->hicr7;
+}
+
+static void aspeed_lpc_realize(DeviceState *dev, Error **errp)
+{
+ AspeedLPCState *s = ASPEED_LPC(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->subdevice_irqs[aspeed_lpc_kcs_1]);
+ sysbus_init_irq(sbd, &s->subdevice_irqs[aspeed_lpc_kcs_2]);
+ sysbus_init_irq(sbd, &s->subdevice_irqs[aspeed_lpc_kcs_3]);
+ sysbus_init_irq(sbd, &s->subdevice_irqs[aspeed_lpc_kcs_4]);
+ sysbus_init_irq(sbd, &s->subdevice_irqs[aspeed_lpc_ibt]);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_lpc_ops, s,
+ TYPE_ASPEED_LPC, 0x1000);
+
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ qdev_init_gpio_in(dev, aspeed_lpc_set_irq, ASPEED_LPC_NR_SUBDEVS);
+}
+
+static void aspeed_lpc_init(Object *obj)
+{
+ object_property_add(obj, "idr1", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+ object_property_add(obj, "odr1", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+ object_property_add(obj, "str1", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+ object_property_add(obj, "idr2", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+ object_property_add(obj, "odr2", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+ object_property_add(obj, "str2", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+ object_property_add(obj, "idr3", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+ object_property_add(obj, "odr3", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+ object_property_add(obj, "str3", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+ object_property_add(obj, "idr4", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+ object_property_add(obj, "odr4", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+ object_property_add(obj, "str4", "uint32", aspeed_kcs_get_register_property,
+ aspeed_kcs_set_register_property, NULL, NULL);
+}
+
+static const VMStateDescription vmstate_aspeed_lpc = {
+ .name = TYPE_ASPEED_LPC,
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AspeedLPCState, ASPEED_LPC_NR_REGS),
+ VMSTATE_UINT32(subdevice_irqs_pending, AspeedLPCState),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static Property aspeed_lpc_properties[] = {
+ DEFINE_PROP_UINT32("hicr7", AspeedLPCState, hicr7, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void aspeed_lpc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = aspeed_lpc_realize;
+ dc->reset = aspeed_lpc_reset;
+ dc->desc = "Aspeed LPC Controller",
+ dc->vmsd = &vmstate_aspeed_lpc;
+ device_class_set_props(dc, aspeed_lpc_properties);
+}
+
+static const TypeInfo aspeed_lpc_info = {
+ .name = TYPE_ASPEED_LPC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AspeedLPCState),
+ .class_init = aspeed_lpc_class_init,
+ .instance_init = aspeed_lpc_init,
+};
+
+static void aspeed_lpc_register_types(void)
+{
+ type_register_static(&aspeed_lpc_info);
+}
+
+type_init(aspeed_lpc_register_types);
diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c
new file mode 100644
index 000000000..d06e179a6
--- /dev/null
+++ b/hw/misc/aspeed_scu.c
@@ -0,0 +1,740 @@
+/*
+ * ASPEED System Control Unit
+ *
+ * Andrew Jeffery <andrew@aj.id.au>
+ *
+ * Copyright 2016 IBM Corp.
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/aspeed_scu.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/guest-random.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+#define TO_REG(offset) ((offset) >> 2)
+
+#define PROT_KEY TO_REG(0x00)
+#define SYS_RST_CTRL TO_REG(0x04)
+#define CLK_SEL TO_REG(0x08)
+#define CLK_STOP_CTRL TO_REG(0x0C)
+#define FREQ_CNTR_CTRL TO_REG(0x10)
+#define FREQ_CNTR_EVAL TO_REG(0x14)
+#define IRQ_CTRL TO_REG(0x18)
+#define D2PLL_PARAM TO_REG(0x1C)
+#define MPLL_PARAM TO_REG(0x20)
+#define HPLL_PARAM TO_REG(0x24)
+#define FREQ_CNTR_RANGE TO_REG(0x28)
+#define MISC_CTRL1 TO_REG(0x2C)
+#define PCI_CTRL1 TO_REG(0x30)
+#define PCI_CTRL2 TO_REG(0x34)
+#define PCI_CTRL3 TO_REG(0x38)
+#define SYS_RST_STATUS TO_REG(0x3C)
+#define SOC_SCRATCH1 TO_REG(0x40)
+#define SOC_SCRATCH2 TO_REG(0x44)
+#define MAC_CLK_DELAY TO_REG(0x48)
+#define MISC_CTRL2 TO_REG(0x4C)
+#define VGA_SCRATCH1 TO_REG(0x50)
+#define VGA_SCRATCH2 TO_REG(0x54)
+#define VGA_SCRATCH3 TO_REG(0x58)
+#define VGA_SCRATCH4 TO_REG(0x5C)
+#define VGA_SCRATCH5 TO_REG(0x60)
+#define VGA_SCRATCH6 TO_REG(0x64)
+#define VGA_SCRATCH7 TO_REG(0x68)
+#define VGA_SCRATCH8 TO_REG(0x6C)
+#define HW_STRAP1 TO_REG(0x70)
+#define RNG_CTRL TO_REG(0x74)
+#define RNG_DATA TO_REG(0x78)
+#define SILICON_REV TO_REG(0x7C)
+#define PINMUX_CTRL1 TO_REG(0x80)
+#define PINMUX_CTRL2 TO_REG(0x84)
+#define PINMUX_CTRL3 TO_REG(0x88)
+#define PINMUX_CTRL4 TO_REG(0x8C)
+#define PINMUX_CTRL5 TO_REG(0x90)
+#define PINMUX_CTRL6 TO_REG(0x94)
+#define WDT_RST_CTRL TO_REG(0x9C)
+#define PINMUX_CTRL7 TO_REG(0xA0)
+#define PINMUX_CTRL8 TO_REG(0xA4)
+#define PINMUX_CTRL9 TO_REG(0xA8)
+#define WAKEUP_EN TO_REG(0xC0)
+#define WAKEUP_CTRL TO_REG(0xC4)
+#define HW_STRAP2 TO_REG(0xD0)
+#define FREE_CNTR4 TO_REG(0xE0)
+#define FREE_CNTR4_EXT TO_REG(0xE4)
+#define CPU2_CTRL TO_REG(0x100)
+#define CPU2_BASE_SEG1 TO_REG(0x104)
+#define CPU2_BASE_SEG2 TO_REG(0x108)
+#define CPU2_BASE_SEG3 TO_REG(0x10C)
+#define CPU2_BASE_SEG4 TO_REG(0x110)
+#define CPU2_BASE_SEG5 TO_REG(0x114)
+#define CPU2_CACHE_CTRL TO_REG(0x118)
+#define CHIP_ID0 TO_REG(0x150)
+#define CHIP_ID1 TO_REG(0x154)
+#define UART_HPLL_CLK TO_REG(0x160)
+#define PCIE_CTRL TO_REG(0x180)
+#define BMC_MMIO_CTRL TO_REG(0x184)
+#define RELOC_DECODE_BASE1 TO_REG(0x188)
+#define RELOC_DECODE_BASE2 TO_REG(0x18C)
+#define MAILBOX_DECODE_BASE TO_REG(0x190)
+#define SRAM_DECODE_BASE1 TO_REG(0x194)
+#define SRAM_DECODE_BASE2 TO_REG(0x198)
+#define BMC_REV TO_REG(0x19C)
+#define BMC_DEV_ID TO_REG(0x1A4)
+
+#define AST2600_PROT_KEY TO_REG(0x00)
+#define AST2600_SILICON_REV TO_REG(0x04)
+#define AST2600_SILICON_REV2 TO_REG(0x14)
+#define AST2600_SYS_RST_CTRL TO_REG(0x40)
+#define AST2600_SYS_RST_CTRL_CLR TO_REG(0x44)
+#define AST2600_SYS_RST_CTRL2 TO_REG(0x50)
+#define AST2600_SYS_RST_CTRL2_CLR TO_REG(0x54)
+#define AST2600_CLK_STOP_CTRL TO_REG(0x80)
+#define AST2600_CLK_STOP_CTRL_CLR TO_REG(0x84)
+#define AST2600_CLK_STOP_CTRL2 TO_REG(0x90)
+#define AST2600_CLK_STOP_CTRL2_CLR TO_REG(0x94)
+#define AST2600_DEBUG_CTRL TO_REG(0xC8)
+#define AST2600_DEBUG_CTRL2 TO_REG(0xD8)
+#define AST2600_SDRAM_HANDSHAKE TO_REG(0x100)
+#define AST2600_HPLL_PARAM TO_REG(0x200)
+#define AST2600_HPLL_EXT TO_REG(0x204)
+#define AST2600_APLL_PARAM TO_REG(0x210)
+#define AST2600_APLL_EXT TO_REG(0x214)
+#define AST2600_MPLL_PARAM TO_REG(0x220)
+#define AST2600_MPLL_EXT TO_REG(0x224)
+#define AST2600_EPLL_PARAM TO_REG(0x240)
+#define AST2600_EPLL_EXT TO_REG(0x244)
+#define AST2600_DPLL_PARAM TO_REG(0x260)
+#define AST2600_DPLL_EXT TO_REG(0x264)
+#define AST2600_CLK_SEL TO_REG(0x300)
+#define AST2600_CLK_SEL2 TO_REG(0x304)
+#define AST2600_CLK_SEL3 TO_REG(0x308)
+#define AST2600_CLK_SEL4 TO_REG(0x310)
+#define AST2600_CLK_SEL5 TO_REG(0x314)
+#define AST2600_UARTCLK TO_REG(0x338)
+#define AST2600_HUARTCLK TO_REG(0x33C)
+#define AST2600_HW_STRAP1 TO_REG(0x500)
+#define AST2600_HW_STRAP1_CLR TO_REG(0x504)
+#define AST2600_HW_STRAP1_PROT TO_REG(0x508)
+#define AST2600_HW_STRAP2 TO_REG(0x510)
+#define AST2600_HW_STRAP2_CLR TO_REG(0x514)
+#define AST2600_HW_STRAP2_PROT TO_REG(0x518)
+#define AST2600_RNG_CTRL TO_REG(0x524)
+#define AST2600_RNG_DATA TO_REG(0x540)
+#define AST2600_CHIP_ID0 TO_REG(0x5B0)
+#define AST2600_CHIP_ID1 TO_REG(0x5B4)
+
+#define AST2600_CLK TO_REG(0x40)
+
+#define SCU_IO_REGION_SIZE 0x1000
+
+static const uint32_t ast2400_a0_resets[ASPEED_SCU_NR_REGS] = {
+ [SYS_RST_CTRL] = 0xFFCFFEDCU,
+ [CLK_SEL] = 0xF3F40000U,
+ [CLK_STOP_CTRL] = 0x19FC3E8BU,
+ [D2PLL_PARAM] = 0x00026108U,
+ [MPLL_PARAM] = 0x00030291U,
+ [HPLL_PARAM] = 0x00000291U,
+ [MISC_CTRL1] = 0x00000010U,
+ [PCI_CTRL1] = 0x20001A03U,
+ [PCI_CTRL2] = 0x20001A03U,
+ [PCI_CTRL3] = 0x04000030U,
+ [SYS_RST_STATUS] = 0x00000001U,
+ [SOC_SCRATCH1] = 0x000000C0U, /* SoC completed DRAM init */
+ [MISC_CTRL2] = 0x00000023U,
+ [RNG_CTRL] = 0x0000000EU,
+ [PINMUX_CTRL2] = 0x0000F000U,
+ [PINMUX_CTRL3] = 0x01000000U,
+ [PINMUX_CTRL4] = 0x000000FFU,
+ [PINMUX_CTRL5] = 0x0000A000U,
+ [WDT_RST_CTRL] = 0x003FFFF3U,
+ [PINMUX_CTRL8] = 0xFFFF0000U,
+ [PINMUX_CTRL9] = 0x000FFFFFU,
+ [FREE_CNTR4] = 0x000000FFU,
+ [FREE_CNTR4_EXT] = 0x000000FFU,
+ [CPU2_BASE_SEG1] = 0x80000000U,
+ [CPU2_BASE_SEG4] = 0x1E600000U,
+ [CPU2_BASE_SEG5] = 0xC0000000U,
+ [UART_HPLL_CLK] = 0x00001903U,
+ [PCIE_CTRL] = 0x0000007BU,
+ [BMC_DEV_ID] = 0x00002402U
+};
+
+/* SCU70 bit 23: 0 24Mhz. bit 11:9: 0b001 AXI:ABH ratio 2:1 */
+/* AST2500 revision A1 */
+
+static const uint32_t ast2500_a1_resets[ASPEED_SCU_NR_REGS] = {
+ [SYS_RST_CTRL] = 0xFFCFFEDCU,
+ [CLK_SEL] = 0xF3F40000U,
+ [CLK_STOP_CTRL] = 0x19FC3E8BU,
+ [D2PLL_PARAM] = 0x00026108U,
+ [MPLL_PARAM] = 0x00030291U,
+ [HPLL_PARAM] = 0x93000400U,
+ [MISC_CTRL1] = 0x00000010U,
+ [PCI_CTRL1] = 0x20001A03U,
+ [PCI_CTRL2] = 0x20001A03U,
+ [PCI_CTRL3] = 0x04000030U,
+ [SYS_RST_STATUS] = 0x00000001U,
+ [SOC_SCRATCH1] = 0x000000C0U, /* SoC completed DRAM init */
+ [MISC_CTRL2] = 0x00000023U,
+ [RNG_CTRL] = 0x0000000EU,
+ [PINMUX_CTRL2] = 0x0000F000U,
+ [PINMUX_CTRL3] = 0x03000000U,
+ [PINMUX_CTRL4] = 0x00000000U,
+ [PINMUX_CTRL5] = 0x0000A000U,
+ [WDT_RST_CTRL] = 0x023FFFF3U,
+ [PINMUX_CTRL8] = 0xFFFF0000U,
+ [PINMUX_CTRL9] = 0x000FFFFFU,
+ [FREE_CNTR4] = 0x000000FFU,
+ [FREE_CNTR4_EXT] = 0x000000FFU,
+ [CPU2_BASE_SEG1] = 0x80000000U,
+ [CPU2_BASE_SEG4] = 0x1E600000U,
+ [CPU2_BASE_SEG5] = 0xC0000000U,
+ [CHIP_ID0] = 0x1234ABCDU,
+ [CHIP_ID1] = 0x88884444U,
+ [UART_HPLL_CLK] = 0x00001903U,
+ [PCIE_CTRL] = 0x0000007BU,
+ [BMC_DEV_ID] = 0x00002402U
+};
+
+static uint32_t aspeed_scu_get_random(void)
+{
+ uint32_t num;
+ qemu_guest_getrandom_nofail(&num, sizeof(num));
+ return num;
+}
+
+uint32_t aspeed_scu_get_apb_freq(AspeedSCUState *s)
+{
+ AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(s);
+ uint32_t hpll = asc->calc_hpll(s, s->regs[HPLL_PARAM]);
+
+ return hpll / (SCU_CLK_GET_PCLK_DIV(s->regs[CLK_SEL]) + 1)
+ / asc->apb_divider;
+}
+
+static uint64_t aspeed_scu_read(void *opaque, hwaddr offset, unsigned size)
+{
+ AspeedSCUState *s = ASPEED_SCU(opaque);
+ int reg = TO_REG(offset);
+
+ if (reg >= ASPEED_SCU_NR_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return 0;
+ }
+
+ switch (reg) {
+ case RNG_DATA:
+ /* On hardware, RNG_DATA works regardless of
+ * the state of the enable bit in RNG_CTRL
+ */
+ s->regs[RNG_DATA] = aspeed_scu_get_random();
+ break;
+ case WAKEUP_EN:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Read of write-only offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ break;
+ }
+
+ return s->regs[reg];
+}
+
+static void aspeed_ast2400_scu_write(void *opaque, hwaddr offset,
+ uint64_t data, unsigned size)
+{
+ AspeedSCUState *s = ASPEED_SCU(opaque);
+ int reg = TO_REG(offset);
+
+ if (reg >= ASPEED_SCU_NR_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return;
+ }
+
+ if (reg > PROT_KEY && reg < CPU2_BASE_SEG1 &&
+ !s->regs[PROT_KEY]) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: SCU is locked!\n", __func__);
+ }
+
+ trace_aspeed_scu_write(offset, size, data);
+
+ switch (reg) {
+ case PROT_KEY:
+ s->regs[reg] = (data == ASPEED_SCU_PROT_KEY) ? 1 : 0;
+ return;
+ case SILICON_REV:
+ case FREQ_CNTR_EVAL:
+ case VGA_SCRATCH1 ... VGA_SCRATCH8:
+ case RNG_DATA:
+ case FREE_CNTR4:
+ case FREE_CNTR4_EXT:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Write to read-only offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return;
+ }
+
+ s->regs[reg] = data;
+}
+
+static void aspeed_ast2500_scu_write(void *opaque, hwaddr offset,
+ uint64_t data, unsigned size)
+{
+ AspeedSCUState *s = ASPEED_SCU(opaque);
+ int reg = TO_REG(offset);
+
+ if (reg >= ASPEED_SCU_NR_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return;
+ }
+
+ if (reg > PROT_KEY && reg < CPU2_BASE_SEG1 &&
+ !s->regs[PROT_KEY]) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: SCU is locked!\n", __func__);
+ return;
+ }
+
+ trace_aspeed_scu_write(offset, size, data);
+
+ switch (reg) {
+ case PROT_KEY:
+ s->regs[reg] = (data == ASPEED_SCU_PROT_KEY) ? 1 : 0;
+ return;
+ case HW_STRAP1:
+ s->regs[HW_STRAP1] |= data;
+ return;
+ case SILICON_REV:
+ s->regs[HW_STRAP1] &= ~data;
+ return;
+ case FREQ_CNTR_EVAL:
+ case VGA_SCRATCH1 ... VGA_SCRATCH8:
+ case RNG_DATA:
+ case FREE_CNTR4:
+ case FREE_CNTR4_EXT:
+ case CHIP_ID0:
+ case CHIP_ID1:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Write to read-only offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return;
+ }
+
+ s->regs[reg] = data;
+}
+
+static const MemoryRegionOps aspeed_ast2400_scu_ops = {
+ .read = aspeed_scu_read,
+ .write = aspeed_ast2400_scu_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps aspeed_ast2500_scu_ops = {
+ .read = aspeed_scu_read,
+ .write = aspeed_ast2500_scu_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .valid.unaligned = false,
+};
+
+static uint32_t aspeed_scu_get_clkin(AspeedSCUState *s)
+{
+ if (s->hw_strap1 & SCU_HW_STRAP_CLK_25M_IN) {
+ return 25000000;
+ } else if (s->hw_strap1 & SCU_HW_STRAP_CLK_48M_IN) {
+ return 48000000;
+ } else {
+ return 24000000;
+ }
+}
+
+/*
+ * Strapped frequencies for the AST2400 in MHz. They depend on the
+ * clkin frequency.
+ */
+static const uint32_t hpll_ast2400_freqs[][4] = {
+ { 384, 360, 336, 408 }, /* 24MHz or 48MHz */
+ { 400, 375, 350, 425 }, /* 25MHz */
+};
+
+static uint32_t aspeed_2400_scu_calc_hpll(AspeedSCUState *s, uint32_t hpll_reg)
+{
+ uint8_t freq_select;
+ bool clk_25m_in;
+ uint32_t clkin = aspeed_scu_get_clkin(s);
+
+ if (hpll_reg & SCU_AST2400_H_PLL_OFF) {
+ return 0;
+ }
+
+ if (hpll_reg & SCU_AST2400_H_PLL_PROGRAMMED) {
+ uint32_t multiplier = 1;
+
+ if (!(hpll_reg & SCU_AST2400_H_PLL_BYPASS_EN)) {
+ uint32_t n = (hpll_reg >> 5) & 0x3f;
+ uint32_t od = (hpll_reg >> 4) & 0x1;
+ uint32_t d = hpll_reg & 0xf;
+
+ multiplier = (2 - od) * ((n + 2) / (d + 1));
+ }
+
+ return clkin * multiplier;
+ }
+
+ /* HW strapping */
+ clk_25m_in = !!(s->hw_strap1 & SCU_HW_STRAP_CLK_25M_IN);
+ freq_select = SCU_AST2400_HW_STRAP_GET_H_PLL_CLK(s->hw_strap1);
+
+ return hpll_ast2400_freqs[clk_25m_in][freq_select] * 1000000;
+}
+
+static uint32_t aspeed_2500_scu_calc_hpll(AspeedSCUState *s, uint32_t hpll_reg)
+{
+ uint32_t multiplier = 1;
+ uint32_t clkin = aspeed_scu_get_clkin(s);
+
+ if (hpll_reg & SCU_H_PLL_OFF) {
+ return 0;
+ }
+
+ if (!(hpll_reg & SCU_H_PLL_BYPASS_EN)) {
+ uint32_t p = (hpll_reg >> 13) & 0x3f;
+ uint32_t m = (hpll_reg >> 5) & 0xff;
+ uint32_t n = hpll_reg & 0x1f;
+
+ multiplier = ((m + 1) / (n + 1)) / (p + 1);
+ }
+
+ return clkin * multiplier;
+}
+
+static void aspeed_scu_reset(DeviceState *dev)
+{
+ AspeedSCUState *s = ASPEED_SCU(dev);
+ AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(dev);
+
+ memcpy(s->regs, asc->resets, asc->nr_regs * 4);
+ s->regs[SILICON_REV] = s->silicon_rev;
+ s->regs[HW_STRAP1] = s->hw_strap1;
+ s->regs[HW_STRAP2] = s->hw_strap2;
+ s->regs[PROT_KEY] = s->hw_prot_key;
+}
+
+static uint32_t aspeed_silicon_revs[] = {
+ AST2400_A0_SILICON_REV,
+ AST2400_A1_SILICON_REV,
+ AST2500_A0_SILICON_REV,
+ AST2500_A1_SILICON_REV,
+ AST2600_A0_SILICON_REV,
+ AST2600_A1_SILICON_REV,
+ AST2600_A2_SILICON_REV,
+ AST2600_A3_SILICON_REV,
+};
+
+bool is_supported_silicon_rev(uint32_t silicon_rev)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(aspeed_silicon_revs); i++) {
+ if (silicon_rev == aspeed_silicon_revs[i]) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void aspeed_scu_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ AspeedSCUState *s = ASPEED_SCU(dev);
+ AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(dev);
+
+ if (!is_supported_silicon_rev(s->silicon_rev)) {
+ error_setg(errp, "Unknown silicon revision: 0x%" PRIx32,
+ s->silicon_rev);
+ return;
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), asc->ops, s,
+ TYPE_ASPEED_SCU, SCU_IO_REGION_SIZE);
+
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription vmstate_aspeed_scu = {
+ .name = "aspeed.scu",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AspeedSCUState, ASPEED_AST2600_SCU_NR_REGS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property aspeed_scu_properties[] = {
+ DEFINE_PROP_UINT32("silicon-rev", AspeedSCUState, silicon_rev, 0),
+ DEFINE_PROP_UINT32("hw-strap1", AspeedSCUState, hw_strap1, 0),
+ DEFINE_PROP_UINT32("hw-strap2", AspeedSCUState, hw_strap2, 0),
+ DEFINE_PROP_UINT32("hw-prot-key", AspeedSCUState, hw_prot_key, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void aspeed_scu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->realize = aspeed_scu_realize;
+ dc->reset = aspeed_scu_reset;
+ dc->desc = "ASPEED System Control Unit";
+ dc->vmsd = &vmstate_aspeed_scu;
+ device_class_set_props(dc, aspeed_scu_properties);
+}
+
+static const TypeInfo aspeed_scu_info = {
+ .name = TYPE_ASPEED_SCU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AspeedSCUState),
+ .class_init = aspeed_scu_class_init,
+ .class_size = sizeof(AspeedSCUClass),
+ .abstract = true,
+};
+
+static void aspeed_2400_scu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass);
+
+ dc->desc = "ASPEED 2400 System Control Unit";
+ asc->resets = ast2400_a0_resets;
+ asc->calc_hpll = aspeed_2400_scu_calc_hpll;
+ asc->apb_divider = 2;
+ asc->nr_regs = ASPEED_SCU_NR_REGS;
+ asc->ops = &aspeed_ast2400_scu_ops;
+}
+
+static const TypeInfo aspeed_2400_scu_info = {
+ .name = TYPE_ASPEED_2400_SCU,
+ .parent = TYPE_ASPEED_SCU,
+ .instance_size = sizeof(AspeedSCUState),
+ .class_init = aspeed_2400_scu_class_init,
+};
+
+static void aspeed_2500_scu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass);
+
+ dc->desc = "ASPEED 2500 System Control Unit";
+ asc->resets = ast2500_a1_resets;
+ asc->calc_hpll = aspeed_2500_scu_calc_hpll;
+ asc->apb_divider = 4;
+ asc->nr_regs = ASPEED_SCU_NR_REGS;
+ asc->ops = &aspeed_ast2500_scu_ops;
+}
+
+static const TypeInfo aspeed_2500_scu_info = {
+ .name = TYPE_ASPEED_2500_SCU,
+ .parent = TYPE_ASPEED_SCU,
+ .instance_size = sizeof(AspeedSCUState),
+ .class_init = aspeed_2500_scu_class_init,
+};
+
+static uint64_t aspeed_ast2600_scu_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ AspeedSCUState *s = ASPEED_SCU(opaque);
+ int reg = TO_REG(offset);
+
+ if (reg >= ASPEED_AST2600_SCU_NR_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return 0;
+ }
+
+ switch (reg) {
+ case AST2600_HPLL_EXT:
+ case AST2600_EPLL_EXT:
+ case AST2600_MPLL_EXT:
+ /* PLLs are always "locked" */
+ return s->regs[reg] | BIT(31);
+ case AST2600_RNG_DATA:
+ /*
+ * On hardware, RNG_DATA works regardless of the state of the
+ * enable bit in RNG_CTRL
+ *
+ * TODO: Check this is true for ast2600
+ */
+ s->regs[AST2600_RNG_DATA] = aspeed_scu_get_random();
+ break;
+ }
+
+ return s->regs[reg];
+}
+
+static void aspeed_ast2600_scu_write(void *opaque, hwaddr offset,
+ uint64_t data64, unsigned size)
+{
+ AspeedSCUState *s = ASPEED_SCU(opaque);
+ int reg = TO_REG(offset);
+ /* Truncate here so bitwise operations below behave as expected */
+ uint32_t data = data64;
+
+ if (reg >= ASPEED_AST2600_SCU_NR_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return;
+ }
+
+ if (reg > PROT_KEY && !s->regs[PROT_KEY]) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: SCU is locked!\n", __func__);
+ }
+
+ trace_aspeed_scu_write(offset, size, data);
+
+ switch (reg) {
+ case AST2600_PROT_KEY:
+ s->regs[reg] = (data == ASPEED_SCU_PROT_KEY) ? 1 : 0;
+ return;
+ case AST2600_HW_STRAP1:
+ case AST2600_HW_STRAP2:
+ if (s->regs[reg + 2]) {
+ return;
+ }
+ /* fall through */
+ case AST2600_SYS_RST_CTRL:
+ case AST2600_SYS_RST_CTRL2:
+ case AST2600_CLK_STOP_CTRL:
+ case AST2600_CLK_STOP_CTRL2:
+ /* W1S (Write 1 to set) registers */
+ s->regs[reg] |= data;
+ return;
+ case AST2600_SYS_RST_CTRL_CLR:
+ case AST2600_SYS_RST_CTRL2_CLR:
+ case AST2600_CLK_STOP_CTRL_CLR:
+ case AST2600_CLK_STOP_CTRL2_CLR:
+ case AST2600_HW_STRAP1_CLR:
+ case AST2600_HW_STRAP2_CLR:
+ /*
+ * W1C (Write 1 to clear) registers are offset by one address from
+ * the data register
+ */
+ s->regs[reg - 1] &= ~data;
+ return;
+
+ case AST2600_RNG_DATA:
+ case AST2600_SILICON_REV:
+ case AST2600_SILICON_REV2:
+ case AST2600_CHIP_ID0:
+ case AST2600_CHIP_ID1:
+ /* Add read only registers here */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Write to read-only offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return;
+ }
+
+ s->regs[reg] = data;
+}
+
+static const MemoryRegionOps aspeed_ast2600_scu_ops = {
+ .read = aspeed_ast2600_scu_read,
+ .write = aspeed_ast2600_scu_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .valid.unaligned = false,
+};
+
+static const uint32_t ast2600_a3_resets[ASPEED_AST2600_SCU_NR_REGS] = {
+ [AST2600_SYS_RST_CTRL] = 0xF7C3FED8,
+ [AST2600_SYS_RST_CTRL2] = 0x0DFFFFFC,
+ [AST2600_CLK_STOP_CTRL] = 0xFFFF7F8A,
+ [AST2600_CLK_STOP_CTRL2] = 0xFFF0FFF0,
+ [AST2600_DEBUG_CTRL] = 0x00000FFF,
+ [AST2600_DEBUG_CTRL2] = 0x000000FF,
+ [AST2600_SDRAM_HANDSHAKE] = 0x00000000,
+ [AST2600_HPLL_PARAM] = 0x1000408F,
+ [AST2600_APLL_PARAM] = 0x1000405F,
+ [AST2600_MPLL_PARAM] = 0x1008405F,
+ [AST2600_EPLL_PARAM] = 0x1004077F,
+ [AST2600_DPLL_PARAM] = 0x1078405F,
+ [AST2600_CLK_SEL] = 0xF3940000,
+ [AST2600_CLK_SEL2] = 0x00700000,
+ [AST2600_CLK_SEL3] = 0x00000000,
+ [AST2600_CLK_SEL4] = 0xF3F40000,
+ [AST2600_CLK_SEL5] = 0x30000000,
+ [AST2600_UARTCLK] = 0x00014506,
+ [AST2600_HUARTCLK] = 0x000145C0,
+ [AST2600_CHIP_ID0] = 0x1234ABCD,
+ [AST2600_CHIP_ID1] = 0x88884444,
+};
+
+static void aspeed_ast2600_scu_reset(DeviceState *dev)
+{
+ AspeedSCUState *s = ASPEED_SCU(dev);
+ AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(dev);
+
+ memcpy(s->regs, asc->resets, asc->nr_regs * 4);
+
+ /*
+ * A0 reports A0 in _REV, but subsequent revisions report A1 regardless
+ * of actual revision. QEMU and Linux only support A1 onwards so this is
+ * sufficient.
+ */
+ s->regs[AST2600_SILICON_REV] = AST2600_A3_SILICON_REV;
+ s->regs[AST2600_SILICON_REV2] = s->silicon_rev;
+ s->regs[AST2600_HW_STRAP1] = s->hw_strap1;
+ s->regs[AST2600_HW_STRAP2] = s->hw_strap2;
+ s->regs[PROT_KEY] = s->hw_prot_key;
+}
+
+static void aspeed_2600_scu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass);
+
+ dc->desc = "ASPEED 2600 System Control Unit";
+ dc->reset = aspeed_ast2600_scu_reset;
+ asc->resets = ast2600_a3_resets;
+ asc->calc_hpll = aspeed_2500_scu_calc_hpll; /* No change since AST2500 */
+ asc->apb_divider = 4;
+ asc->nr_regs = ASPEED_AST2600_SCU_NR_REGS;
+ asc->ops = &aspeed_ast2600_scu_ops;
+}
+
+static const TypeInfo aspeed_2600_scu_info = {
+ .name = TYPE_ASPEED_2600_SCU,
+ .parent = TYPE_ASPEED_SCU,
+ .instance_size = sizeof(AspeedSCUState),
+ .class_init = aspeed_2600_scu_class_init,
+};
+
+static void aspeed_scu_register_types(void)
+{
+ type_register_static(&aspeed_scu_info);
+ type_register_static(&aspeed_2400_scu_info);
+ type_register_static(&aspeed_2500_scu_info);
+ type_register_static(&aspeed_2600_scu_info);
+}
+
+type_init(aspeed_scu_register_types);
diff --git a/hw/misc/aspeed_sdmc.c b/hw/misc/aspeed_sdmc.c
new file mode 100644
index 000000000..08f856cbd
--- /dev/null
+++ b/hw/misc/aspeed_sdmc.c
@@ -0,0 +1,522 @@
+/*
+ * ASPEED SDRAM Memory Controller
+ *
+ * Copyright (C) 2016 IBM Corp.
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/error-report.h"
+#include "hw/misc/aspeed_sdmc.h"
+#include "hw/misc/aspeed_scu.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "qemu/units.h"
+#include "qemu/cutils.h"
+#include "qapi/visitor.h"
+
+/* Protection Key Register */
+#define R_PROT (0x00 / 4)
+#define PROT_UNLOCKED 0x01
+#define PROT_HARDLOCKED 0x10 /* AST2600 */
+#define PROT_SOFTLOCKED 0x00
+
+#define PROT_KEY_UNLOCK 0xFC600309
+#define PROT_KEY_HARDLOCK 0xDEADDEAD /* AST2600 */
+
+/* Configuration Register */
+#define R_CONF (0x04 / 4)
+
+/* Interrupt control/status */
+#define R_ISR (0x50 / 4)
+
+/* Control/Status Register #1 (ast2500) */
+#define R_STATUS1 (0x60 / 4)
+#define PHY_BUSY_STATE BIT(0)
+#define PHY_PLL_LOCK_STATUS BIT(4)
+
+/* Reserved */
+#define R_MCR6C (0x6c / 4)
+
+#define R_ECC_TEST_CTRL (0x70 / 4)
+#define ECC_TEST_FINISHED BIT(12)
+#define ECC_TEST_FAIL BIT(13)
+
+#define R_TEST_START_LEN (0x74 / 4)
+#define R_TEST_FAIL_DQ (0x78 / 4)
+#define R_TEST_INIT_VAL (0x7c / 4)
+#define R_DRAM_SW (0x88 / 4)
+#define R_DRAM_TIME (0x8c / 4)
+#define R_ECC_ERR_INJECT (0xb4 / 4)
+
+/*
+ * Configuration register Ox4 (for Aspeed AST2400 SOC)
+ *
+ * These are for the record and future use. ASPEED_SDMC_DRAM_SIZE is
+ * what we care about right now as it is checked by U-Boot to
+ * determine the RAM size.
+ */
+
+#define ASPEED_SDMC_RESERVED 0xFFFFF800 /* 31:11 reserved */
+#define ASPEED_SDMC_AST2300_COMPAT (1 << 10)
+#define ASPEED_SDMC_SCRAMBLE_PATTERN (1 << 9)
+#define ASPEED_SDMC_DATA_SCRAMBLE (1 << 8)
+#define ASPEED_SDMC_ECC_ENABLE (1 << 7)
+#define ASPEED_SDMC_VGA_COMPAT (1 << 6) /* readonly */
+#define ASPEED_SDMC_DRAM_BANK (1 << 5)
+#define ASPEED_SDMC_DRAM_BURST (1 << 4)
+#define ASPEED_SDMC_VGA_APERTURE(x) ((x & 0x3) << 2) /* readonly */
+#define ASPEED_SDMC_VGA_8MB 0x0
+#define ASPEED_SDMC_VGA_16MB 0x1
+#define ASPEED_SDMC_VGA_32MB 0x2
+#define ASPEED_SDMC_VGA_64MB 0x3
+#define ASPEED_SDMC_DRAM_SIZE(x) (x & 0x3)
+#define ASPEED_SDMC_DRAM_64MB 0x0
+#define ASPEED_SDMC_DRAM_128MB 0x1
+#define ASPEED_SDMC_DRAM_256MB 0x2
+#define ASPEED_SDMC_DRAM_512MB 0x3
+
+#define ASPEED_SDMC_READONLY_MASK \
+ (ASPEED_SDMC_RESERVED | ASPEED_SDMC_VGA_COMPAT | \
+ ASPEED_SDMC_VGA_APERTURE(ASPEED_SDMC_VGA_64MB))
+/*
+ * Configuration register Ox4 (for Aspeed AST2500 SOC and higher)
+ *
+ * Incompatibilities are annotated in the list. ASPEED_SDMC_HW_VERSION
+ * should be set to 1 for the AST2500 SOC.
+ */
+#define ASPEED_SDMC_HW_VERSION(x) ((x & 0xf) << 28) /* readonly */
+#define ASPEED_SDMC_SW_VERSION ((x & 0xff) << 20)
+#define ASPEED_SDMC_CACHE_INITIAL_DONE (1 << 19) /* readonly */
+#define ASPEED_SDMC_AST2500_RESERVED 0x7C000 /* 18:14 reserved */
+#define ASPEED_SDMC_CACHE_DDR4_CONF (1 << 13)
+#define ASPEED_SDMC_CACHE_INITIAL (1 << 12)
+#define ASPEED_SDMC_CACHE_RANGE_CTRL (1 << 11)
+#define ASPEED_SDMC_CACHE_ENABLE (1 << 10) /* differs from AST2400 */
+#define ASPEED_SDMC_DRAM_TYPE (1 << 4) /* differs from AST2400 */
+
+/* DRAM size definitions differs */
+#define ASPEED_SDMC_AST2500_128MB 0x0
+#define ASPEED_SDMC_AST2500_256MB 0x1
+#define ASPEED_SDMC_AST2500_512MB 0x2
+#define ASPEED_SDMC_AST2500_1024MB 0x3
+
+#define ASPEED_SDMC_AST2600_256MB 0x0
+#define ASPEED_SDMC_AST2600_512MB 0x1
+#define ASPEED_SDMC_AST2600_1024MB 0x2
+#define ASPEED_SDMC_AST2600_2048MB 0x3
+
+#define ASPEED_SDMC_AST2500_READONLY_MASK \
+ (ASPEED_SDMC_HW_VERSION(0xf) | ASPEED_SDMC_CACHE_INITIAL_DONE | \
+ ASPEED_SDMC_AST2500_RESERVED | ASPEED_SDMC_VGA_COMPAT | \
+ ASPEED_SDMC_VGA_APERTURE(ASPEED_SDMC_VGA_64MB))
+
+static uint64_t aspeed_sdmc_read(void *opaque, hwaddr addr, unsigned size)
+{
+ AspeedSDMCState *s = ASPEED_SDMC(opaque);
+
+ addr >>= 2;
+
+ if (addr >= ARRAY_SIZE(s->regs)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
+ __func__, addr * 4);
+ return 0;
+ }
+
+ return s->regs[addr];
+}
+
+static void aspeed_sdmc_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ AspeedSDMCState *s = ASPEED_SDMC(opaque);
+ AspeedSDMCClass *asc = ASPEED_SDMC_GET_CLASS(s);
+
+ addr >>= 2;
+
+ if (addr >= ARRAY_SIZE(s->regs)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+ return;
+ }
+
+ asc->write(s, addr, data);
+}
+
+static const MemoryRegionOps aspeed_sdmc_ops = {
+ .read = aspeed_sdmc_read,
+ .write = aspeed_sdmc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static void aspeed_sdmc_reset(DeviceState *dev)
+{
+ AspeedSDMCState *s = ASPEED_SDMC(dev);
+ AspeedSDMCClass *asc = ASPEED_SDMC_GET_CLASS(s);
+
+ memset(s->regs, 0, sizeof(s->regs));
+
+ /* Set ram size bit and defaults values */
+ s->regs[R_CONF] = asc->compute_conf(s, 0);
+
+ /*
+ * PHY status:
+ * - set phy status ok (set bit 1)
+ * - initial PVT calibration ok (clear bit 3)
+ * - runtime calibration ok (clear bit 5)
+ */
+ s->regs[0x100] = BIT(1);
+
+ /* PHY eye window: set all as passing */
+ s->regs[0x100 | (0x68 / 4)] = 0xff;
+ s->regs[0x100 | (0x7c / 4)] = 0xff;
+ s->regs[0x100 | (0x50 / 4)] = 0xfffffff;
+}
+
+static void aspeed_sdmc_get_ram_size(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ AspeedSDMCState *s = ASPEED_SDMC(obj);
+ int64_t value = s->ram_size;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void aspeed_sdmc_set_ram_size(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ int i;
+ char *sz;
+ int64_t value;
+ AspeedSDMCState *s = ASPEED_SDMC(obj);
+ AspeedSDMCClass *asc = ASPEED_SDMC_GET_CLASS(s);
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ for (i = 0; asc->valid_ram_sizes[i]; i++) {
+ if (value == asc->valid_ram_sizes[i]) {
+ s->ram_size = value;
+ return;
+ }
+ }
+
+ sz = size_to_str(value);
+ error_setg(errp, "Invalid RAM size %s", sz);
+ g_free(sz);
+}
+
+static void aspeed_sdmc_initfn(Object *obj)
+{
+ object_property_add(obj, "ram-size", "int",
+ aspeed_sdmc_get_ram_size, aspeed_sdmc_set_ram_size,
+ NULL, NULL);
+}
+
+static void aspeed_sdmc_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ AspeedSDMCState *s = ASPEED_SDMC(dev);
+ AspeedSDMCClass *asc = ASPEED_SDMC_GET_CLASS(s);
+
+ assert(asc->max_ram_size < 4 * GiB); /* 32-bit address bus */
+ s->max_ram_size = asc->max_ram_size;
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_sdmc_ops, s,
+ TYPE_ASPEED_SDMC, 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription vmstate_aspeed_sdmc = {
+ .name = "aspeed.sdmc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AspeedSDMCState, ASPEED_SDMC_NR_REGS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property aspeed_sdmc_properties[] = {
+ DEFINE_PROP_UINT64("max-ram-size", AspeedSDMCState, max_ram_size, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void aspeed_sdmc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->realize = aspeed_sdmc_realize;
+ dc->reset = aspeed_sdmc_reset;
+ dc->desc = "ASPEED SDRAM Memory Controller";
+ dc->vmsd = &vmstate_aspeed_sdmc;
+ device_class_set_props(dc, aspeed_sdmc_properties);
+}
+
+static const TypeInfo aspeed_sdmc_info = {
+ .name = TYPE_ASPEED_SDMC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AspeedSDMCState),
+ .instance_init = aspeed_sdmc_initfn,
+ .class_init = aspeed_sdmc_class_init,
+ .class_size = sizeof(AspeedSDMCClass),
+ .abstract = true,
+};
+
+static int aspeed_sdmc_get_ram_bits(AspeedSDMCState *s)
+{
+ AspeedSDMCClass *asc = ASPEED_SDMC_GET_CLASS(s);
+ int i;
+
+ /*
+ * The bitfield value encoding the RAM size is the index of the
+ * possible RAM size array
+ */
+ for (i = 0; asc->valid_ram_sizes[i]; i++) {
+ if (s->ram_size == asc->valid_ram_sizes[i]) {
+ return i;
+ }
+ }
+
+ /*
+ * Invalid RAM sizes should have been excluded when setting the
+ * SoC RAM size.
+ */
+ g_assert_not_reached();
+}
+
+static uint32_t aspeed_2400_sdmc_compute_conf(AspeedSDMCState *s, uint32_t data)
+{
+ uint32_t fixed_conf = ASPEED_SDMC_VGA_COMPAT |
+ ASPEED_SDMC_DRAM_SIZE(aspeed_sdmc_get_ram_bits(s));
+
+ /* Make sure readonly bits are kept */
+ data &= ~ASPEED_SDMC_READONLY_MASK;
+
+ return data | fixed_conf;
+}
+
+static void aspeed_2400_sdmc_write(AspeedSDMCState *s, uint32_t reg,
+ uint32_t data)
+{
+ if (reg == R_PROT) {
+ s->regs[reg] = (data == PROT_KEY_UNLOCK) ? PROT_UNLOCKED : PROT_SOFTLOCKED;
+ return;
+ }
+
+ if (!s->regs[R_PROT]) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: SDMC is locked!\n", __func__);
+ return;
+ }
+
+ switch (reg) {
+ case R_CONF:
+ data = aspeed_2400_sdmc_compute_conf(s, data);
+ break;
+ default:
+ break;
+ }
+
+ s->regs[reg] = data;
+}
+
+static const uint64_t
+aspeed_2400_ram_sizes[] = { 64 * MiB, 128 * MiB, 256 * MiB, 512 * MiB, 0};
+
+static void aspeed_2400_sdmc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedSDMCClass *asc = ASPEED_SDMC_CLASS(klass);
+
+ dc->desc = "ASPEED 2400 SDRAM Memory Controller";
+ asc->max_ram_size = 512 * MiB;
+ asc->compute_conf = aspeed_2400_sdmc_compute_conf;
+ asc->write = aspeed_2400_sdmc_write;
+ asc->valid_ram_sizes = aspeed_2400_ram_sizes;
+}
+
+static const TypeInfo aspeed_2400_sdmc_info = {
+ .name = TYPE_ASPEED_2400_SDMC,
+ .parent = TYPE_ASPEED_SDMC,
+ .class_init = aspeed_2400_sdmc_class_init,
+};
+
+static uint32_t aspeed_2500_sdmc_compute_conf(AspeedSDMCState *s, uint32_t data)
+{
+ uint32_t fixed_conf = ASPEED_SDMC_HW_VERSION(1) |
+ ASPEED_SDMC_VGA_APERTURE(ASPEED_SDMC_VGA_64MB) |
+ ASPEED_SDMC_CACHE_INITIAL_DONE |
+ ASPEED_SDMC_DRAM_SIZE(aspeed_sdmc_get_ram_bits(s));
+
+ /* Make sure readonly bits are kept */
+ data &= ~ASPEED_SDMC_AST2500_READONLY_MASK;
+
+ return data | fixed_conf;
+}
+
+static void aspeed_2500_sdmc_write(AspeedSDMCState *s, uint32_t reg,
+ uint32_t data)
+{
+ if (reg == R_PROT) {
+ s->regs[reg] = (data == PROT_KEY_UNLOCK) ? PROT_UNLOCKED : PROT_SOFTLOCKED;
+ return;
+ }
+
+ if (!s->regs[R_PROT]) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: SDMC is locked!\n", __func__);
+ return;
+ }
+
+ switch (reg) {
+ case R_CONF:
+ data = aspeed_2500_sdmc_compute_conf(s, data);
+ break;
+ case R_STATUS1:
+ /* Will never return 'busy' */
+ data &= ~PHY_BUSY_STATE;
+ break;
+ case R_ECC_TEST_CTRL:
+ /* Always done, always happy */
+ data |= ECC_TEST_FINISHED;
+ data &= ~ECC_TEST_FAIL;
+ break;
+ default:
+ break;
+ }
+
+ s->regs[reg] = data;
+}
+
+static const uint64_t
+aspeed_2500_ram_sizes[] = { 128 * MiB, 256 * MiB, 512 * MiB, 1024 * MiB, 0};
+
+static void aspeed_2500_sdmc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedSDMCClass *asc = ASPEED_SDMC_CLASS(klass);
+
+ dc->desc = "ASPEED 2500 SDRAM Memory Controller";
+ asc->max_ram_size = 1 * GiB;
+ asc->compute_conf = aspeed_2500_sdmc_compute_conf;
+ asc->write = aspeed_2500_sdmc_write;
+ asc->valid_ram_sizes = aspeed_2500_ram_sizes;
+}
+
+static const TypeInfo aspeed_2500_sdmc_info = {
+ .name = TYPE_ASPEED_2500_SDMC,
+ .parent = TYPE_ASPEED_SDMC,
+ .class_init = aspeed_2500_sdmc_class_init,
+};
+
+static uint32_t aspeed_2600_sdmc_compute_conf(AspeedSDMCState *s, uint32_t data)
+{
+ uint32_t fixed_conf = ASPEED_SDMC_HW_VERSION(3) |
+ ASPEED_SDMC_VGA_APERTURE(ASPEED_SDMC_VGA_64MB) |
+ ASPEED_SDMC_DRAM_SIZE(aspeed_sdmc_get_ram_bits(s));
+
+ /* Make sure readonly bits are kept (use ast2500 mask) */
+ data &= ~ASPEED_SDMC_AST2500_READONLY_MASK;
+
+ return data | fixed_conf;
+}
+
+static void aspeed_2600_sdmc_write(AspeedSDMCState *s, uint32_t reg,
+ uint32_t data)
+{
+ /* Unprotected registers */
+ switch (reg) {
+ case R_ISR:
+ case R_MCR6C:
+ case R_TEST_START_LEN:
+ case R_TEST_FAIL_DQ:
+ case R_TEST_INIT_VAL:
+ case R_DRAM_SW:
+ case R_DRAM_TIME:
+ case R_ECC_ERR_INJECT:
+ s->regs[reg] = data;
+ return;
+ }
+
+ if (s->regs[R_PROT] == PROT_HARDLOCKED) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: SDMC is locked until system reset!\n",
+ __func__);
+ return;
+ }
+
+ if (reg != R_PROT && s->regs[R_PROT] == PROT_SOFTLOCKED) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: SDMC is locked! (write to MCR%02x blocked)\n",
+ __func__, reg * 4);
+ return;
+ }
+
+ switch (reg) {
+ case R_PROT:
+ if (data == PROT_KEY_UNLOCK) {
+ data = PROT_UNLOCKED;
+ } else if (data == PROT_KEY_HARDLOCK) {
+ data = PROT_HARDLOCKED;
+ } else {
+ data = PROT_SOFTLOCKED;
+ }
+ break;
+ case R_CONF:
+ data = aspeed_2600_sdmc_compute_conf(s, data);
+ break;
+ case R_STATUS1:
+ /* Will never return 'busy'. 'lock status' is always set */
+ data &= ~PHY_BUSY_STATE;
+ data |= PHY_PLL_LOCK_STATUS;
+ break;
+ case R_ECC_TEST_CTRL:
+ /* Always done, always happy */
+ data |= ECC_TEST_FINISHED;
+ data &= ~ECC_TEST_FAIL;
+ break;
+ default:
+ break;
+ }
+
+ s->regs[reg] = data;
+}
+
+static const uint64_t
+aspeed_2600_ram_sizes[] = { 256 * MiB, 512 * MiB, 1024 * MiB, 2048 * MiB, 0};
+
+static void aspeed_2600_sdmc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedSDMCClass *asc = ASPEED_SDMC_CLASS(klass);
+
+ dc->desc = "ASPEED 2600 SDRAM Memory Controller";
+ asc->max_ram_size = 2 * GiB;
+ asc->compute_conf = aspeed_2600_sdmc_compute_conf;
+ asc->write = aspeed_2600_sdmc_write;
+ asc->valid_ram_sizes = aspeed_2600_ram_sizes;
+}
+
+static const TypeInfo aspeed_2600_sdmc_info = {
+ .name = TYPE_ASPEED_2600_SDMC,
+ .parent = TYPE_ASPEED_SDMC,
+ .class_init = aspeed_2600_sdmc_class_init,
+};
+
+static void aspeed_sdmc_register_types(void)
+{
+ type_register_static(&aspeed_sdmc_info);
+ type_register_static(&aspeed_2400_sdmc_info);
+ type_register_static(&aspeed_2500_sdmc_info);
+ type_register_static(&aspeed_2600_sdmc_info);
+}
+
+type_init(aspeed_sdmc_register_types);
diff --git a/hw/misc/aspeed_xdma.c b/hw/misc/aspeed_xdma.c
new file mode 100644
index 000000000..1c21577c9
--- /dev/null
+++ b/hw/misc/aspeed_xdma.c
@@ -0,0 +1,245 @@
+/*
+ * ASPEED XDMA Controller
+ * Eddie James <eajames@linux.ibm.com>
+ *
+ * Copyright (C) 2019 IBM Corp
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/error-report.h"
+#include "hw/irq.h"
+#include "hw/misc/aspeed_xdma.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+
+#include "trace.h"
+
+#define XDMA_BMC_CMDQ_ADDR 0x10
+#define XDMA_BMC_CMDQ_ENDP 0x14
+#define XDMA_BMC_CMDQ_WRP 0x18
+#define XDMA_BMC_CMDQ_W_MASK 0x0003FFFF
+#define XDMA_BMC_CMDQ_RDP 0x1C
+#define XDMA_BMC_CMDQ_RDP_MAGIC 0xEE882266
+#define XDMA_IRQ_ENG_CTRL 0x20
+#define XDMA_IRQ_ENG_CTRL_US_COMP BIT(4)
+#define XDMA_IRQ_ENG_CTRL_DS_COMP BIT(5)
+#define XDMA_IRQ_ENG_CTRL_W_MASK 0xBFEFF07F
+#define XDMA_IRQ_ENG_STAT 0x24
+#define XDMA_IRQ_ENG_STAT_US_COMP BIT(4)
+#define XDMA_IRQ_ENG_STAT_DS_COMP BIT(5)
+#define XDMA_IRQ_ENG_STAT_RESET 0xF8000000
+
+#define XDMA_AST2600_BMC_CMDQ_ADDR 0x14
+#define XDMA_AST2600_BMC_CMDQ_ENDP 0x18
+#define XDMA_AST2600_BMC_CMDQ_WRP 0x1c
+#define XDMA_AST2600_BMC_CMDQ_RDP 0x20
+#define XDMA_AST2600_IRQ_CTRL 0x38
+#define XDMA_AST2600_IRQ_CTRL_US_COMP BIT(16)
+#define XDMA_AST2600_IRQ_CTRL_DS_COMP BIT(17)
+#define XDMA_AST2600_IRQ_CTRL_W_MASK 0x017003FF
+#define XDMA_AST2600_IRQ_STATUS 0x3c
+#define XDMA_AST2600_IRQ_STATUS_US_COMP BIT(16)
+#define XDMA_AST2600_IRQ_STATUS_DS_COMP BIT(17)
+
+#define XDMA_MEM_SIZE 0x1000
+
+#define TO_REG(addr) ((addr) / sizeof(uint32_t))
+
+static uint64_t aspeed_xdma_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ uint32_t val = 0;
+ AspeedXDMAState *xdma = opaque;
+
+ if (addr < ASPEED_XDMA_REG_SIZE) {
+ val = xdma->regs[TO_REG(addr)];
+ }
+
+ return (uint64_t)val;
+}
+
+static void aspeed_xdma_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size)
+{
+ unsigned int idx;
+ uint32_t val32 = (uint32_t)val;
+ AspeedXDMAState *xdma = opaque;
+ AspeedXDMAClass *axc = ASPEED_XDMA_GET_CLASS(xdma);
+
+ if (addr >= ASPEED_XDMA_REG_SIZE) {
+ return;
+ }
+
+ if (addr == axc->cmdq_endp) {
+ xdma->regs[TO_REG(addr)] = val32 & XDMA_BMC_CMDQ_W_MASK;
+ } else if (addr == axc->cmdq_wrp) {
+ idx = TO_REG(addr);
+ xdma->regs[idx] = val32 & XDMA_BMC_CMDQ_W_MASK;
+ xdma->regs[TO_REG(axc->cmdq_rdp)] = xdma->regs[idx];
+
+ trace_aspeed_xdma_write(addr, val);
+
+ if (xdma->bmc_cmdq_readp_set) {
+ xdma->bmc_cmdq_readp_set = 0;
+ } else {
+ xdma->regs[TO_REG(axc->intr_status)] |= axc->intr_complete;
+
+ if (xdma->regs[TO_REG(axc->intr_ctrl)] & axc->intr_complete) {
+ qemu_irq_raise(xdma->irq);
+ }
+ }
+ } else if (addr == axc->cmdq_rdp) {
+ trace_aspeed_xdma_write(addr, val);
+
+ if (val32 == XDMA_BMC_CMDQ_RDP_MAGIC) {
+ xdma->bmc_cmdq_readp_set = 1;
+ }
+ } else if (addr == axc->intr_ctrl) {
+ xdma->regs[TO_REG(addr)] = val32 & axc->intr_ctrl_mask;
+ } else if (addr == axc->intr_status) {
+ trace_aspeed_xdma_write(addr, val);
+
+ idx = TO_REG(addr);
+ if (val32 & axc->intr_complete) {
+ xdma->regs[idx] &= ~axc->intr_complete;
+ qemu_irq_lower(xdma->irq);
+ }
+ } else {
+ xdma->regs[TO_REG(addr)] = val32;
+ }
+}
+
+static const MemoryRegionOps aspeed_xdma_ops = {
+ .read = aspeed_xdma_read,
+ .write = aspeed_xdma_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static void aspeed_xdma_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ AspeedXDMAState *xdma = ASPEED_XDMA(dev);
+
+ sysbus_init_irq(sbd, &xdma->irq);
+ memory_region_init_io(&xdma->iomem, OBJECT(xdma), &aspeed_xdma_ops, xdma,
+ TYPE_ASPEED_XDMA, XDMA_MEM_SIZE);
+ sysbus_init_mmio(sbd, &xdma->iomem);
+}
+
+static void aspeed_xdma_reset(DeviceState *dev)
+{
+ AspeedXDMAState *xdma = ASPEED_XDMA(dev);
+ AspeedXDMAClass *axc = ASPEED_XDMA_GET_CLASS(xdma);
+
+ xdma->bmc_cmdq_readp_set = 0;
+ memset(xdma->regs, 0, ASPEED_XDMA_REG_SIZE);
+ xdma->regs[TO_REG(axc->intr_status)] = XDMA_IRQ_ENG_STAT_RESET;
+
+ qemu_irq_lower(xdma->irq);
+}
+
+static const VMStateDescription aspeed_xdma_vmstate = {
+ .name = TYPE_ASPEED_XDMA,
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AspeedXDMAState, ASPEED_XDMA_NUM_REGS),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void aspeed_2600_xdma_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedXDMAClass *axc = ASPEED_XDMA_CLASS(klass);
+
+ dc->desc = "ASPEED 2600 XDMA Controller";
+
+ axc->cmdq_endp = XDMA_AST2600_BMC_CMDQ_ENDP;
+ axc->cmdq_wrp = XDMA_AST2600_BMC_CMDQ_WRP;
+ axc->cmdq_rdp = XDMA_AST2600_BMC_CMDQ_RDP;
+ axc->intr_ctrl = XDMA_AST2600_IRQ_CTRL;
+ axc->intr_ctrl_mask = XDMA_AST2600_IRQ_CTRL_W_MASK;
+ axc->intr_status = XDMA_AST2600_IRQ_STATUS;
+ axc->intr_complete = XDMA_AST2600_IRQ_STATUS_US_COMP |
+ XDMA_AST2600_IRQ_STATUS_DS_COMP;
+}
+
+static const TypeInfo aspeed_2600_xdma_info = {
+ .name = TYPE_ASPEED_2600_XDMA,
+ .parent = TYPE_ASPEED_XDMA,
+ .class_init = aspeed_2600_xdma_class_init,
+};
+
+static void aspeed_2500_xdma_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedXDMAClass *axc = ASPEED_XDMA_CLASS(klass);
+
+ dc->desc = "ASPEED 2500 XDMA Controller";
+
+ axc->cmdq_endp = XDMA_BMC_CMDQ_ENDP;
+ axc->cmdq_wrp = XDMA_BMC_CMDQ_WRP;
+ axc->cmdq_rdp = XDMA_BMC_CMDQ_RDP;
+ axc->intr_ctrl = XDMA_IRQ_ENG_CTRL;
+ axc->intr_ctrl_mask = XDMA_IRQ_ENG_CTRL_W_MASK;
+ axc->intr_status = XDMA_IRQ_ENG_STAT;
+ axc->intr_complete = XDMA_IRQ_ENG_STAT_US_COMP | XDMA_IRQ_ENG_STAT_DS_COMP;
+};
+
+static const TypeInfo aspeed_2500_xdma_info = {
+ .name = TYPE_ASPEED_2500_XDMA,
+ .parent = TYPE_ASPEED_XDMA,
+ .class_init = aspeed_2500_xdma_class_init,
+};
+
+static void aspeed_2400_xdma_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ AspeedXDMAClass *axc = ASPEED_XDMA_CLASS(klass);
+
+ dc->desc = "ASPEED 2400 XDMA Controller";
+
+ axc->cmdq_endp = XDMA_BMC_CMDQ_ENDP;
+ axc->cmdq_wrp = XDMA_BMC_CMDQ_WRP;
+ axc->cmdq_rdp = XDMA_BMC_CMDQ_RDP;
+ axc->intr_ctrl = XDMA_IRQ_ENG_CTRL;
+ axc->intr_ctrl_mask = XDMA_IRQ_ENG_CTRL_W_MASK;
+ axc->intr_status = XDMA_IRQ_ENG_STAT;
+ axc->intr_complete = XDMA_IRQ_ENG_STAT_US_COMP | XDMA_IRQ_ENG_STAT_DS_COMP;
+};
+
+static const TypeInfo aspeed_2400_xdma_info = {
+ .name = TYPE_ASPEED_2400_XDMA,
+ .parent = TYPE_ASPEED_XDMA,
+ .class_init = aspeed_2400_xdma_class_init,
+};
+
+static void aspeed_xdma_class_init(ObjectClass *classp, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(classp);
+
+ dc->realize = aspeed_xdma_realize;
+ dc->reset = aspeed_xdma_reset;
+ dc->vmsd = &aspeed_xdma_vmstate;
+}
+
+static const TypeInfo aspeed_xdma_info = {
+ .name = TYPE_ASPEED_XDMA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AspeedXDMAState),
+ .class_init = aspeed_xdma_class_init,
+ .class_size = sizeof(AspeedXDMAClass),
+ .abstract = true,
+};
+
+static void aspeed_xdma_register_type(void)
+{
+ type_register_static(&aspeed_xdma_info);
+ type_register_static(&aspeed_2400_xdma_info);
+ type_register_static(&aspeed_2500_xdma_info);
+ type_register_static(&aspeed_2600_xdma_info);
+}
+type_init(aspeed_xdma_register_type);
diff --git a/hw/misc/auxbus.c b/hw/misc/auxbus.c
new file mode 100644
index 000000000..8a8012f5f
--- /dev/null
+++ b/hw/misc/auxbus.c
@@ -0,0 +1,337 @@
+/*
+ * auxbus.c
+ *
+ * Copyright 2015 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.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/>.
+ *
+ */
+
+/*
+ * This is an implementation of the AUX bus for VESA Display Port v1.1a.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/misc/auxbus.h"
+#include "hw/i2c/i2c.h"
+#include "monitor/monitor.h"
+#include "qapi/error.h"
+
+#ifndef DEBUG_AUX
+#define DEBUG_AUX 0
+#endif
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_AUX) { \
+ qemu_log("aux: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0)
+
+
+static void aux_slave_dev_print(Monitor *mon, DeviceState *dev, int indent);
+static inline I2CBus *aux_bridge_get_i2c_bus(AUXTOI2CState *bridge);
+
+/* aux-bus implementation (internal not public) */
+static void aux_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+
+ /* AUXSlave has an MMIO so we need to change the way we print information
+ * in monitor.
+ */
+ k->print_dev = aux_slave_dev_print;
+}
+
+AUXBus *aux_bus_init(DeviceState *parent, const char *name)
+{
+ AUXBus *bus;
+ Object *auxtoi2c;
+
+ bus = AUX_BUS(qbus_new(TYPE_AUX_BUS, parent, name));
+ auxtoi2c = object_new_with_props(TYPE_AUXTOI2C, OBJECT(bus), "i2c",
+ &error_abort, NULL);
+
+ bus->bridge = AUXTOI2C(auxtoi2c);
+
+ /* Memory related. */
+ bus->aux_io = g_malloc(sizeof(*bus->aux_io));
+ memory_region_init(bus->aux_io, OBJECT(bus), "aux-io", 1 * MiB);
+ address_space_init(&bus->aux_addr_space, bus->aux_io, "aux-io");
+ return bus;
+}
+
+void aux_bus_realize(AUXBus *bus)
+{
+ qdev_realize(DEVICE(bus->bridge), BUS(bus), &error_fatal);
+}
+
+void aux_map_slave(AUXSlave *aux_dev, hwaddr addr)
+{
+ DeviceState *dev = DEVICE(aux_dev);
+ AUXBus *bus = AUX_BUS(qdev_get_parent_bus(dev));
+ memory_region_add_subregion(bus->aux_io, addr, aux_dev->mmio);
+}
+
+static bool aux_bus_is_bridge(AUXBus *bus, DeviceState *dev)
+{
+ return (dev == DEVICE(bus->bridge));
+}
+
+I2CBus *aux_get_i2c_bus(AUXBus *bus)
+{
+ return aux_bridge_get_i2c_bus(bus->bridge);
+}
+
+AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
+ uint8_t len, uint8_t *data)
+{
+ AUXReply ret = AUX_NACK;
+ I2CBus *i2c_bus = aux_get_i2c_bus(bus);
+ size_t i;
+
+ DPRINTF("request at address 0x%" PRIX32 ", command %u, len %u\n", address,
+ cmd, len);
+
+ switch (cmd) {
+ /*
+ * Forward the request on the AUX bus..
+ */
+ case WRITE_AUX:
+ case READ_AUX:
+ for (i = 0; i < len; i++) {
+ if (!address_space_rw(&bus->aux_addr_space, address++,
+ MEMTXATTRS_UNSPECIFIED, data++, 1,
+ cmd == WRITE_AUX)) {
+ ret = AUX_I2C_ACK;
+ } else {
+ ret = AUX_NACK;
+ break;
+ }
+ }
+ break;
+ /*
+ * Classic I2C transactions..
+ */
+ case READ_I2C:
+ if (i2c_bus_busy(i2c_bus)) {
+ i2c_end_transfer(i2c_bus);
+ }
+
+ if (i2c_start_recv(i2c_bus, address)) {
+ ret = AUX_I2C_NACK;
+ break;
+ }
+
+ ret = AUX_I2C_ACK;
+ for (i = 0; i < len; i++) {
+ data[i] = i2c_recv(i2c_bus);
+ }
+ i2c_end_transfer(i2c_bus);
+ break;
+ case WRITE_I2C:
+ if (i2c_bus_busy(i2c_bus)) {
+ i2c_end_transfer(i2c_bus);
+ }
+
+ if (i2c_start_send(i2c_bus, address)) {
+ ret = AUX_I2C_NACK;
+ break;
+ }
+
+ ret = AUX_I2C_ACK;
+ for (i = 0; i < len; i++) {
+ if (i2c_send(i2c_bus, data[i]) < 0) {
+ ret = AUX_I2C_NACK;
+ break;
+ }
+ }
+ i2c_end_transfer(i2c_bus);
+ break;
+ /*
+ * I2C MOT transactions.
+ *
+ * Here we send a start when:
+ * - We didn't start transaction yet.
+ * - We had a READ and we do a WRITE.
+ * - We changed the address.
+ */
+ case WRITE_I2C_MOT:
+ ret = AUX_I2C_NACK;
+ if (!i2c_bus_busy(i2c_bus)) {
+ /*
+ * No transactions started..
+ */
+ if (i2c_start_send(i2c_bus, address)) {
+ break;
+ }
+ } else if ((address != bus->last_i2c_address) ||
+ (bus->last_transaction != cmd)) {
+ /*
+ * Transaction started but we need to restart..
+ */
+ i2c_end_transfer(i2c_bus);
+ if (i2c_start_send(i2c_bus, address)) {
+ break;
+ }
+ }
+
+ bus->last_transaction = cmd;
+ bus->last_i2c_address = address;
+ ret = AUX_I2C_ACK;
+ for (i = 0; i < len; i++) {
+ if (i2c_send(i2c_bus, data[i]) < 0) {
+ i2c_end_transfer(i2c_bus);
+ ret = AUX_I2C_NACK;
+ break;
+ }
+ }
+ break;
+ case READ_I2C_MOT:
+ ret = AUX_I2C_NACK;
+ if (!i2c_bus_busy(i2c_bus)) {
+ /*
+ * No transactions started..
+ */
+ if (i2c_start_recv(i2c_bus, address)) {
+ break;
+ }
+ } else if ((address != bus->last_i2c_address) ||
+ (bus->last_transaction != cmd)) {
+ /*
+ * Transaction started but we need to restart..
+ */
+ i2c_end_transfer(i2c_bus);
+ if (i2c_start_recv(i2c_bus, address)) {
+ break;
+ }
+ }
+
+ bus->last_transaction = cmd;
+ bus->last_i2c_address = address;
+ for (i = 0; i < len; i++) {
+ data[i] = i2c_recv(i2c_bus);
+ }
+ ret = AUX_I2C_ACK;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "AUX cmd=%u not implemented\n", cmd);
+ return AUX_NACK;
+ }
+
+ DPRINTF("reply: %u\n", ret);
+ return ret;
+}
+
+static const TypeInfo aux_bus_info = {
+ .name = TYPE_AUX_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(AUXBus),
+ .class_init = aux_bus_class_init
+};
+
+/* aux-i2c implementation (internal not public) */
+struct AUXTOI2CState {
+ /*< private >*/
+ DeviceState parent_obj;
+
+ /*< public >*/
+ I2CBus *i2c_bus;
+};
+
+static void aux_bridge_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ /* This device is private and is created only once for each
+ * aux-bus in aux_bus_init(..). So don't allow the user to add one.
+ */
+ dc->user_creatable = false;
+}
+
+static void aux_bridge_init(Object *obj)
+{
+ AUXTOI2CState *s = AUXTOI2C(obj);
+
+ s->i2c_bus = i2c_init_bus(DEVICE(obj), "aux-i2c");
+}
+
+static inline I2CBus *aux_bridge_get_i2c_bus(AUXTOI2CState *bridge)
+{
+ return bridge->i2c_bus;
+}
+
+static const TypeInfo aux_to_i2c_type_info = {
+ .name = TYPE_AUXTOI2C,
+ .parent = TYPE_AUX_SLAVE,
+ .class_init = aux_bridge_class_init,
+ .instance_size = sizeof(AUXTOI2CState),
+ .instance_init = aux_bridge_init
+};
+
+/* aux-slave implementation */
+static void aux_slave_dev_print(Monitor *mon, DeviceState *dev, int indent)
+{
+ AUXBus *bus = AUX_BUS(qdev_get_parent_bus(dev));
+ AUXSlave *s;
+
+ /* Don't print anything if the device is I2C "bridge". */
+ if (aux_bus_is_bridge(bus, dev)) {
+ return;
+ }
+
+ s = AUX_SLAVE(dev);
+
+ monitor_printf(mon, "%*smemory " TARGET_FMT_plx "/" TARGET_FMT_plx "\n",
+ indent, "",
+ object_property_get_uint(OBJECT(s->mmio), "addr", NULL),
+ memory_region_size(s->mmio));
+}
+
+void aux_init_mmio(AUXSlave *aux_slave, MemoryRegion *mmio)
+{
+ assert(!aux_slave->mmio);
+ aux_slave->mmio = mmio;
+}
+
+static void aux_slave_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_MISC, k->categories);
+ k->bus_type = TYPE_AUX_BUS;
+}
+
+static const TypeInfo aux_slave_type_info = {
+ .name = TYPE_AUX_SLAVE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(AUXSlave),
+ .abstract = true,
+ .class_init = aux_slave_class_init,
+};
+
+static void aux_register_types(void)
+{
+ type_register_static(&aux_bus_info);
+ type_register_static(&aux_slave_type_info);
+ type_register_static(&aux_to_i2c_type_info);
+}
+
+type_init(aux_register_types)
diff --git a/hw/misc/avr_power.c b/hw/misc/avr_power.c
new file mode 100644
index 000000000..a5412f2cf
--- /dev/null
+++ b/hw/misc/avr_power.c
@@ -0,0 +1,113 @@
+/*
+ * AVR Power Reduction Management
+ *
+ * Copyright (c) 2019-2020 Michael Rolnik
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/avr_power.h"
+#include "qemu/log.h"
+#include "hw/qdev-properties.h"
+#include "hw/irq.h"
+#include "trace.h"
+
+static void avr_mask_reset(DeviceState *dev)
+{
+ AVRMaskState *s = AVR_MASK(dev);
+
+ s->val = 0x00;
+
+ for (int i = 0; i < 8; i++) {
+ qemu_set_irq(s->irq[i], 0);
+ }
+}
+
+static uint64_t avr_mask_read(void *opaque, hwaddr offset, unsigned size)
+{
+ assert(size == 1);
+ assert(offset == 0);
+ AVRMaskState *s = opaque;
+
+ trace_avr_power_read(s->val);
+
+ return (uint64_t)s->val;
+}
+
+static void avr_mask_write(void *opaque, hwaddr offset,
+ uint64_t val64, unsigned size)
+{
+ assert(size == 1);
+ assert(offset == 0);
+ AVRMaskState *s = opaque;
+ uint8_t val8 = val64;
+
+ trace_avr_power_write(val8);
+ s->val = val8;
+ for (int i = 0; i < 8; i++) {
+ qemu_set_irq(s->irq[i], (val8 & (1 << i)) != 0);
+ }
+}
+
+static const MemoryRegionOps avr_mask_ops = {
+ .read = avr_mask_read,
+ .write = avr_mask_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .max_access_size = 1,
+ },
+};
+
+static void avr_mask_init(Object *dev)
+{
+ AVRMaskState *s = AVR_MASK(dev);
+ SysBusDevice *busdev = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&s->iomem, dev, &avr_mask_ops, s, TYPE_AVR_MASK,
+ 0x01);
+ sysbus_init_mmio(busdev, &s->iomem);
+
+ for (int i = 0; i < 8; i++) {
+ sysbus_init_irq(busdev, &s->irq[i]);
+ }
+ s->val = 0x00;
+}
+
+static void avr_mask_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = avr_mask_reset;
+}
+
+static const TypeInfo avr_mask_info = {
+ .name = TYPE_AVR_MASK,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AVRMaskState),
+ .class_init = avr_mask_class_init,
+ .instance_init = avr_mask_init,
+};
+
+static void avr_mask_register_types(void)
+{
+ type_register_static(&avr_mask_info);
+}
+
+type_init(avr_mask_register_types)
diff --git a/hw/misc/bcm2835_cprman.c b/hw/misc/bcm2835_cprman.c
new file mode 100644
index 000000000..75e6c574d
--- /dev/null
+++ b/hw/misc/bcm2835_cprman.c
@@ -0,0 +1,813 @@
+/*
+ * BCM2835 CPRMAN clock manager
+ *
+ * Copyright (c) 2020 Luc Michel <luc@lmichel.fr>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * This peripheral is roughly divided into 3 main parts:
+ * - the PLLs
+ * - the PLL channels
+ * - the clock muxes
+ *
+ * A main oscillator (xosc) feeds all the PLLs. Each PLLs has one or more
+ * channels. Those channel are then connected to the clock muxes. Each mux has
+ * multiples sources (usually the xosc, some of the PLL channels and some "test
+ * debug" clocks). A mux is configured to select a given source through its
+ * control register. Each mux has one output clock that also goes out of the
+ * CPRMAN. This output clock usually connects to another peripheral in the SoC
+ * (so a given mux is dedicated to a peripheral).
+ *
+ * At each level (PLL, channel and mux), the clock can be altered through
+ * dividers (and multipliers in case of the PLLs), and can be disabled (in this
+ * case, the next levels see no clock).
+ *
+ * This can be sum-up as follows (this is an example and not the actual BCM2835
+ * clock tree):
+ *
+ * /-->[PLL]-|->[PLL channel]--... [mux]--> to peripherals
+ * | |->[PLL channel] muxes takes [mux]
+ * | \->[PLL channel] inputs from [mux]
+ * | some channels [mux]
+ * [xosc]---|-->[PLL]-|->[PLL channel] and other srcs [mux]
+ * | \->[PLL channel] ...-->[mux]
+ * | [mux]
+ * \-->[PLL]--->[PLL channel] [mux]
+ *
+ * The page at https://elinux.org/The_Undocumented_Pi gives the actual clock
+ * tree configuration.
+ *
+ * The CPRMAN exposes clock outputs with the name of the clock mux suffixed
+ * with "-out" (e.g. "uart-out", "h264-out", ...).
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "migration/vmstate.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/bcm2835_cprman.h"
+#include "hw/misc/bcm2835_cprman_internals.h"
+#include "trace.h"
+
+/* PLL */
+
+static void pll_reset(DeviceState *dev)
+{
+ CprmanPllState *s = CPRMAN_PLL(dev);
+ const PLLResetInfo *info = &PLL_RESET_INFO[s->id];
+
+ *s->reg_cm = info->cm;
+ *s->reg_a2w_ctrl = info->a2w_ctrl;
+ memcpy(s->reg_a2w_ana, info->a2w_ana, sizeof(info->a2w_ana));
+ *s->reg_a2w_frac = info->a2w_frac;
+}
+
+static bool pll_is_locked(const CprmanPllState *pll)
+{
+ return !FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, PWRDN)
+ && !FIELD_EX32(*pll->reg_cm, CM_PLLx, ANARST);
+}
+
+static void pll_update(CprmanPllState *pll)
+{
+ uint64_t freq, ndiv, fdiv, pdiv;
+
+ if (!pll_is_locked(pll)) {
+ clock_update(pll->out, 0);
+ return;
+ }
+
+ pdiv = FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, PDIV);
+
+ if (!pdiv) {
+ clock_update(pll->out, 0);
+ return;
+ }
+
+ ndiv = FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, NDIV);
+ fdiv = FIELD_EX32(*pll->reg_a2w_frac, A2W_PLLx_FRAC, FRAC);
+
+ if (pll->reg_a2w_ana[1] & pll->prediv_mask) {
+ /* The prescaler doubles the parent frequency */
+ ndiv *= 2;
+ fdiv *= 2;
+ }
+
+ /*
+ * We have a multiplier with an integer part (ndiv) and a fractional part
+ * (fdiv), and a divider (pdiv).
+ */
+ freq = clock_get_hz(pll->xosc_in) *
+ ((ndiv << R_A2W_PLLx_FRAC_FRAC_LENGTH) + fdiv);
+ freq /= pdiv;
+ freq >>= R_A2W_PLLx_FRAC_FRAC_LENGTH;
+
+ clock_update_hz(pll->out, freq);
+}
+
+static void pll_xosc_update(void *opaque, ClockEvent event)
+{
+ pll_update(CPRMAN_PLL(opaque));
+}
+
+static void pll_init(Object *obj)
+{
+ CprmanPllState *s = CPRMAN_PLL(obj);
+
+ s->xosc_in = qdev_init_clock_in(DEVICE(s), "xosc-in", pll_xosc_update,
+ s, ClockUpdate);
+ s->out = qdev_init_clock_out(DEVICE(s), "out");
+}
+
+static const VMStateDescription pll_vmstate = {
+ .name = TYPE_CPRMAN_PLL,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_CLOCK(xosc_in, CprmanPllState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pll_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = pll_reset;
+ dc->vmsd = &pll_vmstate;
+}
+
+static const TypeInfo cprman_pll_info = {
+ .name = TYPE_CPRMAN_PLL,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(CprmanPllState),
+ .class_init = pll_class_init,
+ .instance_init = pll_init,
+};
+
+
+/* PLL channel */
+
+static void pll_channel_reset(DeviceState *dev)
+{
+ CprmanPllChannelState *s = CPRMAN_PLL_CHANNEL(dev);
+ const PLLChannelResetInfo *info = &PLL_CHANNEL_RESET_INFO[s->id];
+
+ *s->reg_a2w_ctrl = info->a2w_ctrl;
+}
+
+static bool pll_channel_is_enabled(CprmanPllChannelState *channel)
+{
+ /*
+ * XXX I'm not sure of the purpose of the LOAD field. The Linux driver does
+ * not set it when enabling the channel, but does clear it when disabling
+ * it.
+ */
+ return !FIELD_EX32(*channel->reg_a2w_ctrl, A2W_PLLx_CHANNELy, DISABLE)
+ && !(*channel->reg_cm & channel->hold_mask);
+}
+
+static void pll_channel_update(CprmanPllChannelState *channel)
+{
+ uint64_t freq, div;
+
+ if (!pll_channel_is_enabled(channel)) {
+ clock_update(channel->out, 0);
+ return;
+ }
+
+ div = FIELD_EX32(*channel->reg_a2w_ctrl, A2W_PLLx_CHANNELy, DIV);
+
+ if (!div) {
+ /*
+ * It seems that when the divider value is 0, it is considered as
+ * being maximum by the hardware (see the Linux driver).
+ */
+ div = R_A2W_PLLx_CHANNELy_DIV_MASK;
+ }
+
+ /* Some channels have an additional fixed divider */
+ freq = clock_get_hz(channel->pll_in) / (div * channel->fixed_divider);
+
+ clock_update_hz(channel->out, freq);
+}
+
+/* Update a PLL and all its channels */
+static void pll_update_all_channels(BCM2835CprmanState *s,
+ CprmanPllState *pll)
+{
+ size_t i;
+
+ pll_update(pll);
+
+ for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) {
+ CprmanPllChannelState *channel = &s->channels[i];
+ if (channel->parent == pll->id) {
+ pll_channel_update(channel);
+ }
+ }
+}
+
+static void pll_channel_pll_in_update(void *opaque, ClockEvent event)
+{
+ pll_channel_update(CPRMAN_PLL_CHANNEL(opaque));
+}
+
+static void pll_channel_init(Object *obj)
+{
+ CprmanPllChannelState *s = CPRMAN_PLL_CHANNEL(obj);
+
+ s->pll_in = qdev_init_clock_in(DEVICE(s), "pll-in",
+ pll_channel_pll_in_update, s,
+ ClockUpdate);
+ s->out = qdev_init_clock_out(DEVICE(s), "out");
+}
+
+static const VMStateDescription pll_channel_vmstate = {
+ .name = TYPE_CPRMAN_PLL_CHANNEL,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_CLOCK(pll_in, CprmanPllChannelState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pll_channel_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = pll_channel_reset;
+ dc->vmsd = &pll_channel_vmstate;
+}
+
+static const TypeInfo cprman_pll_channel_info = {
+ .name = TYPE_CPRMAN_PLL_CHANNEL,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(CprmanPllChannelState),
+ .class_init = pll_channel_class_init,
+ .instance_init = pll_channel_init,
+};
+
+
+/* clock mux */
+
+static bool clock_mux_is_enabled(CprmanClockMuxState *mux)
+{
+ return FIELD_EX32(*mux->reg_ctl, CM_CLOCKx_CTL, ENABLE);
+}
+
+static void clock_mux_update(CprmanClockMuxState *mux)
+{
+ uint64_t freq;
+ uint32_t div, src = FIELD_EX32(*mux->reg_ctl, CM_CLOCKx_CTL, SRC);
+ bool enabled = clock_mux_is_enabled(mux);
+
+ *mux->reg_ctl = FIELD_DP32(*mux->reg_ctl, CM_CLOCKx_CTL, BUSY, enabled);
+
+ if (!enabled) {
+ clock_update(mux->out, 0);
+ return;
+ }
+
+ freq = clock_get_hz(mux->srcs[src]);
+
+ if (mux->int_bits == 0 && mux->frac_bits == 0) {
+ clock_update_hz(mux->out, freq);
+ return;
+ }
+
+ /*
+ * The divider has an integer and a fractional part. The size of each part
+ * varies with the muxes (int_bits and frac_bits). Both parts are
+ * concatenated, with the integer part always starting at bit 12.
+ *
+ * 31 12 11 0
+ * ------------------------------
+ * CM_DIV | | int | frac | |
+ * ------------------------------
+ * <-----> <------>
+ * int_bits frac_bits
+ */
+ div = extract32(*mux->reg_div,
+ R_CM_CLOCKx_DIV_FRAC_LENGTH - mux->frac_bits,
+ mux->int_bits + mux->frac_bits);
+
+ if (!div) {
+ clock_update(mux->out, 0);
+ return;
+ }
+
+ freq = muldiv64(freq, 1 << mux->frac_bits, div);
+
+ clock_update_hz(mux->out, freq);
+}
+
+static void clock_mux_src_update(void *opaque, ClockEvent event)
+{
+ CprmanClockMuxState **backref = opaque;
+ CprmanClockMuxState *s = *backref;
+ CprmanClockMuxSource src = backref - s->backref;
+
+ if (FIELD_EX32(*s->reg_ctl, CM_CLOCKx_CTL, SRC) != src) {
+ return;
+ }
+
+ clock_mux_update(s);
+}
+
+static void clock_mux_reset(DeviceState *dev)
+{
+ CprmanClockMuxState *clock = CPRMAN_CLOCK_MUX(dev);
+ const ClockMuxResetInfo *info = &CLOCK_MUX_RESET_INFO[clock->id];
+
+ *clock->reg_ctl = info->cm_ctl;
+ *clock->reg_div = info->cm_div;
+}
+
+static void clock_mux_init(Object *obj)
+{
+ CprmanClockMuxState *s = CPRMAN_CLOCK_MUX(obj);
+ size_t i;
+
+ for (i = 0; i < CPRMAN_NUM_CLOCK_MUX_SRC; i++) {
+ char *name = g_strdup_printf("srcs[%zu]", i);
+ s->backref[i] = s;
+ s->srcs[i] = qdev_init_clock_in(DEVICE(s), name,
+ clock_mux_src_update,
+ &s->backref[i],
+ ClockUpdate);
+ g_free(name);
+ }
+
+ s->out = qdev_init_clock_out(DEVICE(s), "out");
+}
+
+static const VMStateDescription clock_mux_vmstate = {
+ .name = TYPE_CPRMAN_CLOCK_MUX,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_ARRAY_CLOCK(srcs, CprmanClockMuxState,
+ CPRMAN_NUM_CLOCK_MUX_SRC),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void clock_mux_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = clock_mux_reset;
+ dc->vmsd = &clock_mux_vmstate;
+}
+
+static const TypeInfo cprman_clock_mux_info = {
+ .name = TYPE_CPRMAN_CLOCK_MUX,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(CprmanClockMuxState),
+ .class_init = clock_mux_class_init,
+ .instance_init = clock_mux_init,
+};
+
+
+/* DSI0HSCK mux */
+
+static void dsi0hsck_mux_update(CprmanDsi0HsckMuxState *s)
+{
+ bool src_is_plld = FIELD_EX32(*s->reg_cm, CM_DSI0HSCK, SELPLLD);
+ Clock *src = src_is_plld ? s->plld_in : s->plla_in;
+
+ clock_update(s->out, clock_get(src));
+}
+
+static void dsi0hsck_mux_in_update(void *opaque, ClockEvent event)
+{
+ dsi0hsck_mux_update(CPRMAN_DSI0HSCK_MUX(opaque));
+}
+
+static void dsi0hsck_mux_init(Object *obj)
+{
+ CprmanDsi0HsckMuxState *s = CPRMAN_DSI0HSCK_MUX(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ s->plla_in = qdev_init_clock_in(dev, "plla-in", dsi0hsck_mux_in_update,
+ s, ClockUpdate);
+ s->plld_in = qdev_init_clock_in(dev, "plld-in", dsi0hsck_mux_in_update,
+ s, ClockUpdate);
+ s->out = qdev_init_clock_out(DEVICE(s), "out");
+}
+
+static const VMStateDescription dsi0hsck_mux_vmstate = {
+ .name = TYPE_CPRMAN_DSI0HSCK_MUX,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_CLOCK(plla_in, CprmanDsi0HsckMuxState),
+ VMSTATE_CLOCK(plld_in, CprmanDsi0HsckMuxState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void dsi0hsck_mux_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &dsi0hsck_mux_vmstate;
+}
+
+static const TypeInfo cprman_dsi0hsck_mux_info = {
+ .name = TYPE_CPRMAN_DSI0HSCK_MUX,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(CprmanDsi0HsckMuxState),
+ .class_init = dsi0hsck_mux_class_init,
+ .instance_init = dsi0hsck_mux_init,
+};
+
+
+/* CPRMAN "top level" model */
+
+static uint32_t get_cm_lock(const BCM2835CprmanState *s)
+{
+ static const int CM_LOCK_MAPPING[CPRMAN_NUM_PLL] = {
+ [CPRMAN_PLLA] = R_CM_LOCK_FLOCKA_SHIFT,
+ [CPRMAN_PLLC] = R_CM_LOCK_FLOCKC_SHIFT,
+ [CPRMAN_PLLD] = R_CM_LOCK_FLOCKD_SHIFT,
+ [CPRMAN_PLLH] = R_CM_LOCK_FLOCKH_SHIFT,
+ [CPRMAN_PLLB] = R_CM_LOCK_FLOCKB_SHIFT,
+ };
+
+ uint32_t r = 0;
+ size_t i;
+
+ for (i = 0; i < CPRMAN_NUM_PLL; i++) {
+ r |= pll_is_locked(&s->plls[i]) << CM_LOCK_MAPPING[i];
+ }
+
+ return r;
+}
+
+static uint64_t cprman_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ BCM2835CprmanState *s = CPRMAN(opaque);
+ uint64_t r = 0;
+ size_t idx = offset / sizeof(uint32_t);
+
+ switch (idx) {
+ case R_CM_LOCK:
+ r = get_cm_lock(s);
+ break;
+
+ default:
+ r = s->regs[idx];
+ }
+
+ trace_bcm2835_cprman_read(offset, r);
+ return r;
+}
+
+static inline void update_pll_and_channels_from_cm(BCM2835CprmanState *s,
+ size_t idx)
+{
+ size_t i;
+
+ for (i = 0; i < CPRMAN_NUM_PLL; i++) {
+ if (PLL_INIT_INFO[i].cm_offset == idx) {
+ pll_update_all_channels(s, &s->plls[i]);
+ return;
+ }
+ }
+}
+
+static inline void update_channel_from_a2w(BCM2835CprmanState *s, size_t idx)
+{
+ size_t i;
+
+ for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) {
+ if (PLL_CHANNEL_INIT_INFO[i].a2w_ctrl_offset == idx) {
+ pll_channel_update(&s->channels[i]);
+ return;
+ }
+ }
+}
+
+static inline void update_mux_from_cm(BCM2835CprmanState *s, size_t idx)
+{
+ size_t i;
+
+ for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) {
+ if ((CLOCK_MUX_INIT_INFO[i].cm_offset == idx) ||
+ (CLOCK_MUX_INIT_INFO[i].cm_offset + 4 == idx)) {
+ /* matches CM_CTL or CM_DIV mux register */
+ clock_mux_update(&s->clock_muxes[i]);
+ return;
+ }
+ }
+}
+
+#define CASE_PLL_A2W_REGS(pll_) \
+ case R_A2W_ ## pll_ ## _CTRL: \
+ case R_A2W_ ## pll_ ## _ANA0: \
+ case R_A2W_ ## pll_ ## _ANA1: \
+ case R_A2W_ ## pll_ ## _ANA2: \
+ case R_A2W_ ## pll_ ## _ANA3: \
+ case R_A2W_ ## pll_ ## _FRAC
+
+static void cprman_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ BCM2835CprmanState *s = CPRMAN(opaque);
+ size_t idx = offset / sizeof(uint32_t);
+
+ if (FIELD_EX32(value, CPRMAN, PASSWORD) != CPRMAN_PASSWORD) {
+ trace_bcm2835_cprman_write_invalid_magic(offset, value);
+ return;
+ }
+
+ value &= ~R_CPRMAN_PASSWORD_MASK;
+
+ trace_bcm2835_cprman_write(offset, value);
+ s->regs[idx] = value;
+
+ switch (idx) {
+ case R_CM_PLLA ... R_CM_PLLH:
+ case R_CM_PLLB:
+ /*
+ * A given CM_PLLx register is shared by both the PLL and the channels
+ * of this PLL.
+ */
+ update_pll_and_channels_from_cm(s, idx);
+ break;
+
+ CASE_PLL_A2W_REGS(PLLA) :
+ pll_update(&s->plls[CPRMAN_PLLA]);
+ break;
+
+ CASE_PLL_A2W_REGS(PLLC) :
+ pll_update(&s->plls[CPRMAN_PLLC]);
+ break;
+
+ CASE_PLL_A2W_REGS(PLLD) :
+ pll_update(&s->plls[CPRMAN_PLLD]);
+ break;
+
+ CASE_PLL_A2W_REGS(PLLH) :
+ pll_update(&s->plls[CPRMAN_PLLH]);
+ break;
+
+ CASE_PLL_A2W_REGS(PLLB) :
+ pll_update(&s->plls[CPRMAN_PLLB]);
+ break;
+
+ case R_A2W_PLLA_DSI0:
+ case R_A2W_PLLA_CORE:
+ case R_A2W_PLLA_PER:
+ case R_A2W_PLLA_CCP2:
+ case R_A2W_PLLC_CORE2:
+ case R_A2W_PLLC_CORE1:
+ case R_A2W_PLLC_PER:
+ case R_A2W_PLLC_CORE0:
+ case R_A2W_PLLD_DSI0:
+ case R_A2W_PLLD_CORE:
+ case R_A2W_PLLD_PER:
+ case R_A2W_PLLD_DSI1:
+ case R_A2W_PLLH_AUX:
+ case R_A2W_PLLH_RCAL:
+ case R_A2W_PLLH_PIX:
+ case R_A2W_PLLB_ARM:
+ update_channel_from_a2w(s, idx);
+ break;
+
+ case R_CM_GNRICCTL ... R_CM_SMIDIV:
+ case R_CM_TCNTCNT ... R_CM_VECDIV:
+ case R_CM_PULSECTL ... R_CM_PULSEDIV:
+ case R_CM_SDCCTL ... R_CM_ARMCTL:
+ case R_CM_AVEOCTL ... R_CM_EMMCDIV:
+ case R_CM_EMMC2CTL ... R_CM_EMMC2DIV:
+ update_mux_from_cm(s, idx);
+ break;
+
+ case R_CM_DSI0HSCK:
+ dsi0hsck_mux_update(&s->dsi0hsck_mux);
+ break;
+ }
+}
+
+#undef CASE_PLL_A2W_REGS
+
+static const MemoryRegionOps cprman_ops = {
+ .read = cprman_read,
+ .write = cprman_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ /*
+ * Although this hasn't been checked against real hardware, nor the
+ * information can be found in a datasheet, it seems reasonable because
+ * of the "PASSWORD" magic value found in every registers.
+ */
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+ .impl = {
+ .max_access_size = 4,
+ },
+};
+
+static void cprman_reset(DeviceState *dev)
+{
+ BCM2835CprmanState *s = CPRMAN(dev);
+ size_t i;
+
+ memset(s->regs, 0, sizeof(s->regs));
+
+ for (i = 0; i < CPRMAN_NUM_PLL; i++) {
+ device_cold_reset(DEVICE(&s->plls[i]));
+ }
+
+ for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) {
+ device_cold_reset(DEVICE(&s->channels[i]));
+ }
+
+ device_cold_reset(DEVICE(&s->dsi0hsck_mux));
+
+ for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) {
+ device_cold_reset(DEVICE(&s->clock_muxes[i]));
+ }
+
+ clock_update_hz(s->xosc, s->xosc_freq);
+}
+
+static void cprman_init(Object *obj)
+{
+ BCM2835CprmanState *s = CPRMAN(obj);
+ size_t i;
+
+ for (i = 0; i < CPRMAN_NUM_PLL; i++) {
+ object_initialize_child(obj, PLL_INIT_INFO[i].name,
+ &s->plls[i], TYPE_CPRMAN_PLL);
+ set_pll_init_info(s, &s->plls[i], i);
+ }
+
+ for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) {
+ object_initialize_child(obj, PLL_CHANNEL_INIT_INFO[i].name,
+ &s->channels[i],
+ TYPE_CPRMAN_PLL_CHANNEL);
+ set_pll_channel_init_info(s, &s->channels[i], i);
+ }
+
+ object_initialize_child(obj, "dsi0hsck-mux",
+ &s->dsi0hsck_mux, TYPE_CPRMAN_DSI0HSCK_MUX);
+ s->dsi0hsck_mux.reg_cm = &s->regs[R_CM_DSI0HSCK];
+
+ for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) {
+ char *alias;
+
+ object_initialize_child(obj, CLOCK_MUX_INIT_INFO[i].name,
+ &s->clock_muxes[i],
+ TYPE_CPRMAN_CLOCK_MUX);
+ set_clock_mux_init_info(s, &s->clock_muxes[i], i);
+
+ /* Expose muxes output as CPRMAN outputs */
+ alias = g_strdup_printf("%s-out", CLOCK_MUX_INIT_INFO[i].name);
+ qdev_alias_clock(DEVICE(&s->clock_muxes[i]), "out", DEVICE(obj), alias);
+ g_free(alias);
+ }
+
+ s->xosc = clock_new(obj, "xosc");
+ s->gnd = clock_new(obj, "gnd");
+
+ clock_set(s->gnd, 0);
+
+ memory_region_init_io(&s->iomem, obj, &cprman_ops,
+ s, "bcm2835-cprman", 0x2000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void connect_mux_sources(BCM2835CprmanState *s,
+ CprmanClockMuxState *mux,
+ const CprmanPllChannel *clk_mapping)
+{
+ size_t i;
+ Clock *td0 = s->clock_muxes[CPRMAN_CLOCK_TD0].out;
+ Clock *td1 = s->clock_muxes[CPRMAN_CLOCK_TD1].out;
+
+ /* For sources from 0 to 3. Source 4 to 9 are mux specific */
+ Clock * const CLK_SRC_MAPPING[] = {
+ [CPRMAN_CLOCK_SRC_GND] = s->gnd,
+ [CPRMAN_CLOCK_SRC_XOSC] = s->xosc,
+ [CPRMAN_CLOCK_SRC_TD0] = td0,
+ [CPRMAN_CLOCK_SRC_TD1] = td1,
+ };
+
+ for (i = 0; i < CPRMAN_NUM_CLOCK_MUX_SRC; i++) {
+ CprmanPllChannel mapping = clk_mapping[i];
+ Clock *src;
+
+ if (mapping == CPRMAN_CLOCK_SRC_FORCE_GROUND) {
+ src = s->gnd;
+ } else if (mapping == CPRMAN_CLOCK_SRC_DSI0HSCK) {
+ src = s->dsi0hsck_mux.out;
+ } else if (i < CPRMAN_CLOCK_SRC_PLLA) {
+ src = CLK_SRC_MAPPING[i];
+ } else {
+ src = s->channels[mapping].out;
+ }
+
+ clock_set_source(mux->srcs[i], src);
+ }
+}
+
+static void cprman_realize(DeviceState *dev, Error **errp)
+{
+ BCM2835CprmanState *s = CPRMAN(dev);
+ size_t i;
+
+ for (i = 0; i < CPRMAN_NUM_PLL; i++) {
+ CprmanPllState *pll = &s->plls[i];
+
+ clock_set_source(pll->xosc_in, s->xosc);
+
+ if (!qdev_realize(DEVICE(pll), NULL, errp)) {
+ return;
+ }
+ }
+
+ for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) {
+ CprmanPllChannelState *channel = &s->channels[i];
+ CprmanPll parent = PLL_CHANNEL_INIT_INFO[i].parent;
+ Clock *parent_clk = s->plls[parent].out;
+
+ clock_set_source(channel->pll_in, parent_clk);
+
+ if (!qdev_realize(DEVICE(channel), NULL, errp)) {
+ return;
+ }
+ }
+
+ clock_set_source(s->dsi0hsck_mux.plla_in,
+ s->channels[CPRMAN_PLLA_CHANNEL_DSI0].out);
+ clock_set_source(s->dsi0hsck_mux.plld_in,
+ s->channels[CPRMAN_PLLD_CHANNEL_DSI0].out);
+
+ if (!qdev_realize(DEVICE(&s->dsi0hsck_mux), NULL, errp)) {
+ return;
+ }
+
+ for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) {
+ CprmanClockMuxState *clock_mux = &s->clock_muxes[i];
+
+ connect_mux_sources(s, clock_mux, CLOCK_MUX_INIT_INFO[i].src_mapping);
+
+ if (!qdev_realize(DEVICE(clock_mux), NULL, errp)) {
+ return;
+ }
+ }
+}
+
+static const VMStateDescription cprman_vmstate = {
+ .name = TYPE_BCM2835_CPRMAN,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, BCM2835CprmanState, CPRMAN_NUM_REGS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property cprman_properties[] = {
+ DEFINE_PROP_UINT32("xosc-freq-hz", BCM2835CprmanState, xosc_freq, 19200000),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void cprman_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = cprman_realize;
+ dc->reset = cprman_reset;
+ dc->vmsd = &cprman_vmstate;
+ device_class_set_props(dc, cprman_properties);
+}
+
+static const TypeInfo cprman_info = {
+ .name = TYPE_BCM2835_CPRMAN,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2835CprmanState),
+ .class_init = cprman_class_init,
+ .instance_init = cprman_init,
+};
+
+static void cprman_register_types(void)
+{
+ type_register_static(&cprman_info);
+ type_register_static(&cprman_pll_info);
+ type_register_static(&cprman_pll_channel_info);
+ type_register_static(&cprman_clock_mux_info);
+ type_register_static(&cprman_dsi0hsck_mux_info);
+}
+
+type_init(cprman_register_types);
diff --git a/hw/misc/bcm2835_mbox.c b/hw/misc/bcm2835_mbox.c
new file mode 100644
index 000000000..9f73cbd5e
--- /dev/null
+++ b/hw/misc/bcm2835_mbox.c
@@ -0,0 +1,340 @@
+/*
+ * Raspberry Pi emulation (c) 2012 Gregory Estrade
+ *
+ * This file models the system mailboxes, which are used for
+ * communication with low-bandwidth GPU peripherals. Refs:
+ * https://github.com/raspberrypi/firmware/wiki/Mailboxes
+ * https://github.com/raspberrypi/firmware/wiki/Accessing-mailboxes
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/irq.h"
+#include "hw/misc/bcm2835_mbox.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+#define MAIL0_PEEK 0x90
+#define MAIL0_SENDER 0x94
+#define MAIL1_STATUS 0xb8
+
+/* Mailbox status register */
+#define MAIL0_STATUS 0x98
+#define ARM_MS_FULL 0x80000000
+#define ARM_MS_EMPTY 0x40000000
+#define ARM_MS_LEVEL 0x400000FF /* Max. value depends on mailbox depth */
+
+/* MAILBOX config/status register */
+#define MAIL0_CONFIG 0x9c
+/* ANY write to this register clears the error bits! */
+#define ARM_MC_IHAVEDATAIRQEN 0x00000001 /* mbox irq enable: has data */
+#define ARM_MC_IHAVESPACEIRQEN 0x00000002 /* mbox irq enable: has space */
+#define ARM_MC_OPPISEMPTYIRQEN 0x00000004 /* mbox irq enable: Opp is empty */
+#define ARM_MC_MAIL_CLEAR 0x00000008 /* mbox clear write 1, then 0 */
+#define ARM_MC_IHAVEDATAIRQPEND 0x00000010 /* mbox irq pending: has space */
+#define ARM_MC_IHAVESPACEIRQPEND 0x00000020 /* mbox irq pending: Opp is empty */
+#define ARM_MC_OPPISEMPTYIRQPEND 0x00000040 /* mbox irq pending */
+/* Bit 7 is unused */
+#define ARM_MC_ERRNOOWN 0x00000100 /* error : none owner read from mailbox */
+#define ARM_MC_ERROVERFLW 0x00000200 /* error : write to fill mailbox */
+#define ARM_MC_ERRUNDRFLW 0x00000400 /* error : read from empty mailbox */
+
+static void mbox_update_status(BCM2835Mbox *mb)
+{
+ mb->status &= ~(ARM_MS_EMPTY | ARM_MS_FULL);
+ if (mb->count == 0) {
+ mb->status |= ARM_MS_EMPTY;
+ } else if (mb->count == MBOX_SIZE) {
+ mb->status |= ARM_MS_FULL;
+ }
+}
+
+static void mbox_reset(BCM2835Mbox *mb)
+{
+ int n;
+
+ mb->count = 0;
+ mb->config = 0;
+ for (n = 0; n < MBOX_SIZE; n++) {
+ mb->reg[n] = MBOX_INVALID_DATA;
+ }
+ mbox_update_status(mb);
+}
+
+static uint32_t mbox_pull(BCM2835Mbox *mb, int index)
+{
+ int n;
+ uint32_t val;
+
+ assert(mb->count > 0);
+ assert(index < mb->count);
+
+ val = mb->reg[index];
+ for (n = index + 1; n < mb->count; n++) {
+ mb->reg[n - 1] = mb->reg[n];
+ }
+ mb->count--;
+ mb->reg[mb->count] = MBOX_INVALID_DATA;
+
+ mbox_update_status(mb);
+
+ return val;
+}
+
+static void mbox_push(BCM2835Mbox *mb, uint32_t val)
+{
+ assert(mb->count < MBOX_SIZE);
+ mb->reg[mb->count++] = val;
+ mbox_update_status(mb);
+}
+
+static void bcm2835_mbox_update(BCM2835MboxState *s)
+{
+ uint32_t value;
+ bool set;
+ int n;
+
+ s->mbox_irq_disabled = true;
+
+ /* Get pending responses and put them in the vc->arm mbox,
+ * as long as it's not full
+ */
+ for (n = 0; n < MBOX_CHAN_COUNT; n++) {
+ while (s->available[n] && !(s->mbox[0].status & ARM_MS_FULL)) {
+ value = ldl_le_phys(&s->mbox_as, n << MBOX_AS_CHAN_SHIFT);
+ assert(value != MBOX_INVALID_DATA); /* Pending interrupt but no data */
+ mbox_push(&s->mbox[0], value);
+ }
+ }
+
+ /* TODO (?): Try to push pending requests from the arm->vc mbox */
+
+ /* Re-enable calls from the IRQ routine */
+ s->mbox_irq_disabled = false;
+
+ /* Update ARM IRQ status */
+ set = false;
+ s->mbox[0].config &= ~ARM_MC_IHAVEDATAIRQPEND;
+ if (!(s->mbox[0].status & ARM_MS_EMPTY)) {
+ s->mbox[0].config |= ARM_MC_IHAVEDATAIRQPEND;
+ if (s->mbox[0].config & ARM_MC_IHAVEDATAIRQEN) {
+ set = true;
+ }
+ }
+ trace_bcm2835_mbox_irq(set);
+ qemu_set_irq(s->arm_irq, set);
+}
+
+static void bcm2835_mbox_set_irq(void *opaque, int irq, int level)
+{
+ BCM2835MboxState *s = opaque;
+
+ s->available[irq] = level;
+
+ /* avoid recursively calling bcm2835_mbox_update when the interrupt
+ * status changes due to the ldl_phys call within that function
+ */
+ if (!s->mbox_irq_disabled) {
+ bcm2835_mbox_update(s);
+ }
+}
+
+static uint64_t bcm2835_mbox_read(void *opaque, hwaddr offset, unsigned size)
+{
+ BCM2835MboxState *s = opaque;
+ uint32_t res = 0;
+
+ offset &= 0xff;
+
+ switch (offset) {
+ case 0x80 ... 0x8c: /* MAIL0_READ */
+ if (s->mbox[0].status & ARM_MS_EMPTY) {
+ res = MBOX_INVALID_DATA;
+ } else {
+ res = mbox_pull(&s->mbox[0], 0);
+ }
+ break;
+
+ case MAIL0_PEEK:
+ res = s->mbox[0].reg[0];
+ break;
+
+ case MAIL0_SENDER:
+ break;
+
+ case MAIL0_STATUS:
+ res = s->mbox[0].status;
+ break;
+
+ case MAIL0_CONFIG:
+ res = s->mbox[0].config;
+ break;
+
+ case MAIL1_STATUS:
+ res = s->mbox[1].status;
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: Unsupported offset 0x%"HWADDR_PRIx"\n",
+ __func__, offset);
+ trace_bcm2835_mbox_read(size, offset, res);
+ return 0;
+ }
+ trace_bcm2835_mbox_read(size, offset, res);
+
+ bcm2835_mbox_update(s);
+
+ return res;
+}
+
+static void bcm2835_mbox_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ BCM2835MboxState *s = opaque;
+ hwaddr childaddr;
+ uint8_t ch;
+
+ offset &= 0xff;
+
+ trace_bcm2835_mbox_write(size, offset, value);
+ switch (offset) {
+ case MAIL0_SENDER:
+ break;
+
+ case MAIL0_CONFIG:
+ s->mbox[0].config &= ~ARM_MC_IHAVEDATAIRQEN;
+ s->mbox[0].config |= value & ARM_MC_IHAVEDATAIRQEN;
+ break;
+
+ case 0xa0 ... 0xac: /* MAIL1_WRITE */
+ if (s->mbox[1].status & ARM_MS_FULL) {
+ /* Mailbox full */
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: mailbox full\n", __func__);
+ } else {
+ ch = value & 0xf;
+ if (ch < MBOX_CHAN_COUNT) {
+ childaddr = ch << MBOX_AS_CHAN_SHIFT;
+ if (ldl_le_phys(&s->mbox_as, childaddr + MBOX_AS_PENDING)) {
+ /* Child busy, push delayed. Push it in the arm->vc mbox */
+ mbox_push(&s->mbox[1], value);
+ } else {
+ /* Push it directly to the child device */
+ stl_le_phys(&s->mbox_as, childaddr, value);
+ }
+ } else {
+ /* Invalid channel number */
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid channel %u\n",
+ __func__, ch);
+ }
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: Unsupported offset 0x%"HWADDR_PRIx
+ " value 0x%"PRIx64"\n",
+ __func__, offset, value);
+ return;
+ }
+
+ bcm2835_mbox_update(s);
+}
+
+static const MemoryRegionOps bcm2835_mbox_ops = {
+ .read = bcm2835_mbox_read,
+ .write = bcm2835_mbox_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+/* vmstate of a single mailbox */
+static const VMStateDescription vmstate_bcm2835_mbox_box = {
+ .name = TYPE_BCM2835_MBOX "_box",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(reg, BCM2835Mbox, MBOX_SIZE),
+ VMSTATE_UINT32(count, BCM2835Mbox),
+ VMSTATE_UINT32(status, BCM2835Mbox),
+ VMSTATE_UINT32(config, BCM2835Mbox),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* vmstate of the entire device */
+static const VMStateDescription vmstate_bcm2835_mbox = {
+ .name = TYPE_BCM2835_MBOX,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL_ARRAY(available, BCM2835MboxState, MBOX_CHAN_COUNT),
+ VMSTATE_STRUCT_ARRAY(mbox, BCM2835MboxState, 2, 1,
+ vmstate_bcm2835_mbox_box, BCM2835Mbox),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void bcm2835_mbox_init(Object *obj)
+{
+ BCM2835MboxState *s = BCM2835_MBOX(obj);
+
+ memory_region_init_io(&s->iomem, obj, &bcm2835_mbox_ops, s,
+ TYPE_BCM2835_MBOX, 0x400);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(s), &s->arm_irq);
+ qdev_init_gpio_in(DEVICE(s), bcm2835_mbox_set_irq, MBOX_CHAN_COUNT);
+}
+
+static void bcm2835_mbox_reset(DeviceState *dev)
+{
+ BCM2835MboxState *s = BCM2835_MBOX(dev);
+ int n;
+
+ mbox_reset(&s->mbox[0]);
+ mbox_reset(&s->mbox[1]);
+ s->mbox_irq_disabled = false;
+ for (n = 0; n < MBOX_CHAN_COUNT; n++) {
+ s->available[n] = false;
+ }
+}
+
+static void bcm2835_mbox_realize(DeviceState *dev, Error **errp)
+{
+ BCM2835MboxState *s = BCM2835_MBOX(dev);
+ Object *obj;
+
+ obj = object_property_get_link(OBJECT(dev), "mbox-mr", &error_abort);
+ s->mbox_mr = MEMORY_REGION(obj);
+ address_space_init(&s->mbox_as, s->mbox_mr, TYPE_BCM2835_MBOX "-memory");
+ bcm2835_mbox_reset(dev);
+}
+
+static void bcm2835_mbox_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = bcm2835_mbox_realize;
+ dc->reset = bcm2835_mbox_reset;
+ dc->vmsd = &vmstate_bcm2835_mbox;
+}
+
+static TypeInfo bcm2835_mbox_info = {
+ .name = TYPE_BCM2835_MBOX,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2835MboxState),
+ .class_init = bcm2835_mbox_class_init,
+ .instance_init = bcm2835_mbox_init,
+};
+
+static void bcm2835_mbox_register_types(void)
+{
+ type_register_static(&bcm2835_mbox_info);
+}
+
+type_init(bcm2835_mbox_register_types)
diff --git a/hw/misc/bcm2835_mphi.c b/hw/misc/bcm2835_mphi.c
new file mode 100644
index 000000000..0428e10ba
--- /dev/null
+++ b/hw/misc/bcm2835_mphi.c
@@ -0,0 +1,191 @@
+/*
+ * BCM2835 SOC MPHI emulation
+ *
+ * Very basic emulation, only providing the FIQ interrupt needed to
+ * allow the dwc-otg USB host controller driver in the Raspbian kernel
+ * to function.
+ *
+ * Copyright (c) 2020 Paul Zimmerman <pauldzim@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.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/misc/bcm2835_mphi.h"
+#include "migration/vmstate.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+
+static inline void mphi_raise_irq(BCM2835MphiState *s)
+{
+ qemu_set_irq(s->irq, 1);
+}
+
+static inline void mphi_lower_irq(BCM2835MphiState *s)
+{
+ qemu_set_irq(s->irq, 0);
+}
+
+static uint64_t mphi_reg_read(void *ptr, hwaddr addr, unsigned size)
+{
+ BCM2835MphiState *s = ptr;
+ uint32_t val = 0;
+
+ switch (addr) {
+ case 0x28: /* outdda */
+ val = s->outdda;
+ break;
+ case 0x2c: /* outddb */
+ val = s->outddb;
+ break;
+ case 0x4c: /* ctrl */
+ val = s->ctrl;
+ val |= 1 << 17;
+ break;
+ case 0x50: /* intstat */
+ val = s->intstat;
+ break;
+ case 0x1f0: /* swirq_set */
+ val = s->swirq;
+ break;
+ case 0x1f4: /* swirq_clr */
+ val = s->swirq;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "read from unknown register");
+ break;
+ }
+
+ return val;
+}
+
+static void mphi_reg_write(void *ptr, hwaddr addr, uint64_t val, unsigned size)
+{
+ BCM2835MphiState *s = ptr;
+ int do_irq = 0;
+
+ switch (addr) {
+ case 0x28: /* outdda */
+ s->outdda = val;
+ break;
+ case 0x2c: /* outddb */
+ s->outddb = val;
+ if (val & (1 << 29)) {
+ do_irq = 1;
+ }
+ break;
+ case 0x4c: /* ctrl */
+ s->ctrl = val;
+ if (val & (1 << 16)) {
+ do_irq = -1;
+ }
+ break;
+ case 0x50: /* intstat */
+ s->intstat = val;
+ if (val & ((1 << 16) | (1 << 29))) {
+ do_irq = -1;
+ }
+ break;
+ case 0x1f0: /* swirq_set */
+ s->swirq |= val;
+ do_irq = 1;
+ break;
+ case 0x1f4: /* swirq_clr */
+ s->swirq &= ~val;
+ do_irq = -1;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "write to unknown register");
+ return;
+ }
+
+ if (do_irq > 0) {
+ mphi_raise_irq(s);
+ } else if (do_irq < 0) {
+ mphi_lower_irq(s);
+ }
+}
+
+static const MemoryRegionOps mphi_mmio_ops = {
+ .read = mphi_reg_read,
+ .write = mphi_reg_write,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void mphi_reset(DeviceState *dev)
+{
+ BCM2835MphiState *s = BCM2835_MPHI(dev);
+
+ s->outdda = 0;
+ s->outddb = 0;
+ s->ctrl = 0;
+ s->intstat = 0;
+ s->swirq = 0;
+}
+
+static void mphi_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ BCM2835MphiState *s = BCM2835_MPHI(dev);
+
+ sysbus_init_irq(sbd, &s->irq);
+}
+
+static void mphi_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ BCM2835MphiState *s = BCM2835_MPHI(obj);
+
+ memory_region_init_io(&s->iomem, obj, &mphi_mmio_ops, s, "mphi", MPHI_MMIO_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+const VMStateDescription vmstate_mphi_state = {
+ .name = "mphi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(outdda, BCM2835MphiState),
+ VMSTATE_UINT32(outddb, BCM2835MphiState),
+ VMSTATE_UINT32(ctrl, BCM2835MphiState),
+ VMSTATE_UINT32(intstat, BCM2835MphiState),
+ VMSTATE_UINT32(swirq, BCM2835MphiState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mphi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = mphi_realize;
+ dc->reset = mphi_reset;
+ dc->vmsd = &vmstate_mphi_state;
+}
+
+static const TypeInfo bcm2835_mphi_type_info = {
+ .name = TYPE_BCM2835_MPHI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2835MphiState),
+ .instance_init = mphi_init,
+ .class_init = mphi_class_init,
+};
+
+static void bcm2835_mphi_register_types(void)
+{
+ type_register_static(&bcm2835_mphi_type_info);
+}
+
+type_init(bcm2835_mphi_register_types)
diff --git a/hw/misc/bcm2835_powermgt.c b/hw/misc/bcm2835_powermgt.c
new file mode 100644
index 000000000..25fa804cb
--- /dev/null
+++ b/hw/misc/bcm2835_powermgt.c
@@ -0,0 +1,160 @@
+/*
+ * BCM2835 Power Management emulation
+ *
+ * Copyright (C) 2017 Marcin Chojnacki <marcinch7@gmail.com>
+ * Copyright (C) 2021 Nolan Leake <nolan@sigbus.net>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/misc/bcm2835_powermgt.h"
+#include "migration/vmstate.h"
+#include "sysemu/runstate.h"
+
+#define PASSWORD 0x5a000000
+#define PASSWORD_MASK 0xff000000
+
+#define R_RSTC 0x1c
+#define V_RSTC_RESET 0x20
+#define R_RSTS 0x20
+#define V_RSTS_POWEROFF 0x555 /* Linux uses partition 63 to indicate halt. */
+#define R_WDOG 0x24
+
+static uint64_t bcm2835_powermgt_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ BCM2835PowerMgtState *s = (BCM2835PowerMgtState *)opaque;
+ uint32_t res = 0;
+
+ switch (offset) {
+ case R_RSTC:
+ res = s->rstc;
+ break;
+ case R_RSTS:
+ res = s->rsts;
+ break;
+ case R_WDOG:
+ res = s->wdog;
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "bcm2835_powermgt_read: Unknown offset 0x%08"HWADDR_PRIx
+ "\n", offset);
+ res = 0;
+ break;
+ }
+
+ return res;
+}
+
+static void bcm2835_powermgt_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ BCM2835PowerMgtState *s = (BCM2835PowerMgtState *)opaque;
+
+ if ((value & PASSWORD_MASK) != PASSWORD) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "bcm2835_powermgt_write: Bad password 0x%"PRIx64
+ " at offset 0x%08"HWADDR_PRIx"\n",
+ value, offset);
+ return;
+ }
+
+ value = value & ~PASSWORD_MASK;
+
+ switch (offset) {
+ case R_RSTC:
+ s->rstc = value;
+ if (value & V_RSTC_RESET) {
+ if ((s->rsts & 0xfff) == V_RSTS_POWEROFF) {
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ } else {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ }
+ }
+ break;
+ case R_RSTS:
+ qemu_log_mask(LOG_UNIMP,
+ "bcm2835_powermgt_write: RSTS\n");
+ s->rsts = value;
+ break;
+ case R_WDOG:
+ qemu_log_mask(LOG_UNIMP,
+ "bcm2835_powermgt_write: WDOG\n");
+ s->wdog = value;
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "bcm2835_powermgt_write: Unknown offset 0x%08"HWADDR_PRIx
+ "\n", offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps bcm2835_powermgt_ops = {
+ .read = bcm2835_powermgt_read,
+ .write = bcm2835_powermgt_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
+static const VMStateDescription vmstate_bcm2835_powermgt = {
+ .name = TYPE_BCM2835_POWERMGT,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(rstc, BCM2835PowerMgtState),
+ VMSTATE_UINT32(rsts, BCM2835PowerMgtState),
+ VMSTATE_UINT32(wdog, BCM2835PowerMgtState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void bcm2835_powermgt_init(Object *obj)
+{
+ BCM2835PowerMgtState *s = BCM2835_POWERMGT(obj);
+
+ memory_region_init_io(&s->iomem, obj, &bcm2835_powermgt_ops, s,
+ TYPE_BCM2835_POWERMGT, 0x200);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+}
+
+static void bcm2835_powermgt_reset(DeviceState *dev)
+{
+ BCM2835PowerMgtState *s = BCM2835_POWERMGT(dev);
+
+ /* https://elinux.org/BCM2835_registers#PM */
+ s->rstc = 0x00000102;
+ s->rsts = 0x00001000;
+ s->wdog = 0x00000000;
+}
+
+static void bcm2835_powermgt_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = bcm2835_powermgt_reset;
+ dc->vmsd = &vmstate_bcm2835_powermgt;
+}
+
+static TypeInfo bcm2835_powermgt_info = {
+ .name = TYPE_BCM2835_POWERMGT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2835PowerMgtState),
+ .class_init = bcm2835_powermgt_class_init,
+ .instance_init = bcm2835_powermgt_init,
+};
+
+static void bcm2835_powermgt_register_types(void)
+{
+ type_register_static(&bcm2835_powermgt_info);
+}
+
+type_init(bcm2835_powermgt_register_types)
diff --git a/hw/misc/bcm2835_property.c b/hw/misc/bcm2835_property.c
new file mode 100644
index 000000000..73941bdae
--- /dev/null
+++ b/hw/misc/bcm2835_property.c
@@ -0,0 +1,436 @@
+/*
+ * Raspberry Pi emulation (c) 2012 Gregory Estrade
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/misc/bcm2835_property.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+#include "hw/misc/bcm2835_mbox_defs.h"
+#include "sysemu/dma.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+/* https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface */
+
+static void bcm2835_property_mbox_push(BCM2835PropertyState *s, uint32_t value)
+{
+ uint32_t tag;
+ uint32_t bufsize;
+ uint32_t tot_len;
+ size_t resplen;
+ uint32_t tmp;
+ int n;
+ uint32_t offset, length, color;
+
+ /*
+ * Copy the current state of the framebuffer config; we will update
+ * this copy as we process tags and then ask the framebuffer to use
+ * it at the end.
+ */
+ BCM2835FBConfig fbconfig = s->fbdev->config;
+ bool fbconfig_updated = false;
+
+ value &= ~0xf;
+
+ s->addr = value;
+
+ tot_len = ldl_le_phys(&s->dma_as, value);
+
+ /* @(addr + 4) : Buffer response code */
+ value = s->addr + 8;
+ while (value + 8 <= s->addr + tot_len) {
+ tag = ldl_le_phys(&s->dma_as, value);
+ bufsize = ldl_le_phys(&s->dma_as, value + 4);
+ /* @(value + 8) : Request/response indicator */
+ resplen = 0;
+ switch (tag) {
+ case 0x00000000: /* End tag */
+ break;
+ case 0x00000001: /* Get firmware revision */
+ stl_le_phys(&s->dma_as, value + 12, 346337);
+ resplen = 4;
+ break;
+ case 0x00010001: /* Get board model */
+ qemu_log_mask(LOG_UNIMP,
+ "bcm2835_property: 0x%08x get board model NYI\n",
+ tag);
+ resplen = 4;
+ break;
+ case 0x00010002: /* Get board revision */
+ stl_le_phys(&s->dma_as, value + 12, s->board_rev);
+ resplen = 4;
+ break;
+ case 0x00010003: /* Get board MAC address */
+ resplen = sizeof(s->macaddr.a);
+ dma_memory_write(&s->dma_as, value + 12, s->macaddr.a, resplen);
+ break;
+ case 0x00010004: /* Get board serial */
+ qemu_log_mask(LOG_UNIMP,
+ "bcm2835_property: 0x%08x get board serial NYI\n",
+ tag);
+ resplen = 8;
+ break;
+ case 0x00010005: /* Get ARM memory */
+ /* base */
+ stl_le_phys(&s->dma_as, value + 12, 0);
+ /* size */
+ stl_le_phys(&s->dma_as, value + 16, s->fbdev->vcram_base);
+ resplen = 8;
+ break;
+ case 0x00010006: /* Get VC memory */
+ /* base */
+ stl_le_phys(&s->dma_as, value + 12, s->fbdev->vcram_base);
+ /* size */
+ stl_le_phys(&s->dma_as, value + 16, s->fbdev->vcram_size);
+ resplen = 8;
+ break;
+ case 0x00028001: /* Set power state */
+ /* Assume that whatever device they asked for exists,
+ * and we'll just claim we set it to the desired state
+ */
+ tmp = ldl_le_phys(&s->dma_as, value + 16);
+ stl_le_phys(&s->dma_as, value + 16, (tmp & 1));
+ resplen = 8;
+ break;
+
+ /* Clocks */
+
+ case 0x00030001: /* Get clock state */
+ stl_le_phys(&s->dma_as, value + 16, 0x1);
+ resplen = 8;
+ break;
+
+ case 0x00038001: /* Set clock state */
+ qemu_log_mask(LOG_UNIMP,
+ "bcm2835_property: 0x%08x set clock state NYI\n",
+ tag);
+ resplen = 8;
+ break;
+
+ case 0x00030002: /* Get clock rate */
+ case 0x00030004: /* Get max clock rate */
+ case 0x00030007: /* Get min clock rate */
+ switch (ldl_le_phys(&s->dma_as, value + 12)) {
+ case 1: /* EMMC */
+ stl_le_phys(&s->dma_as, value + 16, 50000000);
+ break;
+ case 2: /* UART */
+ stl_le_phys(&s->dma_as, value + 16, 3000000);
+ break;
+ default:
+ stl_le_phys(&s->dma_as, value + 16, 700000000);
+ break;
+ }
+ resplen = 8;
+ break;
+
+ case 0x00038002: /* Set clock rate */
+ case 0x00038004: /* Set max clock rate */
+ case 0x00038007: /* Set min clock rate */
+ qemu_log_mask(LOG_UNIMP,
+ "bcm2835_property: 0x%08x set clock rate NYI\n",
+ tag);
+ resplen = 8;
+ break;
+
+ /* Temperature */
+
+ case 0x00030006: /* Get temperature */
+ stl_le_phys(&s->dma_as, value + 16, 25000);
+ resplen = 8;
+ break;
+
+ case 0x0003000A: /* Get max temperature */
+ stl_le_phys(&s->dma_as, value + 16, 99000);
+ resplen = 8;
+ break;
+
+ /* Frame buffer */
+
+ case 0x00040001: /* Allocate buffer */
+ stl_le_phys(&s->dma_as, value + 12, fbconfig.base);
+ stl_le_phys(&s->dma_as, value + 16,
+ bcm2835_fb_get_size(&fbconfig));
+ resplen = 8;
+ break;
+ case 0x00048001: /* Release buffer */
+ resplen = 0;
+ break;
+ case 0x00040002: /* Blank screen */
+ resplen = 4;
+ break;
+ case 0x00044003: /* Test physical display width/height */
+ case 0x00044004: /* Test virtual display width/height */
+ resplen = 8;
+ break;
+ case 0x00048003: /* Set physical display width/height */
+ fbconfig.xres = ldl_le_phys(&s->dma_as, value + 12);
+ fbconfig.yres = ldl_le_phys(&s->dma_as, value + 16);
+ bcm2835_fb_validate_config(&fbconfig);
+ fbconfig_updated = true;
+ /* fall through */
+ case 0x00040003: /* Get physical display width/height */
+ stl_le_phys(&s->dma_as, value + 12, fbconfig.xres);
+ stl_le_phys(&s->dma_as, value + 16, fbconfig.yres);
+ resplen = 8;
+ break;
+ case 0x00048004: /* Set virtual display width/height */
+ fbconfig.xres_virtual = ldl_le_phys(&s->dma_as, value + 12);
+ fbconfig.yres_virtual = ldl_le_phys(&s->dma_as, value + 16);
+ bcm2835_fb_validate_config(&fbconfig);
+ fbconfig_updated = true;
+ /* fall through */
+ case 0x00040004: /* Get virtual display width/height */
+ stl_le_phys(&s->dma_as, value + 12, fbconfig.xres_virtual);
+ stl_le_phys(&s->dma_as, value + 16, fbconfig.yres_virtual);
+ resplen = 8;
+ break;
+ case 0x00044005: /* Test depth */
+ resplen = 4;
+ break;
+ case 0x00048005: /* Set depth */
+ fbconfig.bpp = ldl_le_phys(&s->dma_as, value + 12);
+ bcm2835_fb_validate_config(&fbconfig);
+ fbconfig_updated = true;
+ /* fall through */
+ case 0x00040005: /* Get depth */
+ stl_le_phys(&s->dma_as, value + 12, fbconfig.bpp);
+ resplen = 4;
+ break;
+ case 0x00044006: /* Test pixel order */
+ resplen = 4;
+ break;
+ case 0x00048006: /* Set pixel order */
+ fbconfig.pixo = ldl_le_phys(&s->dma_as, value + 12);
+ bcm2835_fb_validate_config(&fbconfig);
+ fbconfig_updated = true;
+ /* fall through */
+ case 0x00040006: /* Get pixel order */
+ stl_le_phys(&s->dma_as, value + 12, fbconfig.pixo);
+ resplen = 4;
+ break;
+ case 0x00044007: /* Test pixel alpha */
+ resplen = 4;
+ break;
+ case 0x00048007: /* Set alpha */
+ fbconfig.alpha = ldl_le_phys(&s->dma_as, value + 12);
+ bcm2835_fb_validate_config(&fbconfig);
+ fbconfig_updated = true;
+ /* fall through */
+ case 0x00040007: /* Get alpha */
+ stl_le_phys(&s->dma_as, value + 12, fbconfig.alpha);
+ resplen = 4;
+ break;
+ case 0x00040008: /* Get pitch */
+ stl_le_phys(&s->dma_as, value + 12,
+ bcm2835_fb_get_pitch(&fbconfig));
+ resplen = 4;
+ break;
+ case 0x00044009: /* Test virtual offset */
+ resplen = 8;
+ break;
+ case 0x00048009: /* Set virtual offset */
+ fbconfig.xoffset = ldl_le_phys(&s->dma_as, value + 12);
+ fbconfig.yoffset = ldl_le_phys(&s->dma_as, value + 16);
+ bcm2835_fb_validate_config(&fbconfig);
+ fbconfig_updated = true;
+ /* fall through */
+ case 0x00040009: /* Get virtual offset */
+ stl_le_phys(&s->dma_as, value + 12, fbconfig.xoffset);
+ stl_le_phys(&s->dma_as, value + 16, fbconfig.yoffset);
+ resplen = 8;
+ break;
+ case 0x0004000a: /* Get/Test/Set overscan */
+ case 0x0004400a:
+ case 0x0004800a:
+ stl_le_phys(&s->dma_as, value + 12, 0);
+ stl_le_phys(&s->dma_as, value + 16, 0);
+ stl_le_phys(&s->dma_as, value + 20, 0);
+ stl_le_phys(&s->dma_as, value + 24, 0);
+ resplen = 16;
+ break;
+ case 0x0004800b: /* Set palette */
+ offset = ldl_le_phys(&s->dma_as, value + 12);
+ length = ldl_le_phys(&s->dma_as, value + 16);
+ n = 0;
+ while (n < length - offset) {
+ color = ldl_le_phys(&s->dma_as, value + 20 + (n << 2));
+ stl_le_phys(&s->dma_as,
+ s->fbdev->vcram_base + ((offset + n) << 2), color);
+ n++;
+ }
+ stl_le_phys(&s->dma_as, value + 12, 0);
+ resplen = 4;
+ break;
+
+ case 0x00060001: /* Get DMA channels */
+ /* channels 2-5 */
+ stl_le_phys(&s->dma_as, value + 12, 0x003C);
+ resplen = 4;
+ break;
+
+ case 0x00050001: /* Get command line */
+ resplen = 0;
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "bcm2835_property: unhandled tag 0x%08x\n", tag);
+ break;
+ }
+
+ trace_bcm2835_mbox_property(tag, bufsize, resplen);
+ if (tag == 0) {
+ break;
+ }
+
+ stl_le_phys(&s->dma_as, value + 8, (1 << 31) | resplen);
+ value += bufsize + 12;
+ }
+
+ /* Reconfigure framebuffer if required */
+ if (fbconfig_updated) {
+ bcm2835_fb_reconfigure(s->fbdev, &fbconfig);
+ }
+
+ /* Buffer response code */
+ stl_le_phys(&s->dma_as, s->addr + 4, (1 << 31));
+}
+
+static uint64_t bcm2835_property_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ BCM2835PropertyState *s = opaque;
+ uint32_t res = 0;
+
+ switch (offset) {
+ case MBOX_AS_DATA:
+ res = MBOX_CHAN_PROPERTY | s->addr;
+ s->pending = false;
+ qemu_set_irq(s->mbox_irq, 0);
+ break;
+
+ case MBOX_AS_PENDING:
+ res = s->pending;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n",
+ __func__, offset);
+ return 0;
+ }
+
+ return res;
+}
+
+static void bcm2835_property_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ BCM2835PropertyState *s = opaque;
+
+ switch (offset) {
+ case MBOX_AS_DATA:
+ /* bcm2835_mbox should check our pending status before pushing */
+ assert(!s->pending);
+ s->pending = true;
+ bcm2835_property_mbox_push(s, value);
+ qemu_set_irq(s->mbox_irq, 1);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n",
+ __func__, offset);
+ return;
+ }
+}
+
+static const MemoryRegionOps bcm2835_property_ops = {
+ .read = bcm2835_property_read,
+ .write = bcm2835_property_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static const VMStateDescription vmstate_bcm2835_property = {
+ .name = TYPE_BCM2835_PROPERTY,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_MACADDR(macaddr, BCM2835PropertyState),
+ VMSTATE_UINT32(addr, BCM2835PropertyState),
+ VMSTATE_BOOL(pending, BCM2835PropertyState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void bcm2835_property_init(Object *obj)
+{
+ BCM2835PropertyState *s = BCM2835_PROPERTY(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &bcm2835_property_ops, s,
+ TYPE_BCM2835_PROPERTY, 0x10);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(s), &s->mbox_irq);
+}
+
+static void bcm2835_property_reset(DeviceState *dev)
+{
+ BCM2835PropertyState *s = BCM2835_PROPERTY(dev);
+
+ s->pending = false;
+}
+
+static void bcm2835_property_realize(DeviceState *dev, Error **errp)
+{
+ BCM2835PropertyState *s = BCM2835_PROPERTY(dev);
+ Object *obj;
+
+ obj = object_property_get_link(OBJECT(dev), "fb", &error_abort);
+ s->fbdev = BCM2835_FB(obj);
+
+ obj = object_property_get_link(OBJECT(dev), "dma-mr", &error_abort);
+ s->dma_mr = MEMORY_REGION(obj);
+ address_space_init(&s->dma_as, s->dma_mr, TYPE_BCM2835_PROPERTY "-memory");
+
+ /* TODO: connect to MAC address of USB NIC device, once we emulate it */
+ qemu_macaddr_default_if_unset(&s->macaddr);
+
+ bcm2835_property_reset(dev);
+}
+
+static Property bcm2835_property_props[] = {
+ DEFINE_PROP_UINT32("board-rev", BCM2835PropertyState, board_rev, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void bcm2835_property_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, bcm2835_property_props);
+ dc->realize = bcm2835_property_realize;
+ dc->vmsd = &vmstate_bcm2835_property;
+}
+
+static TypeInfo bcm2835_property_info = {
+ .name = TYPE_BCM2835_PROPERTY,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2835PropertyState),
+ .class_init = bcm2835_property_class_init,
+ .instance_init = bcm2835_property_init,
+};
+
+static void bcm2835_property_register_types(void)
+{
+ type_register_static(&bcm2835_property_info);
+}
+
+type_init(bcm2835_property_register_types)
diff --git a/hw/misc/bcm2835_rng.c b/hw/misc/bcm2835_rng.c
new file mode 100644
index 000000000..d0c4e64e8
--- /dev/null
+++ b/hw/misc/bcm2835_rng.c
@@ -0,0 +1,147 @@
+/*
+ * BCM2835 Random Number Generator emulation
+ *
+ * Copyright (C) 2017 Marcin Chojnacki <marcinch7@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/guest-random.h"
+#include "qemu/module.h"
+#include "hw/misc/bcm2835_rng.h"
+#include "migration/vmstate.h"
+
+static uint32_t get_random_bytes(void)
+{
+ uint32_t res;
+
+ /*
+ * On failure we don't want to return the guest a non-random
+ * value in case they're really using it for cryptographic
+ * purposes, so the best we can do is die here.
+ * This shouldn't happen unless something's broken.
+ * In theory we could implement this device's full FIFO
+ * and interrupt semantics and then just stop filling the
+ * FIFO. That's a lot of work, though, so we assume any
+ * errors are systematic problems and trust that if we didn't
+ * fail as the guest inited then we won't fail later on
+ * mid-run.
+ */
+ qemu_guest_getrandom_nofail(&res, sizeof(res));
+ return res;
+}
+
+static uint64_t bcm2835_rng_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ BCM2835RngState *s = (BCM2835RngState *)opaque;
+ uint32_t res = 0;
+
+ assert(size == 4);
+
+ switch (offset) {
+ case 0x0: /* rng_ctrl */
+ res = s->rng_ctrl;
+ break;
+ case 0x4: /* rng_status */
+ res = s->rng_status | (1 << 24);
+ break;
+ case 0x8: /* rng_data */
+ res = get_random_bytes();
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "bcm2835_rng_read: Bad offset %x\n",
+ (int)offset);
+ res = 0;
+ break;
+ }
+
+ return res;
+}
+
+static void bcm2835_rng_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ BCM2835RngState *s = (BCM2835RngState *)opaque;
+
+ assert(size == 4);
+
+ switch (offset) {
+ case 0x0: /* rng_ctrl */
+ s->rng_ctrl = value;
+ break;
+ case 0x4: /* rng_status */
+ /* we shouldn't let the guest write to bits [31..20] */
+ s->rng_status &= ~0xFFFFF; /* clear 20 lower bits */
+ s->rng_status |= value & 0xFFFFF; /* set them to new value */
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "bcm2835_rng_write: Bad offset %x\n",
+ (int)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps bcm2835_rng_ops = {
+ .read = bcm2835_rng_read,
+ .write = bcm2835_rng_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_bcm2835_rng = {
+ .name = TYPE_BCM2835_RNG,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(rng_ctrl, BCM2835RngState),
+ VMSTATE_UINT32(rng_status, BCM2835RngState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void bcm2835_rng_init(Object *obj)
+{
+ BCM2835RngState *s = BCM2835_RNG(obj);
+
+ memory_region_init_io(&s->iomem, obj, &bcm2835_rng_ops, s,
+ TYPE_BCM2835_RNG, 0x10);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+}
+
+static void bcm2835_rng_reset(DeviceState *dev)
+{
+ BCM2835RngState *s = BCM2835_RNG(dev);
+
+ s->rng_ctrl = 0;
+ s->rng_status = 0;
+}
+
+static void bcm2835_rng_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = bcm2835_rng_reset;
+ dc->vmsd = &vmstate_bcm2835_rng;
+}
+
+static TypeInfo bcm2835_rng_info = {
+ .name = TYPE_BCM2835_RNG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2835RngState),
+ .class_init = bcm2835_rng_class_init,
+ .instance_init = bcm2835_rng_init,
+};
+
+static void bcm2835_rng_register_types(void)
+{
+ type_register_static(&bcm2835_rng_info);
+}
+
+type_init(bcm2835_rng_register_types)
diff --git a/hw/misc/bcm2835_thermal.c b/hw/misc/bcm2835_thermal.c
new file mode 100644
index 000000000..c6f3b1ad6
--- /dev/null
+++ b/hw/misc/bcm2835_thermal.c
@@ -0,0 +1,135 @@
+/*
+ * BCM2835 dummy thermal sensor
+ *
+ * Copyright (C) 2019 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/misc/bcm2835_thermal.h"
+#include "hw/registerfields.h"
+#include "migration/vmstate.h"
+
+REG32(CTL, 0)
+FIELD(CTL, POWER_DOWN, 0, 1)
+FIELD(CTL, RESET, 1, 1)
+FIELD(CTL, BANDGAP_CTRL, 2, 3)
+FIELD(CTL, INTERRUPT_ENABLE, 5, 1)
+FIELD(CTL, DIRECT, 6, 1)
+FIELD(CTL, INTERRUPT_CLEAR, 7, 1)
+FIELD(CTL, HOLD, 8, 10)
+FIELD(CTL, RESET_DELAY, 18, 8)
+FIELD(CTL, REGULATOR_ENABLE, 26, 1)
+
+REG32(STAT, 4)
+FIELD(STAT, DATA, 0, 10)
+FIELD(STAT, VALID, 10, 1)
+FIELD(STAT, INTERRUPT, 11, 1)
+
+#define THERMAL_OFFSET_C 412
+#define THERMAL_COEFF (-0.538f)
+
+static uint16_t bcm2835_thermal_temp2adc(int temp_C)
+{
+ return (temp_C - THERMAL_OFFSET_C) / THERMAL_COEFF;
+}
+
+static uint64_t bcm2835_thermal_read(void *opaque, hwaddr addr, unsigned size)
+{
+ Bcm2835ThermalState *s = BCM2835_THERMAL(opaque);
+ uint32_t val = 0;
+
+ switch (addr) {
+ case A_CTL:
+ val = s->ctl;
+ break;
+ case A_STAT:
+ /* Temperature is constantly 25°C. */
+ val = FIELD_DP32(bcm2835_thermal_temp2adc(25), STAT, VALID, true);
+ break;
+ default:
+ /* MemoryRegionOps are aligned, so this can not happen. */
+ g_assert_not_reached();
+ }
+ return val;
+}
+
+static void bcm2835_thermal_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ Bcm2835ThermalState *s = BCM2835_THERMAL(opaque);
+
+ switch (addr) {
+ case A_CTL:
+ s->ctl = value;
+ break;
+ case A_STAT:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: write 0x%" PRIx64
+ " to 0x%" HWADDR_PRIx "\n",
+ __func__, value, addr);
+ break;
+ default:
+ /* MemoryRegionOps are aligned, so this can not happen. */
+ g_assert_not_reached();
+ }
+}
+
+static const MemoryRegionOps bcm2835_thermal_ops = {
+ .read = bcm2835_thermal_read,
+ .write = bcm2835_thermal_write,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 4,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void bcm2835_thermal_reset(DeviceState *dev)
+{
+ Bcm2835ThermalState *s = BCM2835_THERMAL(dev);
+
+ s->ctl = 0;
+}
+
+static void bcm2835_thermal_realize(DeviceState *dev, Error **errp)
+{
+ Bcm2835ThermalState *s = BCM2835_THERMAL(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &bcm2835_thermal_ops,
+ s, TYPE_BCM2835_THERMAL, 8);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+}
+
+static const VMStateDescription bcm2835_thermal_vmstate = {
+ .name = "bcm2835_thermal",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ctl, Bcm2835ThermalState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void bcm2835_thermal_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = bcm2835_thermal_realize;
+ dc->reset = bcm2835_thermal_reset;
+ dc->vmsd = &bcm2835_thermal_vmstate;
+}
+
+static const TypeInfo bcm2835_thermal_info = {
+ .name = TYPE_BCM2835_THERMAL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Bcm2835ThermalState),
+ .class_init = bcm2835_thermal_class_init,
+};
+
+static void bcm2835_thermal_register_types(void)
+{
+ type_register_static(&bcm2835_thermal_info);
+}
+
+type_init(bcm2835_thermal_register_types)
diff --git a/hw/misc/cbus.c b/hw/misc/cbus.c
new file mode 100644
index 000000000..3c3721ad2
--- /dev/null
+++ b/hw/misc/cbus.c
@@ -0,0 +1,619 @@
+/*
+ * CBUS three-pin bus and the Retu / Betty / Tahvo / Vilma / Avilma /
+ * Hinku / Vinku / Ahne / Pihi chips used in various Nokia platforms.
+ * Based on reverse-engineering of a linux driver.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.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 or
+ * (at your option) version 3 of the License.
+ *
+ * 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 "hw/hw.h"
+#include "hw/irq.h"
+#include "hw/misc/cbus.h"
+#include "sysemu/runstate.h"
+
+//#define DEBUG
+
+typedef struct {
+ void *opaque;
+ void (*io)(void *opaque, int rw, int reg, uint16_t *val);
+ int addr;
+} CBusSlave;
+
+typedef struct {
+ CBus cbus;
+
+ int sel;
+ int dat;
+ int clk;
+ int bit;
+ int dir;
+ uint16_t val;
+ qemu_irq dat_out;
+
+ int addr;
+ int reg;
+ int rw;
+ enum {
+ cbus_address,
+ cbus_value,
+ } cycle;
+
+ CBusSlave *slave[8];
+} CBusPriv;
+
+static void cbus_io(CBusPriv *s)
+{
+ if (s->slave[s->addr])
+ s->slave[s->addr]->io(s->slave[s->addr]->opaque,
+ s->rw, s->reg, &s->val);
+ else
+ hw_error("%s: bad slave address %i\n", __func__, s->addr);
+}
+
+static void cbus_cycle(CBusPriv *s)
+{
+ switch (s->cycle) {
+ case cbus_address:
+ s->addr = (s->val >> 6) & 7;
+ s->rw = (s->val >> 5) & 1;
+ s->reg = (s->val >> 0) & 0x1f;
+
+ s->cycle = cbus_value;
+ s->bit = 15;
+ s->dir = !s->rw;
+ s->val = 0;
+
+ if (s->rw)
+ cbus_io(s);
+ break;
+
+ case cbus_value:
+ if (!s->rw)
+ cbus_io(s);
+
+ s->cycle = cbus_address;
+ s->bit = 8;
+ s->dir = 1;
+ s->val = 0;
+ break;
+ }
+}
+
+static void cbus_clk(void *opaque, int line, int level)
+{
+ CBusPriv *s = (CBusPriv *) opaque;
+
+ if (!s->sel && level && !s->clk) {
+ if (s->dir)
+ s->val |= s->dat << (s->bit --);
+ else
+ qemu_set_irq(s->dat_out, (s->val >> (s->bit --)) & 1);
+
+ if (s->bit < 0)
+ cbus_cycle(s);
+ }
+
+ s->clk = level;
+}
+
+static void cbus_dat(void *opaque, int line, int level)
+{
+ CBusPriv *s = (CBusPriv *) opaque;
+
+ s->dat = level;
+}
+
+static void cbus_sel(void *opaque, int line, int level)
+{
+ CBusPriv *s = (CBusPriv *) opaque;
+
+ if (!level) {
+ s->dir = 1;
+ s->bit = 8;
+ s->val = 0;
+ }
+
+ s->sel = level;
+}
+
+CBus *cbus_init(qemu_irq dat)
+{
+ CBusPriv *s = (CBusPriv *) g_malloc0(sizeof(*s));
+
+ s->dat_out = dat;
+ s->cbus.clk = qemu_allocate_irq(cbus_clk, s, 0);
+ s->cbus.dat = qemu_allocate_irq(cbus_dat, s, 0);
+ s->cbus.sel = qemu_allocate_irq(cbus_sel, s, 0);
+
+ s->sel = 1;
+ s->clk = 0;
+ s->dat = 0;
+
+ return &s->cbus;
+}
+
+void cbus_attach(CBus *bus, void *slave_opaque)
+{
+ CBusSlave *slave = (CBusSlave *) slave_opaque;
+ CBusPriv *s = (CBusPriv *) bus;
+
+ s->slave[slave->addr] = slave;
+}
+
+/* Retu/Vilma */
+typedef struct {
+ uint16_t irqst;
+ uint16_t irqen;
+ uint16_t cc[2];
+ int channel;
+ uint16_t result[16];
+ uint16_t sample;
+ uint16_t status;
+
+ struct {
+ uint16_t cal;
+ } rtc;
+
+ int is_vilma;
+ qemu_irq irq;
+ CBusSlave cbus;
+} CBusRetu;
+
+static void retu_interrupt_update(CBusRetu *s)
+{
+ qemu_set_irq(s->irq, s->irqst & ~s->irqen);
+}
+
+#define RETU_REG_ASICR 0x00 /* (RO) ASIC ID & revision */
+#define RETU_REG_IDR 0x01 /* (T) Interrupt ID */
+#define RETU_REG_IMR 0x02 /* (RW) Interrupt mask */
+#define RETU_REG_RTCDSR 0x03 /* (RW) RTC seconds register */
+#define RETU_REG_RTCHMR 0x04 /* (RO) RTC hours and minutes reg */
+#define RETU_REG_RTCHMAR 0x05 /* (RW) RTC hours and minutes set reg */
+#define RETU_REG_RTCCALR 0x06 /* (RW) RTC calibration register */
+#define RETU_REG_ADCR 0x08 /* (RW) ADC result register */
+#define RETU_REG_ADCSCR 0x09 /* (RW) ADC sample control register */
+#define RETU_REG_AFCR 0x0a /* (RW) AFC register */
+#define RETU_REG_ANTIFR 0x0b /* (RW) AntiF register */
+#define RETU_REG_CALIBR 0x0c /* (RW) CalibR register*/
+#define RETU_REG_CCR1 0x0d /* (RW) Common control register 1 */
+#define RETU_REG_CCR2 0x0e /* (RW) Common control register 2 */
+#define RETU_REG_RCTRL_CLR 0x0f /* (T) Regulator clear register */
+#define RETU_REG_RCTRL_SET 0x10 /* (T) Regulator set register */
+#define RETU_REG_TXCR 0x11 /* (RW) TxC register */
+#define RETU_REG_STATUS 0x16 /* (RO) Status register */
+#define RETU_REG_WATCHDOG 0x17 /* (RW) Watchdog register */
+#define RETU_REG_AUDTXR 0x18 /* (RW) Audio Codec Tx register */
+#define RETU_REG_AUDPAR 0x19 /* (RW) AudioPA register */
+#define RETU_REG_AUDRXR1 0x1a /* (RW) Audio receive register 1 */
+#define RETU_REG_AUDRXR2 0x1b /* (RW) Audio receive register 2 */
+#define RETU_REG_SGR1 0x1c /* (RW) */
+#define RETU_REG_SCR1 0x1d /* (RW) */
+#define RETU_REG_SGR2 0x1e /* (RW) */
+#define RETU_REG_SCR2 0x1f /* (RW) */
+
+/* Retu Interrupt sources */
+enum {
+ retu_int_pwr = 0, /* Power button */
+ retu_int_char = 1, /* Charger */
+ retu_int_rtcs = 2, /* Seconds */
+ retu_int_rtcm = 3, /* Minutes */
+ retu_int_rtcd = 4, /* Days */
+ retu_int_rtca = 5, /* Alarm */
+ retu_int_hook = 6, /* Hook */
+ retu_int_head = 7, /* Headset */
+ retu_int_adcs = 8, /* ADC sample */
+};
+
+/* Retu ADC channel wiring */
+enum {
+ retu_adc_bsi = 1, /* BSI */
+ retu_adc_batt_temp = 2, /* Battery temperature */
+ retu_adc_chg_volt = 3, /* Charger voltage */
+ retu_adc_head_det = 4, /* Headset detection */
+ retu_adc_hook_det = 5, /* Hook detection */
+ retu_adc_rf_gp = 6, /* RF GP */
+ retu_adc_tx_det = 7, /* Wideband Tx detection */
+ retu_adc_batt_volt = 8, /* Battery voltage */
+ retu_adc_sens = 10, /* Light sensor */
+ retu_adc_sens_temp = 11, /* Light sensor temperature */
+ retu_adc_bbatt_volt = 12, /* Backup battery voltage */
+ retu_adc_self_temp = 13, /* RETU temperature */
+};
+
+static inline uint16_t retu_read(CBusRetu *s, int reg)
+{
+#ifdef DEBUG
+ printf("RETU read at %02x\n", reg);
+#endif
+
+ switch (reg) {
+ case RETU_REG_ASICR:
+ return 0x0215 | (s->is_vilma << 7);
+
+ case RETU_REG_IDR: /* TODO: Or is this ffs(s->irqst)? */
+ return s->irqst;
+
+ case RETU_REG_IMR:
+ return s->irqen;
+
+ case RETU_REG_RTCDSR:
+ case RETU_REG_RTCHMR:
+ case RETU_REG_RTCHMAR:
+ /* TODO */
+ return 0x0000;
+
+ case RETU_REG_RTCCALR:
+ return s->rtc.cal;
+
+ case RETU_REG_ADCR:
+ return (s->channel << 10) | s->result[s->channel];
+ case RETU_REG_ADCSCR:
+ return s->sample;
+
+ case RETU_REG_AFCR:
+ case RETU_REG_ANTIFR:
+ case RETU_REG_CALIBR:
+ /* TODO */
+ return 0x0000;
+
+ case RETU_REG_CCR1:
+ return s->cc[0];
+ case RETU_REG_CCR2:
+ return s->cc[1];
+
+ case RETU_REG_RCTRL_CLR:
+ case RETU_REG_RCTRL_SET:
+ case RETU_REG_TXCR:
+ /* TODO */
+ return 0x0000;
+
+ case RETU_REG_STATUS:
+ return s->status;
+
+ case RETU_REG_WATCHDOG:
+ case RETU_REG_AUDTXR:
+ case RETU_REG_AUDPAR:
+ case RETU_REG_AUDRXR1:
+ case RETU_REG_AUDRXR2:
+ case RETU_REG_SGR1:
+ case RETU_REG_SCR1:
+ case RETU_REG_SGR2:
+ case RETU_REG_SCR2:
+ /* TODO */
+ return 0x0000;
+
+ default:
+ hw_error("%s: bad register %02x\n", __func__, reg);
+ }
+}
+
+static inline void retu_write(CBusRetu *s, int reg, uint16_t val)
+{
+#ifdef DEBUG
+ printf("RETU write of %04x at %02x\n", val, reg);
+#endif
+
+ switch (reg) {
+ case RETU_REG_IDR:
+ s->irqst ^= val;
+ retu_interrupt_update(s);
+ break;
+
+ case RETU_REG_IMR:
+ s->irqen = val;
+ retu_interrupt_update(s);
+ break;
+
+ case RETU_REG_RTCDSR:
+ case RETU_REG_RTCHMAR:
+ /* TODO */
+ break;
+
+ case RETU_REG_RTCCALR:
+ s->rtc.cal = val;
+ break;
+
+ case RETU_REG_ADCR:
+ s->channel = (val >> 10) & 0xf;
+ s->irqst |= 1 << retu_int_adcs;
+ retu_interrupt_update(s);
+ break;
+ case RETU_REG_ADCSCR:
+ s->sample &= ~val;
+ break;
+
+ case RETU_REG_AFCR:
+ case RETU_REG_ANTIFR:
+ case RETU_REG_CALIBR:
+
+ case RETU_REG_CCR1:
+ s->cc[0] = val;
+ break;
+ case RETU_REG_CCR2:
+ s->cc[1] = val;
+ break;
+
+ case RETU_REG_RCTRL_CLR:
+ case RETU_REG_RCTRL_SET:
+ /* TODO */
+ break;
+
+ case RETU_REG_WATCHDOG:
+ if (val == 0 && (s->cc[0] & 2))
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ break;
+
+ case RETU_REG_TXCR:
+ case RETU_REG_AUDTXR:
+ case RETU_REG_AUDPAR:
+ case RETU_REG_AUDRXR1:
+ case RETU_REG_AUDRXR2:
+ case RETU_REG_SGR1:
+ case RETU_REG_SCR1:
+ case RETU_REG_SGR2:
+ case RETU_REG_SCR2:
+ /* TODO */
+ break;
+
+ default:
+ hw_error("%s: bad register %02x\n", __func__, reg);
+ }
+}
+
+static void retu_io(void *opaque, int rw, int reg, uint16_t *val)
+{
+ CBusRetu *s = (CBusRetu *) opaque;
+
+ if (rw)
+ *val = retu_read(s, reg);
+ else
+ retu_write(s, reg, *val);
+}
+
+void *retu_init(qemu_irq irq, int vilma)
+{
+ CBusRetu *s = (CBusRetu *) g_malloc0(sizeof(*s));
+
+ s->irq = irq;
+ s->irqen = 0xffff;
+ s->irqst = 0x0000;
+ s->status = 0x0020;
+ s->is_vilma = !!vilma;
+ s->rtc.cal = 0x01;
+ s->result[retu_adc_bsi] = 0x3c2;
+ s->result[retu_adc_batt_temp] = 0x0fc;
+ s->result[retu_adc_chg_volt] = 0x165;
+ s->result[retu_adc_head_det] = 123;
+ s->result[retu_adc_hook_det] = 1023;
+ s->result[retu_adc_rf_gp] = 0x11;
+ s->result[retu_adc_tx_det] = 0x11;
+ s->result[retu_adc_batt_volt] = 0x250;
+ s->result[retu_adc_sens] = 2;
+ s->result[retu_adc_sens_temp] = 0x11;
+ s->result[retu_adc_bbatt_volt] = 0x3d0;
+ s->result[retu_adc_self_temp] = 0x330;
+
+ s->cbus.opaque = s;
+ s->cbus.io = retu_io;
+ s->cbus.addr = 1;
+
+ return &s->cbus;
+}
+
+void retu_key_event(void *retu, int state)
+{
+ CBusSlave *slave = (CBusSlave *) retu;
+ CBusRetu *s = (CBusRetu *) slave->opaque;
+
+ s->irqst |= 1 << retu_int_pwr;
+ retu_interrupt_update(s);
+
+ if (state)
+ s->status &= ~(1 << 5);
+ else
+ s->status |= 1 << 5;
+}
+
+#if 0
+static void retu_head_event(void *retu, int state)
+{
+ CBusSlave *slave = (CBusSlave *) retu;
+ CBusRetu *s = (CBusRetu *) slave->opaque;
+
+ if ((s->cc[0] & 0x500) == 0x500) { /* TODO: Which bits? */
+ /* TODO: reissue the interrupt every 100ms or so. */
+ s->irqst |= 1 << retu_int_head;
+ retu_interrupt_update(s);
+ }
+
+ if (state)
+ s->result[retu_adc_head_det] = 50;
+ else
+ s->result[retu_adc_head_det] = 123;
+}
+
+static void retu_hook_event(void *retu, int state)
+{
+ CBusSlave *slave = (CBusSlave *) retu;
+ CBusRetu *s = (CBusRetu *) slave->opaque;
+
+ if ((s->cc[0] & 0x500) == 0x500) {
+ /* TODO: reissue the interrupt every 100ms or so. */
+ s->irqst |= 1 << retu_int_hook;
+ retu_interrupt_update(s);
+ }
+
+ if (state)
+ s->result[retu_adc_hook_det] = 50;
+ else
+ s->result[retu_adc_hook_det] = 123;
+}
+#endif
+
+/* Tahvo/Betty */
+typedef struct {
+ uint16_t irqst;
+ uint16_t irqen;
+ uint8_t charger;
+ uint8_t backlight;
+ uint16_t usbr;
+ uint16_t power;
+
+ int is_betty;
+ qemu_irq irq;
+ CBusSlave cbus;
+} CBusTahvo;
+
+static void tahvo_interrupt_update(CBusTahvo *s)
+{
+ qemu_set_irq(s->irq, s->irqst & ~s->irqen);
+}
+
+#define TAHVO_REG_ASICR 0x00 /* (RO) ASIC ID & revision */
+#define TAHVO_REG_IDR 0x01 /* (T) Interrupt ID */
+#define TAHVO_REG_IDSR 0x02 /* (RO) Interrupt status */
+#define TAHVO_REG_IMR 0x03 /* (RW) Interrupt mask */
+#define TAHVO_REG_CHAPWMR 0x04 /* (RW) Charger PWM */
+#define TAHVO_REG_LEDPWMR 0x05 /* (RW) LED PWM */
+#define TAHVO_REG_USBR 0x06 /* (RW) USB control */
+#define TAHVO_REG_RCR 0x07 /* (RW) Some kind of power management */
+#define TAHVO_REG_CCR1 0x08 /* (RW) Common control register 1 */
+#define TAHVO_REG_CCR2 0x09 /* (RW) Common control register 2 */
+#define TAHVO_REG_TESTR1 0x0a /* (RW) Test register 1 */
+#define TAHVO_REG_TESTR2 0x0b /* (RW) Test register 2 */
+#define TAHVO_REG_NOPR 0x0c /* (RW) Number of periods */
+#define TAHVO_REG_FRR 0x0d /* (RO) FR */
+
+static inline uint16_t tahvo_read(CBusTahvo *s, int reg)
+{
+#ifdef DEBUG
+ printf("TAHVO read at %02x\n", reg);
+#endif
+
+ switch (reg) {
+ case TAHVO_REG_ASICR:
+ return 0x0021 | (s->is_betty ? 0x0b00 : 0x0300); /* 22 in N810 */
+
+ case TAHVO_REG_IDR:
+ case TAHVO_REG_IDSR: /* XXX: what does this do? */
+ return s->irqst;
+
+ case TAHVO_REG_IMR:
+ return s->irqen;
+
+ case TAHVO_REG_CHAPWMR:
+ return s->charger;
+
+ case TAHVO_REG_LEDPWMR:
+ return s->backlight;
+
+ case TAHVO_REG_USBR:
+ return s->usbr;
+
+ case TAHVO_REG_RCR:
+ return s->power;
+
+ case TAHVO_REG_CCR1:
+ case TAHVO_REG_CCR2:
+ case TAHVO_REG_TESTR1:
+ case TAHVO_REG_TESTR2:
+ case TAHVO_REG_NOPR:
+ case TAHVO_REG_FRR:
+ return 0x0000;
+
+ default:
+ hw_error("%s: bad register %02x\n", __func__, reg);
+ }
+}
+
+static inline void tahvo_write(CBusTahvo *s, int reg, uint16_t val)
+{
+#ifdef DEBUG
+ printf("TAHVO write of %04x at %02x\n", val, reg);
+#endif
+
+ switch (reg) {
+ case TAHVO_REG_IDR:
+ s->irqst ^= val;
+ tahvo_interrupt_update(s);
+ break;
+
+ case TAHVO_REG_IMR:
+ s->irqen = val;
+ tahvo_interrupt_update(s);
+ break;
+
+ case TAHVO_REG_CHAPWMR:
+ s->charger = val;
+ break;
+
+ case TAHVO_REG_LEDPWMR:
+ if (s->backlight != (val & 0x7f)) {
+ s->backlight = val & 0x7f;
+ printf("%s: LCD backlight now at %i / 127\n",
+ __func__, s->backlight);
+ }
+ break;
+
+ case TAHVO_REG_USBR:
+ s->usbr = val;
+ break;
+
+ case TAHVO_REG_RCR:
+ s->power = val;
+ break;
+
+ case TAHVO_REG_CCR1:
+ case TAHVO_REG_CCR2:
+ case TAHVO_REG_TESTR1:
+ case TAHVO_REG_TESTR2:
+ case TAHVO_REG_NOPR:
+ case TAHVO_REG_FRR:
+ break;
+
+ default:
+ hw_error("%s: bad register %02x\n", __func__, reg);
+ }
+}
+
+static void tahvo_io(void *opaque, int rw, int reg, uint16_t *val)
+{
+ CBusTahvo *s = (CBusTahvo *) opaque;
+
+ if (rw)
+ *val = tahvo_read(s, reg);
+ else
+ tahvo_write(s, reg, *val);
+}
+
+void *tahvo_init(qemu_irq irq, int betty)
+{
+ CBusTahvo *s = (CBusTahvo *) g_malloc0(sizeof(*s));
+
+ s->irq = irq;
+ s->irqen = 0xffff;
+ s->irqst = 0x0000;
+ s->is_betty = !!betty;
+
+ s->cbus.opaque = s;
+ s->cbus.io = tahvo_io;
+ s->cbus.addr = 2;
+
+ return &s->cbus;
+}
diff --git a/hw/misc/debugexit.c b/hw/misc/debugexit.c
new file mode 100644
index 000000000..ab6de69ce
--- /dev/null
+++ b/hw/misc/debugexit.c
@@ -0,0 +1,84 @@
+/*
+ * debug exit port emulation
+ *
+ * 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 or
+ * (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/isa/isa.h"
+#include "hw/qdev-properties.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+#define TYPE_ISA_DEBUG_EXIT_DEVICE "isa-debug-exit"
+OBJECT_DECLARE_SIMPLE_TYPE(ISADebugExitState, ISA_DEBUG_EXIT_DEVICE)
+
+struct ISADebugExitState {
+ ISADevice parent_obj;
+
+ uint32_t iobase;
+ uint32_t iosize;
+ MemoryRegion io;
+};
+
+static uint64_t debug_exit_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return 0;
+}
+
+static void debug_exit_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ exit((val << 1) | 1);
+}
+
+static const MemoryRegionOps debug_exit_ops = {
+ .read = debug_exit_read,
+ .write = debug_exit_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void debug_exit_realizefn(DeviceState *d, Error **errp)
+{
+ ISADevice *dev = ISA_DEVICE(d);
+ ISADebugExitState *isa = ISA_DEBUG_EXIT_DEVICE(d);
+
+ memory_region_init_io(&isa->io, OBJECT(dev), &debug_exit_ops, isa,
+ TYPE_ISA_DEBUG_EXIT_DEVICE, isa->iosize);
+ memory_region_add_subregion(isa_address_space_io(dev),
+ isa->iobase, &isa->io);
+}
+
+static Property debug_exit_properties[] = {
+ DEFINE_PROP_UINT32("iobase", ISADebugExitState, iobase, 0x501),
+ DEFINE_PROP_UINT32("iosize", ISADebugExitState, iosize, 0x02),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void debug_exit_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = debug_exit_realizefn;
+ device_class_set_props(dc, debug_exit_properties);
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo debug_exit_info = {
+ .name = TYPE_ISA_DEBUG_EXIT_DEVICE,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISADebugExitState),
+ .class_init = debug_exit_class_initfn,
+};
+
+static void debug_exit_register_types(void)
+{
+ type_register_static(&debug_exit_info);
+}
+
+type_init(debug_exit_register_types)
diff --git a/hw/misc/eccmemctl.c b/hw/misc/eccmemctl.c
new file mode 100644
index 000000000..c65806e3d
--- /dev/null
+++ b/hw/misc/eccmemctl.c
@@ -0,0 +1,357 @@
+/*
+ * QEMU Sparc Sun4m ECC memory controller emulation
+ *
+ * Copyright (c) 2007 Robert Reif
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "trace.h"
+#include "qom/object.h"
+
+/* There are 3 versions of this chip used in SMP sun4m systems:
+ * MCC (version 0, implementation 0) SS-600MP
+ * EMC (version 0, implementation 1) SS-10
+ * SMC (version 0, implementation 2) SS-10SX and SS-20
+ *
+ * Chipset docs:
+ * "Sun-4M System Architecture (revision 2.0) by Chuck Narad", 950-1373-01,
+ * http://mediacast.sun.com/users/Barton808/media/Sun4M_SystemArchitecture_edited2.pdf
+ */
+
+#define ECC_MCC 0x00000000
+#define ECC_EMC 0x10000000
+#define ECC_SMC 0x20000000
+
+/* Register indexes */
+#define ECC_MER 0 /* Memory Enable Register */
+#define ECC_MDR 1 /* Memory Delay Register */
+#define ECC_MFSR 2 /* Memory Fault Status Register */
+#define ECC_VCR 3 /* Video Configuration Register */
+#define ECC_MFAR0 4 /* Memory Fault Address Register 0 */
+#define ECC_MFAR1 5 /* Memory Fault Address Register 1 */
+#define ECC_DR 6 /* Diagnostic Register */
+#define ECC_ECR0 7 /* Event Count Register 0 */
+#define ECC_ECR1 8 /* Event Count Register 1 */
+
+/* ECC fault control register */
+#define ECC_MER_EE 0x00000001 /* Enable ECC checking */
+#define ECC_MER_EI 0x00000002 /* Enable Interrupts on
+ correctable errors */
+#define ECC_MER_MRR0 0x00000004 /* SIMM 0 */
+#define ECC_MER_MRR1 0x00000008 /* SIMM 1 */
+#define ECC_MER_MRR2 0x00000010 /* SIMM 2 */
+#define ECC_MER_MRR3 0x00000020 /* SIMM 3 */
+#define ECC_MER_MRR4 0x00000040 /* SIMM 4 */
+#define ECC_MER_MRR5 0x00000080 /* SIMM 5 */
+#define ECC_MER_MRR6 0x00000100 /* SIMM 6 */
+#define ECC_MER_MRR7 0x00000200 /* SIMM 7 */
+#define ECC_MER_REU 0x00000100 /* Memory Refresh Enable (600MP) */
+#define ECC_MER_MRR 0x000003fc /* MRR mask */
+#define ECC_MER_A 0x00000400 /* Memory controller addr map select */
+#define ECC_MER_DCI 0x00000800 /* Disables Coherent Invalidate ACK */
+#define ECC_MER_VER 0x0f000000 /* Version */
+#define ECC_MER_IMPL 0xf0000000 /* Implementation */
+#define ECC_MER_MASK_0 0x00000103 /* Version 0 (MCC) mask */
+#define ECC_MER_MASK_1 0x00000bff /* Version 1 (EMC) mask */
+#define ECC_MER_MASK_2 0x00000bff /* Version 2 (SMC) mask */
+
+/* ECC memory delay register */
+#define ECC_MDR_RRI 0x000003ff /* Refresh Request Interval */
+#define ECC_MDR_MI 0x00001c00 /* MIH Delay */
+#define ECC_MDR_CI 0x0000e000 /* Coherent Invalidate Delay */
+#define ECC_MDR_MDL 0x001f0000 /* MBus Master arbitration delay */
+#define ECC_MDR_MDH 0x03e00000 /* MBus Master arbitration delay */
+#define ECC_MDR_GAD 0x7c000000 /* Graphics Arbitration Delay */
+#define ECC_MDR_RSC 0x80000000 /* Refresh load control */
+#define ECC_MDR_MASK 0x7fffffff
+
+/* ECC fault status register */
+#define ECC_MFSR_CE 0x00000001 /* Correctable error */
+#define ECC_MFSR_BS 0x00000002 /* C2 graphics bad slot access */
+#define ECC_MFSR_TO 0x00000004 /* Timeout on write */
+#define ECC_MFSR_UE 0x00000008 /* Uncorrectable error */
+#define ECC_MFSR_DW 0x000000f0 /* Index of double word in block */
+#define ECC_MFSR_SYND 0x0000ff00 /* Syndrome for correctable error */
+#define ECC_MFSR_ME 0x00010000 /* Multiple errors */
+#define ECC_MFSR_C2ERR 0x00020000 /* C2 graphics error */
+
+/* ECC fault address register 0 */
+#define ECC_MFAR0_PADDR 0x0000000f /* PA[32-35] */
+#define ECC_MFAR0_TYPE 0x000000f0 /* Transaction type */
+#define ECC_MFAR0_SIZE 0x00000700 /* Transaction size */
+#define ECC_MFAR0_CACHE 0x00000800 /* Mapped cacheable */
+#define ECC_MFAR0_LOCK 0x00001000 /* Error occurred in atomic cycle */
+#define ECC_MFAR0_BMODE 0x00002000 /* Boot mode */
+#define ECC_MFAR0_VADDR 0x003fc000 /* VA[12-19] (superset bits) */
+#define ECC_MFAR0_S 0x08000000 /* Supervisor mode */
+#define ECC_MFARO_MID 0xf0000000 /* Module ID */
+
+/* ECC diagnostic register */
+#define ECC_DR_CBX 0x00000001
+#define ECC_DR_CB0 0x00000002
+#define ECC_DR_CB1 0x00000004
+#define ECC_DR_CB2 0x00000008
+#define ECC_DR_CB4 0x00000010
+#define ECC_DR_CB8 0x00000020
+#define ECC_DR_CB16 0x00000040
+#define ECC_DR_CB32 0x00000080
+#define ECC_DR_DMODE 0x00000c00
+
+#define ECC_NREGS 9
+#define ECC_SIZE (ECC_NREGS * sizeof(uint32_t))
+
+#define ECC_DIAG_SIZE 4
+#define ECC_DIAG_MASK (ECC_DIAG_SIZE - 1)
+
+#define TYPE_ECC_MEMCTL "eccmemctl"
+OBJECT_DECLARE_SIMPLE_TYPE(ECCState, ECC_MEMCTL)
+
+struct ECCState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem, iomem_diag;
+ qemu_irq irq;
+ uint32_t regs[ECC_NREGS];
+ uint8_t diag[ECC_DIAG_SIZE];
+ uint32_t version;
+};
+
+static void ecc_mem_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ ECCState *s = opaque;
+
+ switch (addr >> 2) {
+ case ECC_MER:
+ if (s->version == ECC_MCC)
+ s->regs[ECC_MER] = (val & ECC_MER_MASK_0);
+ else if (s->version == ECC_EMC)
+ s->regs[ECC_MER] = s->version | (val & ECC_MER_MASK_1);
+ else if (s->version == ECC_SMC)
+ s->regs[ECC_MER] = s->version | (val & ECC_MER_MASK_2);
+ trace_ecc_mem_writel_mer(val);
+ break;
+ case ECC_MDR:
+ s->regs[ECC_MDR] = val & ECC_MDR_MASK;
+ trace_ecc_mem_writel_mdr(val);
+ break;
+ case ECC_MFSR:
+ s->regs[ECC_MFSR] = val;
+ qemu_irq_lower(s->irq);
+ trace_ecc_mem_writel_mfsr(val);
+ break;
+ case ECC_VCR:
+ s->regs[ECC_VCR] = val;
+ trace_ecc_mem_writel_vcr(val);
+ break;
+ case ECC_DR:
+ s->regs[ECC_DR] = val;
+ trace_ecc_mem_writel_dr(val);
+ break;
+ case ECC_ECR0:
+ s->regs[ECC_ECR0] = val;
+ trace_ecc_mem_writel_ecr0(val);
+ break;
+ case ECC_ECR1:
+ s->regs[ECC_ECR0] = val;
+ trace_ecc_mem_writel_ecr1(val);
+ break;
+ }
+}
+
+static uint64_t ecc_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ ECCState *s = opaque;
+ uint32_t ret = 0;
+
+ switch (addr >> 2) {
+ case ECC_MER:
+ ret = s->regs[ECC_MER];
+ trace_ecc_mem_readl_mer(ret);
+ break;
+ case ECC_MDR:
+ ret = s->regs[ECC_MDR];
+ trace_ecc_mem_readl_mdr(ret);
+ break;
+ case ECC_MFSR:
+ ret = s->regs[ECC_MFSR];
+ trace_ecc_mem_readl_mfsr(ret);
+ break;
+ case ECC_VCR:
+ ret = s->regs[ECC_VCR];
+ trace_ecc_mem_readl_vcr(ret);
+ break;
+ case ECC_MFAR0:
+ ret = s->regs[ECC_MFAR0];
+ trace_ecc_mem_readl_mfar0(ret);
+ break;
+ case ECC_MFAR1:
+ ret = s->regs[ECC_MFAR1];
+ trace_ecc_mem_readl_mfar1(ret);
+ break;
+ case ECC_DR:
+ ret = s->regs[ECC_DR];
+ trace_ecc_mem_readl_dr(ret);
+ break;
+ case ECC_ECR0:
+ ret = s->regs[ECC_ECR0];
+ trace_ecc_mem_readl_ecr0(ret);
+ break;
+ case ECC_ECR1:
+ ret = s->regs[ECC_ECR0];
+ trace_ecc_mem_readl_ecr1(ret);
+ break;
+ }
+ return ret;
+}
+
+static const MemoryRegionOps ecc_mem_ops = {
+ .read = ecc_mem_read,
+ .write = ecc_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void ecc_diag_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ ECCState *s = opaque;
+
+ trace_ecc_diag_mem_writeb(addr, val);
+ s->diag[addr & ECC_DIAG_MASK] = val;
+}
+
+static uint64_t ecc_diag_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ ECCState *s = opaque;
+ uint32_t ret = s->diag[(int)addr];
+
+ trace_ecc_diag_mem_readb(addr, ret);
+ return ret;
+}
+
+static const MemoryRegionOps ecc_diag_mem_ops = {
+ .read = ecc_diag_mem_read,
+ .write = ecc_diag_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const VMStateDescription vmstate_ecc = {
+ .name ="ECC",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, ECCState, ECC_NREGS),
+ VMSTATE_BUFFER(diag, ECCState),
+ VMSTATE_UINT32(version, ECCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ecc_reset(DeviceState *d)
+{
+ ECCState *s = ECC_MEMCTL(d);
+
+ if (s->version == ECC_MCC) {
+ s->regs[ECC_MER] &= ECC_MER_REU;
+ } else {
+ s->regs[ECC_MER] &= (ECC_MER_VER | ECC_MER_IMPL | ECC_MER_MRR |
+ ECC_MER_DCI);
+ }
+ s->regs[ECC_MDR] = 0x20;
+ s->regs[ECC_MFSR] = 0;
+ s->regs[ECC_VCR] = 0;
+ s->regs[ECC_MFAR0] = 0x07c00000;
+ s->regs[ECC_MFAR1] = 0;
+ s->regs[ECC_DR] = 0;
+ s->regs[ECC_ECR0] = 0;
+ s->regs[ECC_ECR1] = 0;
+}
+
+static void ecc_init(Object *obj)
+{
+ ECCState *s = ECC_MEMCTL(obj);
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ memory_region_init_io(&s->iomem, obj, &ecc_mem_ops, s, "ecc", ECC_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+}
+
+static void ecc_realize(DeviceState *dev, Error **errp)
+{
+ ECCState *s = ECC_MEMCTL(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ s->regs[0] = s->version;
+
+ if (s->version == ECC_MCC) { // SS-600MP only
+ memory_region_init_io(&s->iomem_diag, OBJECT(dev), &ecc_diag_mem_ops, s,
+ "ecc.diag", ECC_DIAG_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem_diag);
+ }
+}
+
+static Property ecc_properties[] = {
+ DEFINE_PROP_UINT32("version", ECCState, version, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ecc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = ecc_realize;
+ dc->reset = ecc_reset;
+ dc->vmsd = &vmstate_ecc;
+ device_class_set_props(dc, ecc_properties);
+}
+
+static const TypeInfo ecc_info = {
+ .name = TYPE_ECC_MEMCTL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ECCState),
+ .instance_init = ecc_init,
+ .class_init = ecc_class_init,
+};
+
+
+static void ecc_register_types(void)
+{
+ type_register_static(&ecc_info);
+}
+
+type_init(ecc_register_types)
diff --git a/hw/misc/edu.c b/hw/misc/edu.c
new file mode 100644
index 000000000..e935c418d
--- /dev/null
+++ b/hw/misc/edu.c
@@ -0,0 +1,442 @@
+/*
+ * QEMU educational PCI device
+ *
+ * Copyright (c) 2012-2015 Jiri Slaby
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/pci/pci.h"
+#include "hw/hw.h"
+#include "hw/pci/msi.h"
+#include "qemu/timer.h"
+#include "qom/object.h"
+#include "qemu/main-loop.h" /* iothread mutex */
+#include "qemu/module.h"
+#include "qapi/visitor.h"
+
+#define TYPE_PCI_EDU_DEVICE "edu"
+typedef struct EduState EduState;
+DECLARE_INSTANCE_CHECKER(EduState, EDU,
+ TYPE_PCI_EDU_DEVICE)
+
+#define FACT_IRQ 0x00000001
+#define DMA_IRQ 0x00000100
+
+#define DMA_START 0x40000
+#define DMA_SIZE 4096
+
+struct EduState {
+ PCIDevice pdev;
+ MemoryRegion mmio;
+
+ QemuThread thread;
+ QemuMutex thr_mutex;
+ QemuCond thr_cond;
+ bool stopping;
+
+ uint32_t addr4;
+ uint32_t fact;
+#define EDU_STATUS_COMPUTING 0x01
+#define EDU_STATUS_IRQFACT 0x80
+ uint32_t status;
+
+ uint32_t irq_status;
+
+#define EDU_DMA_RUN 0x1
+#define EDU_DMA_DIR(cmd) (((cmd) & 0x2) >> 1)
+# define EDU_DMA_FROM_PCI 0
+# define EDU_DMA_TO_PCI 1
+#define EDU_DMA_IRQ 0x4
+ struct dma_state {
+ dma_addr_t src;
+ dma_addr_t dst;
+ dma_addr_t cnt;
+ dma_addr_t cmd;
+ } dma;
+ QEMUTimer dma_timer;
+ char dma_buf[DMA_SIZE];
+ uint64_t dma_mask;
+};
+
+static bool edu_msi_enabled(EduState *edu)
+{
+ return msi_enabled(&edu->pdev);
+}
+
+static void edu_raise_irq(EduState *edu, uint32_t val)
+{
+ edu->irq_status |= val;
+ if (edu->irq_status) {
+ if (edu_msi_enabled(edu)) {
+ msi_notify(&edu->pdev, 0);
+ } else {
+ pci_set_irq(&edu->pdev, 1);
+ }
+ }
+}
+
+static void edu_lower_irq(EduState *edu, uint32_t val)
+{
+ edu->irq_status &= ~val;
+
+ if (!edu->irq_status && !edu_msi_enabled(edu)) {
+ pci_set_irq(&edu->pdev, 0);
+ }
+}
+
+static bool within(uint64_t addr, uint64_t start, uint64_t end)
+{
+ return start <= addr && addr < end;
+}
+
+static void edu_check_range(uint64_t addr, uint64_t size1, uint64_t start,
+ uint64_t size2)
+{
+ uint64_t end1 = addr + size1;
+ uint64_t end2 = start + size2;
+
+ if (within(addr, start, end2) &&
+ end1 > addr && within(end1, start, end2)) {
+ return;
+ }
+
+ hw_error("EDU: DMA range 0x%016"PRIx64"-0x%016"PRIx64
+ " out of bounds (0x%016"PRIx64"-0x%016"PRIx64")!",
+ addr, end1 - 1, start, end2 - 1);
+}
+
+static dma_addr_t edu_clamp_addr(const EduState *edu, dma_addr_t addr)
+{
+ dma_addr_t res = addr & edu->dma_mask;
+
+ if (addr != res) {
+ printf("EDU: clamping DMA %#.16"PRIx64" to %#.16"PRIx64"!\n", addr, res);
+ }
+
+ return res;
+}
+
+static void edu_dma_timer(void *opaque)
+{
+ EduState *edu = opaque;
+ bool raise_irq = false;
+
+ if (!(edu->dma.cmd & EDU_DMA_RUN)) {
+ return;
+ }
+
+ if (EDU_DMA_DIR(edu->dma.cmd) == EDU_DMA_FROM_PCI) {
+ uint64_t dst = edu->dma.dst;
+ edu_check_range(dst, edu->dma.cnt, DMA_START, DMA_SIZE);
+ dst -= DMA_START;
+ pci_dma_read(&edu->pdev, edu_clamp_addr(edu, edu->dma.src),
+ edu->dma_buf + dst, edu->dma.cnt);
+ } else {
+ uint64_t src = edu->dma.src;
+ edu_check_range(src, edu->dma.cnt, DMA_START, DMA_SIZE);
+ src -= DMA_START;
+ pci_dma_write(&edu->pdev, edu_clamp_addr(edu, edu->dma.dst),
+ edu->dma_buf + src, edu->dma.cnt);
+ }
+
+ edu->dma.cmd &= ~EDU_DMA_RUN;
+ if (edu->dma.cmd & EDU_DMA_IRQ) {
+ raise_irq = true;
+ }
+
+ if (raise_irq) {
+ edu_raise_irq(edu, DMA_IRQ);
+ }
+}
+
+static void dma_rw(EduState *edu, bool write, dma_addr_t *val, dma_addr_t *dma,
+ bool timer)
+{
+ if (write && (edu->dma.cmd & EDU_DMA_RUN)) {
+ return;
+ }
+
+ if (write) {
+ *dma = *val;
+ } else {
+ *val = *dma;
+ }
+
+ if (timer) {
+ timer_mod(&edu->dma_timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 100);
+ }
+}
+
+static uint64_t edu_mmio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ EduState *edu = opaque;
+ uint64_t val = ~0ULL;
+
+ if (addr < 0x80 && size != 4) {
+ return val;
+ }
+
+ if (addr >= 0x80 && size != 4 && size != 8) {
+ return val;
+ }
+
+ switch (addr) {
+ case 0x00:
+ val = 0x010000edu;
+ break;
+ case 0x04:
+ val = edu->addr4;
+ break;
+ case 0x08:
+ qemu_mutex_lock(&edu->thr_mutex);
+ val = edu->fact;
+ qemu_mutex_unlock(&edu->thr_mutex);
+ break;
+ case 0x20:
+ val = qatomic_read(&edu->status);
+ break;
+ case 0x24:
+ val = edu->irq_status;
+ break;
+ case 0x80:
+ dma_rw(edu, false, &val, &edu->dma.src, false);
+ break;
+ case 0x88:
+ dma_rw(edu, false, &val, &edu->dma.dst, false);
+ break;
+ case 0x90:
+ dma_rw(edu, false, &val, &edu->dma.cnt, false);
+ break;
+ case 0x98:
+ dma_rw(edu, false, &val, &edu->dma.cmd, false);
+ break;
+ }
+
+ return val;
+}
+
+static void edu_mmio_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ EduState *edu = opaque;
+
+ if (addr < 0x80 && size != 4) {
+ return;
+ }
+
+ if (addr >= 0x80 && size != 4 && size != 8) {
+ return;
+ }
+
+ switch (addr) {
+ case 0x04:
+ edu->addr4 = ~val;
+ break;
+ case 0x08:
+ if (qatomic_read(&edu->status) & EDU_STATUS_COMPUTING) {
+ break;
+ }
+ /* EDU_STATUS_COMPUTING cannot go 0->1 concurrently, because it is only
+ * set in this function and it is under the iothread mutex.
+ */
+ qemu_mutex_lock(&edu->thr_mutex);
+ edu->fact = val;
+ qatomic_or(&edu->status, EDU_STATUS_COMPUTING);
+ qemu_cond_signal(&edu->thr_cond);
+ qemu_mutex_unlock(&edu->thr_mutex);
+ break;
+ case 0x20:
+ if (val & EDU_STATUS_IRQFACT) {
+ qatomic_or(&edu->status, EDU_STATUS_IRQFACT);
+ } else {
+ qatomic_and(&edu->status, ~EDU_STATUS_IRQFACT);
+ }
+ break;
+ case 0x60:
+ edu_raise_irq(edu, val);
+ break;
+ case 0x64:
+ edu_lower_irq(edu, val);
+ break;
+ case 0x80:
+ dma_rw(edu, true, &val, &edu->dma.src, false);
+ break;
+ case 0x88:
+ dma_rw(edu, true, &val, &edu->dma.dst, false);
+ break;
+ case 0x90:
+ dma_rw(edu, true, &val, &edu->dma.cnt, false);
+ break;
+ case 0x98:
+ if (!(val & EDU_DMA_RUN)) {
+ break;
+ }
+ dma_rw(edu, true, &val, &edu->dma.cmd, true);
+ break;
+ }
+}
+
+static const MemoryRegionOps edu_mmio_ops = {
+ .read = edu_mmio_read,
+ .write = edu_mmio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+
+};
+
+/*
+ * We purposely use a thread, so that users are forced to wait for the status
+ * register.
+ */
+static void *edu_fact_thread(void *opaque)
+{
+ EduState *edu = opaque;
+
+ while (1) {
+ uint32_t val, ret = 1;
+
+ qemu_mutex_lock(&edu->thr_mutex);
+ while ((qatomic_read(&edu->status) & EDU_STATUS_COMPUTING) == 0 &&
+ !edu->stopping) {
+ qemu_cond_wait(&edu->thr_cond, &edu->thr_mutex);
+ }
+
+ if (edu->stopping) {
+ qemu_mutex_unlock(&edu->thr_mutex);
+ break;
+ }
+
+ val = edu->fact;
+ qemu_mutex_unlock(&edu->thr_mutex);
+
+ while (val > 0) {
+ ret *= val--;
+ }
+
+ /*
+ * We should sleep for a random period here, so that students are
+ * forced to check the status properly.
+ */
+
+ qemu_mutex_lock(&edu->thr_mutex);
+ edu->fact = ret;
+ qemu_mutex_unlock(&edu->thr_mutex);
+ qatomic_and(&edu->status, ~EDU_STATUS_COMPUTING);
+
+ if (qatomic_read(&edu->status) & EDU_STATUS_IRQFACT) {
+ qemu_mutex_lock_iothread();
+ edu_raise_irq(edu, FACT_IRQ);
+ qemu_mutex_unlock_iothread();
+ }
+ }
+
+ return NULL;
+}
+
+static void pci_edu_realize(PCIDevice *pdev, Error **errp)
+{
+ EduState *edu = EDU(pdev);
+ uint8_t *pci_conf = pdev->config;
+
+ pci_config_set_interrupt_pin(pci_conf, 1);
+
+ if (msi_init(pdev, 0, 1, true, false, errp)) {
+ return;
+ }
+
+ timer_init_ms(&edu->dma_timer, QEMU_CLOCK_VIRTUAL, edu_dma_timer, edu);
+
+ qemu_mutex_init(&edu->thr_mutex);
+ qemu_cond_init(&edu->thr_cond);
+ qemu_thread_create(&edu->thread, "edu", edu_fact_thread,
+ edu, QEMU_THREAD_JOINABLE);
+
+ memory_region_init_io(&edu->mmio, OBJECT(edu), &edu_mmio_ops, edu,
+ "edu-mmio", 1 * MiB);
+ pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &edu->mmio);
+}
+
+static void pci_edu_uninit(PCIDevice *pdev)
+{
+ EduState *edu = EDU(pdev);
+
+ qemu_mutex_lock(&edu->thr_mutex);
+ edu->stopping = true;
+ qemu_mutex_unlock(&edu->thr_mutex);
+ qemu_cond_signal(&edu->thr_cond);
+ qemu_thread_join(&edu->thread);
+
+ qemu_cond_destroy(&edu->thr_cond);
+ qemu_mutex_destroy(&edu->thr_mutex);
+
+ timer_del(&edu->dma_timer);
+ msi_uninit(pdev);
+}
+
+static void edu_instance_init(Object *obj)
+{
+ EduState *edu = EDU(obj);
+
+ edu->dma_mask = (1UL << 28) - 1;
+ object_property_add_uint64_ptr(obj, "dma_mask",
+ &edu->dma_mask, OBJ_PROP_FLAG_READWRITE);
+}
+
+static void edu_class_init(ObjectClass *class, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(class);
+
+ k->realize = pci_edu_realize;
+ k->exit = pci_edu_uninit;
+ k->vendor_id = PCI_VENDOR_ID_QEMU;
+ k->device_id = 0x11e8;
+ k->revision = 0x10;
+ k->class_id = PCI_CLASS_OTHERS;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static void pci_edu_register_types(void)
+{
+ static InterfaceInfo interfaces[] = {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ };
+ static const TypeInfo edu_info = {
+ .name = TYPE_PCI_EDU_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(EduState),
+ .instance_init = edu_instance_init,
+ .class_init = edu_class_init,
+ .interfaces = interfaces,
+ };
+
+ type_register_static(&edu_info);
+}
+type_init(pci_edu_register_types)
diff --git a/hw/misc/empty_slot.c b/hw/misc/empty_slot.c
new file mode 100644
index 000000000..37b0ddfb0
--- /dev/null
+++ b/hw/misc/empty_slot.c
@@ -0,0 +1,109 @@
+/*
+ * QEMU Empty Slot
+ *
+ * The empty_slot device emulates known to a bus but not connected devices.
+ *
+ * Copyright (c) 2010 Artyom Tarasenko
+ *
+ * This code is licensed under the GNU GPL v2 or (at your option) any later
+ * version.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/empty_slot.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "qom/object.h"
+
+#define TYPE_EMPTY_SLOT "empty_slot"
+OBJECT_DECLARE_SIMPLE_TYPE(EmptySlot, EMPTY_SLOT)
+
+struct EmptySlot {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ char *name;
+ uint64_t size;
+};
+
+static uint64_t empty_slot_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ EmptySlot *s = EMPTY_SLOT(opaque);
+
+ trace_empty_slot_write(addr, size << 1, 0, size, s->name);
+
+ return 0;
+}
+
+static void empty_slot_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ EmptySlot *s = EMPTY_SLOT(opaque);
+
+ trace_empty_slot_write(addr, size << 1, val, size, s->name);
+}
+
+static const MemoryRegionOps empty_slot_ops = {
+ .read = empty_slot_read,
+ .write = empty_slot_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void empty_slot_init(const char *name, hwaddr addr, uint64_t slot_size)
+{
+ if (slot_size > 0) {
+ /* Only empty slots larger than 0 byte need handling. */
+ DeviceState *dev;
+
+ dev = qdev_new(TYPE_EMPTY_SLOT);
+
+ qdev_prop_set_uint64(dev, "size", slot_size);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+ sysbus_mmio_map_overlap(SYS_BUS_DEVICE(dev), 0, addr, -10000);
+ }
+}
+
+static void empty_slot_realize(DeviceState *dev, Error **errp)
+{
+ EmptySlot *s = EMPTY_SLOT(dev);
+
+ if (s->name == NULL) {
+ s->name = g_strdup("empty-slot");
+ }
+ memory_region_init_io(&s->iomem, OBJECT(s), &empty_slot_ops, s,
+ s->name, s->size);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+}
+
+static Property empty_slot_properties[] = {
+ DEFINE_PROP_UINT64("size", EmptySlot, size, 0),
+ DEFINE_PROP_STRING("name", EmptySlot, name),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void empty_slot_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = empty_slot_realize;
+ device_class_set_props(dc, empty_slot_properties);
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo empty_slot_info = {
+ .name = TYPE_EMPTY_SLOT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(EmptySlot),
+ .class_init = empty_slot_class_init,
+};
+
+static void empty_slot_register_types(void)
+{
+ type_register_static(&empty_slot_info);
+}
+
+type_init(empty_slot_register_types)
diff --git a/hw/misc/exynos4210_clk.c b/hw/misc/exynos4210_clk.c
new file mode 100644
index 000000000..58cec282f
--- /dev/null
+++ b/hw/misc/exynos4210_clk.c
@@ -0,0 +1,166 @@
+/*
+ * Exynos4210 Clock Controller Emulation
+ *
+ * Copyright (c) 2017 Krzysztof Kozlowski <krzk@kernel.org>
+ *
+ * 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 "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+#define TYPE_EXYNOS4210_CLK "exynos4210.clk"
+OBJECT_DECLARE_SIMPLE_TYPE(Exynos4210ClkState, EXYNOS4210_CLK)
+
+#define CLK_PLL_LOCKED BIT(29)
+
+#define EXYNOS4210_CLK_REGS_MEM_SIZE 0x15104
+
+typedef struct Exynos4210Reg {
+ const char *name; /* for debug only */
+ uint32_t offset;
+ uint32_t reset_value;
+} Exynos4210Reg;
+
+/* Clock controller register base: 0x10030000 */
+static const Exynos4210Reg exynos4210_clk_regs[] = {
+ {"EPLL_LOCK", 0xc010, 0x00000fff},
+ {"VPLL_LOCK", 0xc020, 0x00000fff},
+ {"EPLL_CON0", 0xc110, 0x00300301 | CLK_PLL_LOCKED},
+ {"EPLL_CON1", 0xc114, 0x00000000},
+ {"VPLL_CON0", 0xc120, 0x00240201 | CLK_PLL_LOCKED},
+ {"VPLL_CON1", 0xc124, 0x66010464},
+ {"APLL_LOCK", 0x14000, 0x00000fff},
+ {"MPLL_LOCK", 0x14004, 0x00000fff},
+ {"APLL_CON0", 0x14100, 0x00c80601 | CLK_PLL_LOCKED},
+ {"APLL_CON1", 0x14104, 0x0000001c},
+ {"MPLL_CON0", 0x14108, 0x00c80601 | CLK_PLL_LOCKED},
+ {"MPLL_CON1", 0x1410c, 0x0000001c},
+};
+
+#define EXYNOS4210_REGS_NUM ARRAY_SIZE(exynos4210_clk_regs)
+
+struct Exynos4210ClkState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t reg[EXYNOS4210_REGS_NUM];
+};
+
+static uint64_t exynos4210_clk_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const Exynos4210ClkState *s = (Exynos4210ClkState *)opaque;
+ const Exynos4210Reg *regs = exynos4210_clk_regs;
+ unsigned int i;
+
+ for (i = 0; i < EXYNOS4210_REGS_NUM; i++) {
+ if (regs->offset == offset) {
+ return s->reg[i];
+ }
+ regs++;
+ }
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+ return 0;
+}
+
+static void exynos4210_clk_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4210ClkState *s = (Exynos4210ClkState *)opaque;
+ const Exynos4210Reg *regs = exynos4210_clk_regs;
+ unsigned int i;
+
+ for (i = 0; i < EXYNOS4210_REGS_NUM; i++) {
+ if (regs->offset == offset) {
+ s->reg[i] = val;
+ return;
+ }
+ regs++;
+ }
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write offset 0x%04x\n",
+ __func__, (uint32_t)offset);
+}
+
+static const MemoryRegionOps exynos4210_clk_ops = {
+ .read = exynos4210_clk_read,
+ .write = exynos4210_clk_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false
+ }
+};
+
+static void exynos4210_clk_reset(DeviceState *dev)
+{
+ Exynos4210ClkState *s = EXYNOS4210_CLK(dev);
+ unsigned int i;
+
+ /* Set default values for registers */
+ for (i = 0; i < EXYNOS4210_REGS_NUM; i++) {
+ s->reg[i] = exynos4210_clk_regs[i].reset_value;
+ }
+}
+
+static void exynos4210_clk_init(Object *obj)
+{
+ Exynos4210ClkState *s = EXYNOS4210_CLK(obj);
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+
+ /* memory mapping */
+ memory_region_init_io(&s->iomem, obj, &exynos4210_clk_ops, s,
+ TYPE_EXYNOS4210_CLK, EXYNOS4210_CLK_REGS_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+}
+
+static const VMStateDescription exynos4210_clk_vmstate = {
+ .name = TYPE_EXYNOS4210_CLK,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(reg, Exynos4210ClkState, EXYNOS4210_REGS_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void exynos4210_clk_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = exynos4210_clk_reset;
+ dc->vmsd = &exynos4210_clk_vmstate;
+}
+
+static const TypeInfo exynos4210_clk_info = {
+ .name = TYPE_EXYNOS4210_CLK,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210ClkState),
+ .instance_init = exynos4210_clk_init,
+ .class_init = exynos4210_clk_class_init,
+};
+
+static void exynos4210_clk_register(void)
+{
+ qemu_log_mask(LOG_GUEST_ERROR, "Clock init\n");
+ type_register_static(&exynos4210_clk_info);
+}
+
+type_init(exynos4210_clk_register)
diff --git a/hw/misc/exynos4210_pmu.c b/hw/misc/exynos4210_pmu.c
new file mode 100644
index 000000000..e24139c63
--- /dev/null
+++ b/hw/misc/exynos4210_pmu.c
@@ -0,0 +1,522 @@
+/*
+ * Exynos4210 Power Management Unit (PMU) Emulation
+ *
+ * Copyright (C) 2011 Samsung Electronics Co Ltd.
+ * Maksim Kozlov <m.kozlov@samsung.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/>.
+ */
+
+/*
+ * This model implements PMU registers just as a bulk of memory. Currently,
+ * the only reason this device exists is that secondary CPU boot loader
+ * uses PMU INFORM5 register as a holding pen.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "sysemu/runstate.h"
+#include "qom/object.h"
+
+#ifndef DEBUG_PMU
+#define DEBUG_PMU 0
+#endif
+
+#ifndef DEBUG_PMU_EXTEND
+#define DEBUG_PMU_EXTEND 0
+#endif
+
+#if DEBUG_PMU
+#define PRINT_DEBUG(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+#if DEBUG_PMU_EXTEND
+#define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+#else
+#define PRINT_DEBUG_EXTEND(fmt, args...) do {} while (0)
+#endif /* EXTEND */
+
+#else
+#define PRINT_DEBUG(fmt, args...) do {} while (0)
+#define PRINT_DEBUG_EXTEND(fmt, args...) do {} while (0)
+#endif
+
+/*
+ * Offsets for PMU registers
+ */
+#define OM_STAT 0x0000 /* OM status register */
+#define RTC_CLKO_SEL 0x000C /* Controls RTCCLKOUT */
+#define GNSS_RTC_OUT_CTRL 0x0010 /* Controls GNSS_RTC_OUT */
+/* Decides whether system-level low-power mode is used. */
+#define SYSTEM_POWER_DOWN_CTRL 0x0200
+/* Sets control options for CENTRAL_SEQ */
+#define SYSTEM_POWER_DOWN_OPTION 0x0208
+#define SWRESET 0x0400 /* Generate software reset */
+#define RST_STAT 0x0404 /* Reset status register */
+#define WAKEUP_STAT 0x0600 /* Wakeup status register */
+#define EINT_WAKEUP_MASK 0x0604 /* Configure External INTerrupt mask */
+#define WAKEUP_MASK 0x0608 /* Configure wakeup source mask */
+#define HDMI_PHY_CONTROL 0x0700 /* HDMI PHY control register */
+#define USBDEVICE_PHY_CONTROL 0x0704 /* USB Device PHY control register */
+#define USBHOST_PHY_CONTROL 0x0708 /* USB HOST PHY control register */
+#define DAC_PHY_CONTROL 0x070C /* DAC control register */
+#define MIPI_PHY0_CONTROL 0x0710 /* MIPI PHY control register */
+#define MIPI_PHY1_CONTROL 0x0714 /* MIPI PHY control register */
+#define ADC_PHY_CONTROL 0x0718 /* TS-ADC control register */
+#define PCIe_PHY_CONTROL 0x071C /* TS-PCIe control register */
+#define SATA_PHY_CONTROL 0x0720 /* TS-SATA control register */
+#define INFORM0 0x0800 /* Information register 0 */
+#define INFORM1 0x0804 /* Information register 1 */
+#define INFORM2 0x0808 /* Information register 2 */
+#define INFORM3 0x080C /* Information register 3 */
+#define INFORM4 0x0810 /* Information register 4 */
+#define INFORM5 0x0814 /* Information register 5 */
+#define INFORM6 0x0818 /* Information register 6 */
+#define INFORM7 0x081C /* Information register 7 */
+#define PMU_DEBUG 0x0A00 /* PMU debug register */
+/* Registers to set system-level low-power option */
+#define ARM_CORE0_SYS_PWR_REG 0x1000
+#define ARM_CORE1_SYS_PWR_REG 0x1010
+#define ARM_COMMON_SYS_PWR_REG 0x1080
+#define ARM_CPU_L2_0_SYS_PWR_REG 0x10C0
+#define ARM_CPU_L2_1_SYS_PWR_REG 0x10C4
+#define CMU_ACLKSTOP_SYS_PWR_REG 0x1100
+#define CMU_SCLKSTOP_SYS_PWR_REG 0x1104
+#define CMU_RESET_SYS_PWR_REG 0x110C
+#define APLL_SYSCLK_SYS_PWR_REG 0x1120
+#define MPLL_SYSCLK_SYS_PWR_REG 0x1124
+#define VPLL_SYSCLK_SYS_PWR_REG 0x1128
+#define EPLL_SYSCLK_SYS_PWR_REG 0x112C
+#define CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG 0x1138
+#define CMU_RESET_GPS_ALIVE_SYS_PWR_REG 0x113C
+#define CMU_CLKSTOP_CAM_SYS_PWR_REG 0x1140
+#define CMU_CLKSTOP_TV_SYS_PWR_REG 0x1144
+#define CMU_CLKSTOP_MFC_SYS_PWR_REG 0x1148
+#define CMU_CLKSTOP_G3D_SYS_PWR_REG 0x114C
+#define CMU_CLKSTOP_LCD0_SYS_PWR_REG 0x1150
+#define CMU_CLKSTOP_LCD1_SYS_PWR_REG 0x1154
+#define CMU_CLKSTOP_MAUDIO_SYS_PWR_REG 0x1158
+#define CMU_CLKSTOP_GPS_SYS_PWR_REG 0x115C
+#define CMU_RESET_CAM_SYS_PWR_REG 0x1160
+#define CMU_RESET_TV_SYS_PWR_REG 0x1164
+#define CMU_RESET_MFC_SYS_PWR_REG 0x1168
+#define CMU_RESET_G3D_SYS_PWR_REG 0x116C
+#define CMU_RESET_LCD0_SYS_PWR_REG 0x1170
+#define CMU_RESET_LCD1_SYS_PWR_REG 0x1174
+#define CMU_RESET_MAUDIO_SYS_PWR_REG 0x1178
+#define CMU_RESET_GPS_SYS_PWR_REG 0x117C
+#define TOP_BUS_SYS_PWR_REG 0x1180
+#define TOP_RETENTION_SYS_PWR_REG 0x1184
+#define TOP_PWR_SYS_PWR_REG 0x1188
+#define LOGIC_RESET_SYS_PWR_REG 0x11A0
+#define OneNANDXL_MEM_SYS_PWR_REG 0x11C0
+#define MODEMIF_MEM_SYS_PWR_REG 0x11C4
+#define USBDEVICE_MEM_SYS_PWR_REG 0x11CC
+#define SDMMC_MEM_SYS_PWR_REG 0x11D0
+#define CSSYS_MEM_SYS_PWR_REG 0x11D4
+#define SECSS_MEM_SYS_PWR_REG 0x11D8
+#define PCIe_MEM_SYS_PWR_REG 0x11E0
+#define SATA_MEM_SYS_PWR_REG 0x11E4
+#define PAD_RETENTION_DRAM_SYS_PWR_REG 0x1200
+#define PAD_RETENTION_MAUDIO_SYS_PWR_REG 0x1204
+#define PAD_RETENTION_GPIO_SYS_PWR_REG 0x1220
+#define PAD_RETENTION_UART_SYS_PWR_REG 0x1224
+#define PAD_RETENTION_MMCA_SYS_PWR_REG 0x1228
+#define PAD_RETENTION_MMCB_SYS_PWR_REG 0x122C
+#define PAD_RETENTION_EBIA_SYS_PWR_REG 0x1230
+#define PAD_RETENTION_EBIB_SYS_PWR_REG 0x1234
+#define PAD_ISOLATION_SYS_PWR_REG 0x1240
+#define PAD_ALV_SEL_SYS_PWR_REG 0x1260
+#define XUSBXTI_SYS_PWR_REG 0x1280
+#define XXTI_SYS_PWR_REG 0x1284
+#define EXT_REGULATOR_SYS_PWR_REG 0x12C0
+#define GPIO_MODE_SYS_PWR_REG 0x1300
+#define GPIO_MODE_MAUDIO_SYS_PWR_REG 0x1340
+#define CAM_SYS_PWR_REG 0x1380
+#define TV_SYS_PWR_REG 0x1384
+#define MFC_SYS_PWR_REG 0x1388
+#define G3D_SYS_PWR_REG 0x138C
+#define LCD0_SYS_PWR_REG 0x1390
+#define LCD1_SYS_PWR_REG 0x1394
+#define MAUDIO_SYS_PWR_REG 0x1398
+#define GPS_SYS_PWR_REG 0x139C
+#define GPS_ALIVE_SYS_PWR_REG 0x13A0
+#define ARM_CORE0_CONFIGURATION 0x2000 /* Configure power mode of ARM_CORE0 */
+#define ARM_CORE0_STATUS 0x2004 /* Check power mode of ARM_CORE0 */
+#define ARM_CORE0_OPTION 0x2008 /* Sets control options for ARM_CORE0 */
+#define ARM_CORE1_CONFIGURATION 0x2080 /* Configure power mode of ARM_CORE1 */
+#define ARM_CORE1_STATUS 0x2084 /* Check power mode of ARM_CORE1 */
+#define ARM_CORE1_OPTION 0x2088 /* Sets control options for ARM_CORE0 */
+#define ARM_COMMON_OPTION 0x2408 /* Sets control options for ARM_COMMON */
+/* Configure power mode of ARM_CPU_L2_0 */
+#define ARM_CPU_L2_0_CONFIGURATION 0x2600
+#define ARM_CPU_L2_0_STATUS 0x2604 /* Check power mode of ARM_CPU_L2_0 */
+/* Configure power mode of ARM_CPU_L2_1 */
+#define ARM_CPU_L2_1_CONFIGURATION 0x2620
+#define ARM_CPU_L2_1_STATUS 0x2624 /* Check power mode of ARM_CPU_L2_1 */
+/* Sets control options for PAD_RETENTION_MAUDIO */
+#define PAD_RETENTION_MAUDIO_OPTION 0x3028
+/* Sets control options for PAD_RETENTION_GPIO */
+#define PAD_RETENTION_GPIO_OPTION 0x3108
+/* Sets control options for PAD_RETENTION_UART */
+#define PAD_RETENTION_UART_OPTION 0x3128
+/* Sets control options for PAD_RETENTION_MMCA */
+#define PAD_RETENTION_MMCA_OPTION 0x3148
+/* Sets control options for PAD_RETENTION_MMCB */
+#define PAD_RETENTION_MMCB_OPTION 0x3168
+/* Sets control options for PAD_RETENTION_EBIA */
+#define PAD_RETENTION_EBIA_OPTION 0x3188
+/* Sets control options for PAD_RETENTION_EBIB */
+#define PAD_RETENTION_EBIB_OPTION 0x31A8
+#define PS_HOLD_CONTROL 0x330C /* PS_HOLD control register */
+#define XUSBXTI_CONFIGURATION 0x3400 /* Configure the pad of XUSBXTI */
+#define XUSBXTI_STATUS 0x3404 /* Check the pad of XUSBXTI */
+/* Sets time required for XUSBXTI to be stabilized */
+#define XUSBXTI_DURATION 0x341C
+#define XXTI_CONFIGURATION 0x3420 /* Configure the pad of XXTI */
+#define XXTI_STATUS 0x3424 /* Check the pad of XXTI */
+/* Sets time required for XXTI to be stabilized */
+#define XXTI_DURATION 0x343C
+/* Sets time required for EXT_REGULATOR to be stabilized */
+#define EXT_REGULATOR_DURATION 0x361C
+#define CAM_CONFIGURATION 0x3C00 /* Configure power mode of CAM */
+#define CAM_STATUS 0x3C04 /* Check power mode of CAM */
+#define CAM_OPTION 0x3C08 /* Sets control options for CAM */
+#define TV_CONFIGURATION 0x3C20 /* Configure power mode of TV */
+#define TV_STATUS 0x3C24 /* Check power mode of TV */
+#define TV_OPTION 0x3C28 /* Sets control options for TV */
+#define MFC_CONFIGURATION 0x3C40 /* Configure power mode of MFC */
+#define MFC_STATUS 0x3C44 /* Check power mode of MFC */
+#define MFC_OPTION 0x3C48 /* Sets control options for MFC */
+#define G3D_CONFIGURATION 0x3C60 /* Configure power mode of G3D */
+#define G3D_STATUS 0x3C64 /* Check power mode of G3D */
+#define G3D_OPTION 0x3C68 /* Sets control options for G3D */
+#define LCD0_CONFIGURATION 0x3C80 /* Configure power mode of LCD0 */
+#define LCD0_STATUS 0x3C84 /* Check power mode of LCD0 */
+#define LCD0_OPTION 0x3C88 /* Sets control options for LCD0 */
+#define LCD1_CONFIGURATION 0x3CA0 /* Configure power mode of LCD1 */
+#define LCD1_STATUS 0x3CA4 /* Check power mode of LCD1 */
+#define LCD1_OPTION 0x3CA8 /* Sets control options for LCD1 */
+#define GPS_CONFIGURATION 0x3CE0 /* Configure power mode of GPS */
+#define GPS_STATUS 0x3CE4 /* Check power mode of GPS */
+#define GPS_OPTION 0x3CE8 /* Sets control options for GPS */
+#define GPS_ALIVE_CONFIGURATION 0x3D00 /* Configure power mode of GPS */
+#define GPS_ALIVE_STATUS 0x3D04 /* Check power mode of GPS */
+#define GPS_ALIVE_OPTION 0x3D08 /* Sets control options for GPS */
+
+#define EXYNOS4210_PMU_REGS_MEM_SIZE 0x3d0c
+
+typedef struct Exynos4210PmuReg {
+ const char *name; /* for debug only */
+ uint32_t offset;
+ uint32_t reset_value;
+} Exynos4210PmuReg;
+
+static const Exynos4210PmuReg exynos4210_pmu_regs[] = {
+ {"OM_STAT", OM_STAT, 0x00000000},
+ {"RTC_CLKO_SEL", RTC_CLKO_SEL, 0x00000000},
+ {"GNSS_RTC_OUT_CTRL", GNSS_RTC_OUT_CTRL, 0x00000001},
+ {"SYSTEM_POWER_DOWN_CTRL", SYSTEM_POWER_DOWN_CTRL, 0x00010000},
+ {"SYSTEM_POWER_DOWN_OPTION", SYSTEM_POWER_DOWN_OPTION, 0x03030000},
+ {"SWRESET", SWRESET, 0x00000000},
+ {"RST_STAT", RST_STAT, 0x00000000},
+ {"WAKEUP_STAT", WAKEUP_STAT, 0x00000000},
+ {"EINT_WAKEUP_MASK", EINT_WAKEUP_MASK, 0x00000000},
+ {"WAKEUP_MASK", WAKEUP_MASK, 0x00000000},
+ {"HDMI_PHY_CONTROL", HDMI_PHY_CONTROL, 0x00960000},
+ {"USBDEVICE_PHY_CONTROL", USBDEVICE_PHY_CONTROL, 0x00000000},
+ {"USBHOST_PHY_CONTROL", USBHOST_PHY_CONTROL, 0x00000000},
+ {"DAC_PHY_CONTROL", DAC_PHY_CONTROL, 0x00000000},
+ {"MIPI_PHY0_CONTROL", MIPI_PHY0_CONTROL, 0x00000000},
+ {"MIPI_PHY1_CONTROL", MIPI_PHY1_CONTROL, 0x00000000},
+ {"ADC_PHY_CONTROL", ADC_PHY_CONTROL, 0x00000001},
+ {"PCIe_PHY_CONTROL", PCIe_PHY_CONTROL, 0x00000000},
+ {"SATA_PHY_CONTROL", SATA_PHY_CONTROL, 0x00000000},
+ {"INFORM0", INFORM0, 0x00000000},
+ {"INFORM1", INFORM1, 0x00000000},
+ {"INFORM2", INFORM2, 0x00000000},
+ {"INFORM3", INFORM3, 0x00000000},
+ {"INFORM4", INFORM4, 0x00000000},
+ {"INFORM5", INFORM5, 0x00000000},
+ {"INFORM6", INFORM6, 0x00000000},
+ {"INFORM7", INFORM7, 0x00000000},
+ {"PMU_DEBUG", PMU_DEBUG, 0x00000000},
+ {"ARM_CORE0_SYS_PWR_REG", ARM_CORE0_SYS_PWR_REG, 0xFFFFFFFF},
+ {"ARM_CORE1_SYS_PWR_REG", ARM_CORE1_SYS_PWR_REG, 0xFFFFFFFF},
+ {"ARM_COMMON_SYS_PWR_REG", ARM_COMMON_SYS_PWR_REG, 0xFFFFFFFF},
+ {"ARM_CPU_L2_0_SYS_PWR_REG", ARM_CPU_L2_0_SYS_PWR_REG, 0xFFFFFFFF},
+ {"ARM_CPU_L2_1_SYS_PWR_REG", ARM_CPU_L2_1_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_ACLKSTOP_SYS_PWR_REG", CMU_ACLKSTOP_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_SCLKSTOP_SYS_PWR_REG", CMU_SCLKSTOP_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_SYS_PWR_REG", CMU_RESET_SYS_PWR_REG, 0xFFFFFFFF},
+ {"APLL_SYSCLK_SYS_PWR_REG", APLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+ {"MPLL_SYSCLK_SYS_PWR_REG", MPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+ {"VPLL_SYSCLK_SYS_PWR_REG", VPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+ {"EPLL_SYSCLK_SYS_PWR_REG", EPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG", CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"CMU_RESET_GPS_ALIVE_SYS_PWR_REG", CMU_RESET_GPS_ALIVE_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"CMU_CLKSTOP_CAM_SYS_PWR_REG", CMU_CLKSTOP_CAM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_TV_SYS_PWR_REG", CMU_CLKSTOP_TV_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_MFC_SYS_PWR_REG", CMU_CLKSTOP_MFC_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_G3D_SYS_PWR_REG", CMU_CLKSTOP_G3D_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_LCD0_SYS_PWR_REG", CMU_CLKSTOP_LCD0_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_LCD1_SYS_PWR_REG", CMU_CLKSTOP_LCD1_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_CLKSTOP_MAUDIO_SYS_PWR_REG", CMU_CLKSTOP_MAUDIO_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"CMU_CLKSTOP_GPS_SYS_PWR_REG", CMU_CLKSTOP_GPS_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_CAM_SYS_PWR_REG", CMU_RESET_CAM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_TV_SYS_PWR_REG", CMU_RESET_TV_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_MFC_SYS_PWR_REG", CMU_RESET_MFC_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_G3D_SYS_PWR_REG", CMU_RESET_G3D_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_LCD0_SYS_PWR_REG", CMU_RESET_LCD0_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_LCD1_SYS_PWR_REG", CMU_RESET_LCD1_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_MAUDIO_SYS_PWR_REG", CMU_RESET_MAUDIO_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CMU_RESET_GPS_SYS_PWR_REG", CMU_RESET_GPS_SYS_PWR_REG, 0xFFFFFFFF},
+ {"TOP_BUS_SYS_PWR_REG", TOP_BUS_SYS_PWR_REG, 0xFFFFFFFF},
+ {"TOP_RETENTION_SYS_PWR_REG", TOP_RETENTION_SYS_PWR_REG, 0xFFFFFFFF},
+ {"TOP_PWR_SYS_PWR_REG", TOP_PWR_SYS_PWR_REG, 0xFFFFFFFF},
+ {"LOGIC_RESET_SYS_PWR_REG", LOGIC_RESET_SYS_PWR_REG, 0xFFFFFFFF},
+ {"OneNANDXL_MEM_SYS_PWR_REG", OneNANDXL_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"MODEMIF_MEM_SYS_PWR_REG", MODEMIF_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"USBDEVICE_MEM_SYS_PWR_REG", USBDEVICE_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"SDMMC_MEM_SYS_PWR_REG", SDMMC_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CSSYS_MEM_SYS_PWR_REG", CSSYS_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"SECSS_MEM_SYS_PWR_REG", SECSS_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"PCIe_MEM_SYS_PWR_REG", PCIe_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"SATA_MEM_SYS_PWR_REG", SATA_MEM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"PAD_RETENTION_DRAM_SYS_PWR_REG", PAD_RETENTION_DRAM_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_MAUDIO_SYS_PWR_REG", PAD_RETENTION_MAUDIO_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_GPIO_SYS_PWR_REG", PAD_RETENTION_GPIO_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_UART_SYS_PWR_REG", PAD_RETENTION_UART_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_MMCA_SYS_PWR_REG", PAD_RETENTION_MMCA_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_MMCB_SYS_PWR_REG", PAD_RETENTION_MMCB_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_EBIA_SYS_PWR_REG", PAD_RETENTION_EBIA_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_RETENTION_EBIB_SYS_PWR_REG", PAD_RETENTION_EBIB_SYS_PWR_REG,
+ 0xFFFFFFFF},
+ {"PAD_ISOLATION_SYS_PWR_REG", PAD_ISOLATION_SYS_PWR_REG, 0xFFFFFFFF},
+ {"PAD_ALV_SEL_SYS_PWR_REG", PAD_ALV_SEL_SYS_PWR_REG, 0xFFFFFFFF},
+ {"XUSBXTI_SYS_PWR_REG", XUSBXTI_SYS_PWR_REG, 0xFFFFFFFF},
+ {"XXTI_SYS_PWR_REG", XXTI_SYS_PWR_REG, 0xFFFFFFFF},
+ {"EXT_REGULATOR_SYS_PWR_REG", EXT_REGULATOR_SYS_PWR_REG, 0xFFFFFFFF},
+ {"GPIO_MODE_SYS_PWR_REG", GPIO_MODE_SYS_PWR_REG, 0xFFFFFFFF},
+ {"GPIO_MODE_MAUDIO_SYS_PWR_REG", GPIO_MODE_MAUDIO_SYS_PWR_REG, 0xFFFFFFFF},
+ {"CAM_SYS_PWR_REG", CAM_SYS_PWR_REG, 0xFFFFFFFF},
+ {"TV_SYS_PWR_REG", TV_SYS_PWR_REG, 0xFFFFFFFF},
+ {"MFC_SYS_PWR_REG", MFC_SYS_PWR_REG, 0xFFFFFFFF},
+ {"G3D_SYS_PWR_REG", G3D_SYS_PWR_REG, 0xFFFFFFFF},
+ {"LCD0_SYS_PWR_REG", LCD0_SYS_PWR_REG, 0xFFFFFFFF},
+ {"LCD1_SYS_PWR_REG", LCD1_SYS_PWR_REG, 0xFFFFFFFF},
+ {"MAUDIO_SYS_PWR_REG", MAUDIO_SYS_PWR_REG, 0xFFFFFFFF},
+ {"GPS_SYS_PWR_REG", GPS_SYS_PWR_REG, 0xFFFFFFFF},
+ {"GPS_ALIVE_SYS_PWR_REG", GPS_ALIVE_SYS_PWR_REG, 0xFFFFFFFF},
+ {"ARM_CORE0_CONFIGURATION", ARM_CORE0_CONFIGURATION, 0x00000003},
+ {"ARM_CORE0_STATUS", ARM_CORE0_STATUS, 0x00030003},
+ {"ARM_CORE0_OPTION", ARM_CORE0_OPTION, 0x01010001},
+ {"ARM_CORE1_CONFIGURATION", ARM_CORE1_CONFIGURATION, 0x00000003},
+ {"ARM_CORE1_STATUS", ARM_CORE1_STATUS, 0x00030003},
+ {"ARM_CORE1_OPTION", ARM_CORE1_OPTION, 0x01010001},
+ {"ARM_COMMON_OPTION", ARM_COMMON_OPTION, 0x00000001},
+ {"ARM_CPU_L2_0_CONFIGURATION", ARM_CPU_L2_0_CONFIGURATION, 0x00000003},
+ {"ARM_CPU_L2_0_STATUS", ARM_CPU_L2_0_STATUS, 0x00000003},
+ {"ARM_CPU_L2_1_CONFIGURATION", ARM_CPU_L2_1_CONFIGURATION, 0x00000003},
+ {"ARM_CPU_L2_1_STATUS", ARM_CPU_L2_1_STATUS, 0x00000003},
+ {"PAD_RETENTION_MAUDIO_OPTION", PAD_RETENTION_MAUDIO_OPTION, 0x00000000},
+ {"PAD_RETENTION_GPIO_OPTION", PAD_RETENTION_GPIO_OPTION, 0x00000000},
+ {"PAD_RETENTION_UART_OPTION", PAD_RETENTION_UART_OPTION, 0x00000000},
+ {"PAD_RETENTION_MMCA_OPTION", PAD_RETENTION_MMCA_OPTION, 0x00000000},
+ {"PAD_RETENTION_MMCB_OPTION", PAD_RETENTION_MMCB_OPTION, 0x00000000},
+ {"PAD_RETENTION_EBIA_OPTION", PAD_RETENTION_EBIA_OPTION, 0x00000000},
+ {"PAD_RETENTION_EBIB_OPTION", PAD_RETENTION_EBIB_OPTION, 0x00000000},
+ /*
+ * PS_HOLD_CONTROL: reset value and manually toggle high the DATA bit.
+ * DATA bit high, set usually by bootloader, keeps system on.
+ */
+ {"PS_HOLD_CONTROL", PS_HOLD_CONTROL, 0x00005200 | BIT(8)},
+ {"XUSBXTI_CONFIGURATION", XUSBXTI_CONFIGURATION, 0x00000001},
+ {"XUSBXTI_STATUS", XUSBXTI_STATUS, 0x00000001},
+ {"XUSBXTI_DURATION", XUSBXTI_DURATION, 0xFFF00000},
+ {"XXTI_CONFIGURATION", XXTI_CONFIGURATION, 0x00000001},
+ {"XXTI_STATUS", XXTI_STATUS, 0x00000001},
+ {"XXTI_DURATION", XXTI_DURATION, 0xFFF00000},
+ {"EXT_REGULATOR_DURATION", EXT_REGULATOR_DURATION, 0xFFF03FFF},
+ {"CAM_CONFIGURATION", CAM_CONFIGURATION, 0x00000007},
+ {"CAM_STATUS", CAM_STATUS, 0x00060007},
+ {"CAM_OPTION", CAM_OPTION, 0x00000001},
+ {"TV_CONFIGURATION", TV_CONFIGURATION, 0x00000007},
+ {"TV_STATUS", TV_STATUS, 0x00060007},
+ {"TV_OPTION", TV_OPTION, 0x00000001},
+ {"MFC_CONFIGURATION", MFC_CONFIGURATION, 0x00000007},
+ {"MFC_STATUS", MFC_STATUS, 0x00060007},
+ {"MFC_OPTION", MFC_OPTION, 0x00000001},
+ {"G3D_CONFIGURATION", G3D_CONFIGURATION, 0x00000007},
+ {"G3D_STATUS", G3D_STATUS, 0x00060007},
+ {"G3D_OPTION", G3D_OPTION, 0x00000001},
+ {"LCD0_CONFIGURATION", LCD0_CONFIGURATION, 0x00000007},
+ {"LCD0_STATUS", LCD0_STATUS, 0x00060007},
+ {"LCD0_OPTION", LCD0_OPTION, 0x00000001},
+ {"LCD1_CONFIGURATION", LCD1_CONFIGURATION, 0x00000007},
+ {"LCD1_STATUS", LCD1_STATUS, 0x00060007},
+ {"LCD1_OPTION", LCD1_OPTION, 0x00000001},
+ {"GPS_CONFIGURATION", GPS_CONFIGURATION, 0x00000007},
+ {"GPS_STATUS", GPS_STATUS, 0x00060007},
+ {"GPS_OPTION", GPS_OPTION, 0x00000001},
+ {"GPS_ALIVE_CONFIGURATION", GPS_ALIVE_CONFIGURATION, 0x00000007},
+ {"GPS_ALIVE_STATUS", GPS_ALIVE_STATUS, 0x00060007},
+ {"GPS_ALIVE_OPTION", GPS_ALIVE_OPTION, 0x00000001},
+};
+
+#define PMU_NUM_OF_REGISTERS ARRAY_SIZE(exynos4210_pmu_regs)
+
+#define TYPE_EXYNOS4210_PMU "exynos4210.pmu"
+OBJECT_DECLARE_SIMPLE_TYPE(Exynos4210PmuState, EXYNOS4210_PMU)
+
+struct Exynos4210PmuState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t reg[PMU_NUM_OF_REGISTERS];
+};
+
+static void exynos4210_pmu_poweroff(void)
+{
+ PRINT_DEBUG("QEMU PMU: PS_HOLD bit down, powering off\n");
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+}
+
+static uint64_t exynos4210_pmu_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210PmuState *s = (Exynos4210PmuState *)opaque;
+ const Exynos4210PmuReg *reg_p = exynos4210_pmu_regs;
+ unsigned int i;
+
+ for (i = 0; i < PMU_NUM_OF_REGISTERS; i++) {
+ if (reg_p->offset == offset) {
+ PRINT_DEBUG_EXTEND("%s [0x%04x] -> 0x%04x\n", reg_p->name,
+ (uint32_t)offset, s->reg[i]);
+ return s->reg[i];
+ }
+ reg_p++;
+ }
+ PRINT_DEBUG("QEMU PMU ERROR: bad read offset 0x%04x\n", (uint32_t)offset);
+ return 0;
+}
+
+static void exynos4210_pmu_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4210PmuState *s = (Exynos4210PmuState *)opaque;
+ const Exynos4210PmuReg *reg_p = exynos4210_pmu_regs;
+ unsigned int i;
+
+ for (i = 0; i < PMU_NUM_OF_REGISTERS; i++) {
+ if (reg_p->offset == offset) {
+ PRINT_DEBUG_EXTEND("%s <0x%04x> <- 0x%04x\n", reg_p->name,
+ (uint32_t)offset, (uint32_t)val);
+ s->reg[i] = val;
+ if ((offset == PS_HOLD_CONTROL) && ((val & BIT(8)) == 0)) {
+ /*
+ * We are interested only in setting data bit
+ * of PS_HOLD_CONTROL register to indicate power off request.
+ */
+ exynos4210_pmu_poweroff();
+ }
+ return;
+ }
+ reg_p++;
+ }
+ PRINT_DEBUG("QEMU PMU ERROR: bad write offset 0x%04x\n", (uint32_t)offset);
+}
+
+static const MemoryRegionOps exynos4210_pmu_ops = {
+ .read = exynos4210_pmu_read,
+ .write = exynos4210_pmu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false
+ }
+};
+
+static void exynos4210_pmu_reset(DeviceState *dev)
+{
+ Exynos4210PmuState *s = EXYNOS4210_PMU(dev);
+ unsigned i;
+
+ /* Set default values for registers */
+ for (i = 0; i < PMU_NUM_OF_REGISTERS; i++) {
+ s->reg[i] = exynos4210_pmu_regs[i].reset_value;
+ }
+}
+
+static void exynos4210_pmu_init(Object *obj)
+{
+ Exynos4210PmuState *s = EXYNOS4210_PMU(obj);
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+
+ /* memory mapping */
+ memory_region_init_io(&s->iomem, obj, &exynos4210_pmu_ops, s,
+ "exynos4210.pmu", EXYNOS4210_PMU_REGS_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+}
+
+static const VMStateDescription exynos4210_pmu_vmstate = {
+ .name = "exynos4210.pmu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(reg, Exynos4210PmuState, PMU_NUM_OF_REGISTERS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void exynos4210_pmu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = exynos4210_pmu_reset;
+ dc->vmsd = &exynos4210_pmu_vmstate;
+}
+
+static const TypeInfo exynos4210_pmu_info = {
+ .name = TYPE_EXYNOS4210_PMU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210PmuState),
+ .instance_init = exynos4210_pmu_init,
+ .class_init = exynos4210_pmu_class_init,
+};
+
+static void exynos4210_pmu_register(void)
+{
+ type_register_static(&exynos4210_pmu_info);
+}
+
+type_init(exynos4210_pmu_register)
diff --git a/hw/misc/exynos4210_rng.c b/hw/misc/exynos4210_rng.c
new file mode 100644
index 000000000..1b9e8347a
--- /dev/null
+++ b/hw/misc/exynos4210_rng.c
@@ -0,0 +1,277 @@
+/*
+ * Exynos4210 Pseudo Random Nubmer Generator Emulation
+ *
+ * Copyright (c) 2017 Krzysztof Kozlowski <krzk@kernel.org>
+ *
+ * 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 "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/guest-random.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+#define DEBUG_EXYNOS_RNG 0
+
+#define DPRINTF(fmt, ...) \
+ do { \
+ if (DEBUG_EXYNOS_RNG) { \
+ printf("exynos4210_rng: " fmt, ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define TYPE_EXYNOS4210_RNG "exynos4210.rng"
+OBJECT_DECLARE_SIMPLE_TYPE(Exynos4210RngState, EXYNOS4210_RNG)
+
+/*
+ * Exynos4220, PRNG, only polling mode is supported.
+ */
+
+/* RNG_CONTROL_1 register bitfields, reset value: 0x0 */
+#define EXYNOS4210_RNG_CONTROL_1_PRNG 0x8
+#define EXYNOS4210_RNG_CONTROL_1_START_INIT BIT(4)
+/* RNG_STATUS register bitfields, reset value: 0x1 */
+#define EXYNOS4210_RNG_STATUS_PRNG_ERROR BIT(7)
+#define EXYNOS4210_RNG_STATUS_PRNG_DONE BIT(5)
+#define EXYNOS4210_RNG_STATUS_MSG_DONE BIT(4)
+#define EXYNOS4210_RNG_STATUS_PARTIAL_DONE BIT(3)
+#define EXYNOS4210_RNG_STATUS_PRNG_BUSY BIT(2)
+#define EXYNOS4210_RNG_STATUS_SEED_SETTING_DONE BIT(1)
+#define EXYNOS4210_RNG_STATUS_BUFFER_READY BIT(0)
+#define EXYNOS4210_RNG_STATUS_WRITE_MASK (EXYNOS4210_RNG_STATUS_PRNG_DONE \
+ | EXYNOS4210_RNG_STATUS_MSG_DONE \
+ | EXYNOS4210_RNG_STATUS_PARTIAL_DONE)
+
+#define EXYNOS4210_RNG_CONTROL_1 0x0
+#define EXYNOS4210_RNG_STATUS 0x10
+#define EXYNOS4210_RNG_SEED_IN 0x140
+#define EXYNOS4210_RNG_SEED_IN_OFFSET(n) (EXYNOS4210_RNG_SEED_IN + (n * 0x4))
+#define EXYNOS4210_RNG_PRNG 0x160
+#define EXYNOS4210_RNG_PRNG_OFFSET(n) (EXYNOS4210_RNG_PRNG + (n * 0x4))
+
+#define EXYNOS4210_RNG_PRNG_NUM 5
+
+#define EXYNOS4210_RNG_REGS_MEM_SIZE 0x200
+
+struct Exynos4210RngState {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+
+ int32_t randr_value[EXYNOS4210_RNG_PRNG_NUM];
+ /* bits from 0 to EXYNOS4210_RNG_PRNG_NUM if given seed register was set */
+ uint32_t seed_set;
+
+ /* Register values */
+ uint32_t reg_control;
+ uint32_t reg_status;
+};
+
+static bool exynos4210_rng_seed_ready(const Exynos4210RngState *s)
+{
+ uint32_t mask = MAKE_64BIT_MASK(0, EXYNOS4210_RNG_PRNG_NUM);
+
+ /* Return true if all the seed-set bits are set. */
+ return (s->seed_set & mask) == mask;
+}
+
+static void exynos4210_rng_set_seed(Exynos4210RngState *s, unsigned int i,
+ uint64_t val)
+{
+ /*
+ * We actually ignore the seed and always generate true random numbers.
+ * Theoretically this should not match the device as Exynos has
+ * a Pseudo Random Number Generator but testing shown that it always
+ * generates random numbers regardless of the seed value.
+ */
+ s->seed_set |= BIT(i);
+
+ /* If all seeds were written, update the status to reflect it */
+ if (exynos4210_rng_seed_ready(s)) {
+ s->reg_status |= EXYNOS4210_RNG_STATUS_SEED_SETTING_DONE;
+ } else {
+ s->reg_status &= ~EXYNOS4210_RNG_STATUS_SEED_SETTING_DONE;
+ }
+}
+
+static void exynos4210_rng_run_engine(Exynos4210RngState *s)
+{
+ Error *err = NULL;
+
+ /* Seed set? */
+ if ((s->reg_status & EXYNOS4210_RNG_STATUS_SEED_SETTING_DONE) == 0) {
+ goto out;
+ }
+
+ /* PRNG engine chosen? */
+ if ((s->reg_control & EXYNOS4210_RNG_CONTROL_1_PRNG) == 0) {
+ goto out;
+ }
+
+ /* PRNG engine started? */
+ if ((s->reg_control & EXYNOS4210_RNG_CONTROL_1_START_INIT) == 0) {
+ goto out;
+ }
+
+ /* Get randoms */
+ if (qemu_guest_getrandom(s->randr_value, sizeof(s->randr_value), &err)) {
+ error_report_err(err);
+ } else {
+ /* Notify that PRNG is ready */
+ s->reg_status |= EXYNOS4210_RNG_STATUS_PRNG_DONE;
+ }
+
+out:
+ /* Always clear start engine bit */
+ s->reg_control &= ~EXYNOS4210_RNG_CONTROL_1_START_INIT;
+}
+
+static uint64_t exynos4210_rng_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210RngState *s = (Exynos4210RngState *)opaque;
+ uint32_t val = 0;
+
+ assert(size == 4);
+
+ switch (offset) {
+ case EXYNOS4210_RNG_CONTROL_1:
+ val = s->reg_control;
+ break;
+
+ case EXYNOS4210_RNG_STATUS:
+ val = s->reg_status;
+ break;
+
+ case EXYNOS4210_RNG_PRNG_OFFSET(0):
+ case EXYNOS4210_RNG_PRNG_OFFSET(1):
+ case EXYNOS4210_RNG_PRNG_OFFSET(2):
+ case EXYNOS4210_RNG_PRNG_OFFSET(3):
+ case EXYNOS4210_RNG_PRNG_OFFSET(4):
+ val = s->randr_value[(offset - EXYNOS4210_RNG_PRNG_OFFSET(0)) / 4];
+ DPRINTF("returning random @0x%" HWADDR_PRIx ": 0x%" PRIx32 "\n",
+ offset, val);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ }
+
+ return val;
+}
+
+static void exynos4210_rng_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4210RngState *s = (Exynos4210RngState *)opaque;
+
+ assert(size == 4);
+
+ switch (offset) {
+ case EXYNOS4210_RNG_CONTROL_1:
+ DPRINTF("RNG_CONTROL_1 = 0x%" PRIx64 "\n", val);
+ s->reg_control = val;
+ exynos4210_rng_run_engine(s);
+ break;
+
+ case EXYNOS4210_RNG_STATUS:
+ /* For clearing status fields */
+ s->reg_status &= ~EXYNOS4210_RNG_STATUS_WRITE_MASK;
+ s->reg_status |= val & EXYNOS4210_RNG_STATUS_WRITE_MASK;
+ break;
+
+ case EXYNOS4210_RNG_SEED_IN_OFFSET(0):
+ case EXYNOS4210_RNG_SEED_IN_OFFSET(1):
+ case EXYNOS4210_RNG_SEED_IN_OFFSET(2):
+ case EXYNOS4210_RNG_SEED_IN_OFFSET(3):
+ case EXYNOS4210_RNG_SEED_IN_OFFSET(4):
+ exynos4210_rng_set_seed(s,
+ (offset - EXYNOS4210_RNG_SEED_IN_OFFSET(0)) / 4,
+ val);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ }
+}
+
+static const MemoryRegionOps exynos4210_rng_ops = {
+ .read = exynos4210_rng_read,
+ .write = exynos4210_rng_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void exynos4210_rng_reset(DeviceState *dev)
+{
+ Exynos4210RngState *s = EXYNOS4210_RNG(dev);
+
+ s->reg_control = 0;
+ s->reg_status = EXYNOS4210_RNG_STATUS_BUFFER_READY;
+ memset(s->randr_value, 0, sizeof(s->randr_value));
+ s->seed_set = 0;
+}
+
+static void exynos4210_rng_init(Object *obj)
+{
+ Exynos4210RngState *s = EXYNOS4210_RNG(obj);
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &exynos4210_rng_ops, s,
+ TYPE_EXYNOS4210_RNG, EXYNOS4210_RNG_REGS_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+}
+
+static const VMStateDescription exynos4210_rng_vmstate = {
+ .name = TYPE_EXYNOS4210_RNG,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32_ARRAY(randr_value, Exynos4210RngState,
+ EXYNOS4210_RNG_PRNG_NUM),
+ VMSTATE_UINT32(seed_set, Exynos4210RngState),
+ VMSTATE_UINT32(reg_status, Exynos4210RngState),
+ VMSTATE_UINT32(reg_control, Exynos4210RngState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void exynos4210_rng_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = exynos4210_rng_reset;
+ dc->vmsd = &exynos4210_rng_vmstate;
+}
+
+static const TypeInfo exynos4210_rng_info = {
+ .name = TYPE_EXYNOS4210_RNG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210RngState),
+ .instance_init = exynos4210_rng_init,
+ .class_init = exynos4210_rng_class_init,
+};
+
+static void exynos4210_rng_register(void)
+{
+ type_register_static(&exynos4210_rng_info);
+}
+
+type_init(exynos4210_rng_register)
diff --git a/hw/misc/grlib_ahb_apb_pnp.c b/hw/misc/grlib_ahb_apb_pnp.c
new file mode 100644
index 000000000..43e001c3c
--- /dev/null
+++ b/hw/misc/grlib_ahb_apb_pnp.c
@@ -0,0 +1,301 @@
+/*
+ * GRLIB AHB APB PNP
+ *
+ * Copyright (C) 2019 AdaCore
+ *
+ * Developed by :
+ * Frederic Konrad <frederic.konrad@adacore.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/log.h"
+#include "hw/sysbus.h"
+#include "hw/misc/grlib_ahb_apb_pnp.h"
+#include "trace.h"
+
+#define GRLIB_PNP_VENDOR_SHIFT (24)
+#define GRLIB_PNP_VENDOR_SIZE (8)
+#define GRLIB_PNP_DEV_SHIFT (12)
+#define GRLIB_PNP_DEV_SIZE (12)
+#define GRLIB_PNP_VER_SHIFT (5)
+#define GRLIB_PNP_VER_SIZE (5)
+#define GRLIB_PNP_IRQ_SHIFT (0)
+#define GRLIB_PNP_IRQ_SIZE (5)
+#define GRLIB_PNP_ADDR_SHIFT (20)
+#define GRLIB_PNP_ADDR_SIZE (12)
+#define GRLIB_PNP_MASK_SHIFT (4)
+#define GRLIB_PNP_MASK_SIZE (12)
+
+#define GRLIB_AHB_DEV_ADDR_SHIFT (20)
+#define GRLIB_AHB_DEV_ADDR_SIZE (12)
+#define GRLIB_AHB_ENTRY_SIZE (0x20)
+#define GRLIB_AHB_MAX_DEV (64)
+#define GRLIB_AHB_SLAVE_OFFSET (0x800)
+
+#define GRLIB_APB_DEV_ADDR_SHIFT (8)
+#define GRLIB_APB_DEV_ADDR_SIZE (12)
+#define GRLIB_APB_ENTRY_SIZE (0x08)
+#define GRLIB_APB_MAX_DEV (512)
+
+#define GRLIB_PNP_MAX_REGS (0x1000)
+
+typedef struct AHBPnp {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+
+ uint32_t regs[GRLIB_PNP_MAX_REGS >> 2];
+ uint8_t master_count;
+ uint8_t slave_count;
+} AHBPnp;
+
+void grlib_ahb_pnp_add_entry(AHBPnp *dev, uint32_t address, uint32_t mask,
+ uint8_t vendor, uint16_t device, int slave,
+ int type)
+{
+ unsigned int reg_start;
+
+ /*
+ * AHB entries look like this:
+ *
+ * 31 -------- 23 -------- 11 ----- 9 -------- 4 --- 0
+ * | VENDOR ID | DEVICE ID | IRQ ? | VERSION | IRQ |
+ * --------------------------------------------------
+ * | USER |
+ * --------------------------------------------------
+ * | USER |
+ * --------------------------------------------------
+ * | USER |
+ * --------------------------------------------------
+ * | USER |
+ * --------------------------------------------------
+ * 31 ----------- 20 --- 15 ----------------- 3 ---- 0
+ * | ADDR[31..12] | 00PC | MASK | TYPE |
+ * --------------------------------------------------
+ * 31 ----------- 20 --- 15 ----------------- 3 ---- 0
+ * | ADDR[31..12] | 00PC | MASK | TYPE |
+ * --------------------------------------------------
+ * 31 ----------- 20 --- 15 ----------------- 3 ---- 0
+ * | ADDR[31..12] | 00PC | MASK | TYPE |
+ * --------------------------------------------------
+ * 31 ----------- 20 --- 15 ----------------- 3 ---- 0
+ * | ADDR[31..12] | 00PC | MASK | TYPE |
+ * --------------------------------------------------
+ */
+
+ if (slave) {
+ assert(dev->slave_count < GRLIB_AHB_MAX_DEV);
+ reg_start = (GRLIB_AHB_SLAVE_OFFSET
+ + (dev->slave_count * GRLIB_AHB_ENTRY_SIZE)) >> 2;
+ dev->slave_count++;
+ } else {
+ assert(dev->master_count < GRLIB_AHB_MAX_DEV);
+ reg_start = (dev->master_count * GRLIB_AHB_ENTRY_SIZE) >> 2;
+ dev->master_count++;
+ }
+
+ dev->regs[reg_start] = deposit32(dev->regs[reg_start],
+ GRLIB_PNP_VENDOR_SHIFT,
+ GRLIB_PNP_VENDOR_SIZE,
+ vendor);
+ dev->regs[reg_start] = deposit32(dev->regs[reg_start],
+ GRLIB_PNP_DEV_SHIFT,
+ GRLIB_PNP_DEV_SIZE,
+ device);
+ reg_start += 4;
+ /* AHB Memory Space */
+ dev->regs[reg_start] = type;
+ dev->regs[reg_start] = deposit32(dev->regs[reg_start],
+ GRLIB_PNP_ADDR_SHIFT,
+ GRLIB_PNP_ADDR_SIZE,
+ extract32(address,
+ GRLIB_AHB_DEV_ADDR_SHIFT,
+ GRLIB_AHB_DEV_ADDR_SIZE));
+ dev->regs[reg_start] = deposit32(dev->regs[reg_start],
+ GRLIB_PNP_MASK_SHIFT,
+ GRLIB_PNP_MASK_SIZE,
+ mask);
+}
+
+static uint64_t grlib_ahb_pnp_read(void *opaque, hwaddr offset, unsigned size)
+{
+ AHBPnp *ahb_pnp = GRLIB_AHB_PNP(opaque);
+ uint32_t val;
+
+ val = ahb_pnp->regs[offset >> 2];
+ trace_grlib_ahb_pnp_read(offset, val);
+
+ return val;
+}
+
+static void grlib_ahb_pnp_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ qemu_log_mask(LOG_UNIMP, "%s not implemented\n", __func__);
+}
+
+static const MemoryRegionOps grlib_ahb_pnp_ops = {
+ .read = grlib_ahb_pnp_read,
+ .write = grlib_ahb_pnp_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void grlib_ahb_pnp_realize(DeviceState *dev, Error **errp)
+{
+ AHBPnp *ahb_pnp = GRLIB_AHB_PNP(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&ahb_pnp->iomem, OBJECT(dev), &grlib_ahb_pnp_ops,
+ ahb_pnp, TYPE_GRLIB_AHB_PNP, GRLIB_PNP_MAX_REGS);
+ sysbus_init_mmio(sbd, &ahb_pnp->iomem);
+}
+
+static void grlib_ahb_pnp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = grlib_ahb_pnp_realize;
+}
+
+static const TypeInfo grlib_ahb_pnp_info = {
+ .name = TYPE_GRLIB_AHB_PNP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AHBPnp),
+ .class_init = grlib_ahb_pnp_class_init,
+};
+
+/* APBPnp */
+
+typedef struct APBPnp {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+
+ uint32_t regs[GRLIB_PNP_MAX_REGS >> 2];
+ uint32_t entry_count;
+} APBPnp;
+
+void grlib_apb_pnp_add_entry(APBPnp *dev, uint32_t address, uint32_t mask,
+ uint8_t vendor, uint16_t device, uint8_t version,
+ uint8_t irq, int type)
+{
+ unsigned int reg_start;
+
+ /*
+ * APB entries look like this:
+ *
+ * 31 -------- 23 -------- 11 ----- 9 ------- 4 --- 0
+ * | VENDOR ID | DEVICE ID | IRQ ? | VERSION | IRQ |
+ *
+ * 31 ---------- 20 --- 15 ----------------- 3 ---- 0
+ * | ADDR[20..8] | 0000 | MASK | TYPE |
+ */
+
+ assert(dev->entry_count < GRLIB_APB_MAX_DEV);
+ reg_start = (dev->entry_count * GRLIB_APB_ENTRY_SIZE) >> 2;
+ dev->entry_count++;
+
+ dev->regs[reg_start] = deposit32(dev->regs[reg_start],
+ GRLIB_PNP_VENDOR_SHIFT,
+ GRLIB_PNP_VENDOR_SIZE,
+ vendor);
+ dev->regs[reg_start] = deposit32(dev->regs[reg_start],
+ GRLIB_PNP_DEV_SHIFT,
+ GRLIB_PNP_DEV_SIZE,
+ device);
+ dev->regs[reg_start] = deposit32(dev->regs[reg_start],
+ GRLIB_PNP_VER_SHIFT,
+ GRLIB_PNP_VER_SIZE,
+ version);
+ dev->regs[reg_start] = deposit32(dev->regs[reg_start],
+ GRLIB_PNP_IRQ_SHIFT,
+ GRLIB_PNP_IRQ_SIZE,
+ irq);
+ reg_start += 1;
+ dev->regs[reg_start] = type;
+ dev->regs[reg_start] = deposit32(dev->regs[reg_start],
+ GRLIB_PNP_ADDR_SHIFT,
+ GRLIB_PNP_ADDR_SIZE,
+ extract32(address,
+ GRLIB_APB_DEV_ADDR_SHIFT,
+ GRLIB_APB_DEV_ADDR_SIZE));
+ dev->regs[reg_start] = deposit32(dev->regs[reg_start],
+ GRLIB_PNP_MASK_SHIFT,
+ GRLIB_PNP_MASK_SIZE,
+ mask);
+}
+
+static uint64_t grlib_apb_pnp_read(void *opaque, hwaddr offset, unsigned size)
+{
+ APBPnp *apb_pnp = GRLIB_APB_PNP(opaque);
+ uint32_t val;
+
+ val = apb_pnp->regs[offset >> 2];
+ trace_grlib_apb_pnp_read(offset, val);
+
+ return val;
+}
+
+static void grlib_apb_pnp_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ qemu_log_mask(LOG_UNIMP, "%s not implemented\n", __func__);
+}
+
+static const MemoryRegionOps grlib_apb_pnp_ops = {
+ .read = grlib_apb_pnp_read,
+ .write = grlib_apb_pnp_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void grlib_apb_pnp_realize(DeviceState *dev, Error **errp)
+{
+ APBPnp *apb_pnp = GRLIB_APB_PNP(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&apb_pnp->iomem, OBJECT(dev), &grlib_apb_pnp_ops,
+ apb_pnp, TYPE_GRLIB_APB_PNP, GRLIB_PNP_MAX_REGS);
+ sysbus_init_mmio(sbd, &apb_pnp->iomem);
+}
+
+static void grlib_apb_pnp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = grlib_apb_pnp_realize;
+}
+
+static const TypeInfo grlib_apb_pnp_info = {
+ .name = TYPE_GRLIB_APB_PNP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(APBPnp),
+ .class_init = grlib_apb_pnp_class_init,
+};
+
+static void grlib_ahb_apb_pnp_register_types(void)
+{
+ type_register_static(&grlib_ahb_pnp_info);
+ type_register_static(&grlib_apb_pnp_info);
+}
+
+type_init(grlib_ahb_apb_pnp_register_types)
diff --git a/hw/misc/imx25_ccm.c b/hw/misc/imx25_ccm.c
new file mode 100644
index 000000000..ff996e2f2
--- /dev/null
+++ b/hw/misc/imx25_ccm.c
@@ -0,0 +1,320 @@
+/*
+ * IMX25 Clock Control Module
+ *
+ * Copyright (C) 2012 NICTA
+ * Updated by Jean-Christophe Dubois <jcd@tribudubois.net>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * To get the timer frequencies right, we need to emulate at least part of
+ * the CCM.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/imx25_ccm.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#ifndef DEBUG_IMX25_CCM
+#define DEBUG_IMX25_CCM 0
+#endif
+
+#define DPRINTF(fmt, args...) \
+ do { \
+ if (DEBUG_IMX25_CCM) { \
+ fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX25_CCM, \
+ __func__, ##args); \
+ } \
+ } while (0)
+
+static const char *imx25_ccm_reg_name(uint32_t reg)
+{
+ static char unknown[20];
+
+ switch (reg) {
+ case IMX25_CCM_MPCTL_REG:
+ return "mpctl";
+ case IMX25_CCM_UPCTL_REG:
+ return "upctl";
+ case IMX25_CCM_CCTL_REG:
+ return "cctl";
+ case IMX25_CCM_CGCR0_REG:
+ return "cgcr0";
+ case IMX25_CCM_CGCR1_REG:
+ return "cgcr1";
+ case IMX25_CCM_CGCR2_REG:
+ return "cgcr2";
+ case IMX25_CCM_PCDR0_REG:
+ return "pcdr0";
+ case IMX25_CCM_PCDR1_REG:
+ return "pcdr1";
+ case IMX25_CCM_PCDR2_REG:
+ return "pcdr2";
+ case IMX25_CCM_PCDR3_REG:
+ return "pcdr3";
+ case IMX25_CCM_RCSR_REG:
+ return "rcsr";
+ case IMX25_CCM_CRDR_REG:
+ return "crdr";
+ case IMX25_CCM_DCVR0_REG:
+ return "dcvr0";
+ case IMX25_CCM_DCVR1_REG:
+ return "dcvr1";
+ case IMX25_CCM_DCVR2_REG:
+ return "dcvr2";
+ case IMX25_CCM_DCVR3_REG:
+ return "dcvr3";
+ case IMX25_CCM_LTR0_REG:
+ return "ltr0";
+ case IMX25_CCM_LTR1_REG:
+ return "ltr1";
+ case IMX25_CCM_LTR2_REG:
+ return "ltr2";
+ case IMX25_CCM_LTR3_REG:
+ return "ltr3";
+ case IMX25_CCM_LTBR0_REG:
+ return "ltbr0";
+ case IMX25_CCM_LTBR1_REG:
+ return "ltbr1";
+ case IMX25_CCM_PMCR0_REG:
+ return "pmcr0";
+ case IMX25_CCM_PMCR1_REG:
+ return "pmcr1";
+ case IMX25_CCM_PMCR2_REG:
+ return "pmcr2";
+ case IMX25_CCM_MCR_REG:
+ return "mcr";
+ case IMX25_CCM_LPIMR0_REG:
+ return "lpimr0";
+ case IMX25_CCM_LPIMR1_REG:
+ return "lpimr1";
+ default:
+ sprintf(unknown, "[%u ?]", reg);
+ return unknown;
+ }
+}
+#define CKIH_FREQ 24000000 /* 24MHz crystal input */
+
+static const VMStateDescription vmstate_imx25_ccm = {
+ .name = TYPE_IMX25_CCM,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(reg, IMX25CCMState, IMX25_CCM_MAX_REG),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static uint32_t imx25_ccm_get_mpll_clk(IMXCCMState *dev)
+{
+ uint32_t freq;
+ IMX25CCMState *s = IMX25_CCM(dev);
+
+ if (EXTRACT(s->reg[IMX25_CCM_CCTL_REG], MPLL_BYPASS)) {
+ freq = CKIH_FREQ;
+ } else {
+ freq = imx_ccm_calc_pll(s->reg[IMX25_CCM_MPCTL_REG], CKIH_FREQ);
+ }
+
+ DPRINTF("freq = %u\n", freq);
+
+ return freq;
+}
+
+static uint32_t imx25_ccm_get_mcu_clk(IMXCCMState *dev)
+{
+ uint32_t freq;
+ IMX25CCMState *s = IMX25_CCM(dev);
+
+ freq = imx25_ccm_get_mpll_clk(dev);
+
+ if (EXTRACT(s->reg[IMX25_CCM_CCTL_REG], ARM_SRC)) {
+ freq = (freq * 3 / 4);
+ }
+
+ freq = freq / (1 + EXTRACT(s->reg[IMX25_CCM_CCTL_REG], ARM_CLK_DIV));
+
+ DPRINTF("freq = %u\n", freq);
+
+ return freq;
+}
+
+static uint32_t imx25_ccm_get_ahb_clk(IMXCCMState *dev)
+{
+ uint32_t freq;
+ IMX25CCMState *s = IMX25_CCM(dev);
+
+ freq = imx25_ccm_get_mcu_clk(dev)
+ / (1 + EXTRACT(s->reg[IMX25_CCM_CCTL_REG], AHB_CLK_DIV));
+
+ DPRINTF("freq = %u\n", freq);
+
+ return freq;
+}
+
+static uint32_t imx25_ccm_get_ipg_clk(IMXCCMState *dev)
+{
+ uint32_t freq;
+
+ freq = imx25_ccm_get_ahb_clk(dev) / 2;
+
+ DPRINTF("freq = %u\n", freq);
+
+ return freq;
+}
+
+static uint32_t imx25_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
+{
+ uint32_t freq = 0;
+ DPRINTF("Clock = %d)\n", clock);
+
+ switch (clock) {
+ case CLK_NONE:
+ break;
+ case CLK_IPG:
+ case CLK_IPG_HIGH:
+ freq = imx25_ccm_get_ipg_clk(dev);
+ break;
+ case CLK_32k:
+ freq = CKIL_FREQ;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: unsupported clock %d\n",
+ TYPE_IMX25_CCM, __func__, clock);
+ break;
+ }
+
+ DPRINTF("Clock = %d) = %u\n", clock, freq);
+
+ return freq;
+}
+
+static void imx25_ccm_reset(DeviceState *dev)
+{
+ IMX25CCMState *s = IMX25_CCM(dev);
+
+ DPRINTF("\n");
+
+ memset(s->reg, 0, IMX25_CCM_MAX_REG * sizeof(uint32_t));
+ s->reg[IMX25_CCM_MPCTL_REG] = 0x800b2c01;
+ s->reg[IMX25_CCM_UPCTL_REG] = 0x84042800;
+ /*
+ * The value below gives:
+ * CPU = 133 MHz, AHB = 66,5 MHz, IPG = 33 MHz.
+ */
+ s->reg[IMX25_CCM_CCTL_REG] = 0xd0030000;
+ s->reg[IMX25_CCM_CGCR0_REG] = 0x028A0100;
+ s->reg[IMX25_CCM_CGCR1_REG] = 0x04008100;
+ s->reg[IMX25_CCM_CGCR2_REG] = 0x00000438;
+ s->reg[IMX25_CCM_PCDR0_REG] = 0x01010101;
+ s->reg[IMX25_CCM_PCDR1_REG] = 0x01010101;
+ s->reg[IMX25_CCM_PCDR2_REG] = 0x01010101;
+ s->reg[IMX25_CCM_PCDR3_REG] = 0x01010101;
+ s->reg[IMX25_CCM_PMCR0_REG] = 0x00A00000;
+ s->reg[IMX25_CCM_PMCR1_REG] = 0x0000A030;
+ s->reg[IMX25_CCM_PMCR2_REG] = 0x0000A030;
+ s->reg[IMX25_CCM_MCR_REG] = 0x43000000;
+
+ /*
+ * default boot will change the reset values to allow:
+ * CPU = 399 MHz, AHB = 133 MHz, IPG = 66,5 MHz.
+ * For some reason, this doesn't work. With the value below, linux
+ * detects a 88 MHz IPG CLK instead of 66,5 MHz.
+ s->reg[IMX25_CCM_CCTL_REG] = 0x20032000;
+ */
+}
+
+static uint64_t imx25_ccm_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint32_t value = 0;
+ IMX25CCMState *s = (IMX25CCMState *)opaque;
+
+ if (offset < 0x70) {
+ value = s->reg[offset >> 2];
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_IMX25_CCM, __func__, offset);
+ }
+
+ DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx25_ccm_reg_name(offset >> 2),
+ value);
+
+ return value;
+}
+
+static void imx25_ccm_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ IMX25CCMState *s = (IMX25CCMState *)opaque;
+
+ DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx25_ccm_reg_name(offset >> 2),
+ (uint32_t)value);
+
+ if (offset < 0x70) {
+ /*
+ * We will do a better implementation later. In particular some bits
+ * cannot be written to.
+ */
+ s->reg[offset >> 2] = value;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_IMX25_CCM, __func__, offset);
+ }
+}
+
+static const struct MemoryRegionOps imx25_ccm_ops = {
+ .read = imx25_ccm_read,
+ .write = imx25_ccm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ /*
+ * Our device would not work correctly if the guest was doing
+ * unaligned access. This might not be a limitation on the real
+ * device but in practice there is no reason for a guest to access
+ * this device unaligned.
+ */
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void imx25_ccm_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ IMX25CCMState *s = IMX25_CCM(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &imx25_ccm_ops, s,
+ TYPE_IMX25_CCM, 0x1000);
+ sysbus_init_mmio(sd, &s->iomem);
+}
+
+static void imx25_ccm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ IMXCCMClass *ccm = IMX_CCM_CLASS(klass);
+
+ dc->reset = imx25_ccm_reset;
+ dc->vmsd = &vmstate_imx25_ccm;
+ dc->desc = "i.MX25 Clock Control Module";
+
+ ccm->get_clock_frequency = imx25_ccm_get_clock_frequency;
+}
+
+static const TypeInfo imx25_ccm_info = {
+ .name = TYPE_IMX25_CCM,
+ .parent = TYPE_IMX_CCM,
+ .instance_size = sizeof(IMX25CCMState),
+ .instance_init = imx25_ccm_init,
+ .class_init = imx25_ccm_class_init,
+};
+
+static void imx25_ccm_register_types(void)
+{
+ type_register_static(&imx25_ccm_info);
+}
+
+type_init(imx25_ccm_register_types)
diff --git a/hw/misc/imx31_ccm.c b/hw/misc/imx31_ccm.c
new file mode 100644
index 000000000..ad30a4b2c
--- /dev/null
+++ b/hw/misc/imx31_ccm.c
@@ -0,0 +1,347 @@
+/*
+ * IMX31 Clock Control Module
+ *
+ * Copyright (C) 2012 NICTA
+ * Updated by Jean-Christophe Dubois <jcd@tribudubois.net>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * To get the timer frequencies right, we need to emulate at least part of
+ * the i.MX31 CCM.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/imx31_ccm.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define CKIH_FREQ 26000000 /* 26MHz crystal input */
+
+#ifndef DEBUG_IMX31_CCM
+#define DEBUG_IMX31_CCM 0
+#endif
+
+#define DPRINTF(fmt, args...) \
+ do { \
+ if (DEBUG_IMX31_CCM) { \
+ fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX31_CCM, \
+ __func__, ##args); \
+ } \
+ } while (0)
+
+static const char *imx31_ccm_reg_name(uint32_t reg)
+{
+ static char unknown[20];
+
+ switch (reg) {
+ case IMX31_CCM_CCMR_REG:
+ return "CCMR";
+ case IMX31_CCM_PDR0_REG:
+ return "PDR0";
+ case IMX31_CCM_PDR1_REG:
+ return "PDR1";
+ case IMX31_CCM_RCSR_REG:
+ return "RCSR";
+ case IMX31_CCM_MPCTL_REG:
+ return "MPCTL";
+ case IMX31_CCM_UPCTL_REG:
+ return "UPCTL";
+ case IMX31_CCM_SPCTL_REG:
+ return "SPCTL";
+ case IMX31_CCM_COSR_REG:
+ return "COSR";
+ case IMX31_CCM_CGR0_REG:
+ return "CGR0";
+ case IMX31_CCM_CGR1_REG:
+ return "CGR1";
+ case IMX31_CCM_CGR2_REG:
+ return "CGR2";
+ case IMX31_CCM_WIMR_REG:
+ return "WIMR";
+ case IMX31_CCM_LDC_REG:
+ return "LDC";
+ case IMX31_CCM_DCVR0_REG:
+ return "DCVR0";
+ case IMX31_CCM_DCVR1_REG:
+ return "DCVR1";
+ case IMX31_CCM_DCVR2_REG:
+ return "DCVR2";
+ case IMX31_CCM_DCVR3_REG:
+ return "DCVR3";
+ case IMX31_CCM_LTR0_REG:
+ return "LTR0";
+ case IMX31_CCM_LTR1_REG:
+ return "LTR1";
+ case IMX31_CCM_LTR2_REG:
+ return "LTR2";
+ case IMX31_CCM_LTR3_REG:
+ return "LTR3";
+ case IMX31_CCM_LTBR0_REG:
+ return "LTBR0";
+ case IMX31_CCM_LTBR1_REG:
+ return "LTBR1";
+ case IMX31_CCM_PMCR0_REG:
+ return "PMCR0";
+ case IMX31_CCM_PMCR1_REG:
+ return "PMCR1";
+ case IMX31_CCM_PDR2_REG:
+ return "PDR2";
+ default:
+ sprintf(unknown, "[%u ?]", reg);
+ return unknown;
+ }
+}
+
+static const VMStateDescription vmstate_imx31_ccm = {
+ .name = TYPE_IMX31_CCM,
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(reg, IMX31CCMState, IMX31_CCM_MAX_REG),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static uint32_t imx31_ccm_get_pll_ref_clk(IMXCCMState *dev)
+{
+ uint32_t freq = 0;
+ IMX31CCMState *s = IMX31_CCM(dev);
+
+ if ((s->reg[IMX31_CCM_CCMR_REG] & CCMR_PRCS) == 2) {
+ if (s->reg[IMX31_CCM_CCMR_REG] & CCMR_FPME) {
+ freq = CKIL_FREQ;
+ if (s->reg[IMX31_CCM_CCMR_REG] & CCMR_FPMF) {
+ freq *= 1024;
+ }
+ }
+ } else {
+ freq = CKIH_FREQ;
+ }
+
+ DPRINTF("freq = %u\n", freq);
+
+ return freq;
+}
+
+static uint32_t imx31_ccm_get_mpll_clk(IMXCCMState *dev)
+{
+ uint32_t freq;
+ IMX31CCMState *s = IMX31_CCM(dev);
+
+ freq = imx_ccm_calc_pll(s->reg[IMX31_CCM_MPCTL_REG],
+ imx31_ccm_get_pll_ref_clk(dev));
+
+ DPRINTF("freq = %u\n", freq);
+
+ return freq;
+}
+
+static uint32_t imx31_ccm_get_mcu_main_clk(IMXCCMState *dev)
+{
+ uint32_t freq;
+ IMX31CCMState *s = IMX31_CCM(dev);
+
+ if ((s->reg[IMX31_CCM_CCMR_REG] & CCMR_MDS) ||
+ !(s->reg[IMX31_CCM_CCMR_REG] & CCMR_MPE)) {
+ freq = imx31_ccm_get_pll_ref_clk(dev);
+ } else {
+ freq = imx31_ccm_get_mpll_clk(dev);
+ }
+
+ DPRINTF("freq = %u\n", freq);
+
+ return freq;
+}
+
+static uint32_t imx31_ccm_get_hclk_clk(IMXCCMState *dev)
+{
+ uint32_t freq;
+ IMX31CCMState *s = IMX31_CCM(dev);
+
+ freq = imx31_ccm_get_mcu_main_clk(dev)
+ / (1 + EXTRACT(s->reg[IMX31_CCM_PDR0_REG], MAX));
+
+ DPRINTF("freq = %u\n", freq);
+
+ return freq;
+}
+
+static uint32_t imx31_ccm_get_ipg_clk(IMXCCMState *dev)
+{
+ uint32_t freq;
+ IMX31CCMState *s = IMX31_CCM(dev);
+
+ freq = imx31_ccm_get_hclk_clk(dev)
+ / (1 + EXTRACT(s->reg[IMX31_CCM_PDR0_REG], IPG));
+
+ DPRINTF("freq = %u\n", freq);
+
+ return freq;
+}
+
+static uint32_t imx31_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
+{
+ uint32_t freq = 0;
+
+ switch (clock) {
+ case CLK_NONE:
+ break;
+ case CLK_IPG:
+ case CLK_IPG_HIGH:
+ freq = imx31_ccm_get_ipg_clk(dev);
+ break;
+ case CLK_32k:
+ freq = CKIL_FREQ;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: unsupported clock %d\n",
+ TYPE_IMX31_CCM, __func__, clock);
+ break;
+ }
+
+ DPRINTF("Clock = %d) = %u\n", clock, freq);
+
+ return freq;
+}
+
+static void imx31_ccm_reset(DeviceState *dev)
+{
+ IMX31CCMState *s = IMX31_CCM(dev);
+
+ DPRINTF("()\n");
+
+ memset(s->reg, 0, sizeof(uint32_t) * IMX31_CCM_MAX_REG);
+
+ s->reg[IMX31_CCM_CCMR_REG] = 0x074b0b7d;
+ s->reg[IMX31_CCM_PDR0_REG] = 0xff870b48;
+ s->reg[IMX31_CCM_PDR1_REG] = 0x49fcfe7f;
+ s->reg[IMX31_CCM_RCSR_REG] = 0x007f0000;
+ s->reg[IMX31_CCM_MPCTL_REG] = 0x04001800;
+ s->reg[IMX31_CCM_UPCTL_REG] = 0x04051c03;
+ s->reg[IMX31_CCM_SPCTL_REG] = 0x04043001;
+ s->reg[IMX31_CCM_COSR_REG] = 0x00000280;
+ s->reg[IMX31_CCM_CGR0_REG] = 0xffffffff;
+ s->reg[IMX31_CCM_CGR1_REG] = 0xffffffff;
+ s->reg[IMX31_CCM_CGR2_REG] = 0xffffffff;
+ s->reg[IMX31_CCM_WIMR_REG] = 0xffffffff;
+ s->reg[IMX31_CCM_LTR1_REG] = 0x00004040;
+ s->reg[IMX31_CCM_PMCR0_REG] = 0x80209828;
+ s->reg[IMX31_CCM_PMCR1_REG] = 0x00aa0000;
+ s->reg[IMX31_CCM_PDR2_REG] = 0x00000285;
+}
+
+static uint64_t imx31_ccm_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint32_t value = 0;
+ IMX31CCMState *s = (IMX31CCMState *)opaque;
+
+ if ((offset >> 2) < IMX31_CCM_MAX_REG) {
+ value = s->reg[offset >> 2];
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_IMX31_CCM, __func__, offset);
+ }
+
+ DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx31_ccm_reg_name(offset >> 2),
+ value);
+
+ return (uint64_t)value;
+}
+
+static void imx31_ccm_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ IMX31CCMState *s = (IMX31CCMState *)opaque;
+
+ DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx31_ccm_reg_name(offset >> 2),
+ (uint32_t)value);
+
+ switch (offset >> 2) {
+ case IMX31_CCM_CCMR_REG:
+ s->reg[IMX31_CCM_CCMR_REG] = CCMR_FPMF | (value & 0x3b6fdfff);
+ break;
+ case IMX31_CCM_PDR0_REG:
+ s->reg[IMX31_CCM_PDR0_REG] = value & 0xff9f3fff;
+ break;
+ case IMX31_CCM_PDR1_REG:
+ s->reg[IMX31_CCM_PDR1_REG] = value;
+ break;
+ case IMX31_CCM_MPCTL_REG:
+ s->reg[IMX31_CCM_MPCTL_REG] = value & 0xbfff3fff;
+ break;
+ case IMX31_CCM_SPCTL_REG:
+ s->reg[IMX31_CCM_SPCTL_REG] = value & 0xbfff3fff;
+ break;
+ case IMX31_CCM_CGR0_REG:
+ s->reg[IMX31_CCM_CGR0_REG] = value;
+ break;
+ case IMX31_CCM_CGR1_REG:
+ s->reg[IMX31_CCM_CGR1_REG] = value;
+ break;
+ case IMX31_CCM_CGR2_REG:
+ s->reg[IMX31_CCM_CGR2_REG] = value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_IMX31_CCM, __func__, offset);
+ break;
+ }
+}
+
+static const struct MemoryRegionOps imx31_ccm_ops = {
+ .read = imx31_ccm_read,
+ .write = imx31_ccm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ /*
+ * Our device would not work correctly if the guest was doing
+ * unaligned access. This might not be a limitation on the real
+ * device but in practice there is no reason for a guest to access
+ * this device unaligned.
+ */
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+
+};
+
+static void imx31_ccm_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ IMX31CCMState *s = IMX31_CCM(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &imx31_ccm_ops, s,
+ TYPE_IMX31_CCM, 0x1000);
+ sysbus_init_mmio(sd, &s->iomem);
+}
+
+static void imx31_ccm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ IMXCCMClass *ccm = IMX_CCM_CLASS(klass);
+
+ dc->reset = imx31_ccm_reset;
+ dc->vmsd = &vmstate_imx31_ccm;
+ dc->desc = "i.MX31 Clock Control Module";
+
+ ccm->get_clock_frequency = imx31_ccm_get_clock_frequency;
+}
+
+static const TypeInfo imx31_ccm_info = {
+ .name = TYPE_IMX31_CCM,
+ .parent = TYPE_IMX_CCM,
+ .instance_size = sizeof(IMX31CCMState),
+ .instance_init = imx31_ccm_init,
+ .class_init = imx31_ccm_class_init,
+};
+
+static void imx31_ccm_register_types(void)
+{
+ type_register_static(&imx31_ccm_info);
+}
+
+type_init(imx31_ccm_register_types)
diff --git a/hw/misc/imx6_ccm.c b/hw/misc/imx6_ccm.c
new file mode 100644
index 000000000..4c830fd89
--- /dev/null
+++ b/hw/misc/imx6_ccm.c
@@ -0,0 +1,783 @@
+/*
+ * IMX6 Clock Control Module
+ *
+ * Copyright (c) 2015 Jean-Christophe Dubois <jcd@tribudubois.net>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * To get the timer frequencies right, we need to emulate at least part of
+ * the CCM.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/imx6_ccm.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#ifndef DEBUG_IMX6_CCM
+#define DEBUG_IMX6_CCM 0
+#endif
+
+#define DPRINTF(fmt, args...) \
+ do { \
+ if (DEBUG_IMX6_CCM) { \
+ fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX6_CCM, \
+ __func__, ##args); \
+ } \
+ } while (0)
+
+static const char *imx6_ccm_reg_name(uint32_t reg)
+{
+ static char unknown[20];
+
+ switch (reg) {
+ case CCM_CCR:
+ return "CCR";
+ case CCM_CCDR:
+ return "CCDR";
+ case CCM_CSR:
+ return "CSR";
+ case CCM_CCSR:
+ return "CCSR";
+ case CCM_CACRR:
+ return "CACRR";
+ case CCM_CBCDR:
+ return "CBCDR";
+ case CCM_CBCMR:
+ return "CBCMR";
+ case CCM_CSCMR1:
+ return "CSCMR1";
+ case CCM_CSCMR2:
+ return "CSCMR2";
+ case CCM_CSCDR1:
+ return "CSCDR1";
+ case CCM_CS1CDR:
+ return "CS1CDR";
+ case CCM_CS2CDR:
+ return "CS2CDR";
+ case CCM_CDCDR:
+ return "CDCDR";
+ case CCM_CHSCCDR:
+ return "CHSCCDR";
+ case CCM_CSCDR2:
+ return "CSCDR2";
+ case CCM_CSCDR3:
+ return "CSCDR3";
+ case CCM_CDHIPR:
+ return "CDHIPR";
+ case CCM_CTOR:
+ return "CTOR";
+ case CCM_CLPCR:
+ return "CLPCR";
+ case CCM_CISR:
+ return "CISR";
+ case CCM_CIMR:
+ return "CIMR";
+ case CCM_CCOSR:
+ return "CCOSR";
+ case CCM_CGPR:
+ return "CGPR";
+ case CCM_CCGR0:
+ return "CCGR0";
+ case CCM_CCGR1:
+ return "CCGR1";
+ case CCM_CCGR2:
+ return "CCGR2";
+ case CCM_CCGR3:
+ return "CCGR3";
+ case CCM_CCGR4:
+ return "CCGR4";
+ case CCM_CCGR5:
+ return "CCGR5";
+ case CCM_CCGR6:
+ return "CCGR6";
+ case CCM_CMEOR:
+ return "CMEOR";
+ default:
+ sprintf(unknown, "%u ?", reg);
+ return unknown;
+ }
+}
+
+static const char *imx6_analog_reg_name(uint32_t reg)
+{
+ static char unknown[20];
+
+ switch (reg) {
+ case CCM_ANALOG_PLL_ARM:
+ return "PLL_ARM";
+ case CCM_ANALOG_PLL_ARM_SET:
+ return "PLL_ARM_SET";
+ case CCM_ANALOG_PLL_ARM_CLR:
+ return "PLL_ARM_CLR";
+ case CCM_ANALOG_PLL_ARM_TOG:
+ return "PLL_ARM_TOG";
+ case CCM_ANALOG_PLL_USB1:
+ return "PLL_USB1";
+ case CCM_ANALOG_PLL_USB1_SET:
+ return "PLL_USB1_SET";
+ case CCM_ANALOG_PLL_USB1_CLR:
+ return "PLL_USB1_CLR";
+ case CCM_ANALOG_PLL_USB1_TOG:
+ return "PLL_USB1_TOG";
+ case CCM_ANALOG_PLL_USB2:
+ return "PLL_USB2";
+ case CCM_ANALOG_PLL_USB2_SET:
+ return "PLL_USB2_SET";
+ case CCM_ANALOG_PLL_USB2_CLR:
+ return "PLL_USB2_CLR";
+ case CCM_ANALOG_PLL_USB2_TOG:
+ return "PLL_USB2_TOG";
+ case CCM_ANALOG_PLL_SYS:
+ return "PLL_SYS";
+ case CCM_ANALOG_PLL_SYS_SET:
+ return "PLL_SYS_SET";
+ case CCM_ANALOG_PLL_SYS_CLR:
+ return "PLL_SYS_CLR";
+ case CCM_ANALOG_PLL_SYS_TOG:
+ return "PLL_SYS_TOG";
+ case CCM_ANALOG_PLL_SYS_SS:
+ return "PLL_SYS_SS";
+ case CCM_ANALOG_PLL_SYS_NUM:
+ return "PLL_SYS_NUM";
+ case CCM_ANALOG_PLL_SYS_DENOM:
+ return "PLL_SYS_DENOM";
+ case CCM_ANALOG_PLL_AUDIO:
+ return "PLL_AUDIO";
+ case CCM_ANALOG_PLL_AUDIO_SET:
+ return "PLL_AUDIO_SET";
+ case CCM_ANALOG_PLL_AUDIO_CLR:
+ return "PLL_AUDIO_CLR";
+ case CCM_ANALOG_PLL_AUDIO_TOG:
+ return "PLL_AUDIO_TOG";
+ case CCM_ANALOG_PLL_AUDIO_NUM:
+ return "PLL_AUDIO_NUM";
+ case CCM_ANALOG_PLL_AUDIO_DENOM:
+ return "PLL_AUDIO_DENOM";
+ case CCM_ANALOG_PLL_VIDEO:
+ return "PLL_VIDEO";
+ case CCM_ANALOG_PLL_VIDEO_SET:
+ return "PLL_VIDEO_SET";
+ case CCM_ANALOG_PLL_VIDEO_CLR:
+ return "PLL_VIDEO_CLR";
+ case CCM_ANALOG_PLL_VIDEO_TOG:
+ return "PLL_VIDEO_TOG";
+ case CCM_ANALOG_PLL_VIDEO_NUM:
+ return "PLL_VIDEO_NUM";
+ case CCM_ANALOG_PLL_VIDEO_DENOM:
+ return "PLL_VIDEO_DENOM";
+ case CCM_ANALOG_PLL_MLB:
+ return "PLL_MLB";
+ case CCM_ANALOG_PLL_MLB_SET:
+ return "PLL_MLB_SET";
+ case CCM_ANALOG_PLL_MLB_CLR:
+ return "PLL_MLB_CLR";
+ case CCM_ANALOG_PLL_MLB_TOG:
+ return "PLL_MLB_TOG";
+ case CCM_ANALOG_PLL_ENET:
+ return "PLL_ENET";
+ case CCM_ANALOG_PLL_ENET_SET:
+ return "PLL_ENET_SET";
+ case CCM_ANALOG_PLL_ENET_CLR:
+ return "PLL_ENET_CLR";
+ case CCM_ANALOG_PLL_ENET_TOG:
+ return "PLL_ENET_TOG";
+ case CCM_ANALOG_PFD_480:
+ return "PFD_480";
+ case CCM_ANALOG_PFD_480_SET:
+ return "PFD_480_SET";
+ case CCM_ANALOG_PFD_480_CLR:
+ return "PFD_480_CLR";
+ case CCM_ANALOG_PFD_480_TOG:
+ return "PFD_480_TOG";
+ case CCM_ANALOG_PFD_528:
+ return "PFD_528";
+ case CCM_ANALOG_PFD_528_SET:
+ return "PFD_528_SET";
+ case CCM_ANALOG_PFD_528_CLR:
+ return "PFD_528_CLR";
+ case CCM_ANALOG_PFD_528_TOG:
+ return "PFD_528_TOG";
+ case CCM_ANALOG_MISC0:
+ return "MISC0";
+ case CCM_ANALOG_MISC0_SET:
+ return "MISC0_SET";
+ case CCM_ANALOG_MISC0_CLR:
+ return "MISC0_CLR";
+ case CCM_ANALOG_MISC0_TOG:
+ return "MISC0_TOG";
+ case CCM_ANALOG_MISC2:
+ return "MISC2";
+ case CCM_ANALOG_MISC2_SET:
+ return "MISC2_SET";
+ case CCM_ANALOG_MISC2_CLR:
+ return "MISC2_CLR";
+ case CCM_ANALOG_MISC2_TOG:
+ return "MISC2_TOG";
+ case PMU_REG_1P1:
+ return "PMU_REG_1P1";
+ case PMU_REG_3P0:
+ return "PMU_REG_3P0";
+ case PMU_REG_2P5:
+ return "PMU_REG_2P5";
+ case PMU_REG_CORE:
+ return "PMU_REG_CORE";
+ case PMU_MISC1:
+ return "PMU_MISC1";
+ case PMU_MISC1_SET:
+ return "PMU_MISC1_SET";
+ case PMU_MISC1_CLR:
+ return "PMU_MISC1_CLR";
+ case PMU_MISC1_TOG:
+ return "PMU_MISC1_TOG";
+ case USB_ANALOG_DIGPROG:
+ return "USB_ANALOG_DIGPROG";
+ default:
+ sprintf(unknown, "%u ?", reg);
+ return unknown;
+ }
+}
+
+#define CKIH_FREQ 24000000 /* 24MHz crystal input */
+
+static const VMStateDescription vmstate_imx6_ccm = {
+ .name = TYPE_IMX6_CCM,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(ccm, IMX6CCMState, CCM_MAX),
+ VMSTATE_UINT32_ARRAY(analog, IMX6CCMState, CCM_ANALOG_MAX),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static uint64_t imx6_analog_get_pll2_clk(IMX6CCMState *dev)
+{
+ uint64_t freq = 24000000;
+
+ if (EXTRACT(dev->analog[CCM_ANALOG_PLL_SYS], DIV_SELECT)) {
+ freq *= 22;
+ } else {
+ freq *= 20;
+ }
+
+ DPRINTF("freq = %u\n", (uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6_analog_get_pll2_pfd0_clk(IMX6CCMState *dev)
+{
+ uint64_t freq = 0;
+
+ freq = imx6_analog_get_pll2_clk(dev) * 18
+ / EXTRACT(dev->analog[CCM_ANALOG_PFD_528], PFD0_FRAC);
+
+ DPRINTF("freq = %u\n", (uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6_analog_get_pll2_pfd2_clk(IMX6CCMState *dev)
+{
+ uint64_t freq = 0;
+
+ freq = imx6_analog_get_pll2_clk(dev) * 18
+ / EXTRACT(dev->analog[CCM_ANALOG_PFD_528], PFD2_FRAC);
+
+ DPRINTF("freq = %u\n", (uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6_analog_get_periph_clk(IMX6CCMState *dev)
+{
+ uint64_t freq = 0;
+
+ switch (EXTRACT(dev->ccm[CCM_CBCMR], PRE_PERIPH_CLK_SEL)) {
+ case 0:
+ freq = imx6_analog_get_pll2_clk(dev);
+ break;
+ case 1:
+ freq = imx6_analog_get_pll2_pfd2_clk(dev);
+ break;
+ case 2:
+ freq = imx6_analog_get_pll2_pfd0_clk(dev);
+ break;
+ case 3:
+ freq = imx6_analog_get_pll2_pfd2_clk(dev) / 2;
+ break;
+ default:
+ /* We should never get there */
+ g_assert_not_reached();
+ break;
+ }
+
+ DPRINTF("freq = %u\n", (uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6_ccm_get_ahb_clk(IMX6CCMState *dev)
+{
+ uint64_t freq = 0;
+
+ freq = imx6_analog_get_periph_clk(dev)
+ / (1 + EXTRACT(dev->ccm[CCM_CBCDR], AHB_PODF));
+
+ DPRINTF("freq = %u\n", (uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6_ccm_get_ipg_clk(IMX6CCMState *dev)
+{
+ uint64_t freq = 0;
+
+ freq = imx6_ccm_get_ahb_clk(dev)
+ / (1 + EXTRACT(dev->ccm[CCM_CBCDR], IPG_PODF));
+
+ DPRINTF("freq = %u\n", (uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6_ccm_get_per_clk(IMX6CCMState *dev)
+{
+ uint64_t freq = 0;
+
+ freq = imx6_ccm_get_ipg_clk(dev)
+ / (1 + EXTRACT(dev->ccm[CCM_CSCMR1], PERCLK_PODF));
+
+ DPRINTF("freq = %u\n", (uint32_t)freq);
+
+ return freq;
+}
+
+static uint32_t imx6_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
+{
+ uint32_t freq = 0;
+ IMX6CCMState *s = IMX6_CCM(dev);
+
+ switch (clock) {
+ case CLK_NONE:
+ break;
+ case CLK_IPG:
+ freq = imx6_ccm_get_ipg_clk(s);
+ break;
+ case CLK_IPG_HIGH:
+ freq = imx6_ccm_get_per_clk(s);
+ break;
+ case CLK_32k:
+ freq = CKIL_FREQ;
+ break;
+ case CLK_HIGH:
+ freq = 24000000;
+ break;
+ case CLK_HIGH_DIV:
+ freq = 24000000 / 8;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: unsupported clock %d\n",
+ TYPE_IMX6_CCM, __func__, clock);
+ break;
+ }
+
+ DPRINTF("Clock = %d) = %u\n", clock, freq);
+
+ return freq;
+}
+
+static void imx6_ccm_reset(DeviceState *dev)
+{
+ IMX6CCMState *s = IMX6_CCM(dev);
+
+ DPRINTF("\n");
+
+ s->ccm[CCM_CCR] = 0x040116FF;
+ s->ccm[CCM_CCDR] = 0x00000000;
+ s->ccm[CCM_CSR] = 0x00000010;
+ s->ccm[CCM_CCSR] = 0x00000100;
+ s->ccm[CCM_CACRR] = 0x00000000;
+ s->ccm[CCM_CBCDR] = 0x00018D40;
+ s->ccm[CCM_CBCMR] = 0x00022324;
+ s->ccm[CCM_CSCMR1] = 0x00F00000;
+ s->ccm[CCM_CSCMR2] = 0x02B92F06;
+ s->ccm[CCM_CSCDR1] = 0x00490B00;
+ s->ccm[CCM_CS1CDR] = 0x0EC102C1;
+ s->ccm[CCM_CS2CDR] = 0x000736C1;
+ s->ccm[CCM_CDCDR] = 0x33F71F92;
+ s->ccm[CCM_CHSCCDR] = 0x0002A150;
+ s->ccm[CCM_CSCDR2] = 0x0002A150;
+ s->ccm[CCM_CSCDR3] = 0x00014841;
+ s->ccm[CCM_CDHIPR] = 0x00000000;
+ s->ccm[CCM_CTOR] = 0x00000000;
+ s->ccm[CCM_CLPCR] = 0x00000079;
+ s->ccm[CCM_CISR] = 0x00000000;
+ s->ccm[CCM_CIMR] = 0xFFFFFFFF;
+ s->ccm[CCM_CCOSR] = 0x000A0001;
+ s->ccm[CCM_CGPR] = 0x0000FE62;
+ s->ccm[CCM_CCGR0] = 0xFFFFFFFF;
+ s->ccm[CCM_CCGR1] = 0xFFFFFFFF;
+ s->ccm[CCM_CCGR2] = 0xFC3FFFFF;
+ s->ccm[CCM_CCGR3] = 0xFFFFFFFF;
+ s->ccm[CCM_CCGR4] = 0xFFFFFFFF;
+ s->ccm[CCM_CCGR5] = 0xFFFFFFFF;
+ s->ccm[CCM_CCGR6] = 0xFFFFFFFF;
+ s->ccm[CCM_CMEOR] = 0xFFFFFFFF;
+
+ s->analog[CCM_ANALOG_PLL_ARM] = 0x00013042;
+ s->analog[CCM_ANALOG_PLL_USB1] = 0x00012000;
+ s->analog[CCM_ANALOG_PLL_USB2] = 0x00012000;
+ s->analog[CCM_ANALOG_PLL_SYS] = 0x00013001;
+ s->analog[CCM_ANALOG_PLL_SYS_SS] = 0x00000000;
+ s->analog[CCM_ANALOG_PLL_SYS_NUM] = 0x00000000;
+ s->analog[CCM_ANALOG_PLL_SYS_DENOM] = 0x00000012;
+ s->analog[CCM_ANALOG_PLL_AUDIO] = 0x00011006;
+ s->analog[CCM_ANALOG_PLL_AUDIO_NUM] = 0x05F5E100;
+ s->analog[CCM_ANALOG_PLL_AUDIO_DENOM] = 0x2964619C;
+ s->analog[CCM_ANALOG_PLL_VIDEO] = 0x0001100C;
+ s->analog[CCM_ANALOG_PLL_VIDEO_NUM] = 0x05F5E100;
+ s->analog[CCM_ANALOG_PLL_VIDEO_DENOM] = 0x10A24447;
+ s->analog[CCM_ANALOG_PLL_MLB] = 0x00010000;
+ s->analog[CCM_ANALOG_PLL_ENET] = 0x00011001;
+ s->analog[CCM_ANALOG_PFD_480] = 0x1311100C;
+ s->analog[CCM_ANALOG_PFD_528] = 0x1018101B;
+
+ s->analog[PMU_REG_1P1] = 0x00001073;
+ s->analog[PMU_REG_3P0] = 0x00000F74;
+ s->analog[PMU_REG_2P5] = 0x00005071;
+ s->analog[PMU_REG_CORE] = 0x00402010;
+ s->analog[PMU_MISC0] = 0x04000080;
+ s->analog[PMU_MISC1] = 0x00000000;
+ s->analog[PMU_MISC2] = 0x00272727;
+
+ s->analog[USB_ANALOG_USB1_VBUS_DETECT] = 0x00000004;
+ s->analog[USB_ANALOG_USB1_CHRG_DETECT] = 0x00000000;
+ s->analog[USB_ANALOG_USB1_VBUS_DETECT_STAT] = 0x00000000;
+ s->analog[USB_ANALOG_USB1_CHRG_DETECT_STAT] = 0x00000000;
+ s->analog[USB_ANALOG_USB1_MISC] = 0x00000002;
+ s->analog[USB_ANALOG_USB2_VBUS_DETECT] = 0x00000004;
+ s->analog[USB_ANALOG_USB2_CHRG_DETECT] = 0x00000000;
+ s->analog[USB_ANALOG_USB2_MISC] = 0x00000002;
+ s->analog[USB_ANALOG_DIGPROG] = 0x00630000;
+
+ /* all PLLs need to be locked */
+ s->analog[CCM_ANALOG_PLL_ARM] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_USB1] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_USB2] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_SYS] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_AUDIO] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_VIDEO] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_MLB] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_ENET] |= CCM_ANALOG_PLL_LOCK;
+}
+
+static uint64_t imx6_ccm_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint32_t value = 0;
+ uint32_t index = offset >> 2;
+ IMX6CCMState *s = (IMX6CCMState *)opaque;
+
+ value = s->ccm[index];
+
+ DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx6_ccm_reg_name(index), value);
+
+ return (uint64_t)value;
+}
+
+static void imx6_ccm_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ uint32_t index = offset >> 2;
+ IMX6CCMState *s = (IMX6CCMState *)opaque;
+
+ DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx6_ccm_reg_name(index),
+ (uint32_t)value);
+
+ /*
+ * We will do a better implementation later. In particular some bits
+ * cannot be written to.
+ */
+ s->ccm[index] = (uint32_t)value;
+}
+
+static uint64_t imx6_analog_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint32_t value;
+ uint32_t index = offset >> 2;
+ IMX6CCMState *s = (IMX6CCMState *)opaque;
+
+ switch (index) {
+ case CCM_ANALOG_PLL_ARM_SET:
+ case CCM_ANALOG_PLL_USB1_SET:
+ case CCM_ANALOG_PLL_USB2_SET:
+ case CCM_ANALOG_PLL_SYS_SET:
+ case CCM_ANALOG_PLL_AUDIO_SET:
+ case CCM_ANALOG_PLL_VIDEO_SET:
+ case CCM_ANALOG_PLL_MLB_SET:
+ case CCM_ANALOG_PLL_ENET_SET:
+ case CCM_ANALOG_PFD_480_SET:
+ case CCM_ANALOG_PFD_528_SET:
+ case CCM_ANALOG_MISC0_SET:
+ case PMU_MISC1_SET:
+ case CCM_ANALOG_MISC2_SET:
+ case USB_ANALOG_USB1_VBUS_DETECT_SET:
+ case USB_ANALOG_USB1_CHRG_DETECT_SET:
+ case USB_ANALOG_USB1_MISC_SET:
+ case USB_ANALOG_USB2_VBUS_DETECT_SET:
+ case USB_ANALOG_USB2_CHRG_DETECT_SET:
+ case USB_ANALOG_USB2_MISC_SET:
+ /*
+ * All REG_NAME_SET register access are in fact targeting the
+ * the REG_NAME register.
+ */
+ value = s->analog[index - 1];
+ break;
+ case CCM_ANALOG_PLL_ARM_CLR:
+ case CCM_ANALOG_PLL_USB1_CLR:
+ case CCM_ANALOG_PLL_USB2_CLR:
+ case CCM_ANALOG_PLL_SYS_CLR:
+ case CCM_ANALOG_PLL_AUDIO_CLR:
+ case CCM_ANALOG_PLL_VIDEO_CLR:
+ case CCM_ANALOG_PLL_MLB_CLR:
+ case CCM_ANALOG_PLL_ENET_CLR:
+ case CCM_ANALOG_PFD_480_CLR:
+ case CCM_ANALOG_PFD_528_CLR:
+ case CCM_ANALOG_MISC0_CLR:
+ case PMU_MISC1_CLR:
+ case CCM_ANALOG_MISC2_CLR:
+ case USB_ANALOG_USB1_VBUS_DETECT_CLR:
+ case USB_ANALOG_USB1_CHRG_DETECT_CLR:
+ case USB_ANALOG_USB1_MISC_CLR:
+ case USB_ANALOG_USB2_VBUS_DETECT_CLR:
+ case USB_ANALOG_USB2_CHRG_DETECT_CLR:
+ case USB_ANALOG_USB2_MISC_CLR:
+ /*
+ * All REG_NAME_CLR register access are in fact targeting the
+ * the REG_NAME register.
+ */
+ value = s->analog[index - 2];
+ break;
+ case CCM_ANALOG_PLL_ARM_TOG:
+ case CCM_ANALOG_PLL_USB1_TOG:
+ case CCM_ANALOG_PLL_USB2_TOG:
+ case CCM_ANALOG_PLL_SYS_TOG:
+ case CCM_ANALOG_PLL_AUDIO_TOG:
+ case CCM_ANALOG_PLL_VIDEO_TOG:
+ case CCM_ANALOG_PLL_MLB_TOG:
+ case CCM_ANALOG_PLL_ENET_TOG:
+ case CCM_ANALOG_PFD_480_TOG:
+ case CCM_ANALOG_PFD_528_TOG:
+ case CCM_ANALOG_MISC0_TOG:
+ case PMU_MISC1_TOG:
+ case CCM_ANALOG_MISC2_TOG:
+ case USB_ANALOG_USB1_VBUS_DETECT_TOG:
+ case USB_ANALOG_USB1_CHRG_DETECT_TOG:
+ case USB_ANALOG_USB1_MISC_TOG:
+ case USB_ANALOG_USB2_VBUS_DETECT_TOG:
+ case USB_ANALOG_USB2_CHRG_DETECT_TOG:
+ case USB_ANALOG_USB2_MISC_TOG:
+ /*
+ * All REG_NAME_TOG register access are in fact targeting the
+ * the REG_NAME register.
+ */
+ value = s->analog[index - 3];
+ break;
+ default:
+ value = s->analog[index];
+ break;
+ }
+
+ DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx6_analog_reg_name(index), value);
+
+ return (uint64_t)value;
+}
+
+static void imx6_analog_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ uint32_t index = offset >> 2;
+ IMX6CCMState *s = (IMX6CCMState *)opaque;
+
+ DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx6_analog_reg_name(index),
+ (uint32_t)value);
+
+ switch (index) {
+ case CCM_ANALOG_PLL_ARM_SET:
+ case CCM_ANALOG_PLL_USB1_SET:
+ case CCM_ANALOG_PLL_USB2_SET:
+ case CCM_ANALOG_PLL_SYS_SET:
+ case CCM_ANALOG_PLL_AUDIO_SET:
+ case CCM_ANALOG_PLL_VIDEO_SET:
+ case CCM_ANALOG_PLL_MLB_SET:
+ case CCM_ANALOG_PLL_ENET_SET:
+ case CCM_ANALOG_PFD_480_SET:
+ case CCM_ANALOG_PFD_528_SET:
+ case CCM_ANALOG_MISC0_SET:
+ case PMU_MISC1_SET:
+ case CCM_ANALOG_MISC2_SET:
+ case USB_ANALOG_USB1_VBUS_DETECT_SET:
+ case USB_ANALOG_USB1_CHRG_DETECT_SET:
+ case USB_ANALOG_USB1_MISC_SET:
+ case USB_ANALOG_USB2_VBUS_DETECT_SET:
+ case USB_ANALOG_USB2_CHRG_DETECT_SET:
+ case USB_ANALOG_USB2_MISC_SET:
+ /*
+ * All REG_NAME_SET register access are in fact targeting the
+ * the REG_NAME register. So we change the value of the
+ * REG_NAME register, setting bits passed in the value.
+ */
+ s->analog[index - 1] |= value;
+ break;
+ case CCM_ANALOG_PLL_ARM_CLR:
+ case CCM_ANALOG_PLL_USB1_CLR:
+ case CCM_ANALOG_PLL_USB2_CLR:
+ case CCM_ANALOG_PLL_SYS_CLR:
+ case CCM_ANALOG_PLL_AUDIO_CLR:
+ case CCM_ANALOG_PLL_VIDEO_CLR:
+ case CCM_ANALOG_PLL_MLB_CLR:
+ case CCM_ANALOG_PLL_ENET_CLR:
+ case CCM_ANALOG_PFD_480_CLR:
+ case CCM_ANALOG_PFD_528_CLR:
+ case CCM_ANALOG_MISC0_CLR:
+ case PMU_MISC1_CLR:
+ case CCM_ANALOG_MISC2_CLR:
+ case USB_ANALOG_USB1_VBUS_DETECT_CLR:
+ case USB_ANALOG_USB1_CHRG_DETECT_CLR:
+ case USB_ANALOG_USB1_MISC_CLR:
+ case USB_ANALOG_USB2_VBUS_DETECT_CLR:
+ case USB_ANALOG_USB2_CHRG_DETECT_CLR:
+ case USB_ANALOG_USB2_MISC_CLR:
+ /*
+ * All REG_NAME_CLR register access are in fact targeting the
+ * the REG_NAME register. So we change the value of the
+ * REG_NAME register, unsetting bits passed in the value.
+ */
+ s->analog[index - 2] &= ~value;
+ break;
+ case CCM_ANALOG_PLL_ARM_TOG:
+ case CCM_ANALOG_PLL_USB1_TOG:
+ case CCM_ANALOG_PLL_USB2_TOG:
+ case CCM_ANALOG_PLL_SYS_TOG:
+ case CCM_ANALOG_PLL_AUDIO_TOG:
+ case CCM_ANALOG_PLL_VIDEO_TOG:
+ case CCM_ANALOG_PLL_MLB_TOG:
+ case CCM_ANALOG_PLL_ENET_TOG:
+ case CCM_ANALOG_PFD_480_TOG:
+ case CCM_ANALOG_PFD_528_TOG:
+ case CCM_ANALOG_MISC0_TOG:
+ case PMU_MISC1_TOG:
+ case CCM_ANALOG_MISC2_TOG:
+ case USB_ANALOG_USB1_VBUS_DETECT_TOG:
+ case USB_ANALOG_USB1_CHRG_DETECT_TOG:
+ case USB_ANALOG_USB1_MISC_TOG:
+ case USB_ANALOG_USB2_VBUS_DETECT_TOG:
+ case USB_ANALOG_USB2_CHRG_DETECT_TOG:
+ case USB_ANALOG_USB2_MISC_TOG:
+ /*
+ * All REG_NAME_TOG register access are in fact targeting the
+ * the REG_NAME register. So we change the value of the
+ * REG_NAME register, toggling bits passed in the value.
+ */
+ s->analog[index - 3] ^= value;
+ break;
+ default:
+ /*
+ * We will do a better implementation later. In particular some bits
+ * cannot be written to.
+ */
+ s->analog[index] = value;
+ break;
+ }
+}
+
+static const struct MemoryRegionOps imx6_ccm_ops = {
+ .read = imx6_ccm_read,
+ .write = imx6_ccm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ /*
+ * Our device would not work correctly if the guest was doing
+ * unaligned access. This might not be a limitation on the real
+ * device but in practice there is no reason for a guest to access
+ * this device unaligned.
+ */
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static const struct MemoryRegionOps imx6_analog_ops = {
+ .read = imx6_analog_read,
+ .write = imx6_analog_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ /*
+ * Our device would not work correctly if the guest was doing
+ * unaligned access. This might not be a limitation on the real
+ * device but in practice there is no reason for a guest to access
+ * this device unaligned.
+ */
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void imx6_ccm_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ IMX6CCMState *s = IMX6_CCM(obj);
+
+ /* initialize a container for the all memory range */
+ memory_region_init(&s->container, OBJECT(dev), TYPE_IMX6_CCM, 0x5000);
+
+ /* We initialize an IO memory region for the CCM part */
+ memory_region_init_io(&s->ioccm, OBJECT(dev), &imx6_ccm_ops, s,
+ TYPE_IMX6_CCM ".ccm", CCM_MAX * sizeof(uint32_t));
+
+ /* Add the CCM as a subregion at offset 0 */
+ memory_region_add_subregion(&s->container, 0, &s->ioccm);
+
+ /* We initialize an IO memory region for the ANALOG part */
+ memory_region_init_io(&s->ioanalog, OBJECT(dev), &imx6_analog_ops, s,
+ TYPE_IMX6_CCM ".analog",
+ CCM_ANALOG_MAX * sizeof(uint32_t));
+
+ /* Add the ANALOG as a subregion at offset 0x4000 */
+ memory_region_add_subregion(&s->container, 0x4000, &s->ioanalog);
+
+ sysbus_init_mmio(sd, &s->container);
+}
+
+static void imx6_ccm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ IMXCCMClass *ccm = IMX_CCM_CLASS(klass);
+
+ dc->reset = imx6_ccm_reset;
+ dc->vmsd = &vmstate_imx6_ccm;
+ dc->desc = "i.MX6 Clock Control Module";
+
+ ccm->get_clock_frequency = imx6_ccm_get_clock_frequency;
+}
+
+static const TypeInfo imx6_ccm_info = {
+ .name = TYPE_IMX6_CCM,
+ .parent = TYPE_IMX_CCM,
+ .instance_size = sizeof(IMX6CCMState),
+ .instance_init = imx6_ccm_init,
+ .class_init = imx6_ccm_class_init,
+};
+
+static void imx6_ccm_register_types(void)
+{
+ type_register_static(&imx6_ccm_info);
+}
+
+type_init(imx6_ccm_register_types)
diff --git a/hw/misc/imx6_src.c b/hw/misc/imx6_src.c
new file mode 100644
index 000000000..79f437591
--- /dev/null
+++ b/hw/misc/imx6_src.c
@@ -0,0 +1,311 @@
+/*
+ * IMX6 System Reset Controller
+ *
+ * Copyright (c) 2015 Jean-Christophe Dubois <jcd@tribudubois.net>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/imx6_src.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "arm-powerctl.h"
+#include "hw/core/cpu.h"
+
+#ifndef DEBUG_IMX6_SRC
+#define DEBUG_IMX6_SRC 0
+#endif
+
+#define DPRINTF(fmt, args...) \
+ do { \
+ if (DEBUG_IMX6_SRC) { \
+ fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX6_SRC, \
+ __func__, ##args); \
+ } \
+ } while (0)
+
+static const char *imx6_src_reg_name(uint32_t reg)
+{
+ static char unknown[20];
+
+ switch (reg) {
+ case SRC_SCR:
+ return "SRC_SCR";
+ case SRC_SBMR1:
+ return "SRC_SBMR1";
+ case SRC_SRSR:
+ return "SRC_SRSR";
+ case SRC_SISR:
+ return "SRC_SISR";
+ case SRC_SIMR:
+ return "SRC_SIMR";
+ case SRC_SBMR2:
+ return "SRC_SBMR2";
+ case SRC_GPR1:
+ return "SRC_GPR1";
+ case SRC_GPR2:
+ return "SRC_GPR2";
+ case SRC_GPR3:
+ return "SRC_GPR3";
+ case SRC_GPR4:
+ return "SRC_GPR4";
+ case SRC_GPR5:
+ return "SRC_GPR5";
+ case SRC_GPR6:
+ return "SRC_GPR6";
+ case SRC_GPR7:
+ return "SRC_GPR7";
+ case SRC_GPR8:
+ return "SRC_GPR8";
+ case SRC_GPR9:
+ return "SRC_GPR9";
+ case SRC_GPR10:
+ return "SRC_GPR10";
+ default:
+ sprintf(unknown, "%u ?", reg);
+ return unknown;
+ }
+}
+
+static const VMStateDescription vmstate_imx6_src = {
+ .name = TYPE_IMX6_SRC,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, IMX6SRCState, SRC_MAX),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void imx6_src_reset(DeviceState *dev)
+{
+ IMX6SRCState *s = IMX6_SRC(dev);
+
+ DPRINTF("\n");
+
+ memset(s->regs, 0, sizeof(s->regs));
+
+ /* Set reset values */
+ s->regs[SRC_SCR] = 0x521;
+ s->regs[SRC_SRSR] = 0x1;
+ s->regs[SRC_SIMR] = 0x1F;
+}
+
+static uint64_t imx6_src_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint32_t value = 0;
+ IMX6SRCState *s = (IMX6SRCState *)opaque;
+ uint32_t index = offset >> 2;
+
+ if (index < SRC_MAX) {
+ value = s->regs[index];
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_IMX6_SRC, __func__, offset);
+
+ }
+
+ DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx6_src_reg_name(index), value);
+
+ return value;
+}
+
+
+/* The reset is asynchronous so we need to defer clearing the reset
+ * bit until the work is completed.
+ */
+
+struct SRCSCRResetInfo {
+ IMX6SRCState *s;
+ int reset_bit;
+};
+
+static void imx6_clear_reset_bit(CPUState *cpu, run_on_cpu_data data)
+{
+ struct SRCSCRResetInfo *ri = data.host_ptr;
+ IMX6SRCState *s = ri->s;
+
+ assert(qemu_mutex_iothread_locked());
+
+ s->regs[SRC_SCR] = deposit32(s->regs[SRC_SCR], ri->reset_bit, 1, 0);
+ DPRINTF("reg[%s] <= 0x%" PRIx32 "\n",
+ imx6_src_reg_name(SRC_SCR), s->regs[SRC_SCR]);
+
+ g_free(ri);
+}
+
+static void imx6_defer_clear_reset_bit(int cpuid,
+ IMX6SRCState *s,
+ unsigned long reset_shift)
+{
+ struct SRCSCRResetInfo *ri;
+ CPUState *cpu = arm_get_cpu_by_id(cpuid);
+
+ if (!cpu) {
+ return;
+ }
+
+ ri = g_malloc(sizeof(struct SRCSCRResetInfo));
+ ri->s = s;
+ ri->reset_bit = reset_shift;
+
+ async_run_on_cpu(cpu, imx6_clear_reset_bit, RUN_ON_CPU_HOST_PTR(ri));
+}
+
+
+static void imx6_src_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ IMX6SRCState *s = (IMX6SRCState *)opaque;
+ uint32_t index = offset >> 2;
+ unsigned long change_mask;
+ unsigned long current_value = value;
+
+ if (index >= SRC_MAX) {
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_IMX6_SRC, __func__, offset);
+ return;
+ }
+
+ DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx6_src_reg_name(index),
+ (uint32_t)current_value);
+
+ change_mask = s->regs[index] ^ (uint32_t)current_value;
+
+ switch (index) {
+ case SRC_SCR:
+ /*
+ * On real hardware when the system reset controller starts a
+ * secondary CPU it runs through some boot ROM code which reads
+ * the SRC_GPRX registers controlling the start address and branches
+ * to it.
+ * Here we are taking a short cut and branching directly to the
+ * requested address (we don't want to run the boot ROM code inside
+ * QEMU)
+ */
+ if (EXTRACT(change_mask, CORE3_ENABLE)) {
+ if (EXTRACT(current_value, CORE3_ENABLE)) {
+ /* CORE 3 is brought up */
+ arm_set_cpu_on(3, s->regs[SRC_GPR7], s->regs[SRC_GPR8],
+ 3, false);
+ } else {
+ /* CORE 3 is shut down */
+ arm_set_cpu_off(3);
+ }
+ /* We clear the reset bits as the processor changed state */
+ imx6_defer_clear_reset_bit(3, s, CORE3_RST_SHIFT);
+ clear_bit(CORE3_RST_SHIFT, &change_mask);
+ }
+ if (EXTRACT(change_mask, CORE2_ENABLE)) {
+ if (EXTRACT(current_value, CORE2_ENABLE)) {
+ /* CORE 2 is brought up */
+ arm_set_cpu_on(2, s->regs[SRC_GPR5], s->regs[SRC_GPR6],
+ 3, false);
+ } else {
+ /* CORE 2 is shut down */
+ arm_set_cpu_off(2);
+ }
+ /* We clear the reset bits as the processor changed state */
+ imx6_defer_clear_reset_bit(2, s, CORE2_RST_SHIFT);
+ clear_bit(CORE2_RST_SHIFT, &change_mask);
+ }
+ if (EXTRACT(change_mask, CORE1_ENABLE)) {
+ if (EXTRACT(current_value, CORE1_ENABLE)) {
+ /* CORE 1 is brought up */
+ arm_set_cpu_on(1, s->regs[SRC_GPR3], s->regs[SRC_GPR4],
+ 3, false);
+ } else {
+ /* CORE 1 is shut down */
+ arm_set_cpu_off(1);
+ }
+ /* We clear the reset bits as the processor changed state */
+ imx6_defer_clear_reset_bit(1, s, CORE1_RST_SHIFT);
+ clear_bit(CORE1_RST_SHIFT, &change_mask);
+ }
+ if (EXTRACT(change_mask, CORE0_RST)) {
+ arm_reset_cpu(0);
+ imx6_defer_clear_reset_bit(0, s, CORE0_RST_SHIFT);
+ }
+ if (EXTRACT(change_mask, CORE1_RST)) {
+ arm_reset_cpu(1);
+ imx6_defer_clear_reset_bit(1, s, CORE1_RST_SHIFT);
+ }
+ if (EXTRACT(change_mask, CORE2_RST)) {
+ arm_reset_cpu(2);
+ imx6_defer_clear_reset_bit(2, s, CORE2_RST_SHIFT);
+ }
+ if (EXTRACT(change_mask, CORE3_RST)) {
+ arm_reset_cpu(3);
+ imx6_defer_clear_reset_bit(3, s, CORE3_RST_SHIFT);
+ }
+ if (EXTRACT(change_mask, SW_IPU2_RST)) {
+ /* We pretend the IPU2 is reset */
+ clear_bit(SW_IPU2_RST_SHIFT, &current_value);
+ }
+ if (EXTRACT(change_mask, SW_IPU1_RST)) {
+ /* We pretend the IPU1 is reset */
+ clear_bit(SW_IPU1_RST_SHIFT, &current_value);
+ }
+ s->regs[index] = current_value;
+ break;
+ default:
+ s->regs[index] = current_value;
+ break;
+ }
+}
+
+static const struct MemoryRegionOps imx6_src_ops = {
+ .read = imx6_src_read,
+ .write = imx6_src_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ /*
+ * Our device would not work correctly if the guest was doing
+ * unaligned access. This might not be a limitation on the real
+ * device but in practice there is no reason for a guest to access
+ * this device unaligned.
+ */
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void imx6_src_realize(DeviceState *dev, Error **errp)
+{
+ IMX6SRCState *s = IMX6_SRC(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &imx6_src_ops, s,
+ TYPE_IMX6_SRC, 0x1000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+}
+
+static void imx6_src_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = imx6_src_realize;
+ dc->reset = imx6_src_reset;
+ dc->vmsd = &vmstate_imx6_src;
+ dc->desc = "i.MX6 System Reset Controller";
+}
+
+static const TypeInfo imx6_src_info = {
+ .name = TYPE_IMX6_SRC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMX6SRCState),
+ .class_init = imx6_src_class_init,
+};
+
+static void imx6_src_register_types(void)
+{
+ type_register_static(&imx6_src_info);
+}
+
+type_init(imx6_src_register_types)
diff --git a/hw/misc/imx6ul_ccm.c b/hw/misc/imx6ul_ccm.c
new file mode 100644
index 000000000..a65d03145
--- /dev/null
+++ b/hw/misc/imx6ul_ccm.c
@@ -0,0 +1,938 @@
+/*
+ * IMX6UL Clock Control Module
+ *
+ * Copyright (c) 2018 Jean-Christophe Dubois <jcd@tribudubois.net>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * To get the timer frequencies right, we need to emulate at least part of
+ * the CCM.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/registerfields.h"
+#include "migration/vmstate.h"
+#include "hw/misc/imx6ul_ccm.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#include "trace.h"
+
+static const uint32_t ccm_mask[CCM_MAX] = {
+ [CCM_CCR] = 0xf01fef80,
+ [CCM_CCDR] = 0xfffeffff,
+ [CCM_CSR] = 0xffffffff,
+ [CCM_CCSR] = 0xfffffef2,
+ [CCM_CACRR] = 0xfffffff8,
+ [CCM_CBCDR] = 0xc1f8e000,
+ [CCM_CBCMR] = 0xfc03cfff,
+ [CCM_CSCMR1] = 0x80700000,
+ [CCM_CSCMR2] = 0xe01ff003,
+ [CCM_CSCDR1] = 0xfe00c780,
+ [CCM_CS1CDR] = 0xfe00fe00,
+ [CCM_CS2CDR] = 0xf8007000,
+ [CCM_CDCDR] = 0xf00fffff,
+ [CCM_CHSCCDR] = 0xfffc01ff,
+ [CCM_CSCDR2] = 0xfe0001ff,
+ [CCM_CSCDR3] = 0xffffc1ff,
+ [CCM_CDHIPR] = 0xffffffff,
+ [CCM_CTOR] = 0x00000000,
+ [CCM_CLPCR] = 0xf39ff01c,
+ [CCM_CISR] = 0xfb85ffbe,
+ [CCM_CIMR] = 0xfb85ffbf,
+ [CCM_CCOSR] = 0xfe00fe00,
+ [CCM_CGPR] = 0xfffc3fea,
+ [CCM_CCGR0] = 0x00000000,
+ [CCM_CCGR1] = 0x00000000,
+ [CCM_CCGR2] = 0x00000000,
+ [CCM_CCGR3] = 0x00000000,
+ [CCM_CCGR4] = 0x00000000,
+ [CCM_CCGR5] = 0x00000000,
+ [CCM_CCGR6] = 0x00000000,
+ [CCM_CMEOR] = 0xafffff1f,
+};
+
+static const uint32_t analog_mask[CCM_ANALOG_MAX] = {
+ [CCM_ANALOG_PLL_ARM] = 0xfff60f80,
+ [CCM_ANALOG_PLL_USB1] = 0xfffe0fbc,
+ [CCM_ANALOG_PLL_USB2] = 0xfffe0fbc,
+ [CCM_ANALOG_PLL_SYS] = 0xfffa0ffe,
+ [CCM_ANALOG_PLL_SYS_SS] = 0x00000000,
+ [CCM_ANALOG_PLL_SYS_NUM] = 0xc0000000,
+ [CCM_ANALOG_PLL_SYS_DENOM] = 0xc0000000,
+ [CCM_ANALOG_PLL_AUDIO] = 0xffe20f80,
+ [CCM_ANALOG_PLL_AUDIO_NUM] = 0xc0000000,
+ [CCM_ANALOG_PLL_AUDIO_DENOM] = 0xc0000000,
+ [CCM_ANALOG_PLL_VIDEO] = 0xffe20f80,
+ [CCM_ANALOG_PLL_VIDEO_NUM] = 0xc0000000,
+ [CCM_ANALOG_PLL_VIDEO_DENOM] = 0xc0000000,
+ [CCM_ANALOG_PLL_ENET] = 0xffc20ff0,
+ [CCM_ANALOG_PFD_480] = 0x40404040,
+ [CCM_ANALOG_PFD_528] = 0x40404040,
+ [PMU_MISC0] = 0x01fe8306,
+ [PMU_MISC1] = 0x07fcede0,
+ [PMU_MISC2] = 0x005f5f5f,
+};
+
+static const char *imx6ul_ccm_reg_name(uint32_t reg)
+{
+ static char unknown[20];
+
+ switch (reg) {
+ case CCM_CCR:
+ return "CCR";
+ case CCM_CCDR:
+ return "CCDR";
+ case CCM_CSR:
+ return "CSR";
+ case CCM_CCSR:
+ return "CCSR";
+ case CCM_CACRR:
+ return "CACRR";
+ case CCM_CBCDR:
+ return "CBCDR";
+ case CCM_CBCMR:
+ return "CBCMR";
+ case CCM_CSCMR1:
+ return "CSCMR1";
+ case CCM_CSCMR2:
+ return "CSCMR2";
+ case CCM_CSCDR1:
+ return "CSCDR1";
+ case CCM_CS1CDR:
+ return "CS1CDR";
+ case CCM_CS2CDR:
+ return "CS2CDR";
+ case CCM_CDCDR:
+ return "CDCDR";
+ case CCM_CHSCCDR:
+ return "CHSCCDR";
+ case CCM_CSCDR2:
+ return "CSCDR2";
+ case CCM_CSCDR3:
+ return "CSCDR3";
+ case CCM_CDHIPR:
+ return "CDHIPR";
+ case CCM_CTOR:
+ return "CTOR";
+ case CCM_CLPCR:
+ return "CLPCR";
+ case CCM_CISR:
+ return "CISR";
+ case CCM_CIMR:
+ return "CIMR";
+ case CCM_CCOSR:
+ return "CCOSR";
+ case CCM_CGPR:
+ return "CGPR";
+ case CCM_CCGR0:
+ return "CCGR0";
+ case CCM_CCGR1:
+ return "CCGR1";
+ case CCM_CCGR2:
+ return "CCGR2";
+ case CCM_CCGR3:
+ return "CCGR3";
+ case CCM_CCGR4:
+ return "CCGR4";
+ case CCM_CCGR5:
+ return "CCGR5";
+ case CCM_CCGR6:
+ return "CCGR6";
+ case CCM_CMEOR:
+ return "CMEOR";
+ default:
+ sprintf(unknown, "%u ?", reg);
+ return unknown;
+ }
+}
+
+static const char *imx6ul_analog_reg_name(uint32_t reg)
+{
+ static char unknown[20];
+
+ switch (reg) {
+ case CCM_ANALOG_PLL_ARM:
+ return "PLL_ARM";
+ case CCM_ANALOG_PLL_ARM_SET:
+ return "PLL_ARM_SET";
+ case CCM_ANALOG_PLL_ARM_CLR:
+ return "PLL_ARM_CLR";
+ case CCM_ANALOG_PLL_ARM_TOG:
+ return "PLL_ARM_TOG";
+ case CCM_ANALOG_PLL_USB1:
+ return "PLL_USB1";
+ case CCM_ANALOG_PLL_USB1_SET:
+ return "PLL_USB1_SET";
+ case CCM_ANALOG_PLL_USB1_CLR:
+ return "PLL_USB1_CLR";
+ case CCM_ANALOG_PLL_USB1_TOG:
+ return "PLL_USB1_TOG";
+ case CCM_ANALOG_PLL_USB2:
+ return "PLL_USB2";
+ case CCM_ANALOG_PLL_USB2_SET:
+ return "PLL_USB2_SET";
+ case CCM_ANALOG_PLL_USB2_CLR:
+ return "PLL_USB2_CLR";
+ case CCM_ANALOG_PLL_USB2_TOG:
+ return "PLL_USB2_TOG";
+ case CCM_ANALOG_PLL_SYS:
+ return "PLL_SYS";
+ case CCM_ANALOG_PLL_SYS_SET:
+ return "PLL_SYS_SET";
+ case CCM_ANALOG_PLL_SYS_CLR:
+ return "PLL_SYS_CLR";
+ case CCM_ANALOG_PLL_SYS_TOG:
+ return "PLL_SYS_TOG";
+ case CCM_ANALOG_PLL_SYS_SS:
+ return "PLL_SYS_SS";
+ case CCM_ANALOG_PLL_SYS_NUM:
+ return "PLL_SYS_NUM";
+ case CCM_ANALOG_PLL_SYS_DENOM:
+ return "PLL_SYS_DENOM";
+ case CCM_ANALOG_PLL_AUDIO:
+ return "PLL_AUDIO";
+ case CCM_ANALOG_PLL_AUDIO_SET:
+ return "PLL_AUDIO_SET";
+ case CCM_ANALOG_PLL_AUDIO_CLR:
+ return "PLL_AUDIO_CLR";
+ case CCM_ANALOG_PLL_AUDIO_TOG:
+ return "PLL_AUDIO_TOG";
+ case CCM_ANALOG_PLL_AUDIO_NUM:
+ return "PLL_AUDIO_NUM";
+ case CCM_ANALOG_PLL_AUDIO_DENOM:
+ return "PLL_AUDIO_DENOM";
+ case CCM_ANALOG_PLL_VIDEO:
+ return "PLL_VIDEO";
+ case CCM_ANALOG_PLL_VIDEO_SET:
+ return "PLL_VIDEO_SET";
+ case CCM_ANALOG_PLL_VIDEO_CLR:
+ return "PLL_VIDEO_CLR";
+ case CCM_ANALOG_PLL_VIDEO_TOG:
+ return "PLL_VIDEO_TOG";
+ case CCM_ANALOG_PLL_VIDEO_NUM:
+ return "PLL_VIDEO_NUM";
+ case CCM_ANALOG_PLL_VIDEO_DENOM:
+ return "PLL_VIDEO_DENOM";
+ case CCM_ANALOG_PLL_ENET:
+ return "PLL_ENET";
+ case CCM_ANALOG_PLL_ENET_SET:
+ return "PLL_ENET_SET";
+ case CCM_ANALOG_PLL_ENET_CLR:
+ return "PLL_ENET_CLR";
+ case CCM_ANALOG_PLL_ENET_TOG:
+ return "PLL_ENET_TOG";
+ case CCM_ANALOG_PFD_480:
+ return "PFD_480";
+ case CCM_ANALOG_PFD_480_SET:
+ return "PFD_480_SET";
+ case CCM_ANALOG_PFD_480_CLR:
+ return "PFD_480_CLR";
+ case CCM_ANALOG_PFD_480_TOG:
+ return "PFD_480_TOG";
+ case CCM_ANALOG_PFD_528:
+ return "PFD_528";
+ case CCM_ANALOG_PFD_528_SET:
+ return "PFD_528_SET";
+ case CCM_ANALOG_PFD_528_CLR:
+ return "PFD_528_CLR";
+ case CCM_ANALOG_PFD_528_TOG:
+ return "PFD_528_TOG";
+ case CCM_ANALOG_MISC0:
+ return "MISC0";
+ case CCM_ANALOG_MISC0_SET:
+ return "MISC0_SET";
+ case CCM_ANALOG_MISC0_CLR:
+ return "MISC0_CLR";
+ case CCM_ANALOG_MISC0_TOG:
+ return "MISC0_TOG";
+ case CCM_ANALOG_MISC2:
+ return "MISC2";
+ case CCM_ANALOG_MISC2_SET:
+ return "MISC2_SET";
+ case CCM_ANALOG_MISC2_CLR:
+ return "MISC2_CLR";
+ case CCM_ANALOG_MISC2_TOG:
+ return "MISC2_TOG";
+ case PMU_REG_1P1:
+ return "PMU_REG_1P1";
+ case PMU_REG_3P0:
+ return "PMU_REG_3P0";
+ case PMU_REG_2P5:
+ return "PMU_REG_2P5";
+ case PMU_REG_CORE:
+ return "PMU_REG_CORE";
+ case PMU_MISC1:
+ return "PMU_MISC1";
+ case PMU_MISC1_SET:
+ return "PMU_MISC1_SET";
+ case PMU_MISC1_CLR:
+ return "PMU_MISC1_CLR";
+ case PMU_MISC1_TOG:
+ return "PMU_MISC1_TOG";
+ case USB_ANALOG_DIGPROG:
+ return "USB_ANALOG_DIGPROG";
+ default:
+ sprintf(unknown, "%u ?", reg);
+ return unknown;
+ }
+}
+
+#define CKIH_FREQ 24000000 /* 24MHz crystal input */
+
+static const VMStateDescription vmstate_imx6ul_ccm = {
+ .name = TYPE_IMX6UL_CCM,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(ccm, IMX6ULCCMState, CCM_MAX),
+ VMSTATE_UINT32_ARRAY(analog, IMX6ULCCMState, CCM_ANALOG_MAX),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static uint64_t imx6ul_analog_get_osc_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = CKIH_FREQ;
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_analog_get_pll2_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = imx6ul_analog_get_osc_clk(dev);
+
+ if (FIELD_EX32(dev->analog[CCM_ANALOG_PLL_SYS],
+ ANALOG_PLL_SYS, DIV_SELECT)) {
+ freq *= 22;
+ } else {
+ freq *= 20;
+ }
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_analog_get_pll3_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = imx6ul_analog_get_osc_clk(dev) * 20;
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_analog_get_pll2_pfd0_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = 0;
+
+ freq = imx6ul_analog_get_pll2_clk(dev) * 18
+ / FIELD_EX32(dev->analog[CCM_ANALOG_PFD_528],
+ ANALOG_PFD_528, PFD0_FRAC);
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_analog_get_pll2_pfd2_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = 0;
+
+ freq = imx6ul_analog_get_pll2_clk(dev) * 18
+ / FIELD_EX32(dev->analog[CCM_ANALOG_PFD_528],
+ ANALOG_PFD_528, PFD2_FRAC);
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_analog_pll2_bypass_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = 0;
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_ccm_get_periph_clk2_sel_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = 0;
+
+ switch (FIELD_EX32(dev->ccm[CCM_CBCMR], CBCMR, PERIPH_CLK2_SEL)) {
+ case 0:
+ freq = imx6ul_analog_get_pll3_clk(dev);
+ break;
+ case 1:
+ freq = imx6ul_analog_get_osc_clk(dev);
+ break;
+ case 2:
+ freq = imx6ul_analog_pll2_bypass_clk(dev);
+ break;
+ case 3:
+ /* We should never get there as 3 is a reserved value */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "[%s]%s: unsupported PERIPH_CLK2_SEL value 3\n",
+ TYPE_IMX6UL_CCM, __func__);
+ /* freq is set to 0 as we don't know what it should be */
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_ccm_get_periph_clk_sel_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = 0;
+
+ switch (FIELD_EX32(dev->ccm[CCM_CBCMR], CBCMR, PRE_PERIPH_CLK_SEL)) {
+ case 0:
+ freq = imx6ul_analog_get_pll2_clk(dev);
+ break;
+ case 1:
+ freq = imx6ul_analog_get_pll2_pfd2_clk(dev);
+ break;
+ case 2:
+ freq = imx6ul_analog_get_pll2_pfd0_clk(dev);
+ break;
+ case 3:
+ freq = imx6ul_analog_get_pll2_pfd2_clk(dev) / 2;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_ccm_get_periph_clk2_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = 0;
+
+ freq = imx6ul_ccm_get_periph_clk2_sel_clk(dev)
+ / (1 + FIELD_EX32(dev->ccm[CCM_CBCDR], CBCDR, PERIPH_CLK2_PODF));
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_ccm_get_periph_sel_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = 0;
+
+ switch (FIELD_EX32(dev->ccm[CCM_CBCDR], CBCDR, PERIPH_CLK_SEL)) {
+ case 0:
+ freq = imx6ul_ccm_get_periph_clk_sel_clk(dev);
+ break;
+ case 1:
+ freq = imx6ul_ccm_get_periph_clk2_clk(dev);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_ccm_get_ahb_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = 0;
+
+ freq = imx6ul_ccm_get_periph_sel_clk(dev)
+ / (1 + FIELD_EX32(dev->ccm[CCM_CBCDR], CBCDR, AHB_PODF));
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_ccm_get_ipg_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = 0;
+
+ freq = imx6ul_ccm_get_ahb_clk(dev)
+ / (1 + FIELD_EX32(dev->ccm[CCM_CBCDR], CBCDR, IPG_PODF));
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_ccm_get_per_sel_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = 0;
+
+ switch (FIELD_EX32(dev->ccm[CCM_CSCMR1], CSCMR1, PERCLK_CLK_SEL)) {
+ case 0:
+ freq = imx6ul_ccm_get_ipg_clk(dev);
+ break;
+ case 1:
+ freq = imx6ul_analog_get_osc_clk(dev);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint64_t imx6ul_ccm_get_per_clk(IMX6ULCCMState *dev)
+{
+ uint64_t freq = 0;
+
+ freq = imx6ul_ccm_get_per_sel_clk(dev)
+ / (1 + FIELD_EX32(dev->ccm[CCM_CSCMR1], CSCMR1, PERCLK_PODF));
+
+ trace_ccm_freq((uint32_t)freq);
+
+ return freq;
+}
+
+static uint32_t imx6ul_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
+{
+ uint32_t freq = 0;
+ IMX6ULCCMState *s = IMX6UL_CCM(dev);
+
+ switch (clock) {
+ case CLK_NONE:
+ break;
+ case CLK_IPG:
+ freq = imx6ul_ccm_get_ipg_clk(s);
+ break;
+ case CLK_IPG_HIGH:
+ freq = imx6ul_ccm_get_per_clk(s);
+ break;
+ case CLK_32k:
+ freq = CKIL_FREQ;
+ break;
+ case CLK_HIGH:
+ freq = CKIH_FREQ;
+ break;
+ case CLK_HIGH_DIV:
+ freq = CKIH_FREQ / 8;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: unsupported clock %d\n",
+ TYPE_IMX6UL_CCM, __func__, clock);
+ break;
+ }
+
+ trace_ccm_clock_freq(clock, freq);
+
+ return freq;
+}
+
+static void imx6ul_ccm_reset(DeviceState *dev)
+{
+ IMX6ULCCMState *s = IMX6UL_CCM(dev);
+
+ trace_ccm_entry();
+
+ s->ccm[CCM_CCR] = 0x0401167F;
+ s->ccm[CCM_CCDR] = 0x00000000;
+ s->ccm[CCM_CSR] = 0x00000010;
+ s->ccm[CCM_CCSR] = 0x00000100;
+ s->ccm[CCM_CACRR] = 0x00000000;
+ s->ccm[CCM_CBCDR] = 0x00018D00;
+ s->ccm[CCM_CBCMR] = 0x24860324;
+ s->ccm[CCM_CSCMR1] = 0x04900080;
+ s->ccm[CCM_CSCMR2] = 0x03192F06;
+ s->ccm[CCM_CSCDR1] = 0x00490B00;
+ s->ccm[CCM_CS1CDR] = 0x0EC102C1;
+ s->ccm[CCM_CS2CDR] = 0x000336C1;
+ s->ccm[CCM_CDCDR] = 0x33F71F92;
+ s->ccm[CCM_CHSCCDR] = 0x000248A4;
+ s->ccm[CCM_CSCDR2] = 0x00029B48;
+ s->ccm[CCM_CSCDR3] = 0x00014841;
+ s->ccm[CCM_CDHIPR] = 0x00000000;
+ s->ccm[CCM_CTOR] = 0x00000000;
+ s->ccm[CCM_CLPCR] = 0x00000079;
+ s->ccm[CCM_CISR] = 0x00000000;
+ s->ccm[CCM_CIMR] = 0xFFFFFFFF;
+ s->ccm[CCM_CCOSR] = 0x000A0001;
+ s->ccm[CCM_CGPR] = 0x0000FE62;
+ s->ccm[CCM_CCGR0] = 0xFFFFFFFF;
+ s->ccm[CCM_CCGR1] = 0xFFFFFFFF;
+ s->ccm[CCM_CCGR2] = 0xFC3FFFFF;
+ s->ccm[CCM_CCGR3] = 0xFFFFFFFF;
+ s->ccm[CCM_CCGR4] = 0xFFFFFFFF;
+ s->ccm[CCM_CCGR5] = 0xFFFFFFFF;
+ s->ccm[CCM_CCGR6] = 0xFFFFFFFF;
+ s->ccm[CCM_CMEOR] = 0xFFFFFFFF;
+
+ s->analog[CCM_ANALOG_PLL_ARM] = 0x00013063;
+ s->analog[CCM_ANALOG_PLL_USB1] = 0x00012000;
+ s->analog[CCM_ANALOG_PLL_USB2] = 0x00012000;
+ s->analog[CCM_ANALOG_PLL_SYS] = 0x00013001;
+ s->analog[CCM_ANALOG_PLL_SYS_SS] = 0x00000000;
+ s->analog[CCM_ANALOG_PLL_SYS_NUM] = 0x00000000;
+ s->analog[CCM_ANALOG_PLL_SYS_DENOM] = 0x00000012;
+ s->analog[CCM_ANALOG_PLL_AUDIO] = 0x00011006;
+ s->analog[CCM_ANALOG_PLL_AUDIO_NUM] = 0x05F5E100;
+ s->analog[CCM_ANALOG_PLL_AUDIO_DENOM] = 0x2964619C;
+ s->analog[CCM_ANALOG_PLL_VIDEO] = 0x0001100C;
+ s->analog[CCM_ANALOG_PLL_VIDEO_NUM] = 0x05F5E100;
+ s->analog[CCM_ANALOG_PLL_VIDEO_DENOM] = 0x10A24447;
+ s->analog[CCM_ANALOG_PLL_ENET] = 0x00011001;
+ s->analog[CCM_ANALOG_PFD_480] = 0x1311100C;
+ s->analog[CCM_ANALOG_PFD_528] = 0x1018101B;
+
+ s->analog[PMU_REG_1P1] = 0x00001073;
+ s->analog[PMU_REG_3P0] = 0x00000F74;
+ s->analog[PMU_REG_2P5] = 0x00001073;
+ s->analog[PMU_REG_CORE] = 0x00482012;
+ s->analog[PMU_MISC0] = 0x04000000;
+ s->analog[PMU_MISC1] = 0x00000000;
+ s->analog[PMU_MISC2] = 0x00272727;
+ s->analog[PMU_LOWPWR_CTRL] = 0x00004009;
+
+ s->analog[USB_ANALOG_USB1_VBUS_DETECT] = 0x01000004;
+ s->analog[USB_ANALOG_USB1_CHRG_DETECT] = 0x00000000;
+ s->analog[USB_ANALOG_USB1_VBUS_DETECT_STAT] = 0x00000000;
+ s->analog[USB_ANALOG_USB1_CHRG_DETECT_STAT] = 0x00000000;
+ s->analog[USB_ANALOG_USB1_MISC] = 0x00000002;
+ s->analog[USB_ANALOG_USB2_VBUS_DETECT] = 0x01000004;
+ s->analog[USB_ANALOG_USB2_CHRG_DETECT] = 0x00000000;
+ s->analog[USB_ANALOG_USB2_MISC] = 0x00000002;
+ s->analog[USB_ANALOG_DIGPROG] = 0x00640000;
+
+ /* all PLLs need to be locked */
+ s->analog[CCM_ANALOG_PLL_ARM] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_USB1] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_USB2] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_SYS] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_AUDIO] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_VIDEO] |= CCM_ANALOG_PLL_LOCK;
+ s->analog[CCM_ANALOG_PLL_ENET] |= CCM_ANALOG_PLL_LOCK;
+
+ s->analog[TEMPMON_TEMPSENSE0] = 0x00000001;
+ s->analog[TEMPMON_TEMPSENSE1] = 0x00000001;
+ s->analog[TEMPMON_TEMPSENSE2] = 0x00000000;
+}
+
+static uint64_t imx6ul_ccm_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint32_t value = 0;
+ uint32_t index = offset >> 2;
+ IMX6ULCCMState *s = (IMX6ULCCMState *)opaque;
+
+ assert(index < CCM_MAX);
+
+ value = s->ccm[index];
+
+ trace_ccm_read_reg(imx6ul_ccm_reg_name(index), (uint32_t)value);
+
+ return (uint64_t)value;
+}
+
+static void imx6ul_ccm_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ uint32_t index = offset >> 2;
+ IMX6ULCCMState *s = (IMX6ULCCMState *)opaque;
+
+ assert(index < CCM_MAX);
+
+ trace_ccm_write_reg(imx6ul_ccm_reg_name(index), (uint32_t)value);
+
+ s->ccm[index] = (s->ccm[index] & ccm_mask[index]) |
+ ((uint32_t)value & ~ccm_mask[index]);
+}
+
+static uint64_t imx6ul_analog_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint32_t value;
+ uint32_t index = offset >> 2;
+ IMX6ULCCMState *s = (IMX6ULCCMState *)opaque;
+
+ assert(index < CCM_ANALOG_MAX);
+
+ switch (index) {
+ case CCM_ANALOG_PLL_ARM_SET:
+ case CCM_ANALOG_PLL_USB1_SET:
+ case CCM_ANALOG_PLL_USB2_SET:
+ case CCM_ANALOG_PLL_SYS_SET:
+ case CCM_ANALOG_PLL_AUDIO_SET:
+ case CCM_ANALOG_PLL_VIDEO_SET:
+ case CCM_ANALOG_PLL_ENET_SET:
+ case CCM_ANALOG_PFD_480_SET:
+ case CCM_ANALOG_PFD_528_SET:
+ case CCM_ANALOG_MISC0_SET:
+ case PMU_MISC1_SET:
+ case CCM_ANALOG_MISC2_SET:
+ case USB_ANALOG_USB1_VBUS_DETECT_SET:
+ case USB_ANALOG_USB1_CHRG_DETECT_SET:
+ case USB_ANALOG_USB1_MISC_SET:
+ case USB_ANALOG_USB2_VBUS_DETECT_SET:
+ case USB_ANALOG_USB2_CHRG_DETECT_SET:
+ case USB_ANALOG_USB2_MISC_SET:
+ case TEMPMON_TEMPSENSE0_SET:
+ case TEMPMON_TEMPSENSE1_SET:
+ case TEMPMON_TEMPSENSE2_SET:
+ /*
+ * All REG_NAME_SET register access are in fact targeting
+ * the REG_NAME register.
+ */
+ value = s->analog[index - 1];
+ break;
+ case CCM_ANALOG_PLL_ARM_CLR:
+ case CCM_ANALOG_PLL_USB1_CLR:
+ case CCM_ANALOG_PLL_USB2_CLR:
+ case CCM_ANALOG_PLL_SYS_CLR:
+ case CCM_ANALOG_PLL_AUDIO_CLR:
+ case CCM_ANALOG_PLL_VIDEO_CLR:
+ case CCM_ANALOG_PLL_ENET_CLR:
+ case CCM_ANALOG_PFD_480_CLR:
+ case CCM_ANALOG_PFD_528_CLR:
+ case CCM_ANALOG_MISC0_CLR:
+ case PMU_MISC1_CLR:
+ case CCM_ANALOG_MISC2_CLR:
+ case USB_ANALOG_USB1_VBUS_DETECT_CLR:
+ case USB_ANALOG_USB1_CHRG_DETECT_CLR:
+ case USB_ANALOG_USB1_MISC_CLR:
+ case USB_ANALOG_USB2_VBUS_DETECT_CLR:
+ case USB_ANALOG_USB2_CHRG_DETECT_CLR:
+ case USB_ANALOG_USB2_MISC_CLR:
+ case TEMPMON_TEMPSENSE0_CLR:
+ case TEMPMON_TEMPSENSE1_CLR:
+ case TEMPMON_TEMPSENSE2_CLR:
+ /*
+ * All REG_NAME_CLR register access are in fact targeting
+ * the REG_NAME register.
+ */
+ value = s->analog[index - 2];
+ break;
+ case CCM_ANALOG_PLL_ARM_TOG:
+ case CCM_ANALOG_PLL_USB1_TOG:
+ case CCM_ANALOG_PLL_USB2_TOG:
+ case CCM_ANALOG_PLL_SYS_TOG:
+ case CCM_ANALOG_PLL_AUDIO_TOG:
+ case CCM_ANALOG_PLL_VIDEO_TOG:
+ case CCM_ANALOG_PLL_ENET_TOG:
+ case CCM_ANALOG_PFD_480_TOG:
+ case CCM_ANALOG_PFD_528_TOG:
+ case CCM_ANALOG_MISC0_TOG:
+ case PMU_MISC1_TOG:
+ case CCM_ANALOG_MISC2_TOG:
+ case USB_ANALOG_USB1_VBUS_DETECT_TOG:
+ case USB_ANALOG_USB1_CHRG_DETECT_TOG:
+ case USB_ANALOG_USB1_MISC_TOG:
+ case USB_ANALOG_USB2_VBUS_DETECT_TOG:
+ case USB_ANALOG_USB2_CHRG_DETECT_TOG:
+ case USB_ANALOG_USB2_MISC_TOG:
+ case TEMPMON_TEMPSENSE0_TOG:
+ case TEMPMON_TEMPSENSE1_TOG:
+ case TEMPMON_TEMPSENSE2_TOG:
+ /*
+ * All REG_NAME_TOG register access are in fact targeting
+ * the REG_NAME register.
+ */
+ value = s->analog[index - 3];
+ break;
+ default:
+ value = s->analog[index];
+ break;
+ }
+
+ trace_ccm_read_reg(imx6ul_analog_reg_name(index), (uint32_t)value);
+
+ return (uint64_t)value;
+}
+
+static void imx6ul_analog_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ uint32_t index = offset >> 2;
+ IMX6ULCCMState *s = (IMX6ULCCMState *)opaque;
+
+ assert(index < CCM_ANALOG_MAX);
+
+ trace_ccm_write_reg(imx6ul_analog_reg_name(index), (uint32_t)value);
+
+ switch (index) {
+ case CCM_ANALOG_PLL_ARM_SET:
+ case CCM_ANALOG_PLL_USB1_SET:
+ case CCM_ANALOG_PLL_USB2_SET:
+ case CCM_ANALOG_PLL_SYS_SET:
+ case CCM_ANALOG_PLL_AUDIO_SET:
+ case CCM_ANALOG_PLL_VIDEO_SET:
+ case CCM_ANALOG_PLL_ENET_SET:
+ case CCM_ANALOG_PFD_480_SET:
+ case CCM_ANALOG_PFD_528_SET:
+ case CCM_ANALOG_MISC0_SET:
+ case PMU_MISC1_SET:
+ case CCM_ANALOG_MISC2_SET:
+ case USB_ANALOG_USB1_VBUS_DETECT_SET:
+ case USB_ANALOG_USB1_CHRG_DETECT_SET:
+ case USB_ANALOG_USB1_MISC_SET:
+ case USB_ANALOG_USB2_VBUS_DETECT_SET:
+ case USB_ANALOG_USB2_CHRG_DETECT_SET:
+ case USB_ANALOG_USB2_MISC_SET:
+ /*
+ * All REG_NAME_SET register access are in fact targeting
+ * the REG_NAME register. So we change the value of the
+ * REG_NAME register, setting bits passed in the value.
+ */
+ s->analog[index - 1] |= (value & ~analog_mask[index - 1]);
+ break;
+ case CCM_ANALOG_PLL_ARM_CLR:
+ case CCM_ANALOG_PLL_USB1_CLR:
+ case CCM_ANALOG_PLL_USB2_CLR:
+ case CCM_ANALOG_PLL_SYS_CLR:
+ case CCM_ANALOG_PLL_AUDIO_CLR:
+ case CCM_ANALOG_PLL_VIDEO_CLR:
+ case CCM_ANALOG_PLL_ENET_CLR:
+ case CCM_ANALOG_PFD_480_CLR:
+ case CCM_ANALOG_PFD_528_CLR:
+ case CCM_ANALOG_MISC0_CLR:
+ case PMU_MISC1_CLR:
+ case CCM_ANALOG_MISC2_CLR:
+ case USB_ANALOG_USB1_VBUS_DETECT_CLR:
+ case USB_ANALOG_USB1_CHRG_DETECT_CLR:
+ case USB_ANALOG_USB1_MISC_CLR:
+ case USB_ANALOG_USB2_VBUS_DETECT_CLR:
+ case USB_ANALOG_USB2_CHRG_DETECT_CLR:
+ case USB_ANALOG_USB2_MISC_CLR:
+ /*
+ * All REG_NAME_CLR register access are in fact targeting
+ * the REG_NAME register. So we change the value of the
+ * REG_NAME register, unsetting bits passed in the value.
+ */
+ s->analog[index - 2] &= ~(value & ~analog_mask[index - 2]);
+ break;
+ case CCM_ANALOG_PLL_ARM_TOG:
+ case CCM_ANALOG_PLL_USB1_TOG:
+ case CCM_ANALOG_PLL_USB2_TOG:
+ case CCM_ANALOG_PLL_SYS_TOG:
+ case CCM_ANALOG_PLL_AUDIO_TOG:
+ case CCM_ANALOG_PLL_VIDEO_TOG:
+ case CCM_ANALOG_PLL_ENET_TOG:
+ case CCM_ANALOG_PFD_480_TOG:
+ case CCM_ANALOG_PFD_528_TOG:
+ case CCM_ANALOG_MISC0_TOG:
+ case PMU_MISC1_TOG:
+ case CCM_ANALOG_MISC2_TOG:
+ case USB_ANALOG_USB1_VBUS_DETECT_TOG:
+ case USB_ANALOG_USB1_CHRG_DETECT_TOG:
+ case USB_ANALOG_USB1_MISC_TOG:
+ case USB_ANALOG_USB2_VBUS_DETECT_TOG:
+ case USB_ANALOG_USB2_CHRG_DETECT_TOG:
+ case USB_ANALOG_USB2_MISC_TOG:
+ /*
+ * All REG_NAME_TOG register access are in fact targeting
+ * the REG_NAME register. So we change the value of the
+ * REG_NAME register, toggling bits passed in the value.
+ */
+ s->analog[index - 3] ^= (value & ~analog_mask[index - 3]);
+ break;
+ default:
+ s->analog[index] = (s->analog[index] & analog_mask[index]) |
+ (value & ~analog_mask[index]);
+ break;
+ }
+}
+
+static const struct MemoryRegionOps imx6ul_ccm_ops = {
+ .read = imx6ul_ccm_read,
+ .write = imx6ul_ccm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ /*
+ * Our device would not work correctly if the guest was doing
+ * unaligned access. This might not be a limitation on the real
+ * device but in practice there is no reason for a guest to access
+ * this device unaligned.
+ */
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static const struct MemoryRegionOps imx6ul_analog_ops = {
+ .read = imx6ul_analog_read,
+ .write = imx6ul_analog_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ /*
+ * Our device would not work correctly if the guest was doing
+ * unaligned access. This might not be a limitation on the real
+ * device but in practice there is no reason for a guest to access
+ * this device unaligned.
+ */
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void imx6ul_ccm_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ IMX6ULCCMState *s = IMX6UL_CCM(obj);
+
+ /* initialize a container for the all memory range */
+ memory_region_init(&s->container, OBJECT(dev), TYPE_IMX6UL_CCM, 0x8000);
+
+ /* We initialize an IO memory region for the CCM part */
+ memory_region_init_io(&s->ioccm, OBJECT(dev), &imx6ul_ccm_ops, s,
+ TYPE_IMX6UL_CCM ".ccm", CCM_MAX * sizeof(uint32_t));
+
+ /* Add the CCM as a subregion at offset 0 */
+ memory_region_add_subregion(&s->container, 0, &s->ioccm);
+
+ /* We initialize an IO memory region for the ANALOG part */
+ memory_region_init_io(&s->ioanalog, OBJECT(dev), &imx6ul_analog_ops, s,
+ TYPE_IMX6UL_CCM ".analog",
+ CCM_ANALOG_MAX * sizeof(uint32_t));
+
+ /* Add the ANALOG as a subregion at offset 0x4000 */
+ memory_region_add_subregion(&s->container, 0x4000, &s->ioanalog);
+
+ sysbus_init_mmio(sd, &s->container);
+}
+
+static void imx6ul_ccm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ IMXCCMClass *ccm = IMX_CCM_CLASS(klass);
+
+ dc->reset = imx6ul_ccm_reset;
+ dc->vmsd = &vmstate_imx6ul_ccm;
+ dc->desc = "i.MX6UL Clock Control Module";
+
+ ccm->get_clock_frequency = imx6ul_ccm_get_clock_frequency;
+}
+
+static const TypeInfo imx6ul_ccm_info = {
+ .name = TYPE_IMX6UL_CCM,
+ .parent = TYPE_IMX_CCM,
+ .instance_size = sizeof(IMX6ULCCMState),
+ .instance_init = imx6ul_ccm_init,
+ .class_init = imx6ul_ccm_class_init,
+};
+
+static void imx6ul_ccm_register_types(void)
+{
+ type_register_static(&imx6ul_ccm_info);
+}
+
+type_init(imx6ul_ccm_register_types)
diff --git a/hw/misc/imx7_ccm.c b/hw/misc/imx7_ccm.c
new file mode 100644
index 000000000..075159e49
--- /dev/null
+++ b/hw/misc/imx7_ccm.c
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2018, Impinj, Inc.
+ *
+ * i.MX7 CCM, PMU and ANALOG IP blocks emulation code
+ *
+ * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#include "hw/misc/imx7_ccm.h"
+#include "migration/vmstate.h"
+
+static void imx7_analog_reset(DeviceState *dev)
+{
+ IMX7AnalogState *s = IMX7_ANALOG(dev);
+
+ memset(s->pmu, 0, sizeof(s->pmu));
+ memset(s->analog, 0, sizeof(s->analog));
+
+ s->analog[ANALOG_PLL_ARM] = 0x00002042;
+ s->analog[ANALOG_PLL_DDR] = 0x0060302c;
+ s->analog[ANALOG_PLL_DDR_SS] = 0x00000000;
+ s->analog[ANALOG_PLL_DDR_NUM] = 0x06aaac4d;
+ s->analog[ANALOG_PLL_DDR_DENOM] = 0x100003ec;
+ s->analog[ANALOG_PLL_480] = 0x00002000;
+ s->analog[ANALOG_PLL_480A] = 0x52605a56;
+ s->analog[ANALOG_PLL_480B] = 0x52525216;
+ s->analog[ANALOG_PLL_ENET] = 0x00001fc0;
+ s->analog[ANALOG_PLL_AUDIO] = 0x0001301b;
+ s->analog[ANALOG_PLL_AUDIO_SS] = 0x00000000;
+ s->analog[ANALOG_PLL_AUDIO_NUM] = 0x05f5e100;
+ s->analog[ANALOG_PLL_AUDIO_DENOM] = 0x2964619c;
+ s->analog[ANALOG_PLL_VIDEO] = 0x0008201b;
+ s->analog[ANALOG_PLL_VIDEO_SS] = 0x00000000;
+ s->analog[ANALOG_PLL_VIDEO_NUM] = 0x0000f699;
+ s->analog[ANALOG_PLL_VIDEO_DENOM] = 0x000f4240;
+ s->analog[ANALOG_PLL_MISC0] = 0x00000000;
+
+ /* all PLLs need to be locked */
+ s->analog[ANALOG_PLL_ARM] |= ANALOG_PLL_LOCK;
+ s->analog[ANALOG_PLL_DDR] |= ANALOG_PLL_LOCK;
+ s->analog[ANALOG_PLL_480] |= ANALOG_PLL_LOCK;
+ s->analog[ANALOG_PLL_480A] |= ANALOG_PLL_LOCK;
+ s->analog[ANALOG_PLL_480B] |= ANALOG_PLL_LOCK;
+ s->analog[ANALOG_PLL_ENET] |= ANALOG_PLL_LOCK;
+ s->analog[ANALOG_PLL_AUDIO] |= ANALOG_PLL_LOCK;
+ s->analog[ANALOG_PLL_VIDEO] |= ANALOG_PLL_LOCK;
+ s->analog[ANALOG_PLL_MISC0] |= ANALOG_PLL_LOCK;
+
+ /*
+ * Since I couldn't find any info about this in the reference
+ * manual the value of this register is based strictly on matching
+ * what Linux kernel expects it to be.
+ */
+ s->analog[ANALOG_DIGPROG] = 0x720000;
+ /*
+ * Set revision to be 1.0 (Arbitrary choice, no particular
+ * reason).
+ */
+ s->analog[ANALOG_DIGPROG] |= 0x000010;
+}
+
+static void imx7_ccm_reset(DeviceState *dev)
+{
+ IMX7CCMState *s = IMX7_CCM(dev);
+
+ memset(s->ccm, 0, sizeof(s->ccm));
+}
+
+#define CCM_INDEX(offset) (((offset) & ~(hwaddr)0xF) / sizeof(uint32_t))
+#define CCM_BITOP(offset) ((offset) & (hwaddr)0xF)
+
+enum {
+ CCM_BITOP_NONE = 0x00,
+ CCM_BITOP_SET = 0x04,
+ CCM_BITOP_CLR = 0x08,
+ CCM_BITOP_TOG = 0x0C,
+};
+
+static uint64_t imx7_set_clr_tog_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ const uint32_t *mmio = opaque;
+
+ return mmio[CCM_INDEX(offset)];
+}
+
+static void imx7_set_clr_tog_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ const uint8_t bitop = CCM_BITOP(offset);
+ const uint32_t index = CCM_INDEX(offset);
+ uint32_t *mmio = opaque;
+
+ switch (bitop) {
+ case CCM_BITOP_NONE:
+ mmio[index] = value;
+ break;
+ case CCM_BITOP_SET:
+ mmio[index] |= value;
+ break;
+ case CCM_BITOP_CLR:
+ mmio[index] &= ~value;
+ break;
+ case CCM_BITOP_TOG:
+ mmio[index] ^= value;
+ break;
+ };
+}
+
+static const struct MemoryRegionOps imx7_set_clr_tog_ops = {
+ .read = imx7_set_clr_tog_read,
+ .write = imx7_set_clr_tog_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ /*
+ * Our device would not work correctly if the guest was doing
+ * unaligned access. This might not be a limitation on the real
+ * device but in practice there is no reason for a guest to access
+ * this device unaligned.
+ */
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void imx7_digprog_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "Guest write to read-only ANALOG_DIGPROG register\n");
+}
+
+static const struct MemoryRegionOps imx7_digprog_ops = {
+ .read = imx7_set_clr_tog_read,
+ .write = imx7_digprog_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void imx7_ccm_init(Object *obj)
+{
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ IMX7CCMState *s = IMX7_CCM(obj);
+
+ memory_region_init_io(&s->iomem,
+ obj,
+ &imx7_set_clr_tog_ops,
+ s->ccm,
+ TYPE_IMX7_CCM ".ccm",
+ sizeof(s->ccm));
+
+ sysbus_init_mmio(sd, &s->iomem);
+}
+
+static void imx7_analog_init(Object *obj)
+{
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ IMX7AnalogState *s = IMX7_ANALOG(obj);
+
+ memory_region_init(&s->mmio.container, obj, TYPE_IMX7_ANALOG,
+ 0x10000);
+
+ memory_region_init_io(&s->mmio.analog,
+ obj,
+ &imx7_set_clr_tog_ops,
+ s->analog,
+ TYPE_IMX7_ANALOG,
+ sizeof(s->analog));
+
+ memory_region_add_subregion(&s->mmio.container,
+ 0x60, &s->mmio.analog);
+
+ memory_region_init_io(&s->mmio.pmu,
+ obj,
+ &imx7_set_clr_tog_ops,
+ s->pmu,
+ TYPE_IMX7_ANALOG ".pmu",
+ sizeof(s->pmu));
+
+ memory_region_add_subregion(&s->mmio.container,
+ 0x200, &s->mmio.pmu);
+
+ memory_region_init_io(&s->mmio.digprog,
+ obj,
+ &imx7_digprog_ops,
+ &s->analog[ANALOG_DIGPROG],
+ TYPE_IMX7_ANALOG ".digprog",
+ sizeof(uint32_t));
+
+ memory_region_add_subregion_overlap(&s->mmio.container,
+ 0x800, &s->mmio.digprog, 10);
+
+
+ sysbus_init_mmio(sd, &s->mmio.container);
+}
+
+static const VMStateDescription vmstate_imx7_ccm = {
+ .name = TYPE_IMX7_CCM,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(ccm, IMX7CCMState, CCM_MAX),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static uint32_t imx7_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
+{
+ /*
+ * This function is "consumed" by GPT emulation code, however on
+ * i.MX7 each GPT block can have their own clock root. This means
+ * that this functions needs somehow to know requester's identity
+ * and the way to pass it: be it via additional IMXClk constants
+ * or by adding another argument to this method needs to be
+ * figured out
+ */
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Not implemented\n",
+ TYPE_IMX7_CCM, __func__);
+ return 0;
+}
+
+static void imx7_ccm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ IMXCCMClass *ccm = IMX_CCM_CLASS(klass);
+
+ dc->reset = imx7_ccm_reset;
+ dc->vmsd = &vmstate_imx7_ccm;
+ dc->desc = "i.MX7 Clock Control Module";
+
+ ccm->get_clock_frequency = imx7_ccm_get_clock_frequency;
+}
+
+static const TypeInfo imx7_ccm_info = {
+ .name = TYPE_IMX7_CCM,
+ .parent = TYPE_IMX_CCM,
+ .instance_size = sizeof(IMX7CCMState),
+ .instance_init = imx7_ccm_init,
+ .class_init = imx7_ccm_class_init,
+};
+
+static const VMStateDescription vmstate_imx7_analog = {
+ .name = TYPE_IMX7_ANALOG,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(analog, IMX7AnalogState, ANALOG_MAX),
+ VMSTATE_UINT32_ARRAY(pmu, IMX7AnalogState, PMU_MAX),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void imx7_analog_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = imx7_analog_reset;
+ dc->vmsd = &vmstate_imx7_analog;
+ dc->desc = "i.MX7 Analog Module";
+}
+
+static const TypeInfo imx7_analog_info = {
+ .name = TYPE_IMX7_ANALOG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMX7AnalogState),
+ .instance_init = imx7_analog_init,
+ .class_init = imx7_analog_class_init,
+};
+
+static void imx7_ccm_register_type(void)
+{
+ type_register_static(&imx7_ccm_info);
+ type_register_static(&imx7_analog_info);
+}
+type_init(imx7_ccm_register_type)
diff --git a/hw/misc/imx7_gpr.c b/hw/misc/imx7_gpr.c
new file mode 100644
index 000000000..b03341a2e
--- /dev/null
+++ b/hw/misc/imx7_gpr.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2018, Impinj, Inc.
+ *
+ * i.MX7 GPR IP block emulation code
+ *
+ * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Bare minimum emulation code needed to support being able to shut
+ * down linux guest gracefully.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/imx7_gpr.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#include "trace.h"
+
+enum IMX7GPRRegisters {
+ IOMUXC_GPR0 = 0x00,
+ IOMUXC_GPR1 = 0x04,
+ IOMUXC_GPR2 = 0x08,
+ IOMUXC_GPR3 = 0x0c,
+ IOMUXC_GPR4 = 0x10,
+ IOMUXC_GPR5 = 0x14,
+ IOMUXC_GPR6 = 0x18,
+ IOMUXC_GPR7 = 0x1c,
+ IOMUXC_GPR8 = 0x20,
+ IOMUXC_GPR9 = 0x24,
+ IOMUXC_GPR10 = 0x28,
+ IOMUXC_GPR11 = 0x2c,
+ IOMUXC_GPR12 = 0x30,
+ IOMUXC_GPR13 = 0x34,
+ IOMUXC_GPR14 = 0x38,
+ IOMUXC_GPR15 = 0x3c,
+ IOMUXC_GPR16 = 0x40,
+ IOMUXC_GPR17 = 0x44,
+ IOMUXC_GPR18 = 0x48,
+ IOMUXC_GPR19 = 0x4c,
+ IOMUXC_GPR20 = 0x50,
+ IOMUXC_GPR21 = 0x54,
+ IOMUXC_GPR22 = 0x58,
+};
+
+#define IMX7D_GPR1_IRQ_MASK BIT(12)
+#define IMX7D_GPR1_ENET1_TX_CLK_SEL_MASK BIT(13)
+#define IMX7D_GPR1_ENET2_TX_CLK_SEL_MASK BIT(14)
+#define IMX7D_GPR1_ENET_TX_CLK_SEL_MASK (0x3 << 13)
+#define IMX7D_GPR1_ENET1_CLK_DIR_MASK BIT(17)
+#define IMX7D_GPR1_ENET2_CLK_DIR_MASK BIT(18)
+#define IMX7D_GPR1_ENET_CLK_DIR_MASK (0x3 << 17)
+
+#define IMX7D_GPR5_CSI_MUX_CONTROL_MIPI BIT(4)
+#define IMX7D_GPR12_PCIE_PHY_REFCLK_SEL BIT(5)
+#define IMX7D_GPR22_PCIE_PHY_PLL_LOCKED BIT(31)
+
+
+static uint64_t imx7_gpr_read(void *opaque, hwaddr offset, unsigned size)
+{
+ trace_imx7_gpr_read(offset);
+
+ if (offset == IOMUXC_GPR22) {
+ return IMX7D_GPR22_PCIE_PHY_PLL_LOCKED;
+ }
+
+ return 0;
+}
+
+static void imx7_gpr_write(void *opaque, hwaddr offset,
+ uint64_t v, unsigned size)
+{
+ trace_imx7_gpr_write(offset, v);
+}
+
+static const struct MemoryRegionOps imx7_gpr_ops = {
+ .read = imx7_gpr_read,
+ .write = imx7_gpr_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ /*
+ * Our device would not work correctly if the guest was doing
+ * unaligned access. This might not be a limitation on the
+ * real device but in practice there is no reason for a guest
+ * to access this device unaligned.
+ */
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void imx7_gpr_init(Object *obj)
+{
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ IMX7GPRState *s = IMX7_GPR(obj);
+
+ memory_region_init_io(&s->mmio, obj, &imx7_gpr_ops, s,
+ TYPE_IMX7_GPR, 64 * 1024);
+ sysbus_init_mmio(sd, &s->mmio);
+}
+
+static void imx7_gpr_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "i.MX7 General Purpose Registers Module";
+}
+
+static const TypeInfo imx7_gpr_info = {
+ .name = TYPE_IMX7_GPR,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMX7GPRState),
+ .instance_init = imx7_gpr_init,
+ .class_init = imx7_gpr_class_init,
+};
+
+static void imx7_gpr_register_type(void)
+{
+ type_register_static(&imx7_gpr_info);
+}
+type_init(imx7_gpr_register_type)
diff --git a/hw/misc/imx7_snvs.c b/hw/misc/imx7_snvs.c
new file mode 100644
index 000000000..ee7698bd9
--- /dev/null
+++ b/hw/misc/imx7_snvs.c
@@ -0,0 +1,83 @@
+/*
+ * IMX7 Secure Non-Volatile Storage
+ *
+ * Copyright (c) 2018, Impinj, Inc.
+ *
+ * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Bare minimum emulation code needed to support being able to shut
+ * down linux guest gracefully.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/imx7_snvs.h"
+#include "qemu/module.h"
+#include "sysemu/runstate.h"
+
+static uint64_t imx7_snvs_read(void *opaque, hwaddr offset, unsigned size)
+{
+ return 0;
+}
+
+static void imx7_snvs_write(void *opaque, hwaddr offset,
+ uint64_t v, unsigned size)
+{
+ const uint32_t value = v;
+ const uint32_t mask = SNVS_LPCR_TOP | SNVS_LPCR_DP_EN;
+
+ if (offset == SNVS_LPCR && ((value & mask) == mask)) {
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ }
+}
+
+static const struct MemoryRegionOps imx7_snvs_ops = {
+ .read = imx7_snvs_read,
+ .write = imx7_snvs_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ /*
+ * Our device would not work correctly if the guest was doing
+ * unaligned access. This might not be a limitation on the real
+ * device but in practice there is no reason for a guest to access
+ * this device unaligned.
+ */
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void imx7_snvs_init(Object *obj)
+{
+ SysBusDevice *sd = SYS_BUS_DEVICE(obj);
+ IMX7SNVSState *s = IMX7_SNVS(obj);
+
+ memory_region_init_io(&s->mmio, obj, &imx7_snvs_ops, s,
+ TYPE_IMX7_SNVS, 0x1000);
+
+ sysbus_init_mmio(sd, &s->mmio);
+}
+
+static void imx7_snvs_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "i.MX7 Secure Non-Volatile Storage Module";
+}
+
+static const TypeInfo imx7_snvs_info = {
+ .name = TYPE_IMX7_SNVS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMX7SNVSState),
+ .instance_init = imx7_snvs_init,
+ .class_init = imx7_snvs_class_init,
+};
+
+static void imx7_snvs_register_type(void)
+{
+ type_register_static(&imx7_snvs_info);
+}
+type_init(imx7_snvs_register_type)
diff --git a/hw/misc/imx_ccm.c b/hw/misc/imx_ccm.c
new file mode 100644
index 000000000..9403c5daa
--- /dev/null
+++ b/hw/misc/imx_ccm.c
@@ -0,0 +1,86 @@
+/*
+ * IMX31 Clock Control Module
+ *
+ * Copyright (C) 2012 NICTA
+ * Updated by Jean-Christophe Dubois <jcd@tribudubois.net>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * This is an abstract base class used to get a common interface to
+ * retrieve the CCM frequencies from the various i.MX SOC.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/imx_ccm.h"
+#include "qemu/module.h"
+
+#ifndef DEBUG_IMX_CCM
+#define DEBUG_IMX_CCM 0
+#endif
+
+#define DPRINTF(fmt, args...) \
+ do { \
+ if (DEBUG_IMX_CCM) { \
+ fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX_CCM, \
+ __func__, ##args); \
+ } \
+ } while (0)
+
+
+uint32_t imx_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
+{
+ uint32_t freq = 0;
+ IMXCCMClass *klass = IMX_CCM_GET_CLASS(dev);
+
+ if (klass->get_clock_frequency) {
+ freq = klass->get_clock_frequency(dev, clock);
+ }
+
+ DPRINTF("(clock = %d) = %u\n", clock, freq);
+
+ return freq;
+}
+
+/*
+ * Calculate PLL output frequency
+ */
+uint32_t imx_ccm_calc_pll(uint32_t pllreg, uint32_t base_freq)
+{
+ int32_t freq;
+ int32_t mfn = MFN(pllreg); /* Numerator */
+ uint32_t mfi = MFI(pllreg); /* Integer part */
+ uint32_t mfd = 1 + MFD(pllreg); /* Denominator */
+ uint32_t pd = 1 + PD(pllreg); /* Pre-divider */
+
+ if (mfi < 5) {
+ mfi = 5;
+ }
+
+ /* mfn is 10-bit signed twos-complement */
+ mfn <<= 32 - 10;
+ mfn >>= 32 - 10;
+
+ freq = ((2 * (base_freq >> 10) * (mfi * mfd + mfn)) /
+ (mfd * pd)) << 10;
+
+ DPRINTF("(pllreg = 0x%08x, base_freq = %u) = %d\n", pllreg, base_freq,
+ freq);
+
+ return freq;
+}
+
+static const TypeInfo imx_ccm_info = {
+ .name = TYPE_IMX_CCM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXCCMState),
+ .class_size = sizeof(IMXCCMClass),
+ .abstract = true,
+};
+
+static void imx_ccm_register_types(void)
+{
+ type_register_static(&imx_ccm_info);
+}
+
+type_init(imx_ccm_register_types)
diff --git a/hw/misc/imx_rngc.c b/hw/misc/imx_rngc.c
new file mode 100644
index 000000000..632c03779
--- /dev/null
+++ b/hw/misc/imx_rngc.c
@@ -0,0 +1,277 @@
+/*
+ * Freescale i.MX RNGC emulation
+ *
+ * Copyright (C) 2020 Martin Kaiser <martin@kaiser.cx>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * This driver provides the minimum functionality to initialize and seed
+ * an rngc and to read random numbers. The rngb that is found in imx25
+ * chipsets is also supported.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/guest-random.h"
+#include "hw/irq.h"
+#include "hw/misc/imx_rngc.h"
+#include "migration/vmstate.h"
+
+#define RNGC_NAME "i.MX RNGC"
+
+#define RNGC_VER_ID 0x00
+#define RNGC_COMMAND 0x04
+#define RNGC_CONTROL 0x08
+#define RNGC_STATUS 0x0C
+#define RNGC_FIFO 0x14
+
+/* These version info are reported by the rngb in an imx258 chip. */
+#define RNG_TYPE_RNGB 0x1
+#define V_MAJ 0x2
+#define V_MIN 0x40
+
+#define RNGC_CMD_BIT_SW_RST 0x40
+#define RNGC_CMD_BIT_CLR_ERR 0x20
+#define RNGC_CMD_BIT_CLR_INT 0x10
+#define RNGC_CMD_BIT_SEED 0x02
+#define RNGC_CMD_BIT_SELF_TEST 0x01
+
+#define RNGC_CTRL_BIT_MASK_ERR 0x40
+#define RNGC_CTRL_BIT_MASK_DONE 0x20
+#define RNGC_CTRL_BIT_AUTO_SEED 0x10
+
+/* the current status for self-test and seed operations */
+#define OP_IDLE 0
+#define OP_RUN 1
+#define OP_DONE 2
+
+static uint64_t imx_rngc_read(void *opaque, hwaddr offset, unsigned size)
+{
+ IMXRNGCState *s = IMX_RNGC(opaque);
+ uint64_t val = 0;
+
+ switch (offset) {
+ case RNGC_VER_ID:
+ val |= RNG_TYPE_RNGB << 28 | V_MAJ << 8 | V_MIN;
+ break;
+
+ case RNGC_COMMAND:
+ if (s->op_seed == OP_RUN) {
+ val |= RNGC_CMD_BIT_SEED;
+ }
+ if (s->op_self_test == OP_RUN) {
+ val |= RNGC_CMD_BIT_SELF_TEST;
+ }
+ break;
+
+ case RNGC_CONTROL:
+ /*
+ * The CTL_ACC and VERIF_MODE bits are not supported yet.
+ * They read as 0.
+ */
+ val |= s->mask;
+ if (s->auto_seed) {
+ val |= RNGC_CTRL_BIT_AUTO_SEED;
+ }
+ /*
+ * We don't have an internal fifo like the real hardware.
+ * There's no need for strategy to handle fifo underflows.
+ * We return the FIFO_UFLOW_RESPONSE bits as 0.
+ */
+ break;
+
+ case RNGC_STATUS:
+ /*
+ * We never report any statistics test or self-test errors or any
+ * other errors. STAT_TEST_PF, ST_PF and ERROR are always 0.
+ */
+
+ /*
+ * We don't have an internal fifo, see above. Therefore, we
+ * report back the default fifo size (5 32-bit words) and
+ * indicate that our fifo is always full.
+ */
+ val |= 5 << 12 | 5 << 8;
+
+ /* We always have a new seed available. */
+ val |= 1 << 6;
+
+ if (s->op_seed == OP_DONE) {
+ val |= 1 << 5;
+ }
+ if (s->op_self_test == OP_DONE) {
+ val |= 1 << 4;
+ }
+ if (s->op_seed == OP_RUN || s->op_self_test == OP_RUN) {
+ /*
+ * We're busy if self-test is running or if we're
+ * seeding the prng.
+ */
+ val |= 1 << 1;
+ } else {
+ /*
+ * We're ready to provide secure random numbers whenever
+ * we're not busy.
+ */
+ val |= 1;
+ }
+ break;
+
+ case RNGC_FIFO:
+ qemu_guest_getrandom_nofail(&val, sizeof(val));
+ break;
+ }
+
+ return val;
+}
+
+static void imx_rngc_do_reset(IMXRNGCState *s)
+{
+ s->op_self_test = OP_IDLE;
+ s->op_seed = OP_IDLE;
+ s->mask = 0;
+ s->auto_seed = false;
+}
+
+static void imx_rngc_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ IMXRNGCState *s = IMX_RNGC(opaque);
+
+ switch (offset) {
+ case RNGC_COMMAND:
+ if (value & RNGC_CMD_BIT_SW_RST) {
+ imx_rngc_do_reset(s);
+ }
+
+ /*
+ * For now, both CLR_ERR and CLR_INT clear the interrupt. We
+ * don't report any errors yet.
+ */
+ if (value & (RNGC_CMD_BIT_CLR_ERR | RNGC_CMD_BIT_CLR_INT)) {
+ qemu_irq_lower(s->irq);
+ }
+
+ if (value & RNGC_CMD_BIT_SEED) {
+ s->op_seed = OP_RUN;
+ qemu_bh_schedule(s->seed_bh);
+ }
+
+ if (value & RNGC_CMD_BIT_SELF_TEST) {
+ s->op_self_test = OP_RUN;
+ qemu_bh_schedule(s->self_test_bh);
+ }
+ break;
+
+ case RNGC_CONTROL:
+ /*
+ * The CTL_ACC and VERIF_MODE bits are not supported yet.
+ * We ignore them if they're set by the caller.
+ */
+
+ if (value & RNGC_CTRL_BIT_MASK_ERR) {
+ s->mask |= RNGC_CTRL_BIT_MASK_ERR;
+ } else {
+ s->mask &= ~RNGC_CTRL_BIT_MASK_ERR;
+ }
+
+ if (value & RNGC_CTRL_BIT_MASK_DONE) {
+ s->mask |= RNGC_CTRL_BIT_MASK_DONE;
+ } else {
+ s->mask &= ~RNGC_CTRL_BIT_MASK_DONE;
+ }
+
+ if (value & RNGC_CTRL_BIT_AUTO_SEED) {
+ s->auto_seed = true;
+ } else {
+ s->auto_seed = false;
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps imx_rngc_ops = {
+ .read = imx_rngc_read,
+ .write = imx_rngc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void imx_rngc_self_test(void *opaque)
+{
+ IMXRNGCState *s = IMX_RNGC(opaque);
+
+ s->op_self_test = OP_DONE;
+ if (!(s->mask & RNGC_CTRL_BIT_MASK_DONE)) {
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static void imx_rngc_seed(void *opaque)
+{
+ IMXRNGCState *s = IMX_RNGC(opaque);
+
+ s->op_seed = OP_DONE;
+ if (!(s->mask & RNGC_CTRL_BIT_MASK_DONE)) {
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static void imx_rngc_realize(DeviceState *dev, Error **errp)
+{
+ IMXRNGCState *s = IMX_RNGC(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &imx_rngc_ops, s,
+ TYPE_IMX_RNGC, 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ sysbus_init_irq(sbd, &s->irq);
+ s->self_test_bh = qemu_bh_new(imx_rngc_self_test, s);
+ s->seed_bh = qemu_bh_new(imx_rngc_seed, s);
+}
+
+static void imx_rngc_reset(DeviceState *dev)
+{
+ IMXRNGCState *s = IMX_RNGC(dev);
+
+ imx_rngc_do_reset(s);
+}
+
+static const VMStateDescription vmstate_imx_rngc = {
+ .name = RNGC_NAME,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(op_self_test, IMXRNGCState),
+ VMSTATE_UINT8(op_seed, IMXRNGCState),
+ VMSTATE_UINT8(mask, IMXRNGCState),
+ VMSTATE_BOOL(auto_seed, IMXRNGCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void imx_rngc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = imx_rngc_realize;
+ dc->reset = imx_rngc_reset;
+ dc->desc = RNGC_NAME,
+ dc->vmsd = &vmstate_imx_rngc;
+}
+
+static const TypeInfo imx_rngc_info = {
+ .name = TYPE_IMX_RNGC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXRNGCState),
+ .class_init = imx_rngc_class_init,
+};
+
+static void imx_rngc_register_types(void)
+{
+ type_register_static(&imx_rngc_info);
+}
+
+type_init(imx_rngc_register_types)
diff --git a/hw/misc/iotkit-secctl.c b/hw/misc/iotkit-secctl.c
new file mode 100644
index 000000000..7b41cfa8f
--- /dev/null
+++ b/hw/misc/iotkit-secctl.c
@@ -0,0 +1,845 @@
+/*
+ * Arm IoT Kit security controller
+ *
+ * Copyright (c) 2018 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/registerfields.h"
+#include "hw/irq.h"
+#include "hw/misc/iotkit-secctl.h"
+#include "hw/arm/armsse-version.h"
+#include "hw/qdev-properties.h"
+
+/* Registers in the secure privilege control block */
+REG32(SECRESPCFG, 0x10)
+REG32(NSCCFG, 0x14)
+REG32(SECMPCINTSTATUS, 0x1c)
+REG32(SECPPCINTSTAT, 0x20)
+REG32(SECPPCINTCLR, 0x24)
+REG32(SECPPCINTEN, 0x28)
+REG32(SECMSCINTSTAT, 0x30)
+REG32(SECMSCINTCLR, 0x34)
+REG32(SECMSCINTEN, 0x38)
+REG32(BRGINTSTAT, 0x40)
+REG32(BRGINTCLR, 0x44)
+REG32(BRGINTEN, 0x48)
+REG32(AHBNSPPC0, 0x50)
+REG32(AHBNSPPCEXP0, 0x60)
+REG32(AHBNSPPCEXP1, 0x64)
+REG32(AHBNSPPCEXP2, 0x68)
+REG32(AHBNSPPCEXP3, 0x6c)
+REG32(APBNSPPC0, 0x70)
+REG32(APBNSPPC1, 0x74)
+REG32(APBNSPPCEXP0, 0x80)
+REG32(APBNSPPCEXP1, 0x84)
+REG32(APBNSPPCEXP2, 0x88)
+REG32(APBNSPPCEXP3, 0x8c)
+REG32(AHBSPPPC0, 0x90)
+REG32(AHBSPPPCEXP0, 0xa0)
+REG32(AHBSPPPCEXP1, 0xa4)
+REG32(AHBSPPPCEXP2, 0xa8)
+REG32(AHBSPPPCEXP3, 0xac)
+REG32(APBSPPPC0, 0xb0)
+REG32(APBSPPPC1, 0xb4)
+REG32(APBSPPPCEXP0, 0xc0)
+REG32(APBSPPPCEXP1, 0xc4)
+REG32(APBSPPPCEXP2, 0xc8)
+REG32(APBSPPPCEXP3, 0xcc)
+REG32(NSMSCEXP, 0xd0)
+REG32(PID4, 0xfd0)
+REG32(PID5, 0xfd4)
+REG32(PID6, 0xfd8)
+REG32(PID7, 0xfdc)
+REG32(PID0, 0xfe0)
+REG32(PID1, 0xfe4)
+REG32(PID2, 0xfe8)
+REG32(PID3, 0xfec)
+REG32(CID0, 0xff0)
+REG32(CID1, 0xff4)
+REG32(CID2, 0xff8)
+REG32(CID3, 0xffc)
+
+/* Registers in the non-secure privilege control block */
+REG32(AHBNSPPPC0, 0x90)
+REG32(AHBNSPPPCEXP0, 0xa0)
+REG32(AHBNSPPPCEXP1, 0xa4)
+REG32(AHBNSPPPCEXP2, 0xa8)
+REG32(AHBNSPPPCEXP3, 0xac)
+REG32(APBNSPPPC0, 0xb0)
+REG32(APBNSPPPC1, 0xb4)
+REG32(APBNSPPPCEXP0, 0xc0)
+REG32(APBNSPPPCEXP1, 0xc4)
+REG32(APBNSPPPCEXP2, 0xc8)
+REG32(APBNSPPPCEXP3, 0xcc)
+/* PID and CID registers are also present in the NS block */
+
+static const uint8_t iotkit_secctl_s_idregs[] = {
+ 0x04, 0x00, 0x00, 0x00,
+ 0x52, 0xb8, 0x0b, 0x00,
+ 0x0d, 0xf0, 0x05, 0xb1,
+};
+
+static const uint8_t iotkit_secctl_ns_idregs[] = {
+ 0x04, 0x00, 0x00, 0x00,
+ 0x53, 0xb8, 0x0b, 0x00,
+ 0x0d, 0xf0, 0x05, 0xb1,
+};
+
+static const uint8_t iotkit_secctl_s_sse300_idregs[] = {
+ 0x04, 0x00, 0x00, 0x00,
+ 0x52, 0xb8, 0x2b, 0x00,
+ 0x0d, 0xf0, 0x05, 0xb1,
+};
+
+static const uint8_t iotkit_secctl_ns_sse300_idregs[] = {
+ 0x04, 0x00, 0x00, 0x00,
+ 0x53, 0xb8, 0x2b, 0x00,
+ 0x0d, 0xf0, 0x05, 0xb1,
+};
+
+
+/* The register sets for the various PPCs (AHB internal, APB internal,
+ * AHB expansion, APB expansion) are all set up so that they are
+ * in 16-aligned blocks so offsets 0xN0, 0xN4, 0xN8, 0xNC are PPCs
+ * 0, 1, 2, 3 of that type, so we can convert a register address offset
+ * into an an index into a PPC array easily.
+ */
+static inline int offset_to_ppc_idx(uint32_t offset)
+{
+ return extract32(offset, 2, 2);
+}
+
+typedef void PerPPCFunction(IoTKitSecCtlPPC *ppc);
+
+static void foreach_ppc(IoTKitSecCtl *s, PerPPCFunction *fn)
+{
+ int i;
+
+ for (i = 0; i < IOTS_NUM_APB_PPC; i++) {
+ fn(&s->apb[i]);
+ }
+ for (i = 0; i < IOTS_NUM_APB_EXP_PPC; i++) {
+ fn(&s->apbexp[i]);
+ }
+ for (i = 0; i < IOTS_NUM_AHB_EXP_PPC; i++) {
+ fn(&s->ahbexp[i]);
+ }
+}
+
+static MemTxResult iotkit_secctl_s_read(void *opaque, hwaddr addr,
+ uint64_t *pdata,
+ unsigned size, MemTxAttrs attrs)
+{
+ uint64_t r;
+ uint32_t offset = addr & ~0x3;
+ IoTKitSecCtl *s = IOTKIT_SECCTL(opaque);
+
+ switch (offset) {
+ case A_AHBNSPPC0:
+ case A_AHBSPPPC0:
+ r = 0;
+ break;
+ case A_SECRESPCFG:
+ r = s->secrespcfg;
+ break;
+ case A_NSCCFG:
+ r = s->nsccfg;
+ break;
+ case A_SECMPCINTSTATUS:
+ r = s->mpcintstatus;
+ break;
+ case A_SECPPCINTSTAT:
+ r = s->secppcintstat;
+ break;
+ case A_SECPPCINTEN:
+ r = s->secppcinten;
+ break;
+ case A_BRGINTSTAT:
+ /* QEMU's bus fabric can never report errors as it doesn't buffer
+ * writes, so we never report bridge interrupts.
+ */
+ r = 0;
+ break;
+ case A_BRGINTEN:
+ r = s->brginten;
+ break;
+ case A_AHBNSPPCEXP0:
+ case A_AHBNSPPCEXP1:
+ case A_AHBNSPPCEXP2:
+ case A_AHBNSPPCEXP3:
+ r = s->ahbexp[offset_to_ppc_idx(offset)].ns;
+ break;
+ case A_APBNSPPC0:
+ case A_APBNSPPC1:
+ r = s->apb[offset_to_ppc_idx(offset)].ns;
+ break;
+ case A_APBNSPPCEXP0:
+ case A_APBNSPPCEXP1:
+ case A_APBNSPPCEXP2:
+ case A_APBNSPPCEXP3:
+ r = s->apbexp[offset_to_ppc_idx(offset)].ns;
+ break;
+ case A_AHBSPPPCEXP0:
+ case A_AHBSPPPCEXP1:
+ case A_AHBSPPPCEXP2:
+ case A_AHBSPPPCEXP3:
+ r = s->apbexp[offset_to_ppc_idx(offset)].sp;
+ break;
+ case A_APBSPPPC0:
+ case A_APBSPPPC1:
+ r = s->apb[offset_to_ppc_idx(offset)].sp;
+ break;
+ case A_APBSPPPCEXP0:
+ case A_APBSPPPCEXP1:
+ case A_APBSPPPCEXP2:
+ case A_APBSPPPCEXP3:
+ r = s->apbexp[offset_to_ppc_idx(offset)].sp;
+ break;
+ case A_SECMSCINTSTAT:
+ r = s->secmscintstat;
+ break;
+ case A_SECMSCINTEN:
+ r = s->secmscinten;
+ break;
+ case A_NSMSCEXP:
+ r = s->nsmscexp;
+ break;
+ case A_PID4:
+ case A_PID5:
+ case A_PID6:
+ case A_PID7:
+ case A_PID0:
+ case A_PID1:
+ case A_PID2:
+ case A_PID3:
+ case A_CID0:
+ case A_CID1:
+ case A_CID2:
+ case A_CID3:
+ switch (s->sse_version) {
+ case ARMSSE_SSE300:
+ r = iotkit_secctl_s_sse300_idregs[(offset - A_PID4) / 4];
+ break;
+ default:
+ r = iotkit_secctl_s_idregs[(offset - A_PID4) / 4];
+ break;
+ }
+ break;
+ case A_SECPPCINTCLR:
+ case A_SECMSCINTCLR:
+ case A_BRGINTCLR:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IotKit SecCtl S block read: write-only offset 0x%x\n",
+ offset);
+ r = 0;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IotKit SecCtl S block read: bad offset 0x%x\n", offset);
+ r = 0;
+ break;
+ }
+
+ if (size != 4) {
+ /* None of our registers are access-sensitive, so just pull the right
+ * byte out of the word read result.
+ */
+ r = extract32(r, (addr & 3) * 8, size * 8);
+ }
+
+ trace_iotkit_secctl_s_read(offset, r, size);
+ *pdata = r;
+ return MEMTX_OK;
+}
+
+static void iotkit_secctl_update_ppc_ap(IoTKitSecCtlPPC *ppc)
+{
+ int i;
+
+ for (i = 0; i < ppc->numports; i++) {
+ bool v;
+
+ if (extract32(ppc->ns, i, 1)) {
+ v = extract32(ppc->nsp, i, 1);
+ } else {
+ v = extract32(ppc->sp, i, 1);
+ }
+ qemu_set_irq(ppc->ap[i], v);
+ }
+}
+
+static void iotkit_secctl_ppc_ns_write(IoTKitSecCtlPPC *ppc, uint32_t value)
+{
+ int i;
+
+ ppc->ns = value & MAKE_64BIT_MASK(0, ppc->numports);
+ for (i = 0; i < ppc->numports; i++) {
+ qemu_set_irq(ppc->nonsec[i], extract32(ppc->ns, i, 1));
+ }
+ iotkit_secctl_update_ppc_ap(ppc);
+}
+
+static void iotkit_secctl_ppc_sp_write(IoTKitSecCtlPPC *ppc, uint32_t value)
+{
+ ppc->sp = value & MAKE_64BIT_MASK(0, ppc->numports);
+ iotkit_secctl_update_ppc_ap(ppc);
+}
+
+static void iotkit_secctl_ppc_nsp_write(IoTKitSecCtlPPC *ppc, uint32_t value)
+{
+ ppc->nsp = value & MAKE_64BIT_MASK(0, ppc->numports);
+ iotkit_secctl_update_ppc_ap(ppc);
+}
+
+static void iotkit_secctl_ppc_update_irq_clear(IoTKitSecCtlPPC *ppc)
+{
+ uint32_t value = ppc->parent->secppcintstat;
+
+ qemu_set_irq(ppc->irq_clear, extract32(value, ppc->irq_bit_offset, 1));
+}
+
+static void iotkit_secctl_ppc_update_irq_enable(IoTKitSecCtlPPC *ppc)
+{
+ uint32_t value = ppc->parent->secppcinten;
+
+ qemu_set_irq(ppc->irq_enable, extract32(value, ppc->irq_bit_offset, 1));
+}
+
+static void iotkit_secctl_update_mscexp_irqs(qemu_irq *msc_irqs, uint32_t value)
+{
+ int i;
+
+ for (i = 0; i < IOTS_NUM_EXP_MSC; i++) {
+ qemu_set_irq(msc_irqs[i], extract32(value, i + 16, 1));
+ }
+}
+
+static void iotkit_secctl_update_msc_irq(IoTKitSecCtl *s)
+{
+ /* Update the combined MSC IRQ, based on S_MSCEXP_STATUS and S_MSCEXP_EN */
+ bool level = s->secmscintstat & s->secmscinten;
+
+ qemu_set_irq(s->msc_irq, level);
+}
+
+static MemTxResult iotkit_secctl_s_write(void *opaque, hwaddr addr,
+ uint64_t value,
+ unsigned size, MemTxAttrs attrs)
+{
+ IoTKitSecCtl *s = IOTKIT_SECCTL(opaque);
+ uint32_t offset = addr;
+ IoTKitSecCtlPPC *ppc;
+
+ trace_iotkit_secctl_s_write(offset, value, size);
+
+ if (size != 4) {
+ /* Byte and halfword writes are ignored */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IotKit SecCtl S block write: bad size, ignored\n");
+ return MEMTX_OK;
+ }
+
+ switch (offset) {
+ case A_NSCCFG:
+ s->nsccfg = value & 3;
+ qemu_set_irq(s->nsc_cfg_irq, s->nsccfg);
+ break;
+ case A_SECRESPCFG:
+ value &= 1;
+ s->secrespcfg = value;
+ qemu_set_irq(s->sec_resp_cfg, s->secrespcfg);
+ break;
+ case A_SECPPCINTCLR:
+ s->secppcintstat &= ~(value & 0x00f000f3);
+ foreach_ppc(s, iotkit_secctl_ppc_update_irq_clear);
+ break;
+ case A_SECPPCINTEN:
+ s->secppcinten = value & 0x00f000f3;
+ foreach_ppc(s, iotkit_secctl_ppc_update_irq_enable);
+ break;
+ case A_BRGINTCLR:
+ break;
+ case A_BRGINTEN:
+ s->brginten = value & 0xffff0000;
+ break;
+ case A_AHBNSPPCEXP0:
+ case A_AHBNSPPCEXP1:
+ case A_AHBNSPPCEXP2:
+ case A_AHBNSPPCEXP3:
+ ppc = &s->ahbexp[offset_to_ppc_idx(offset)];
+ iotkit_secctl_ppc_ns_write(ppc, value);
+ break;
+ case A_APBNSPPC0:
+ case A_APBNSPPC1:
+ ppc = &s->apb[offset_to_ppc_idx(offset)];
+ iotkit_secctl_ppc_ns_write(ppc, value);
+ break;
+ case A_APBNSPPCEXP0:
+ case A_APBNSPPCEXP1:
+ case A_APBNSPPCEXP2:
+ case A_APBNSPPCEXP3:
+ ppc = &s->apbexp[offset_to_ppc_idx(offset)];
+ iotkit_secctl_ppc_ns_write(ppc, value);
+ break;
+ case A_AHBSPPPCEXP0:
+ case A_AHBSPPPCEXP1:
+ case A_AHBSPPPCEXP2:
+ case A_AHBSPPPCEXP3:
+ ppc = &s->ahbexp[offset_to_ppc_idx(offset)];
+ iotkit_secctl_ppc_sp_write(ppc, value);
+ break;
+ case A_APBSPPPC0:
+ case A_APBSPPPC1:
+ ppc = &s->apb[offset_to_ppc_idx(offset)];
+ iotkit_secctl_ppc_sp_write(ppc, value);
+ break;
+ case A_APBSPPPCEXP0:
+ case A_APBSPPPCEXP1:
+ case A_APBSPPPCEXP2:
+ case A_APBSPPPCEXP3:
+ ppc = &s->apbexp[offset_to_ppc_idx(offset)];
+ iotkit_secctl_ppc_sp_write(ppc, value);
+ break;
+ case A_SECMSCINTCLR:
+ iotkit_secctl_update_mscexp_irqs(s->mscexp_clear, value);
+ break;
+ case A_SECMSCINTEN:
+ s->secmscinten = value;
+ iotkit_secctl_update_msc_irq(s);
+ break;
+ case A_NSMSCEXP:
+ s->nsmscexp = value;
+ iotkit_secctl_update_mscexp_irqs(s->mscexp_ns, value);
+ break;
+ case A_SECMPCINTSTATUS:
+ case A_SECPPCINTSTAT:
+ case A_SECMSCINTSTAT:
+ case A_BRGINTSTAT:
+ case A_AHBNSPPC0:
+ case A_AHBSPPPC0:
+ case A_PID4:
+ case A_PID5:
+ case A_PID6:
+ case A_PID7:
+ case A_PID0:
+ case A_PID1:
+ case A_PID2:
+ case A_PID3:
+ case A_CID0:
+ case A_CID1:
+ case A_CID2:
+ case A_CID3:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IoTKit SecCtl S block write: "
+ "read-only offset 0x%x\n", offset);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IotKit SecCtl S block write: bad offset 0x%x\n",
+ offset);
+ break;
+ }
+
+ return MEMTX_OK;
+}
+
+static MemTxResult iotkit_secctl_ns_read(void *opaque, hwaddr addr,
+ uint64_t *pdata,
+ unsigned size, MemTxAttrs attrs)
+{
+ IoTKitSecCtl *s = IOTKIT_SECCTL(opaque);
+ uint64_t r;
+ uint32_t offset = addr & ~0x3;
+
+ switch (offset) {
+ case A_AHBNSPPPC0:
+ r = 0;
+ break;
+ case A_AHBNSPPPCEXP0:
+ case A_AHBNSPPPCEXP1:
+ case A_AHBNSPPPCEXP2:
+ case A_AHBNSPPPCEXP3:
+ r = s->ahbexp[offset_to_ppc_idx(offset)].nsp;
+ break;
+ case A_APBNSPPPC0:
+ case A_APBNSPPPC1:
+ r = s->apb[offset_to_ppc_idx(offset)].nsp;
+ break;
+ case A_APBNSPPPCEXP0:
+ case A_APBNSPPPCEXP1:
+ case A_APBNSPPPCEXP2:
+ case A_APBNSPPPCEXP3:
+ r = s->apbexp[offset_to_ppc_idx(offset)].nsp;
+ break;
+ case A_PID4:
+ case A_PID5:
+ case A_PID6:
+ case A_PID7:
+ case A_PID0:
+ case A_PID1:
+ case A_PID2:
+ case A_PID3:
+ case A_CID0:
+ case A_CID1:
+ case A_CID2:
+ case A_CID3:
+ switch (s->sse_version) {
+ case ARMSSE_SSE300:
+ r = iotkit_secctl_ns_sse300_idregs[(offset - A_PID4) / 4];
+ break;
+ default:
+ r = iotkit_secctl_ns_idregs[(offset - A_PID4) / 4];
+ break;
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IotKit SecCtl NS block write: bad offset 0x%x\n",
+ offset);
+ r = 0;
+ break;
+ }
+
+ if (size != 4) {
+ /* None of our registers are access-sensitive, so just pull the right
+ * byte out of the word read result.
+ */
+ r = extract32(r, (addr & 3) * 8, size * 8);
+ }
+
+ trace_iotkit_secctl_ns_read(offset, r, size);
+ *pdata = r;
+ return MEMTX_OK;
+}
+
+static MemTxResult iotkit_secctl_ns_write(void *opaque, hwaddr addr,
+ uint64_t value,
+ unsigned size, MemTxAttrs attrs)
+{
+ IoTKitSecCtl *s = IOTKIT_SECCTL(opaque);
+ uint32_t offset = addr;
+ IoTKitSecCtlPPC *ppc;
+
+ trace_iotkit_secctl_ns_write(offset, value, size);
+
+ if (size != 4) {
+ /* Byte and halfword writes are ignored */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IotKit SecCtl NS block write: bad size, ignored\n");
+ return MEMTX_OK;
+ }
+
+ switch (offset) {
+ case A_AHBNSPPPCEXP0:
+ case A_AHBNSPPPCEXP1:
+ case A_AHBNSPPPCEXP2:
+ case A_AHBNSPPPCEXP3:
+ ppc = &s->ahbexp[offset_to_ppc_idx(offset)];
+ iotkit_secctl_ppc_nsp_write(ppc, value);
+ break;
+ case A_APBNSPPPC0:
+ case A_APBNSPPPC1:
+ ppc = &s->apb[offset_to_ppc_idx(offset)];
+ iotkit_secctl_ppc_nsp_write(ppc, value);
+ break;
+ case A_APBNSPPPCEXP0:
+ case A_APBNSPPPCEXP1:
+ case A_APBNSPPPCEXP2:
+ case A_APBNSPPPCEXP3:
+ ppc = &s->apbexp[offset_to_ppc_idx(offset)];
+ iotkit_secctl_ppc_nsp_write(ppc, value);
+ break;
+ case A_AHBNSPPPC0:
+ case A_PID4:
+ case A_PID5:
+ case A_PID6:
+ case A_PID7:
+ case A_PID0:
+ case A_PID1:
+ case A_PID2:
+ case A_PID3:
+ case A_CID0:
+ case A_CID1:
+ case A_CID2:
+ case A_CID3:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IoTKit SecCtl NS block write: "
+ "read-only offset 0x%x\n", offset);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IotKit SecCtl NS block write: bad offset 0x%x\n",
+ offset);
+ break;
+ }
+
+ return MEMTX_OK;
+}
+
+static const MemoryRegionOps iotkit_secctl_s_ops = {
+ .read_with_attrs = iotkit_secctl_s_read,
+ .write_with_attrs = iotkit_secctl_s_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+};
+
+static const MemoryRegionOps iotkit_secctl_ns_ops = {
+ .read_with_attrs = iotkit_secctl_ns_read,
+ .write_with_attrs = iotkit_secctl_ns_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+};
+
+static void iotkit_secctl_reset_ppc(IoTKitSecCtlPPC *ppc)
+{
+ ppc->ns = 0;
+ ppc->sp = 0;
+ ppc->nsp = 0;
+}
+
+static void iotkit_secctl_reset(DeviceState *dev)
+{
+ IoTKitSecCtl *s = IOTKIT_SECCTL(dev);
+
+ s->secppcintstat = 0;
+ s->secppcinten = 0;
+ s->secrespcfg = 0;
+ s->nsccfg = 0;
+ s->brginten = 0;
+
+ foreach_ppc(s, iotkit_secctl_reset_ppc);
+}
+
+static void iotkit_secctl_mpc_status(void *opaque, int n, int level)
+{
+ IoTKitSecCtl *s = IOTKIT_SECCTL(opaque);
+
+ s->mpcintstatus = deposit32(s->mpcintstatus, n, 1, !!level);
+}
+
+static void iotkit_secctl_mpcexp_status(void *opaque, int n, int level)
+{
+ IoTKitSecCtl *s = IOTKIT_SECCTL(opaque);
+
+ s->mpcintstatus = deposit32(s->mpcintstatus, n + 16, 1, !!level);
+}
+
+static void iotkit_secctl_mscexp_status(void *opaque, int n, int level)
+{
+ IoTKitSecCtl *s = IOTKIT_SECCTL(opaque);
+
+ s->secmscintstat = deposit32(s->secmscintstat, n + 16, 1, !!level);
+ iotkit_secctl_update_msc_irq(s);
+}
+
+static void iotkit_secctl_ppc_irqstatus(void *opaque, int n, int level)
+{
+ IoTKitSecCtlPPC *ppc = opaque;
+ IoTKitSecCtl *s = IOTKIT_SECCTL(ppc->parent);
+ int irqbit = ppc->irq_bit_offset + n;
+
+ s->secppcintstat = deposit32(s->secppcintstat, irqbit, 1, level);
+}
+
+static void iotkit_secctl_init_ppc(IoTKitSecCtl *s,
+ IoTKitSecCtlPPC *ppc,
+ const char *name,
+ int numports,
+ int irq_bit_offset)
+{
+ char *gpioname;
+ DeviceState *dev = DEVICE(s);
+
+ ppc->numports = numports;
+ ppc->irq_bit_offset = irq_bit_offset;
+ ppc->parent = s;
+
+ gpioname = g_strdup_printf("%s_nonsec", name);
+ qdev_init_gpio_out_named(dev, ppc->nonsec, gpioname, numports);
+ g_free(gpioname);
+ gpioname = g_strdup_printf("%s_ap", name);
+ qdev_init_gpio_out_named(dev, ppc->ap, gpioname, numports);
+ g_free(gpioname);
+ gpioname = g_strdup_printf("%s_irq_enable", name);
+ qdev_init_gpio_out_named(dev, &ppc->irq_enable, gpioname, 1);
+ g_free(gpioname);
+ gpioname = g_strdup_printf("%s_irq_clear", name);
+ qdev_init_gpio_out_named(dev, &ppc->irq_clear, gpioname, 1);
+ g_free(gpioname);
+ gpioname = g_strdup_printf("%s_irq_status", name);
+ qdev_init_gpio_in_named_with_opaque(dev, iotkit_secctl_ppc_irqstatus,
+ ppc, gpioname, 1);
+ g_free(gpioname);
+}
+
+static void iotkit_secctl_init(Object *obj)
+{
+ IoTKitSecCtl *s = IOTKIT_SECCTL(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ DeviceState *dev = DEVICE(obj);
+ int i;
+
+ iotkit_secctl_init_ppc(s, &s->apb[0], "apb_ppc0",
+ IOTS_APB_PPC0_NUM_PORTS, 0);
+ iotkit_secctl_init_ppc(s, &s->apb[1], "apb_ppc1",
+ IOTS_APB_PPC1_NUM_PORTS, 1);
+
+ for (i = 0; i < IOTS_NUM_APB_EXP_PPC; i++) {
+ IoTKitSecCtlPPC *ppc = &s->apbexp[i];
+ char *ppcname = g_strdup_printf("apb_ppcexp%d", i);
+ iotkit_secctl_init_ppc(s, ppc, ppcname, IOTS_PPC_NUM_PORTS, 4 + i);
+ g_free(ppcname);
+ }
+ for (i = 0; i < IOTS_NUM_AHB_EXP_PPC; i++) {
+ IoTKitSecCtlPPC *ppc = &s->ahbexp[i];
+ char *ppcname = g_strdup_printf("ahb_ppcexp%d", i);
+ iotkit_secctl_init_ppc(s, ppc, ppcname, IOTS_PPC_NUM_PORTS, 20 + i);
+ g_free(ppcname);
+ }
+
+ qdev_init_gpio_out_named(dev, &s->sec_resp_cfg, "sec_resp_cfg", 1);
+ qdev_init_gpio_out_named(dev, &s->nsc_cfg_irq, "nsc_cfg", 1);
+
+ qdev_init_gpio_in_named(dev, iotkit_secctl_mpc_status, "mpc_status",
+ IOTS_NUM_MPC);
+ qdev_init_gpio_in_named(dev, iotkit_secctl_mpcexp_status,
+ "mpcexp_status", IOTS_NUM_EXP_MPC);
+
+ qdev_init_gpio_in_named(dev, iotkit_secctl_mscexp_status,
+ "mscexp_status", IOTS_NUM_EXP_MSC);
+ qdev_init_gpio_out_named(dev, s->mscexp_clear, "mscexp_clear",
+ IOTS_NUM_EXP_MSC);
+ qdev_init_gpio_out_named(dev, s->mscexp_ns, "mscexp_ns",
+ IOTS_NUM_EXP_MSC);
+ qdev_init_gpio_out_named(dev, &s->msc_irq, "msc_irq", 1);
+
+ memory_region_init_io(&s->s_regs, obj, &iotkit_secctl_s_ops,
+ s, "iotkit-secctl-s-regs", 0x1000);
+ memory_region_init_io(&s->ns_regs, obj, &iotkit_secctl_ns_ops,
+ s, "iotkit-secctl-ns-regs", 0x1000);
+ sysbus_init_mmio(sbd, &s->s_regs);
+ sysbus_init_mmio(sbd, &s->ns_regs);
+}
+
+static void iotkit_secctl_realize(DeviceState *dev, Error **errp)
+{
+ IoTKitSecCtl *s = IOTKIT_SECCTL(dev);
+
+ if (!armsse_version_valid(s->sse_version)) {
+ error_setg(errp, "invalid sse-version value %d", s->sse_version);
+ return;
+ }
+}
+
+static const VMStateDescription iotkit_secctl_ppc_vmstate = {
+ .name = "iotkit-secctl-ppc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ns, IoTKitSecCtlPPC),
+ VMSTATE_UINT32(sp, IoTKitSecCtlPPC),
+ VMSTATE_UINT32(nsp, IoTKitSecCtlPPC),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription iotkit_secctl_mpcintstatus_vmstate = {
+ .name = "iotkit-secctl-mpcintstatus",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(mpcintstatus, IoTKitSecCtl),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool needed_always(void *opaque)
+{
+ return true;
+}
+
+static const VMStateDescription iotkit_secctl_msc_vmstate = {
+ .name = "iotkit-secctl/msc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = needed_always,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(secmscintstat, IoTKitSecCtl),
+ VMSTATE_UINT32(secmscinten, IoTKitSecCtl),
+ VMSTATE_UINT32(nsmscexp, IoTKitSecCtl),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription iotkit_secctl_vmstate = {
+ .name = "iotkit-secctl",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(secppcintstat, IoTKitSecCtl),
+ VMSTATE_UINT32(secppcinten, IoTKitSecCtl),
+ VMSTATE_UINT32(secrespcfg, IoTKitSecCtl),
+ VMSTATE_UINT32(nsccfg, IoTKitSecCtl),
+ VMSTATE_UINT32(brginten, IoTKitSecCtl),
+ VMSTATE_STRUCT_ARRAY(apb, IoTKitSecCtl, IOTS_NUM_APB_PPC, 1,
+ iotkit_secctl_ppc_vmstate, IoTKitSecCtlPPC),
+ VMSTATE_STRUCT_ARRAY(apbexp, IoTKitSecCtl, IOTS_NUM_APB_EXP_PPC, 1,
+ iotkit_secctl_ppc_vmstate, IoTKitSecCtlPPC),
+ VMSTATE_STRUCT_ARRAY(ahbexp, IoTKitSecCtl, IOTS_NUM_AHB_EXP_PPC, 1,
+ iotkit_secctl_ppc_vmstate, IoTKitSecCtlPPC),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &iotkit_secctl_mpcintstatus_vmstate,
+ &iotkit_secctl_msc_vmstate,
+ NULL
+ },
+};
+
+static Property iotkit_secctl_props[] = {
+ DEFINE_PROP_UINT32("sse-version", IoTKitSecCtl, sse_version, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void iotkit_secctl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &iotkit_secctl_vmstate;
+ dc->reset = iotkit_secctl_reset;
+ dc->realize = iotkit_secctl_realize;
+ device_class_set_props(dc, iotkit_secctl_props);
+}
+
+static const TypeInfo iotkit_secctl_info = {
+ .name = TYPE_IOTKIT_SECCTL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IoTKitSecCtl),
+ .instance_init = iotkit_secctl_init,
+ .class_init = iotkit_secctl_class_init,
+};
+
+static void iotkit_secctl_register_types(void)
+{
+ type_register_static(&iotkit_secctl_info);
+}
+
+type_init(iotkit_secctl_register_types);
diff --git a/hw/misc/iotkit-sysctl.c b/hw/misc/iotkit-sysctl.c
new file mode 100644
index 000000000..9ee8fe849
--- /dev/null
+++ b/hw/misc/iotkit-sysctl.c
@@ -0,0 +1,872 @@
+/*
+ * ARM IoTKit system control element
+ *
+ * Copyright (c) 2018 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+/*
+ * This is a model of the "system control element" which is part of the
+ * Arm IoTKit and documented in
+ * https://developer.arm.com/documentation/ecm0601256/latest
+ * Specifically, it implements the "system control register" blocks.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "sysemu/runstate.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/registerfields.h"
+#include "hw/misc/iotkit-sysctl.h"
+#include "hw/qdev-properties.h"
+#include "hw/arm/armsse-version.h"
+#include "target/arm/arm-powerctl.h"
+#include "target/arm/cpu.h"
+
+REG32(SECDBGSTAT, 0x0)
+REG32(SECDBGSET, 0x4)
+REG32(SECDBGCLR, 0x8)
+REG32(SCSECCTRL, 0xc)
+REG32(FCLK_DIV, 0x10)
+REG32(SYSCLK_DIV, 0x14)
+REG32(CLOCK_FORCE, 0x18)
+REG32(RESET_SYNDROME, 0x100)
+REG32(RESET_MASK, 0x104)
+REG32(SWRESET, 0x108)
+ FIELD(SWRESET, SWRESETREQ, 9, 1)
+REG32(GRETREG, 0x10c)
+REG32(INITSVTOR0, 0x110)
+ FIELD(INITSVTOR0, LOCK, 0, 1)
+ FIELD(INITSVTOR0, VTOR, 7, 25)
+REG32(INITSVTOR1, 0x114)
+REG32(CPUWAIT, 0x118)
+REG32(NMI_ENABLE, 0x11c) /* BUSWAIT in IoTKit */
+REG32(WICCTRL, 0x120)
+REG32(EWCTRL, 0x124)
+REG32(PWRCTRL, 0x1fc)
+ FIELD(PWRCTRL, PPU_ACCESS_UNLOCK, 0, 1)
+ FIELD(PWRCTRL, PPU_ACCESS_FILTER, 1, 1)
+REG32(PDCM_PD_SYS_SENSE, 0x200)
+REG32(PDCM_PD_CPU0_SENSE, 0x204)
+REG32(PDCM_PD_SRAM0_SENSE, 0x20c)
+REG32(PDCM_PD_SRAM1_SENSE, 0x210)
+REG32(PDCM_PD_SRAM2_SENSE, 0x214) /* PDCM_PD_VMR0_SENSE on SSE300 */
+REG32(PDCM_PD_SRAM3_SENSE, 0x218) /* PDCM_PD_VMR1_SENSE on SSE300 */
+REG32(PID4, 0xfd0)
+REG32(PID5, 0xfd4)
+REG32(PID6, 0xfd8)
+REG32(PID7, 0xfdc)
+REG32(PID0, 0xfe0)
+REG32(PID1, 0xfe4)
+REG32(PID2, 0xfe8)
+REG32(PID3, 0xfec)
+REG32(CID0, 0xff0)
+REG32(CID1, 0xff4)
+REG32(CID2, 0xff8)
+REG32(CID3, 0xffc)
+
+/* PID/CID values */
+static const int iotkit_sysctl_id[] = {
+ 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
+ 0x54, 0xb8, 0x0b, 0x00, /* PID0..PID3 */
+ 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
+};
+
+/* Also used by the SSE300 */
+static const int sse200_sysctl_id[] = {
+ 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
+ 0x54, 0xb8, 0x1b, 0x00, /* PID0..PID3 */
+ 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
+};
+
+/*
+ * Set the initial secure vector table offset address for the core.
+ * This will take effect when the CPU next resets.
+ */
+static void set_init_vtor(uint64_t cpuid, uint32_t vtor)
+{
+ Object *cpuobj = OBJECT(arm_get_cpu_by_id(cpuid));
+
+ if (cpuobj) {
+ if (object_property_find(cpuobj, "init-svtor")) {
+ object_property_set_uint(cpuobj, "init-svtor", vtor, &error_abort);
+ }
+ }
+}
+
+static uint64_t iotkit_sysctl_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ IoTKitSysCtl *s = IOTKIT_SYSCTL(opaque);
+ uint64_t r;
+
+ switch (offset) {
+ case A_SECDBGSTAT:
+ r = s->secure_debug;
+ break;
+ case A_SCSECCTRL:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ case ARMSSE_SSE300:
+ r = s->scsecctrl;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_FCLK_DIV:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ case ARMSSE_SSE300:
+ r = s->fclk_div;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_SYSCLK_DIV:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ case ARMSSE_SSE300:
+ r = s->sysclk_div;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_CLOCK_FORCE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ case ARMSSE_SSE300:
+ r = s->clock_force;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_RESET_SYNDROME:
+ r = s->reset_syndrome;
+ break;
+ case A_RESET_MASK:
+ r = s->reset_mask;
+ break;
+ case A_GRETREG:
+ r = s->gretreg;
+ break;
+ case A_INITSVTOR0:
+ r = s->initsvtor0;
+ break;
+ case A_INITSVTOR1:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ r = s->initsvtor1;
+ break;
+ case ARMSSE_SSE300:
+ goto bad_offset;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_CPUWAIT:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ case ARMSSE_SSE200:
+ r = s->cpuwait;
+ break;
+ case ARMSSE_SSE300:
+ /* In SSE300 this is reserved (for INITSVTOR2) */
+ goto bad_offset;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_NMI_ENABLE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ /* In IoTKit this is named BUSWAIT but marked reserved, R/O, zero */
+ r = 0;
+ break;
+ case ARMSSE_SSE200:
+ r = s->nmi_enable;
+ break;
+ case ARMSSE_SSE300:
+ /* In SSE300 this is reserved (for INITSVTOR3) */
+ goto bad_offset;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_WICCTRL:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ case ARMSSE_SSE200:
+ r = s->wicctrl;
+ break;
+ case ARMSSE_SSE300:
+ /* In SSE300 this offset is CPUWAIT */
+ r = s->cpuwait;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_EWCTRL:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ r = s->ewctrl;
+ break;
+ case ARMSSE_SSE300:
+ /* In SSE300 this offset is is NMI_ENABLE */
+ r = s->nmi_enable;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PWRCTRL:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ case ARMSSE_SSE200:
+ goto bad_offset;
+ case ARMSSE_SSE300:
+ r = s->pwrctrl;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_SYS_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ case ARMSSE_SSE300:
+ r = s->pdcm_pd_sys_sense;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_CPU0_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ case ARMSSE_SSE200:
+ goto bad_offset;
+ case ARMSSE_SSE300:
+ r = s->pdcm_pd_cpu0_sense;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_SRAM0_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ r = s->pdcm_pd_sram0_sense;
+ break;
+ case ARMSSE_SSE300:
+ goto bad_offset;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_SRAM1_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ r = s->pdcm_pd_sram1_sense;
+ break;
+ case ARMSSE_SSE300:
+ goto bad_offset;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_SRAM2_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ r = s->pdcm_pd_sram2_sense;
+ break;
+ case ARMSSE_SSE300:
+ r = s->pdcm_pd_vmr0_sense;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_SRAM3_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ r = s->pdcm_pd_sram3_sense;
+ break;
+ case ARMSSE_SSE300:
+ r = s->pdcm_pd_vmr1_sense;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PID4 ... A_CID3:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ r = iotkit_sysctl_id[(offset - A_PID4) / 4];
+ break;
+ case ARMSSE_SSE200:
+ case ARMSSE_SSE300:
+ r = sse200_sysctl_id[(offset - A_PID4) / 4];
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_SECDBGSET:
+ case A_SECDBGCLR:
+ case A_SWRESET:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IoTKit SysCtl read: read of WO offset %x\n",
+ (int)offset);
+ r = 0;
+ break;
+ default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IoTKit SysCtl read: bad offset %x\n", (int)offset);
+ r = 0;
+ break;
+ }
+ trace_iotkit_sysctl_read(offset, r, size);
+ return r;
+}
+
+static void cpuwait_write(IoTKitSysCtl *s, uint32_t value)
+{
+ int num_cpus = (s->sse_version == ARMSSE_SSE300) ? 1 : 2;
+ int i;
+
+ for (i = 0; i < num_cpus; i++) {
+ uint32_t mask = 1 << i;
+ if ((s->cpuwait & mask) && !(value & mask)) {
+ /* Powering up CPU 0 */
+ arm_set_cpu_on_and_reset(i);
+ }
+ }
+ s->cpuwait = value;
+}
+
+static void iotkit_sysctl_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ IoTKitSysCtl *s = IOTKIT_SYSCTL(opaque);
+
+ trace_iotkit_sysctl_write(offset, value, size);
+
+ /*
+ * Most of the state here has to do with control of reset and
+ * similar kinds of power up -- for instance the guest can ask
+ * what the reason for the last reset was, or forbid reset for
+ * some causes (like the non-secure watchdog). Most of this is
+ * not relevant to QEMU, which doesn't really model anything other
+ * than a full power-on reset.
+ * We just model the registers as reads-as-written.
+ */
+
+ switch (offset) {
+ case A_RESET_SYNDROME:
+ qemu_log_mask(LOG_UNIMP,
+ "IoTKit SysCtl RESET_SYNDROME unimplemented\n");
+ s->reset_syndrome = value;
+ break;
+ case A_RESET_MASK:
+ qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl RESET_MASK unimplemented\n");
+ s->reset_mask = value;
+ break;
+ case A_GRETREG:
+ /*
+ * General retention register, which is only reset by a power-on
+ * reset. Technically this implementation is complete, since
+ * QEMU only supports power-on resets...
+ */
+ s->gretreg = value;
+ break;
+ case A_INITSVTOR0:
+ switch (s->sse_version) {
+ case ARMSSE_SSE300:
+ /* SSE300 has a LOCK bit which prevents further writes when set */
+ if (s->initsvtor0 & R_INITSVTOR0_LOCK_MASK) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IoTKit INITSVTOR0 write when register locked\n");
+ break;
+ }
+ s->initsvtor0 = value;
+ set_init_vtor(0, s->initsvtor0 & R_INITSVTOR0_VTOR_MASK);
+ break;
+ case ARMSSE_IOTKIT:
+ case ARMSSE_SSE200:
+ s->initsvtor0 = value;
+ set_init_vtor(0, s->initsvtor0);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_CPUWAIT:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ case ARMSSE_SSE200:
+ cpuwait_write(s, value);
+ break;
+ case ARMSSE_SSE300:
+ /* In SSE300 this is reserved (for INITSVTOR2) */
+ goto bad_offset;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_WICCTRL:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ case ARMSSE_SSE200:
+ qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl WICCTRL unimplemented\n");
+ s->wicctrl = value;
+ break;
+ case ARMSSE_SSE300:
+ /* In SSE300 this offset is CPUWAIT */
+ cpuwait_write(s, value);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_SECDBGSET:
+ /* write-1-to-set */
+ qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl SECDBGSET unimplemented\n");
+ s->secure_debug |= value;
+ break;
+ case A_SECDBGCLR:
+ /* write-1-to-clear */
+ s->secure_debug &= ~value;
+ break;
+ case A_SWRESET:
+ /* One w/o bit to request a reset; all other bits reserved */
+ if (value & R_SWRESET_SWRESETREQ_MASK) {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ }
+ break;
+ case A_SCSECCTRL:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ case ARMSSE_SSE300:
+ qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl SCSECCTRL unimplemented\n");
+ s->scsecctrl = value;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_FCLK_DIV:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ case ARMSSE_SSE300:
+ qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl FCLK_DIV unimplemented\n");
+ s->fclk_div = value;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_SYSCLK_DIV:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ case ARMSSE_SSE300:
+ qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl SYSCLK_DIV unimplemented\n");
+ s->sysclk_div = value;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_CLOCK_FORCE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ case ARMSSE_SSE300:
+ qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl CLOCK_FORCE unimplemented\n");
+ s->clock_force = value;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_INITSVTOR1:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ s->initsvtor1 = value;
+ set_init_vtor(1, s->initsvtor1);
+ break;
+ case ARMSSE_SSE300:
+ goto bad_offset;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_EWCTRL:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl EWCTRL unimplemented\n");
+ s->ewctrl = value;
+ break;
+ case ARMSSE_SSE300:
+ /* In SSE300 this offset is is NMI_ENABLE */
+ qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl NMI_ENABLE unimplemented\n");
+ s->nmi_enable = value;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PWRCTRL:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ case ARMSSE_SSE200:
+ goto bad_offset;
+ case ARMSSE_SSE300:
+ if (!(s->pwrctrl & R_PWRCTRL_PPU_ACCESS_UNLOCK_MASK)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IoTKit PWRCTRL write when register locked\n");
+ break;
+ }
+ s->pwrctrl = value;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_SYS_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ case ARMSSE_SSE300:
+ qemu_log_mask(LOG_UNIMP,
+ "IoTKit SysCtl PDCM_PD_SYS_SENSE unimplemented\n");
+ s->pdcm_pd_sys_sense = value;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_CPU0_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ case ARMSSE_SSE200:
+ goto bad_offset;
+ case ARMSSE_SSE300:
+ qemu_log_mask(LOG_UNIMP,
+ "IoTKit SysCtl PDCM_PD_CPU0_SENSE unimplemented\n");
+ s->pdcm_pd_cpu0_sense = value;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_SRAM0_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ qemu_log_mask(LOG_UNIMP,
+ "IoTKit SysCtl PDCM_PD_SRAM0_SENSE unimplemented\n");
+ s->pdcm_pd_sram0_sense = value;
+ break;
+ case ARMSSE_SSE300:
+ goto bad_offset;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_SRAM1_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ qemu_log_mask(LOG_UNIMP,
+ "IoTKit SysCtl PDCM_PD_SRAM1_SENSE unimplemented\n");
+ s->pdcm_pd_sram1_sense = value;
+ break;
+ case ARMSSE_SSE300:
+ goto bad_offset;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_SRAM2_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ qemu_log_mask(LOG_UNIMP,
+ "IoTKit SysCtl PDCM_PD_SRAM2_SENSE unimplemented\n");
+ s->pdcm_pd_sram2_sense = value;
+ break;
+ case ARMSSE_SSE300:
+ qemu_log_mask(LOG_UNIMP,
+ "IoTKit SysCtl PDCM_PD_VMR0_SENSE unimplemented\n");
+ s->pdcm_pd_vmr0_sense = value;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_PDCM_PD_SRAM3_SENSE:
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto bad_offset;
+ case ARMSSE_SSE200:
+ qemu_log_mask(LOG_UNIMP,
+ "IoTKit SysCtl PDCM_PD_SRAM3_SENSE unimplemented\n");
+ s->pdcm_pd_sram3_sense = value;
+ break;
+ case ARMSSE_SSE300:
+ qemu_log_mask(LOG_UNIMP,
+ "IoTKit SysCtl PDCM_PD_VMR1_SENSE unimplemented\n");
+ s->pdcm_pd_vmr1_sense = value;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_NMI_ENABLE:
+ /* In IoTKit this is BUSWAIT: reserved, R/O, zero */
+ switch (s->sse_version) {
+ case ARMSSE_IOTKIT:
+ goto ro_offset;
+ case ARMSSE_SSE200:
+ qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl NMI_ENABLE unimplemented\n");
+ s->nmi_enable = value;
+ break;
+ case ARMSSE_SSE300:
+ /* In SSE300 this is reserved (for INITSVTOR3) */
+ goto bad_offset;
+ default:
+ g_assert_not_reached();
+ }
+ break;
+ case A_SECDBGSTAT:
+ case A_PID4 ... A_CID3:
+ ro_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IoTKit SysCtl write: write of RO offset %x\n",
+ (int)offset);
+ break;
+ default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IoTKit SysCtl write: bad offset %x\n", (int)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps iotkit_sysctl_ops = {
+ .read = iotkit_sysctl_read,
+ .write = iotkit_sysctl_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ /* byte/halfword accesses are just zero-padded on reads and writes */
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+};
+
+static void iotkit_sysctl_reset(DeviceState *dev)
+{
+ IoTKitSysCtl *s = IOTKIT_SYSCTL(dev);
+
+ trace_iotkit_sysctl_reset();
+ s->secure_debug = 0;
+ s->reset_syndrome = 1;
+ s->reset_mask = 0;
+ s->gretreg = 0;
+ s->initsvtor0 = s->initsvtor0_rst;
+ s->initsvtor1 = s->initsvtor1_rst;
+ s->cpuwait = s->cpuwait_rst;
+ s->wicctrl = 0;
+ s->scsecctrl = 0;
+ s->fclk_div = 0;
+ s->sysclk_div = 0;
+ s->clock_force = 0;
+ s->nmi_enable = 0;
+ s->ewctrl = 0;
+ s->pwrctrl = 0x3;
+ s->pdcm_pd_sys_sense = 0x7f;
+ s->pdcm_pd_sram0_sense = 0;
+ s->pdcm_pd_sram1_sense = 0;
+ s->pdcm_pd_sram2_sense = 0;
+ s->pdcm_pd_sram3_sense = 0;
+ s->pdcm_pd_cpu0_sense = 0;
+ s->pdcm_pd_vmr0_sense = 0;
+ s->pdcm_pd_vmr1_sense = 0;
+}
+
+static void iotkit_sysctl_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ IoTKitSysCtl *s = IOTKIT_SYSCTL(obj);
+
+ memory_region_init_io(&s->iomem, obj, &iotkit_sysctl_ops,
+ s, "iotkit-sysctl", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void iotkit_sysctl_realize(DeviceState *dev, Error **errp)
+{
+ IoTKitSysCtl *s = IOTKIT_SYSCTL(dev);
+
+ if (!armsse_version_valid(s->sse_version)) {
+ error_setg(errp, "invalid sse-version value %d", s->sse_version);
+ return;
+ }
+}
+
+static bool sse300_needed(void *opaque)
+{
+ IoTKitSysCtl *s = IOTKIT_SYSCTL(opaque);
+
+ return s->sse_version == ARMSSE_SSE300;
+}
+
+static const VMStateDescription iotkit_sysctl_sse300_vmstate = {
+ .name = "iotkit-sysctl/sse-300",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = sse300_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(pwrctrl, IoTKitSysCtl),
+ VMSTATE_UINT32(pdcm_pd_cpu0_sense, IoTKitSysCtl),
+ VMSTATE_UINT32(pdcm_pd_vmr0_sense, IoTKitSysCtl),
+ VMSTATE_UINT32(pdcm_pd_vmr1_sense, IoTKitSysCtl),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool sse200_needed(void *opaque)
+{
+ IoTKitSysCtl *s = IOTKIT_SYSCTL(opaque);
+
+ return s->sse_version != ARMSSE_IOTKIT;
+}
+
+static const VMStateDescription iotkit_sysctl_sse200_vmstate = {
+ .name = "iotkit-sysctl/sse-200",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = sse200_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(scsecctrl, IoTKitSysCtl),
+ VMSTATE_UINT32(fclk_div, IoTKitSysCtl),
+ VMSTATE_UINT32(sysclk_div, IoTKitSysCtl),
+ VMSTATE_UINT32(clock_force, IoTKitSysCtl),
+ VMSTATE_UINT32(initsvtor1, IoTKitSysCtl),
+ VMSTATE_UINT32(nmi_enable, IoTKitSysCtl),
+ VMSTATE_UINT32(pdcm_pd_sys_sense, IoTKitSysCtl),
+ VMSTATE_UINT32(pdcm_pd_sram0_sense, IoTKitSysCtl),
+ VMSTATE_UINT32(pdcm_pd_sram1_sense, IoTKitSysCtl),
+ VMSTATE_UINT32(pdcm_pd_sram2_sense, IoTKitSysCtl),
+ VMSTATE_UINT32(pdcm_pd_sram3_sense, IoTKitSysCtl),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription iotkit_sysctl_vmstate = {
+ .name = "iotkit-sysctl",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(secure_debug, IoTKitSysCtl),
+ VMSTATE_UINT32(reset_syndrome, IoTKitSysCtl),
+ VMSTATE_UINT32(reset_mask, IoTKitSysCtl),
+ VMSTATE_UINT32(gretreg, IoTKitSysCtl),
+ VMSTATE_UINT32(initsvtor0, IoTKitSysCtl),
+ VMSTATE_UINT32(cpuwait, IoTKitSysCtl),
+ VMSTATE_UINT32(wicctrl, IoTKitSysCtl),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &iotkit_sysctl_sse200_vmstate,
+ &iotkit_sysctl_sse300_vmstate,
+ NULL
+ }
+};
+
+static Property iotkit_sysctl_props[] = {
+ DEFINE_PROP_UINT32("sse-version", IoTKitSysCtl, sse_version, 0),
+ DEFINE_PROP_UINT32("CPUWAIT_RST", IoTKitSysCtl, cpuwait_rst, 0),
+ DEFINE_PROP_UINT32("INITSVTOR0_RST", IoTKitSysCtl, initsvtor0_rst,
+ 0x10000000),
+ DEFINE_PROP_UINT32("INITSVTOR1_RST", IoTKitSysCtl, initsvtor1_rst,
+ 0x10000000),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void iotkit_sysctl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &iotkit_sysctl_vmstate;
+ dc->reset = iotkit_sysctl_reset;
+ device_class_set_props(dc, iotkit_sysctl_props);
+ dc->realize = iotkit_sysctl_realize;
+}
+
+static const TypeInfo iotkit_sysctl_info = {
+ .name = TYPE_IOTKIT_SYSCTL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IoTKitSysCtl),
+ .instance_init = iotkit_sysctl_init,
+ .class_init = iotkit_sysctl_class_init,
+};
+
+static void iotkit_sysctl_register_types(void)
+{
+ type_register_static(&iotkit_sysctl_info);
+}
+
+type_init(iotkit_sysctl_register_types);
diff --git a/hw/misc/iotkit-sysinfo.c b/hw/misc/iotkit-sysinfo.c
new file mode 100644
index 000000000..aaa9305b2
--- /dev/null
+++ b/hw/misc/iotkit-sysinfo.c
@@ -0,0 +1,187 @@
+/*
+ * ARM IoTKit system information block
+ *
+ * Copyright (c) 2018 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+/*
+ * This is a model of the "system information block" which is part of the
+ * Arm IoTKit and documented in
+ * https://developer.arm.com/documentation/ecm0601256/latest
+ * It consists of 2 read-only version/config registers, plus the
+ * usual ID registers.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/registerfields.h"
+#include "hw/misc/iotkit-sysinfo.h"
+#include "hw/qdev-properties.h"
+#include "hw/arm/armsse-version.h"
+
+REG32(SYS_VERSION, 0x0)
+REG32(SYS_CONFIG, 0x4)
+REG32(SYS_CONFIG1, 0x8)
+REG32(IIDR, 0xfc8)
+REG32(PID4, 0xfd0)
+REG32(PID5, 0xfd4)
+REG32(PID6, 0xfd8)
+REG32(PID7, 0xfdc)
+REG32(PID0, 0xfe0)
+REG32(PID1, 0xfe4)
+REG32(PID2, 0xfe8)
+REG32(PID3, 0xfec)
+REG32(CID0, 0xff0)
+REG32(CID1, 0xff4)
+REG32(CID2, 0xff8)
+REG32(CID3, 0xffc)
+
+/* PID/CID values */
+static const int sysinfo_id[] = {
+ 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
+ 0x58, 0xb8, 0x0b, 0x00, /* PID0..PID3 */
+ 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
+};
+
+static const int sysinfo_sse300_id[] = {
+ 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
+ 0x58, 0xb8, 0x1b, 0x00, /* PID0..PID3 */
+ 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
+};
+
+static uint64_t iotkit_sysinfo_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ IoTKitSysInfo *s = IOTKIT_SYSINFO(opaque);
+ uint64_t r;
+
+ switch (offset) {
+ case A_SYS_VERSION:
+ r = s->sys_version;
+ break;
+
+ case A_SYS_CONFIG:
+ r = s->sys_config;
+ break;
+ case A_SYS_CONFIG1:
+ switch (s->sse_version) {
+ case ARMSSE_SSE300:
+ return 0;
+ break;
+ default:
+ goto bad_read;
+ }
+ break;
+ case A_IIDR:
+ switch (s->sse_version) {
+ case ARMSSE_SSE300:
+ return s->iidr;
+ break;
+ default:
+ goto bad_read;
+ }
+ break;
+ case A_PID4 ... A_CID3:
+ switch (s->sse_version) {
+ case ARMSSE_SSE300:
+ r = sysinfo_sse300_id[(offset - A_PID4) / 4];
+ break;
+ default:
+ r = sysinfo_id[(offset - A_PID4) / 4];
+ break;
+ }
+ break;
+ default:
+ bad_read:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IoTKit SysInfo read: bad offset %x\n", (int)offset);
+ r = 0;
+ break;
+ }
+ trace_iotkit_sysinfo_read(offset, r, size);
+ return r;
+}
+
+static void iotkit_sysinfo_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ trace_iotkit_sysinfo_write(offset, value, size);
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "IoTKit SysInfo: write to RO offset 0x%x\n", (int)offset);
+}
+
+static const MemoryRegionOps iotkit_sysinfo_ops = {
+ .read = iotkit_sysinfo_read,
+ .write = iotkit_sysinfo_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ /* byte/halfword accesses are just zero-padded on reads and writes */
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+};
+
+static Property iotkit_sysinfo_props[] = {
+ DEFINE_PROP_UINT32("SYS_VERSION", IoTKitSysInfo, sys_version, 0),
+ DEFINE_PROP_UINT32("SYS_CONFIG", IoTKitSysInfo, sys_config, 0),
+ DEFINE_PROP_UINT32("sse-version", IoTKitSysInfo, sse_version, 0),
+ DEFINE_PROP_UINT32("IIDR", IoTKitSysInfo, iidr, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void iotkit_sysinfo_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ IoTKitSysInfo *s = IOTKIT_SYSINFO(obj);
+
+ memory_region_init_io(&s->iomem, obj, &iotkit_sysinfo_ops,
+ s, "iotkit-sysinfo", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void iotkit_sysinfo_realize(DeviceState *dev, Error **errp)
+{
+ IoTKitSysInfo *s = IOTKIT_SYSINFO(dev);
+
+ if (!armsse_version_valid(s->sse_version)) {
+ error_setg(errp, "invalid sse-version value %d", s->sse_version);
+ return;
+ }
+}
+
+static void iotkit_sysinfo_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ /*
+ * This device has no guest-modifiable state and so it
+ * does not need a reset function or VMState.
+ */
+ dc->realize = iotkit_sysinfo_realize;
+ device_class_set_props(dc, iotkit_sysinfo_props);
+}
+
+static const TypeInfo iotkit_sysinfo_info = {
+ .name = TYPE_IOTKIT_SYSINFO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IoTKitSysInfo),
+ .instance_init = iotkit_sysinfo_init,
+ .class_init = iotkit_sysinfo_class_init,
+};
+
+static void iotkit_sysinfo_register_types(void)
+{
+ type_register_static(&iotkit_sysinfo_info);
+}
+
+type_init(iotkit_sysinfo_register_types);
diff --git a/hw/misc/ivshmem.c b/hw/misc/ivshmem.c
new file mode 100644
index 000000000..1ba4a9837
--- /dev/null
+++ b/hw/misc/ivshmem.c
@@ -0,0 +1,1135 @@
+/*
+ * Inter-VM Shared Memory PCI device.
+ *
+ * Author:
+ * Cam Macdonell <cam@cs.ualberta.ca>
+ *
+ * Based On: cirrus_vga.c
+ * Copyright (c) 2004 Fabrice Bellard
+ * Copyright (c) 2004 Makoto Suzuki (suzu)
+ *
+ * and rtl8139.c
+ * Copyright (c) 2006 Igor Kovalenko
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "qemu/cutils.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
+#include "sysemu/kvm.h"
+#include "migration/blocker.h"
+#include "migration/vmstate.h"
+#include "qemu/error-report.h"
+#include "qemu/event_notifier.h"
+#include "qemu/module.h"
+#include "qom/object_interfaces.h"
+#include "chardev/char-fe.h"
+#include "sysemu/hostmem.h"
+#include "qapi/visitor.h"
+
+#include "hw/misc/ivshmem.h"
+#include "qom/object.h"
+
+#define PCI_VENDOR_ID_IVSHMEM PCI_VENDOR_ID_REDHAT_QUMRANET
+#define PCI_DEVICE_ID_IVSHMEM 0x1110
+
+#define IVSHMEM_MAX_PEERS UINT16_MAX
+#define IVSHMEM_IOEVENTFD 0
+#define IVSHMEM_MSI 1
+
+#define IVSHMEM_REG_BAR_SIZE 0x100
+
+#define IVSHMEM_DEBUG 0
+#define IVSHMEM_DPRINTF(fmt, ...) \
+ do { \
+ if (IVSHMEM_DEBUG) { \
+ printf("IVSHMEM: " fmt, ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define TYPE_IVSHMEM_COMMON "ivshmem-common"
+typedef struct IVShmemState IVShmemState;
+DECLARE_INSTANCE_CHECKER(IVShmemState, IVSHMEM_COMMON,
+ TYPE_IVSHMEM_COMMON)
+
+#define TYPE_IVSHMEM_PLAIN "ivshmem-plain"
+DECLARE_INSTANCE_CHECKER(IVShmemState, IVSHMEM_PLAIN,
+ TYPE_IVSHMEM_PLAIN)
+
+#define TYPE_IVSHMEM_DOORBELL "ivshmem-doorbell"
+DECLARE_INSTANCE_CHECKER(IVShmemState, IVSHMEM_DOORBELL,
+ TYPE_IVSHMEM_DOORBELL)
+
+#define TYPE_IVSHMEM "ivshmem"
+DECLARE_INSTANCE_CHECKER(IVShmemState, IVSHMEM,
+ TYPE_IVSHMEM)
+
+typedef struct Peer {
+ int nb_eventfds;
+ EventNotifier *eventfds;
+} Peer;
+
+typedef struct MSIVector {
+ PCIDevice *pdev;
+ int virq;
+ bool unmasked;
+} MSIVector;
+
+struct IVShmemState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ uint32_t features;
+
+ /* exactly one of these two may be set */
+ HostMemoryBackend *hostmem; /* with interrupts */
+ CharBackend server_chr; /* without interrupts */
+
+ /* registers */
+ uint32_t intrmask;
+ uint32_t intrstatus;
+ int vm_id;
+
+ /* BARs */
+ MemoryRegion ivshmem_mmio; /* BAR 0 (registers) */
+ MemoryRegion *ivshmem_bar2; /* BAR 2 (shared memory) */
+ MemoryRegion server_bar2; /* used with server_chr */
+
+ /* interrupt support */
+ Peer *peers;
+ int nb_peers; /* space in @peers[] */
+ uint32_t vectors;
+ MSIVector *msi_vectors;
+ uint64_t msg_buf; /* buffer for receiving server messages */
+ int msg_buffered_bytes; /* #bytes in @msg_buf */
+
+ /* migration stuff */
+ OnOffAuto master;
+ Error *migration_blocker;
+};
+
+/* registers for the Inter-VM shared memory device */
+enum ivshmem_registers {
+ INTRMASK = 0,
+ INTRSTATUS = 4,
+ IVPOSITION = 8,
+ DOORBELL = 12,
+};
+
+static inline uint32_t ivshmem_has_feature(IVShmemState *ivs,
+ unsigned int feature) {
+ return (ivs->features & (1 << feature));
+}
+
+static inline bool ivshmem_is_master(IVShmemState *s)
+{
+ assert(s->master != ON_OFF_AUTO_AUTO);
+ return s->master == ON_OFF_AUTO_ON;
+}
+
+static void ivshmem_IntrMask_write(IVShmemState *s, uint32_t val)
+{
+ IVSHMEM_DPRINTF("IntrMask write(w) val = 0x%04x\n", val);
+
+ s->intrmask = val;
+}
+
+static uint32_t ivshmem_IntrMask_read(IVShmemState *s)
+{
+ uint32_t ret = s->intrmask;
+
+ IVSHMEM_DPRINTF("intrmask read(w) val = 0x%04x\n", ret);
+ return ret;
+}
+
+static void ivshmem_IntrStatus_write(IVShmemState *s, uint32_t val)
+{
+ IVSHMEM_DPRINTF("IntrStatus write(w) val = 0x%04x\n", val);
+
+ s->intrstatus = val;
+}
+
+static uint32_t ivshmem_IntrStatus_read(IVShmemState *s)
+{
+ uint32_t ret = s->intrstatus;
+
+ /* reading ISR clears all interrupts */
+ s->intrstatus = 0;
+ return ret;
+}
+
+static void ivshmem_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ IVShmemState *s = opaque;
+
+ uint16_t dest = val >> 16;
+ uint16_t vector = val & 0xff;
+
+ addr &= 0xfc;
+
+ IVSHMEM_DPRINTF("writing to addr " TARGET_FMT_plx "\n", addr);
+ switch (addr)
+ {
+ case INTRMASK:
+ ivshmem_IntrMask_write(s, val);
+ break;
+
+ case INTRSTATUS:
+ ivshmem_IntrStatus_write(s, val);
+ break;
+
+ case DOORBELL:
+ /* check that dest VM ID is reasonable */
+ if (dest >= s->nb_peers) {
+ IVSHMEM_DPRINTF("Invalid destination VM ID (%d)\n", dest);
+ break;
+ }
+
+ /* check doorbell range */
+ if (vector < s->peers[dest].nb_eventfds) {
+ IVSHMEM_DPRINTF("Notifying VM %d on vector %d\n", dest, vector);
+ event_notifier_set(&s->peers[dest].eventfds[vector]);
+ } else {
+ IVSHMEM_DPRINTF("Invalid destination vector %d on VM %d\n",
+ vector, dest);
+ }
+ break;
+ default:
+ IVSHMEM_DPRINTF("Unhandled write " TARGET_FMT_plx "\n", addr);
+ }
+}
+
+static uint64_t ivshmem_io_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+
+ IVShmemState *s = opaque;
+ uint32_t ret;
+
+ switch (addr)
+ {
+ case INTRMASK:
+ ret = ivshmem_IntrMask_read(s);
+ break;
+
+ case INTRSTATUS:
+ ret = ivshmem_IntrStatus_read(s);
+ break;
+
+ case IVPOSITION:
+ ret = s->vm_id;
+ break;
+
+ default:
+ IVSHMEM_DPRINTF("why are we reading " TARGET_FMT_plx "\n", addr);
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static const MemoryRegionOps ivshmem_mmio_ops = {
+ .read = ivshmem_io_read,
+ .write = ivshmem_io_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void ivshmem_vector_notify(void *opaque)
+{
+ MSIVector *entry = opaque;
+ PCIDevice *pdev = entry->pdev;
+ IVShmemState *s = IVSHMEM_COMMON(pdev);
+ int vector = entry - s->msi_vectors;
+ EventNotifier *n = &s->peers[s->vm_id].eventfds[vector];
+
+ if (!event_notifier_test_and_clear(n)) {
+ return;
+ }
+
+ IVSHMEM_DPRINTF("interrupt on vector %p %d\n", pdev, vector);
+ if (ivshmem_has_feature(s, IVSHMEM_MSI)) {
+ if (msix_enabled(pdev)) {
+ msix_notify(pdev, vector);
+ }
+ } else {
+ ivshmem_IntrStatus_write(s, 1);
+ }
+}
+
+static int ivshmem_vector_unmask(PCIDevice *dev, unsigned vector,
+ MSIMessage msg)
+{
+ IVShmemState *s = IVSHMEM_COMMON(dev);
+ EventNotifier *n = &s->peers[s->vm_id].eventfds[vector];
+ MSIVector *v = &s->msi_vectors[vector];
+ int ret;
+
+ IVSHMEM_DPRINTF("vector unmask %p %d\n", dev, vector);
+ if (!v->pdev) {
+ error_report("ivshmem: vector %d route does not exist", vector);
+ return -EINVAL;
+ }
+ assert(!v->unmasked);
+
+ ret = kvm_irqchip_update_msi_route(kvm_state, v->virq, msg, dev);
+ if (ret < 0) {
+ return ret;
+ }
+ kvm_irqchip_commit_routes(kvm_state);
+
+ ret = kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, n, NULL, v->virq);
+ if (ret < 0) {
+ return ret;
+ }
+ v->unmasked = true;
+
+ return 0;
+}
+
+static void ivshmem_vector_mask(PCIDevice *dev, unsigned vector)
+{
+ IVShmemState *s = IVSHMEM_COMMON(dev);
+ EventNotifier *n = &s->peers[s->vm_id].eventfds[vector];
+ MSIVector *v = &s->msi_vectors[vector];
+ int ret;
+
+ IVSHMEM_DPRINTF("vector mask %p %d\n", dev, vector);
+ if (!v->pdev) {
+ error_report("ivshmem: vector %d route does not exist", vector);
+ return;
+ }
+ assert(v->unmasked);
+
+ ret = kvm_irqchip_remove_irqfd_notifier_gsi(kvm_state, n, v->virq);
+ if (ret < 0) {
+ error_report("remove_irqfd_notifier_gsi failed");
+ return;
+ }
+ v->unmasked = false;
+}
+
+static void ivshmem_vector_poll(PCIDevice *dev,
+ unsigned int vector_start,
+ unsigned int vector_end)
+{
+ IVShmemState *s = IVSHMEM_COMMON(dev);
+ unsigned int vector;
+
+ IVSHMEM_DPRINTF("vector poll %p %d-%d\n", dev, vector_start, vector_end);
+
+ vector_end = MIN(vector_end, s->vectors);
+
+ for (vector = vector_start; vector < vector_end; vector++) {
+ EventNotifier *notifier = &s->peers[s->vm_id].eventfds[vector];
+
+ if (!msix_is_masked(dev, vector)) {
+ continue;
+ }
+
+ if (event_notifier_test_and_clear(notifier)) {
+ msix_set_pending(dev, vector);
+ }
+ }
+}
+
+static void watch_vector_notifier(IVShmemState *s, EventNotifier *n,
+ int vector)
+{
+ int eventfd = event_notifier_get_fd(n);
+
+ assert(!s->msi_vectors[vector].pdev);
+ s->msi_vectors[vector].pdev = PCI_DEVICE(s);
+
+ qemu_set_fd_handler(eventfd, ivshmem_vector_notify,
+ NULL, &s->msi_vectors[vector]);
+}
+
+static void ivshmem_add_eventfd(IVShmemState *s, int posn, int i)
+{
+ memory_region_add_eventfd(&s->ivshmem_mmio,
+ DOORBELL,
+ 4,
+ true,
+ (posn << 16) | i,
+ &s->peers[posn].eventfds[i]);
+}
+
+static void ivshmem_del_eventfd(IVShmemState *s, int posn, int i)
+{
+ memory_region_del_eventfd(&s->ivshmem_mmio,
+ DOORBELL,
+ 4,
+ true,
+ (posn << 16) | i,
+ &s->peers[posn].eventfds[i]);
+}
+
+static void close_peer_eventfds(IVShmemState *s, int posn)
+{
+ int i, n;
+
+ assert(posn >= 0 && posn < s->nb_peers);
+ n = s->peers[posn].nb_eventfds;
+
+ if (ivshmem_has_feature(s, IVSHMEM_IOEVENTFD)) {
+ memory_region_transaction_begin();
+ for (i = 0; i < n; i++) {
+ ivshmem_del_eventfd(s, posn, i);
+ }
+ memory_region_transaction_commit();
+ }
+
+ for (i = 0; i < n; i++) {
+ event_notifier_cleanup(&s->peers[posn].eventfds[i]);
+ }
+
+ g_free(s->peers[posn].eventfds);
+ s->peers[posn].nb_eventfds = 0;
+}
+
+static void resize_peers(IVShmemState *s, int nb_peers)
+{
+ int old_nb_peers = s->nb_peers;
+ int i;
+
+ assert(nb_peers > old_nb_peers);
+ IVSHMEM_DPRINTF("bumping storage to %d peers\n", nb_peers);
+
+ s->peers = g_realloc(s->peers, nb_peers * sizeof(Peer));
+ s->nb_peers = nb_peers;
+
+ for (i = old_nb_peers; i < nb_peers; i++) {
+ s->peers[i].eventfds = g_new0(EventNotifier, s->vectors);
+ s->peers[i].nb_eventfds = 0;
+ }
+}
+
+static void ivshmem_add_kvm_msi_virq(IVShmemState *s, int vector,
+ Error **errp)
+{
+ PCIDevice *pdev = PCI_DEVICE(s);
+ int ret;
+
+ IVSHMEM_DPRINTF("ivshmem_add_kvm_msi_virq vector:%d\n", vector);
+ assert(!s->msi_vectors[vector].pdev);
+
+ ret = kvm_irqchip_add_msi_route(kvm_state, vector, pdev);
+ if (ret < 0) {
+ error_setg(errp, "kvm_irqchip_add_msi_route failed");
+ return;
+ }
+
+ s->msi_vectors[vector].virq = ret;
+ s->msi_vectors[vector].pdev = pdev;
+}
+
+static void setup_interrupt(IVShmemState *s, int vector, Error **errp)
+{
+ EventNotifier *n = &s->peers[s->vm_id].eventfds[vector];
+ bool with_irqfd = kvm_msi_via_irqfd_enabled() &&
+ ivshmem_has_feature(s, IVSHMEM_MSI);
+ PCIDevice *pdev = PCI_DEVICE(s);
+ Error *err = NULL;
+
+ IVSHMEM_DPRINTF("setting up interrupt for vector: %d\n", vector);
+
+ if (!with_irqfd) {
+ IVSHMEM_DPRINTF("with eventfd\n");
+ watch_vector_notifier(s, n, vector);
+ } else if (msix_enabled(pdev)) {
+ IVSHMEM_DPRINTF("with irqfd\n");
+ ivshmem_add_kvm_msi_virq(s, vector, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ if (!msix_is_masked(pdev, vector)) {
+ kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, n, NULL,
+ s->msi_vectors[vector].virq);
+ /* TODO handle error */
+ }
+ } else {
+ /* it will be delayed until msix is enabled, in write_config */
+ IVSHMEM_DPRINTF("with irqfd, delayed until msix enabled\n");
+ }
+}
+
+static void process_msg_shmem(IVShmemState *s, int fd, Error **errp)
+{
+ Error *local_err = NULL;
+ struct stat buf;
+ size_t size;
+
+ if (s->ivshmem_bar2) {
+ error_setg(errp, "server sent unexpected shared memory message");
+ close(fd);
+ return;
+ }
+
+ if (fstat(fd, &buf) < 0) {
+ error_setg_errno(errp, errno,
+ "can't determine size of shared memory sent by server");
+ close(fd);
+ return;
+ }
+
+ size = buf.st_size;
+
+ /* mmap the region and map into the BAR2 */
+ memory_region_init_ram_from_fd(&s->server_bar2, OBJECT(s), "ivshmem.bar2",
+ size, RAM_SHARED, fd, 0, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ s->ivshmem_bar2 = &s->server_bar2;
+}
+
+static void process_msg_disconnect(IVShmemState *s, uint16_t posn,
+ Error **errp)
+{
+ IVSHMEM_DPRINTF("posn %d has gone away\n", posn);
+ if (posn >= s->nb_peers || posn == s->vm_id) {
+ error_setg(errp, "invalid peer %d", posn);
+ return;
+ }
+ close_peer_eventfds(s, posn);
+}
+
+static void process_msg_connect(IVShmemState *s, uint16_t posn, int fd,
+ Error **errp)
+{
+ Peer *peer = &s->peers[posn];
+ int vector;
+
+ /*
+ * The N-th connect message for this peer comes with the file
+ * descriptor for vector N-1. Count messages to find the vector.
+ */
+ if (peer->nb_eventfds >= s->vectors) {
+ error_setg(errp, "Too many eventfd received, device has %d vectors",
+ s->vectors);
+ close(fd);
+ return;
+ }
+ vector = peer->nb_eventfds++;
+
+ IVSHMEM_DPRINTF("eventfds[%d][%d] = %d\n", posn, vector, fd);
+ event_notifier_init_fd(&peer->eventfds[vector], fd);
+ fcntl_setfl(fd, O_NONBLOCK); /* msix/irqfd poll non block */
+
+ if (posn == s->vm_id) {
+ setup_interrupt(s, vector, errp);
+ /* TODO do we need to handle the error? */
+ }
+
+ if (ivshmem_has_feature(s, IVSHMEM_IOEVENTFD)) {
+ ivshmem_add_eventfd(s, posn, vector);
+ }
+}
+
+static void process_msg(IVShmemState *s, int64_t msg, int fd, Error **errp)
+{
+ IVSHMEM_DPRINTF("posn is %" PRId64 ", fd is %d\n", msg, fd);
+
+ if (msg < -1 || msg > IVSHMEM_MAX_PEERS) {
+ error_setg(errp, "server sent invalid message %" PRId64, msg);
+ close(fd);
+ return;
+ }
+
+ if (msg == -1) {
+ process_msg_shmem(s, fd, errp);
+ return;
+ }
+
+ if (msg >= s->nb_peers) {
+ resize_peers(s, msg + 1);
+ }
+
+ if (fd >= 0) {
+ process_msg_connect(s, msg, fd, errp);
+ } else {
+ process_msg_disconnect(s, msg, errp);
+ }
+}
+
+static int ivshmem_can_receive(void *opaque)
+{
+ IVShmemState *s = opaque;
+
+ assert(s->msg_buffered_bytes < sizeof(s->msg_buf));
+ return sizeof(s->msg_buf) - s->msg_buffered_bytes;
+}
+
+static void ivshmem_read(void *opaque, const uint8_t *buf, int size)
+{
+ IVShmemState *s = opaque;
+ Error *err = NULL;
+ int fd;
+ int64_t msg;
+
+ assert(size >= 0 && s->msg_buffered_bytes + size <= sizeof(s->msg_buf));
+ memcpy((unsigned char *)&s->msg_buf + s->msg_buffered_bytes, buf, size);
+ s->msg_buffered_bytes += size;
+ if (s->msg_buffered_bytes < sizeof(s->msg_buf)) {
+ return;
+ }
+ msg = le64_to_cpu(s->msg_buf);
+ s->msg_buffered_bytes = 0;
+
+ fd = qemu_chr_fe_get_msgfd(&s->server_chr);
+
+ process_msg(s, msg, fd, &err);
+ if (err) {
+ error_report_err(err);
+ }
+}
+
+static int64_t ivshmem_recv_msg(IVShmemState *s, int *pfd, Error **errp)
+{
+ int64_t msg;
+ int n, ret;
+
+ n = 0;
+ do {
+ ret = qemu_chr_fe_read_all(&s->server_chr, (uint8_t *)&msg + n,
+ sizeof(msg) - n);
+ if (ret < 0) {
+ if (ret == -EINTR) {
+ continue;
+ }
+ error_setg_errno(errp, -ret, "read from server failed");
+ return INT64_MIN;
+ }
+ n += ret;
+ } while (n < sizeof(msg));
+
+ *pfd = qemu_chr_fe_get_msgfd(&s->server_chr);
+ return le64_to_cpu(msg);
+}
+
+static void ivshmem_recv_setup(IVShmemState *s, Error **errp)
+{
+ Error *err = NULL;
+ int64_t msg;
+ int fd;
+
+ msg = ivshmem_recv_msg(s, &fd, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ if (msg != IVSHMEM_PROTOCOL_VERSION) {
+ error_setg(errp, "server sent version %" PRId64 ", expecting %d",
+ msg, IVSHMEM_PROTOCOL_VERSION);
+ return;
+ }
+ if (fd != -1) {
+ error_setg(errp, "server sent invalid version message");
+ return;
+ }
+
+ /*
+ * ivshmem-server sends the remaining initial messages in a fixed
+ * order, but the device has always accepted them in any order.
+ * Stay as compatible as practical, just in case people use
+ * servers that behave differently.
+ */
+
+ /*
+ * ivshmem_device_spec.txt has always required the ID message
+ * right here, and ivshmem-server has always complied. However,
+ * older versions of the device accepted it out of order, but
+ * broke when an interrupt setup message arrived before it.
+ */
+ msg = ivshmem_recv_msg(s, &fd, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ if (fd != -1 || msg < 0 || msg > IVSHMEM_MAX_PEERS) {
+ error_setg(errp, "server sent invalid ID message");
+ return;
+ }
+ s->vm_id = msg;
+
+ /*
+ * Receive more messages until we got shared memory.
+ */
+ do {
+ msg = ivshmem_recv_msg(s, &fd, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ process_msg(s, msg, fd, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ } while (msg != -1);
+
+ /*
+ * This function must either map the shared memory or fail. The
+ * loop above ensures that: it terminates normally only after it
+ * successfully processed the server's shared memory message.
+ * Assert that actually mapped the shared memory:
+ */
+ assert(s->ivshmem_bar2);
+}
+
+/* Select the MSI-X vectors used by device.
+ * ivshmem maps events to vectors statically, so
+ * we just enable all vectors on init and after reset. */
+static void ivshmem_msix_vector_use(IVShmemState *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ int i;
+
+ for (i = 0; i < s->vectors; i++) {
+ msix_vector_use(d, i);
+ }
+}
+
+static void ivshmem_disable_irqfd(IVShmemState *s);
+
+static void ivshmem_reset(DeviceState *d)
+{
+ IVShmemState *s = IVSHMEM_COMMON(d);
+
+ ivshmem_disable_irqfd(s);
+
+ s->intrstatus = 0;
+ s->intrmask = 0;
+ if (ivshmem_has_feature(s, IVSHMEM_MSI)) {
+ ivshmem_msix_vector_use(s);
+ }
+}
+
+static int ivshmem_setup_interrupts(IVShmemState *s, Error **errp)
+{
+ /* allocate QEMU callback data for receiving interrupts */
+ s->msi_vectors = g_malloc0(s->vectors * sizeof(MSIVector));
+
+ if (ivshmem_has_feature(s, IVSHMEM_MSI)) {
+ if (msix_init_exclusive_bar(PCI_DEVICE(s), s->vectors, 1, errp)) {
+ return -1;
+ }
+
+ IVSHMEM_DPRINTF("msix initialized (%d vectors)\n", s->vectors);
+ ivshmem_msix_vector_use(s);
+ }
+
+ return 0;
+}
+
+static void ivshmem_remove_kvm_msi_virq(IVShmemState *s, int vector)
+{
+ IVSHMEM_DPRINTF("ivshmem_remove_kvm_msi_virq vector:%d\n", vector);
+
+ if (s->msi_vectors[vector].pdev == NULL) {
+ return;
+ }
+
+ /* it was cleaned when masked in the frontend. */
+ kvm_irqchip_release_virq(kvm_state, s->msi_vectors[vector].virq);
+
+ s->msi_vectors[vector].pdev = NULL;
+}
+
+static void ivshmem_enable_irqfd(IVShmemState *s)
+{
+ PCIDevice *pdev = PCI_DEVICE(s);
+ int i;
+
+ for (i = 0; i < s->peers[s->vm_id].nb_eventfds; i++) {
+ Error *err = NULL;
+
+ ivshmem_add_kvm_msi_virq(s, i, &err);
+ if (err) {
+ error_report_err(err);
+ goto undo;
+ }
+ }
+
+ if (msix_set_vector_notifiers(pdev,
+ ivshmem_vector_unmask,
+ ivshmem_vector_mask,
+ ivshmem_vector_poll)) {
+ error_report("ivshmem: msix_set_vector_notifiers failed");
+ goto undo;
+ }
+ return;
+
+undo:
+ while (--i >= 0) {
+ ivshmem_remove_kvm_msi_virq(s, i);
+ }
+}
+
+static void ivshmem_disable_irqfd(IVShmemState *s)
+{
+ PCIDevice *pdev = PCI_DEVICE(s);
+ int i;
+
+ if (!pdev->msix_vector_use_notifier) {
+ return;
+ }
+
+ msix_unset_vector_notifiers(pdev);
+
+ for (i = 0; i < s->peers[s->vm_id].nb_eventfds; i++) {
+ /*
+ * MSI-X is already disabled here so msix_unset_vector_notifiers()
+ * didn't call our release notifier. Do it now to keep our masks and
+ * unmasks balanced.
+ */
+ if (s->msi_vectors[i].unmasked) {
+ ivshmem_vector_mask(pdev, i);
+ }
+ ivshmem_remove_kvm_msi_virq(s, i);
+ }
+
+}
+
+static void ivshmem_write_config(PCIDevice *pdev, uint32_t address,
+ uint32_t val, int len)
+{
+ IVShmemState *s = IVSHMEM_COMMON(pdev);
+ int is_enabled, was_enabled = msix_enabled(pdev);
+
+ pci_default_write_config(pdev, address, val, len);
+ is_enabled = msix_enabled(pdev);
+
+ if (kvm_msi_via_irqfd_enabled()) {
+ if (!was_enabled && is_enabled) {
+ ivshmem_enable_irqfd(s);
+ } else if (was_enabled && !is_enabled) {
+ ivshmem_disable_irqfd(s);
+ }
+ }
+}
+
+static void ivshmem_common_realize(PCIDevice *dev, Error **errp)
+{
+ IVShmemState *s = IVSHMEM_COMMON(dev);
+ Error *err = NULL;
+ uint8_t *pci_conf;
+
+ /* IRQFD requires MSI */
+ if (ivshmem_has_feature(s, IVSHMEM_IOEVENTFD) &&
+ !ivshmem_has_feature(s, IVSHMEM_MSI)) {
+ error_setg(errp, "ioeventfd/irqfd requires MSI");
+ return;
+ }
+
+ pci_conf = dev->config;
+ pci_conf[PCI_COMMAND] = PCI_COMMAND_IO | PCI_COMMAND_MEMORY;
+
+ memory_region_init_io(&s->ivshmem_mmio, OBJECT(s), &ivshmem_mmio_ops, s,
+ "ivshmem-mmio", IVSHMEM_REG_BAR_SIZE);
+
+ /* region for registers*/
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY,
+ &s->ivshmem_mmio);
+
+ if (s->hostmem != NULL) {
+ IVSHMEM_DPRINTF("using hostmem\n");
+
+ s->ivshmem_bar2 = host_memory_backend_get_memory(s->hostmem);
+ host_memory_backend_set_mapped(s->hostmem, true);
+ } else {
+ Chardev *chr = qemu_chr_fe_get_driver(&s->server_chr);
+ assert(chr);
+
+ IVSHMEM_DPRINTF("using shared memory server (socket = %s)\n",
+ chr->filename);
+
+ /* we allocate enough space for 16 peers and grow as needed */
+ resize_peers(s, 16);
+
+ /*
+ * Receive setup messages from server synchronously.
+ * Older versions did it asynchronously, but that creates a
+ * number of entertaining race conditions.
+ */
+ ivshmem_recv_setup(s, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ if (s->master == ON_OFF_AUTO_ON && s->vm_id != 0) {
+ error_setg(errp,
+ "master must connect to the server before any peers");
+ return;
+ }
+
+ qemu_chr_fe_set_handlers(&s->server_chr, ivshmem_can_receive,
+ ivshmem_read, NULL, NULL, s, NULL, true);
+
+ if (ivshmem_setup_interrupts(s, errp) < 0) {
+ error_prepend(errp, "Failed to initialize interrupts: ");
+ return;
+ }
+ }
+
+ if (s->master == ON_OFF_AUTO_AUTO) {
+ s->master = s->vm_id == 0 ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
+ }
+
+ if (!ivshmem_is_master(s)) {
+ error_setg(&s->migration_blocker,
+ "Migration is disabled when using feature 'peer mode' in device 'ivshmem'");
+ if (migrate_add_blocker(s->migration_blocker, errp) < 0) {
+ error_free(s->migration_blocker);
+ return;
+ }
+ }
+
+ vmstate_register_ram(s->ivshmem_bar2, DEVICE(s));
+ pci_register_bar(PCI_DEVICE(s), 2,
+ PCI_BASE_ADDRESS_SPACE_MEMORY |
+ PCI_BASE_ADDRESS_MEM_PREFETCH |
+ PCI_BASE_ADDRESS_MEM_TYPE_64,
+ s->ivshmem_bar2);
+}
+
+static void ivshmem_exit(PCIDevice *dev)
+{
+ IVShmemState *s = IVSHMEM_COMMON(dev);
+ int i;
+
+ if (s->migration_blocker) {
+ migrate_del_blocker(s->migration_blocker);
+ error_free(s->migration_blocker);
+ }
+
+ if (memory_region_is_mapped(s->ivshmem_bar2)) {
+ if (!s->hostmem) {
+ void *addr = memory_region_get_ram_ptr(s->ivshmem_bar2);
+ int fd;
+
+ if (munmap(addr, memory_region_size(s->ivshmem_bar2) == -1)) {
+ error_report("Failed to munmap shared memory %s",
+ strerror(errno));
+ }
+
+ fd = memory_region_get_fd(s->ivshmem_bar2);
+ close(fd);
+ }
+
+ vmstate_unregister_ram(s->ivshmem_bar2, DEVICE(dev));
+ }
+
+ if (s->hostmem) {
+ host_memory_backend_set_mapped(s->hostmem, false);
+ }
+
+ if (s->peers) {
+ for (i = 0; i < s->nb_peers; i++) {
+ close_peer_eventfds(s, i);
+ }
+ g_free(s->peers);
+ }
+
+ if (ivshmem_has_feature(s, IVSHMEM_MSI)) {
+ msix_uninit_exclusive_bar(dev);
+ }
+
+ g_free(s->msi_vectors);
+}
+
+static int ivshmem_pre_load(void *opaque)
+{
+ IVShmemState *s = opaque;
+
+ if (!ivshmem_is_master(s)) {
+ error_report("'peer' devices are not migratable");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ivshmem_post_load(void *opaque, int version_id)
+{
+ IVShmemState *s = opaque;
+
+ if (ivshmem_has_feature(s, IVSHMEM_MSI)) {
+ ivshmem_msix_vector_use(s);
+ }
+ return 0;
+}
+
+static void ivshmem_common_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = ivshmem_common_realize;
+ k->exit = ivshmem_exit;
+ k->config_write = ivshmem_write_config;
+ k->vendor_id = PCI_VENDOR_ID_IVSHMEM;
+ k->device_id = PCI_DEVICE_ID_IVSHMEM;
+ k->class_id = PCI_CLASS_MEMORY_RAM;
+ k->revision = 1;
+ dc->reset = ivshmem_reset;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->desc = "Inter-VM shared memory";
+}
+
+static const TypeInfo ivshmem_common_info = {
+ .name = TYPE_IVSHMEM_COMMON,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(IVShmemState),
+ .abstract = true,
+ .class_init = ivshmem_common_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static const VMStateDescription ivshmem_plain_vmsd = {
+ .name = TYPE_IVSHMEM_PLAIN,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .pre_load = ivshmem_pre_load,
+ .post_load = ivshmem_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, IVShmemState),
+ VMSTATE_UINT32(intrstatus, IVShmemState),
+ VMSTATE_UINT32(intrmask, IVShmemState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static Property ivshmem_plain_properties[] = {
+ DEFINE_PROP_ON_OFF_AUTO("master", IVShmemState, master, ON_OFF_AUTO_OFF),
+ DEFINE_PROP_LINK("memdev", IVShmemState, hostmem, TYPE_MEMORY_BACKEND,
+ HostMemoryBackend *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ivshmem_plain_realize(PCIDevice *dev, Error **errp)
+{
+ IVShmemState *s = IVSHMEM_COMMON(dev);
+
+ if (!s->hostmem) {
+ error_setg(errp, "You must specify a 'memdev'");
+ return;
+ } else if (host_memory_backend_is_mapped(s->hostmem)) {
+ error_setg(errp, "can't use already busy memdev: %s",
+ object_get_canonical_path_component(OBJECT(s->hostmem)));
+ return;
+ }
+
+ ivshmem_common_realize(dev, errp);
+}
+
+static void ivshmem_plain_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = ivshmem_plain_realize;
+ device_class_set_props(dc, ivshmem_plain_properties);
+ dc->vmsd = &ivshmem_plain_vmsd;
+}
+
+static const TypeInfo ivshmem_plain_info = {
+ .name = TYPE_IVSHMEM_PLAIN,
+ .parent = TYPE_IVSHMEM_COMMON,
+ .instance_size = sizeof(IVShmemState),
+ .class_init = ivshmem_plain_class_init,
+};
+
+static const VMStateDescription ivshmem_doorbell_vmsd = {
+ .name = TYPE_IVSHMEM_DOORBELL,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .pre_load = ivshmem_pre_load,
+ .post_load = ivshmem_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, IVShmemState),
+ VMSTATE_MSIX(parent_obj, IVShmemState),
+ VMSTATE_UINT32(intrstatus, IVShmemState),
+ VMSTATE_UINT32(intrmask, IVShmemState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static Property ivshmem_doorbell_properties[] = {
+ DEFINE_PROP_CHR("chardev", IVShmemState, server_chr),
+ DEFINE_PROP_UINT32("vectors", IVShmemState, vectors, 1),
+ DEFINE_PROP_BIT("ioeventfd", IVShmemState, features, IVSHMEM_IOEVENTFD,
+ true),
+ DEFINE_PROP_ON_OFF_AUTO("master", IVShmemState, master, ON_OFF_AUTO_OFF),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ivshmem_doorbell_init(Object *obj)
+{
+ IVShmemState *s = IVSHMEM_DOORBELL(obj);
+
+ s->features |= (1 << IVSHMEM_MSI);
+}
+
+static void ivshmem_doorbell_realize(PCIDevice *dev, Error **errp)
+{
+ IVShmemState *s = IVSHMEM_COMMON(dev);
+
+ if (!qemu_chr_fe_backend_connected(&s->server_chr)) {
+ error_setg(errp, "You must specify a 'chardev'");
+ return;
+ }
+
+ ivshmem_common_realize(dev, errp);
+}
+
+static void ivshmem_doorbell_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = ivshmem_doorbell_realize;
+ device_class_set_props(dc, ivshmem_doorbell_properties);
+ dc->vmsd = &ivshmem_doorbell_vmsd;
+}
+
+static const TypeInfo ivshmem_doorbell_info = {
+ .name = TYPE_IVSHMEM_DOORBELL,
+ .parent = TYPE_IVSHMEM_COMMON,
+ .instance_size = sizeof(IVShmemState),
+ .instance_init = ivshmem_doorbell_init,
+ .class_init = ivshmem_doorbell_class_init,
+};
+
+static void ivshmem_register_types(void)
+{
+ type_register_static(&ivshmem_common_info);
+ type_register_static(&ivshmem_plain_info);
+ type_register_static(&ivshmem_doorbell_info);
+}
+
+type_init(ivshmem_register_types)
diff --git a/hw/misc/led.c b/hw/misc/led.c
new file mode 100644
index 000000000..f6d6d68bc
--- /dev/null
+++ b/hw/misc/led.c
@@ -0,0 +1,161 @@
+/*
+ * QEMU single LED device
+ *
+ * Copyright (C) 2020 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/led.h"
+#include "trace.h"
+
+#define LED_INTENSITY_PERCENT_MAX 100
+
+static const char * const led_color_name[] = {
+ [LED_COLOR_VIOLET] = "violet",
+ [LED_COLOR_BLUE] = "blue",
+ [LED_COLOR_CYAN] = "cyan",
+ [LED_COLOR_GREEN] = "green",
+ [LED_COLOR_YELLOW] = "yellow",
+ [LED_COLOR_AMBER] = "amber",
+ [LED_COLOR_ORANGE] = "orange",
+ [LED_COLOR_RED] = "red",
+};
+
+static bool led_color_name_is_valid(const char *color_name)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(led_color_name); i++) {
+ if (strcmp(color_name, led_color_name[i]) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void led_set_intensity(LEDState *s, unsigned intensity_percent)
+{
+ if (intensity_percent > LED_INTENSITY_PERCENT_MAX) {
+ intensity_percent = LED_INTENSITY_PERCENT_MAX;
+ }
+ trace_led_set_intensity(s->description, s->color, intensity_percent);
+ if (intensity_percent != s->intensity_percent) {
+ trace_led_change_intensity(s->description, s->color,
+ s->intensity_percent, intensity_percent);
+ }
+ s->intensity_percent = intensity_percent;
+}
+
+unsigned led_get_intensity(LEDState *s)
+{
+ return s->intensity_percent;
+}
+
+void led_set_state(LEDState *s, bool is_emitting)
+{
+ led_set_intensity(s, is_emitting ? LED_INTENSITY_PERCENT_MAX : 0);
+}
+
+static void led_set_state_gpio_handler(void *opaque, int line, int new_state)
+{
+ LEDState *s = LED(opaque);
+
+ assert(line == 0);
+ led_set_state(s, !!new_state != s->gpio_active_high);
+}
+
+static void led_reset(DeviceState *dev)
+{
+ LEDState *s = LED(dev);
+
+ led_set_state(s, s->gpio_active_high);
+}
+
+static const VMStateDescription vmstate_led = {
+ .name = TYPE_LED,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(intensity_percent, LEDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void led_realize(DeviceState *dev, Error **errp)
+{
+ LEDState *s = LED(dev);
+
+ if (s->color == NULL) {
+ error_setg(errp, "property 'color' not specified");
+ return;
+ } else if (!led_color_name_is_valid(s->color)) {
+ error_setg(errp, "property 'color' invalid or not supported");
+ return;
+ }
+ if (s->description == NULL) {
+ s->description = g_strdup("n/a");
+ }
+
+ qdev_init_gpio_in(DEVICE(s), led_set_state_gpio_handler, 1);
+}
+
+static Property led_properties[] = {
+ DEFINE_PROP_STRING("color", LEDState, color),
+ DEFINE_PROP_STRING("description", LEDState, description),
+ DEFINE_PROP_BOOL("gpio-active-high", LEDState, gpio_active_high, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void led_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "LED";
+ dc->vmsd = &vmstate_led;
+ dc->reset = led_reset;
+ dc->realize = led_realize;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ device_class_set_props(dc, led_properties);
+}
+
+static const TypeInfo led_info = {
+ .name = TYPE_LED,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(LEDState),
+ .class_init = led_class_init
+};
+
+static void led_register_types(void)
+{
+ type_register_static(&led_info);
+}
+
+type_init(led_register_types)
+
+LEDState *led_create_simple(Object *parentobj,
+ GpioPolarity gpio_polarity,
+ LEDColor color,
+ const char *description)
+{
+ g_autofree char *name = NULL;
+ DeviceState *dev;
+
+ dev = qdev_new(TYPE_LED);
+ qdev_prop_set_bit(dev, "gpio-active-high",
+ gpio_polarity == GPIO_POLARITY_ACTIVE_HIGH);
+ qdev_prop_set_string(dev, "color", led_color_name[color]);
+ if (!description) {
+ static unsigned undescribed_led_id;
+ name = g_strdup_printf("undescribed-led-#%u", undescribed_led_id++);
+ } else {
+ qdev_prop_set_string(dev, "description", description);
+ name = g_ascii_strdown(description, -1);
+ name = g_strdelimit(name, " #", '-');
+ }
+ object_property_add_child(parentobj, name, OBJECT(dev));
+ qdev_realize_and_unref(dev, NULL, &error_fatal);
+
+ return LED(dev);
+}
diff --git a/hw/misc/mac_via.c b/hw/misc/mac_via.c
new file mode 100644
index 000000000..b378e6b30
--- /dev/null
+++ b/hw/misc/mac_via.c
@@ -0,0 +1,1221 @@
+/*
+ * QEMU m68k Macintosh VIA device support
+ *
+ * Copyright (c) 2011-2018 Laurent Vivier
+ * Copyright (c) 2018 Mark Cave-Ayland
+ *
+ * Some parts from hw/misc/macio/cuda.c
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * some parts from linux-2.6.29, arch/m68k/include/asm/mac_via.h
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "migration/vmstate.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+#include "qemu/timer.h"
+#include "hw/misc/mac_via.h"
+#include "hw/misc/mos6522.h"
+#include "hw/input/adb.h"
+#include "sysemu/runstate.h"
+#include "qapi/error.h"
+#include "qemu/cutils.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "sysemu/block-backend.h"
+#include "trace.h"
+#include "qemu/log.h"
+
+/*
+ * VIAs: There are two in every machine
+ */
+
+/*
+ * Not all of these are true post MacII I think.
+ * CSA: probably the ones CHRP marks as 'unused' change purposes
+ * when the IWM becomes the SWIM.
+ * http://www.rs6000.ibm.com/resource/technology/chrpio/via5.mak.html
+ * ftp://ftp.austin.ibm.com/pub/technology/spec/chrp/inwork/CHRP_IORef_1.0.pdf
+ *
+ * also, http://developer.apple.com/technotes/hw/hw_09.html claims the
+ * following changes for IIfx:
+ * VIA1A_vSccWrReq not available and that VIA1A_vSync has moved to an IOP.
+ * Also, "All of the functionality of VIA2 has been moved to other chips".
+ */
+
+#define VIA1A_vSccWrReq 0x80 /*
+ * SCC write. (input)
+ * [CHRP] SCC WREQ: Reflects the state of the
+ * Wait/Request pins from the SCC.
+ * [Macintosh Family Hardware]
+ * as CHRP on SE/30,II,IIx,IIcx,IIci.
+ * on IIfx, "0 means an active request"
+ */
+#define VIA1A_vRev8 0x40 /*
+ * Revision 8 board ???
+ * [CHRP] En WaitReqB: Lets the WaitReq_L
+ * signal from port B of the SCC appear on
+ * the PA7 input pin. Output.
+ * [Macintosh Family] On the SE/30, this
+ * is the bit to flip screen buffers.
+ * 0=alternate, 1=main.
+ * on II,IIx,IIcx,IIci,IIfx this is a bit
+ * for Rev ID. 0=II,IIx, 1=IIcx,IIci,IIfx
+ */
+#define VIA1A_vHeadSel 0x20 /*
+ * Head select for IWM.
+ * [CHRP] unused.
+ * [Macintosh Family] "Floppy disk
+ * state-control line SEL" on all but IIfx
+ */
+#define VIA1A_vOverlay 0x10 /*
+ * [Macintosh Family] On SE/30,II,IIx,IIcx
+ * this bit enables the "Overlay" address
+ * map in the address decoders as it is on
+ * reset for mapping the ROM over the reset
+ * vector. 1=use overlay map.
+ * On the IIci,IIfx it is another bit of the
+ * CPU ID: 0=normal IIci, 1=IIci with parity
+ * feature or IIfx.
+ * [CHRP] En WaitReqA: Lets the WaitReq_L
+ * signal from port A of the SCC appear
+ * on the PA7 input pin (CHRP). Output.
+ * [MkLinux] "Drive Select"
+ * (with 0x20 being 'disk head select')
+ */
+#define VIA1A_vSync 0x08 /*
+ * [CHRP] Sync Modem: modem clock select:
+ * 1: select the external serial clock to
+ * drive the SCC's /RTxCA pin.
+ * 0: Select the 3.6864MHz clock to drive
+ * the SCC cell.
+ * [Macintosh Family] Correct on all but IIfx
+ */
+
+/*
+ * Macintosh Family Hardware sez: bits 0-2 of VIA1A are volume control
+ * on Macs which had the PWM sound hardware. Reserved on newer models.
+ * On IIci,IIfx, bits 1-2 are the rest of the CPU ID:
+ * bit 2: 1=IIci, 0=IIfx
+ * bit 1: 1 on both IIci and IIfx.
+ * MkLinux sez bit 0 is 'burnin flag' in this case.
+ * CHRP sez: VIA1A bits 0-2 and 5 are 'unused': if programmed as
+ * inputs, these bits will read 0.
+ */
+#define VIA1A_vVolume 0x07 /* Audio volume mask for PWM */
+#define VIA1A_CPUID0 0x02 /* CPU id bit 0 on RBV, others */
+#define VIA1A_CPUID1 0x04 /* CPU id bit 0 on RBV, others */
+#define VIA1A_CPUID2 0x10 /* CPU id bit 0 on RBV, others */
+#define VIA1A_CPUID3 0x40 /* CPU id bit 0 on RBV, others */
+
+/*
+ * Info on VIA1B is from Macintosh Family Hardware & MkLinux.
+ * CHRP offers no info.
+ */
+#define VIA1B_vSound 0x80 /*
+ * Sound enable (for compatibility with
+ * PWM hardware) 0=enabled.
+ * Also, on IIci w/parity, shows parity error
+ * 0=error, 1=OK.
+ */
+#define VIA1B_vMystery 0x40 /*
+ * On IIci, parity enable. 0=enabled,1=disabled
+ * On SE/30, vertical sync interrupt enable.
+ * 0=enabled. This vSync interrupt shows up
+ * as a slot $E interrupt.
+ * On Quadra 800 this bit toggles A/UX mode which
+ * configures the glue logic to deliver some IRQs
+ * at different levels compared to a classic
+ * Mac.
+ */
+#define VIA1B_vADBS2 0x20 /* ADB state input bit 1 (unused on IIfx) */
+#define VIA1B_vADBS1 0x10 /* ADB state input bit 0 (unused on IIfx) */
+#define VIA1B_vADBInt 0x08 /* ADB interrupt 0=interrupt (unused on IIfx)*/
+#define VIA1B_vRTCEnb 0x04 /* Enable Real time clock. 0=enabled. */
+#define VIA1B_vRTCClk 0x02 /* Real time clock serial-clock line. */
+#define VIA1B_vRTCData 0x01 /* Real time clock serial-data line. */
+
+/*
+ * VIA2 A register is the interrupt lines raised off the nubus
+ * slots.
+ * The below info is from 'Macintosh Family Hardware.'
+ * MkLinux calls the 'IIci internal video IRQ' below the 'RBV slot 0 irq.'
+ * It also notes that the slot $9 IRQ is the 'Ethernet IRQ' and
+ * defines the 'Video IRQ' as 0x40 for the 'EVR' VIA work-alike.
+ * Perhaps OSS uses vRAM1 and vRAM2 for ADB.
+ */
+
+#define VIA2A_vRAM1 0x80 /* RAM size bit 1 (IIci: reserved) */
+#define VIA2A_vRAM0 0x40 /* RAM size bit 0 (IIci: internal video IRQ) */
+#define VIA2A_vIRQE 0x20 /* IRQ from slot $E */
+#define VIA2A_vIRQD 0x10 /* IRQ from slot $D */
+#define VIA2A_vIRQC 0x08 /* IRQ from slot $C */
+#define VIA2A_vIRQB 0x04 /* IRQ from slot $B */
+#define VIA2A_vIRQA 0x02 /* IRQ from slot $A */
+#define VIA2A_vIRQ9 0x01 /* IRQ from slot $9 */
+
+/*
+ * RAM size bits decoded as follows:
+ * bit1 bit0 size of ICs in bank A
+ * 0 0 256 kbit
+ * 0 1 1 Mbit
+ * 1 0 4 Mbit
+ * 1 1 16 Mbit
+ */
+
+/*
+ * Register B has the fun stuff in it
+ */
+
+#define VIA2B_vVBL 0x80 /*
+ * VBL output to VIA1 (60.15Hz) driven by
+ * timer T1.
+ * on IIci, parity test: 0=test mode.
+ * [MkLinux] RBV_PARODD: 1=odd,0=even.
+ */
+#define VIA2B_vSndJck 0x40 /*
+ * External sound jack status.
+ * 0=plug is inserted. On SE/30, always 0
+ */
+#define VIA2B_vTfr0 0x20 /* Transfer mode bit 0 ack from NuBus */
+#define VIA2B_vTfr1 0x10 /* Transfer mode bit 1 ack from NuBus */
+#define VIA2B_vMode32 0x08 /*
+ * 24/32bit switch - doubles as cache flush
+ * on II, AMU/PMMU control.
+ * if AMU, 0=24bit to 32bit translation
+ * if PMMU, 1=PMMU is accessing page table.
+ * on SE/30 tied low.
+ * on IIx,IIcx,IIfx, unused.
+ * on IIci/RBV, cache control. 0=flush cache.
+ */
+#define VIA2B_vPower 0x04 /*
+ * Power off, 0=shut off power.
+ * on SE/30 this signal sent to PDS card.
+ */
+#define VIA2B_vBusLk 0x02 /*
+ * Lock NuBus transactions, 0=locked.
+ * on SE/30 sent to PDS card.
+ */
+#define VIA2B_vCDis 0x01 /*
+ * Cache control. On IIci, 1=disable cache card
+ * on others, 0=disable processor's instruction
+ * and data caches.
+ */
+
+/* interrupt flags */
+
+#define IRQ_SET 0x80
+
+/* common */
+
+#define VIA_IRQ_TIMER1 0x40
+#define VIA_IRQ_TIMER2 0x20
+
+/*
+ * Apple sez: http://developer.apple.com/technotes/ov/ov_04.html
+ * Another example of a valid function that has no ROM support is the use
+ * of the alternate video page for page-flipping animation. Since there
+ * is no ROM call to flip pages, it is necessary to go play with the
+ * right bit in the VIA chip (6522 Versatile Interface Adapter).
+ * [CSA: don't know which one this is, but it's one of 'em!]
+ */
+
+/*
+ * 6522 registers - see databook.
+ * CSA: Assignments for VIA1 confirmed from CHRP spec.
+ */
+
+/* partial address decode. 0xYYXX : XX part for RBV, YY part for VIA */
+/* Note: 15 VIA regs, 8 RBV regs */
+
+#define vBufB 0x0000 /* [VIA/RBV] Register B */
+#define vBufAH 0x0200 /* [VIA only] Buffer A, with handshake. DON'T USE! */
+#define vDirB 0x0400 /* [VIA only] Data Direction Register B. */
+#define vDirA 0x0600 /* [VIA only] Data Direction Register A. */
+#define vT1CL 0x0800 /* [VIA only] Timer one counter low. */
+#define vT1CH 0x0a00 /* [VIA only] Timer one counter high. */
+#define vT1LL 0x0c00 /* [VIA only] Timer one latches low. */
+#define vT1LH 0x0e00 /* [VIA only] Timer one latches high. */
+#define vT2CL 0x1000 /* [VIA only] Timer two counter low. */
+#define vT2CH 0x1200 /* [VIA only] Timer two counter high. */
+#define vSR 0x1400 /* [VIA only] Shift register. */
+#define vACR 0x1600 /* [VIA only] Auxilary control register. */
+#define vPCR 0x1800 /* [VIA only] Peripheral control register. */
+ /*
+ * CHRP sez never ever to *write* this.
+ * Mac family says never to *change* this.
+ * In fact we need to initialize it once at start.
+ */
+#define vIFR 0x1a00 /* [VIA/RBV] Interrupt flag register. */
+#define vIER 0x1c00 /* [VIA/RBV] Interrupt enable register. */
+#define vBufA 0x1e00 /* [VIA/RBV] register A (no handshake) */
+
+/* from linux 2.6 drivers/macintosh/via-macii.c */
+
+/* Bits in ACR */
+
+#define VIA1ACR_vShiftCtrl 0x1c /* Shift register control bits */
+#define VIA1ACR_vShiftExtClk 0x0c /* Shift on external clock */
+#define VIA1ACR_vShiftOut 0x10 /* Shift out if 1 */
+
+/*
+ * Apple Macintosh Family Hardware Refenece
+ * Table 19-10 ADB transaction states
+ */
+
+#define ADB_STATE_NEW 0
+#define ADB_STATE_EVEN 1
+#define ADB_STATE_ODD 2
+#define ADB_STATE_IDLE 3
+
+#define VIA1B_vADB_StateMask (VIA1B_vADBS1 | VIA1B_vADBS2)
+#define VIA1B_vADB_StateShift 4
+
+#define VIA_TIMER_FREQ (783360)
+#define VIA_ADB_POLL_FREQ 50 /* XXX: not real */
+
+/*
+ * Guide to the Macintosh Family Hardware ch. 12 "Displays" p. 401 gives the
+ * precise 60Hz interrupt frequency as ~60.15Hz with a period of 16625.8 us
+ */
+#define VIA_60HZ_TIMER_PERIOD_NS 16625800
+
+/* VIA returns time offset from Jan 1, 1904, not 1970 */
+#define RTC_OFFSET 2082844800
+
+enum {
+ REG_0,
+ REG_1,
+ REG_2,
+ REG_3,
+ REG_TEST,
+ REG_WPROTECT,
+ REG_PRAM_ADDR,
+ REG_PRAM_ADDR_LAST = REG_PRAM_ADDR + 19,
+ REG_PRAM_SECT,
+ REG_PRAM_SECT_LAST = REG_PRAM_SECT + 7,
+ REG_INVALID,
+ REG_EMPTY = 0xff,
+};
+
+static void via1_sixty_hz_update(MOS6522Q800VIA1State *v1s)
+{
+ /* 60 Hz irq */
+ v1s->next_sixty_hz = (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ VIA_60HZ_TIMER_PERIOD_NS) /
+ VIA_60HZ_TIMER_PERIOD_NS * VIA_60HZ_TIMER_PERIOD_NS;
+ timer_mod(v1s->sixty_hz_timer, v1s->next_sixty_hz);
+}
+
+static void via1_one_second_update(MOS6522Q800VIA1State *v1s)
+{
+ v1s->next_second = (qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000) /
+ 1000 * 1000;
+ timer_mod(v1s->one_second_timer, v1s->next_second);
+}
+
+static void via1_sixty_hz(void *opaque)
+{
+ MOS6522Q800VIA1State *v1s = opaque;
+ MOS6522State *s = MOS6522(v1s);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(s);
+
+ s->ifr |= VIA1_IRQ_60HZ;
+ mdc->update_irq(s);
+
+ via1_sixty_hz_update(v1s);
+}
+
+static void via1_one_second(void *opaque)
+{
+ MOS6522Q800VIA1State *v1s = opaque;
+ MOS6522State *s = MOS6522(v1s);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(s);
+
+ s->ifr |= VIA1_IRQ_ONE_SECOND;
+ mdc->update_irq(s);
+
+ via1_one_second_update(v1s);
+}
+
+static void via1_irq_request(void *opaque, int irq, int level)
+{
+ MOS6522Q800VIA1State *v1s = opaque;
+ MOS6522State *s = MOS6522(v1s);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(s);
+
+ if (level) {
+ s->ifr |= 1 << irq;
+ } else {
+ s->ifr &= ~(1 << irq);
+ }
+
+ mdc->update_irq(s);
+}
+
+static void via2_irq_request(void *opaque, int irq, int level)
+{
+ MOS6522Q800VIA2State *v2s = opaque;
+ MOS6522State *s = MOS6522(v2s);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(s);
+
+ if (level) {
+ s->ifr |= 1 << irq;
+ } else {
+ s->ifr &= ~(1 << irq);
+ }
+
+ mdc->update_irq(s);
+}
+
+
+static void pram_update(MOS6522Q800VIA1State *v1s)
+{
+ if (v1s->blk) {
+ if (blk_pwrite(v1s->blk, 0, v1s->PRAM, sizeof(v1s->PRAM), 0) < 0) {
+ qemu_log("pram_update: cannot write to file\n");
+ }
+ }
+}
+
+/*
+ * RTC Commands
+ *
+ * Command byte Register addressed by the command
+ *
+ * z0000001 Seconds register 0 (lowest-order byte)
+ * z0000101 Seconds register 1
+ * z0001001 Seconds register 2
+ * z0001101 Seconds register 3 (highest-order byte)
+ * 00110001 Test register (write-only)
+ * 00110101 Write-Protect Register (write-only)
+ * z010aa01 RAM address 100aa ($10-$13) (first 20 bytes only)
+ * z1aaaa01 RAM address 0aaaa ($00-$0F) (first 20 bytes only)
+ * z0111aaa Extended memory designator and sector number
+ *
+ * For a read request, z=1, for a write z=0
+ * The letter a indicates bits whose value depend on what parameter
+ * RAM byte you want to address
+ */
+static int via1_rtc_compact_cmd(uint8_t value)
+{
+ uint8_t read = value & 0x80;
+
+ value &= 0x7f;
+
+ /* the last 2 bits of a command byte must always be 0b01 ... */
+ if ((value & 0x78) == 0x38) {
+ /* except for the extended memory designator */
+ return read | (REG_PRAM_SECT + (value & 0x07));
+ }
+ if ((value & 0x03) == 0x01) {
+ value >>= 2;
+ if ((value & 0x1c) == 0) {
+ /* seconds registers */
+ return read | (REG_0 + (value & 0x03));
+ } else if ((value == 0x0c) && !read) {
+ return REG_TEST;
+ } else if ((value == 0x0d) && !read) {
+ return REG_WPROTECT;
+ } else if ((value & 0x1c) == 0x08) {
+ /* RAM address 0x10 to 0x13 */
+ return read | (REG_PRAM_ADDR + 0x10 + (value & 0x03));
+ } else if ((value & 0x43) == 0x41) {
+ /* RAM address 0x00 to 0x0f */
+ return read | (REG_PRAM_ADDR + (value & 0x0f));
+ }
+ }
+ return REG_INVALID;
+}
+
+static void via1_rtc_update(MOS6522Q800VIA1State *v1s)
+{
+ MOS6522State *s = MOS6522(v1s);
+ int cmd, sector, addr;
+ uint32_t time;
+
+ if (s->b & VIA1B_vRTCEnb) {
+ return;
+ }
+
+ if (s->dirb & VIA1B_vRTCData) {
+ /* send bits to the RTC */
+ if (!(v1s->last_b & VIA1B_vRTCClk) && (s->b & VIA1B_vRTCClk)) {
+ v1s->data_out <<= 1;
+ v1s->data_out |= s->b & VIA1B_vRTCData;
+ v1s->data_out_cnt++;
+ }
+ trace_via1_rtc_update_data_out(v1s->data_out_cnt, v1s->data_out);
+ } else {
+ trace_via1_rtc_update_data_in(v1s->data_in_cnt, v1s->data_in);
+ /* receive bits from the RTC */
+ if ((v1s->last_b & VIA1B_vRTCClk) &&
+ !(s->b & VIA1B_vRTCClk) &&
+ v1s->data_in_cnt) {
+ s->b = (s->b & ~VIA1B_vRTCData) |
+ ((v1s->data_in >> 7) & VIA1B_vRTCData);
+ v1s->data_in <<= 1;
+ v1s->data_in_cnt--;
+ }
+ return;
+ }
+
+ if (v1s->data_out_cnt != 8) {
+ return;
+ }
+
+ v1s->data_out_cnt = 0;
+
+ trace_via1_rtc_internal_status(v1s->cmd, v1s->alt, v1s->data_out);
+ /* first byte: it's a command */
+ if (v1s->cmd == REG_EMPTY) {
+
+ cmd = via1_rtc_compact_cmd(v1s->data_out);
+ trace_via1_rtc_internal_cmd(cmd);
+
+ if (cmd == REG_INVALID) {
+ trace_via1_rtc_cmd_invalid(v1s->data_out);
+ return;
+ }
+
+ if (cmd & 0x80) { /* this is a read command */
+ switch (cmd & 0x7f) {
+ case REG_0...REG_3: /* seconds registers */
+ /*
+ * register 0 is lowest-order byte
+ * register 3 is highest-order byte
+ */
+
+ time = v1s->tick_offset + (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
+ / NANOSECONDS_PER_SECOND);
+ trace_via1_rtc_internal_time(time);
+ v1s->data_in = (time >> ((cmd & 0x03) << 3)) & 0xff;
+ v1s->data_in_cnt = 8;
+ trace_via1_rtc_cmd_seconds_read((cmd & 0x7f) - REG_0,
+ v1s->data_in);
+ break;
+ case REG_PRAM_ADDR...REG_PRAM_ADDR_LAST:
+ /* PRAM address 0x00 -> 0x13 */
+ v1s->data_in = v1s->PRAM[(cmd & 0x7f) - REG_PRAM_ADDR];
+ v1s->data_in_cnt = 8;
+ trace_via1_rtc_cmd_pram_read((cmd & 0x7f) - REG_PRAM_ADDR,
+ v1s->data_in);
+ break;
+ case REG_PRAM_SECT...REG_PRAM_SECT_LAST:
+ /*
+ * extended memory designator and sector number
+ * the only two-byte read command
+ */
+ trace_via1_rtc_internal_set_cmd(cmd);
+ v1s->cmd = cmd;
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ return;
+ }
+
+ /* this is a write command, needs a parameter */
+ if (cmd == REG_WPROTECT || !v1s->wprotect) {
+ trace_via1_rtc_internal_set_cmd(cmd);
+ v1s->cmd = cmd;
+ } else {
+ trace_via1_rtc_internal_ignore_cmd(cmd);
+ }
+ return;
+ }
+
+ /* second byte: it's a parameter */
+ if (v1s->alt == REG_EMPTY) {
+ switch (v1s->cmd & 0x7f) {
+ case REG_0...REG_3: /* seconds register */
+ /* FIXME */
+ trace_via1_rtc_cmd_seconds_write(v1s->cmd - REG_0, v1s->data_out);
+ v1s->cmd = REG_EMPTY;
+ break;
+ case REG_TEST:
+ /* device control: nothing to do */
+ trace_via1_rtc_cmd_test_write(v1s->data_out);
+ v1s->cmd = REG_EMPTY;
+ break;
+ case REG_WPROTECT:
+ /* Write Protect register */
+ trace_via1_rtc_cmd_wprotect_write(v1s->data_out);
+ v1s->wprotect = !!(v1s->data_out & 0x80);
+ v1s->cmd = REG_EMPTY;
+ break;
+ case REG_PRAM_ADDR...REG_PRAM_ADDR_LAST:
+ /* PRAM address 0x00 -> 0x13 */
+ trace_via1_rtc_cmd_pram_write(v1s->cmd - REG_PRAM_ADDR,
+ v1s->data_out);
+ v1s->PRAM[v1s->cmd - REG_PRAM_ADDR] = v1s->data_out;
+ pram_update(v1s);
+ v1s->cmd = REG_EMPTY;
+ break;
+ case REG_PRAM_SECT...REG_PRAM_SECT_LAST:
+ addr = (v1s->data_out >> 2) & 0x1f;
+ sector = (v1s->cmd & 0x7f) - REG_PRAM_SECT;
+ if (v1s->cmd & 0x80) {
+ /* it's a read */
+ v1s->data_in = v1s->PRAM[sector * 32 + addr];
+ v1s->data_in_cnt = 8;
+ trace_via1_rtc_cmd_pram_sect_read(sector, addr,
+ sector * 32 + addr,
+ v1s->data_in);
+ v1s->cmd = REG_EMPTY;
+ } else {
+ /* it's a write, we need one more parameter */
+ trace_via1_rtc_internal_set_alt(addr, sector, addr);
+ v1s->alt = addr;
+ }
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ return;
+ }
+
+ /* third byte: it's the data of a REG_PRAM_SECT write */
+ g_assert(REG_PRAM_SECT <= v1s->cmd && v1s->cmd <= REG_PRAM_SECT_LAST);
+ sector = v1s->cmd - REG_PRAM_SECT;
+ v1s->PRAM[sector * 32 + v1s->alt] = v1s->data_out;
+ pram_update(v1s);
+ trace_via1_rtc_cmd_pram_sect_write(sector, v1s->alt, sector * 32 + v1s->alt,
+ v1s->data_out);
+ v1s->alt = REG_EMPTY;
+ v1s->cmd = REG_EMPTY;
+}
+
+static void adb_via_poll(void *opaque)
+{
+ MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(opaque);
+ MOS6522State *s = MOS6522(v1s);
+ ADBBusState *adb_bus = &v1s->adb_bus;
+ uint8_t obuf[9];
+ uint8_t *data = &s->sr;
+ int olen;
+
+ /*
+ * Setting vADBInt below indicates that an autopoll reply has been
+ * received, however we must block autopoll until the point where
+ * the entire reply has been read back to the host
+ */
+ adb_autopoll_block(adb_bus);
+
+ if (v1s->adb_data_in_size > 0 && v1s->adb_data_in_index == 0) {
+ /*
+ * For older Linux kernels that switch to IDLE mode after sending the
+ * ADB command, detect if there is an existing response and return that
+ * as a a "fake" autopoll reply or bus timeout accordingly
+ */
+ *data = v1s->adb_data_out[0];
+ olen = v1s->adb_data_in_size;
+
+ s->b &= ~VIA1B_vADBInt;
+ qemu_irq_raise(v1s->adb_data_ready);
+ } else {
+ /*
+ * Otherwise poll as normal
+ */
+ v1s->adb_data_in_index = 0;
+ v1s->adb_data_out_index = 0;
+ olen = adb_poll(adb_bus, obuf, adb_bus->autopoll_mask);
+
+ if (olen > 0) {
+ /* Autopoll response */
+ *data = obuf[0];
+ olen--;
+ memcpy(v1s->adb_data_in, &obuf[1], olen);
+ v1s->adb_data_in_size = olen;
+
+ s->b &= ~VIA1B_vADBInt;
+ qemu_irq_raise(v1s->adb_data_ready);
+ } else {
+ *data = v1s->adb_autopoll_cmd;
+ obuf[0] = 0xff;
+ obuf[1] = 0xff;
+ olen = 2;
+
+ memcpy(v1s->adb_data_in, obuf, olen);
+ v1s->adb_data_in_size = olen;
+
+ s->b &= ~VIA1B_vADBInt;
+ qemu_irq_raise(v1s->adb_data_ready);
+ }
+ }
+
+ trace_via1_adb_poll(*data, (s->b & VIA1B_vADBInt) ? "+" : "-",
+ adb_bus->status, v1s->adb_data_in_index, olen);
+}
+
+static int adb_via_send_len(uint8_t data)
+{
+ /* Determine the send length from the given ADB command */
+ uint8_t cmd = data & 0xc;
+ uint8_t reg = data & 0x3;
+
+ switch (cmd) {
+ case 0x8:
+ /* Listen command */
+ switch (reg) {
+ case 2:
+ /* Register 2 is only used for the keyboard */
+ return 3;
+ case 3:
+ /*
+ * Fortunately our devices only implement writes
+ * to register 3 which is fixed at 2 bytes
+ */
+ return 3;
+ default:
+ qemu_log_mask(LOG_UNIMP, "ADB unknown length for register %d\n",
+ reg);
+ return 1;
+ }
+ default:
+ /* Talk, BusReset */
+ return 1;
+ }
+}
+
+static void adb_via_send(MOS6522Q800VIA1State *v1s, int state, uint8_t data)
+{
+ MOS6522State *ms = MOS6522(v1s);
+ ADBBusState *adb_bus = &v1s->adb_bus;
+ uint16_t autopoll_mask;
+
+ switch (state) {
+ case ADB_STATE_NEW:
+ /*
+ * Command byte: vADBInt tells host autopoll data already present
+ * in VIA shift register and ADB transceiver
+ */
+ adb_autopoll_block(adb_bus);
+
+ if (adb_bus->status & ADB_STATUS_POLLREPLY) {
+ /* Tell the host the existing data is from autopoll */
+ ms->b &= ~VIA1B_vADBInt;
+ } else {
+ ms->b |= VIA1B_vADBInt;
+ v1s->adb_data_out_index = 0;
+ v1s->adb_data_out[v1s->adb_data_out_index++] = data;
+ }
+
+ trace_via1_adb_send(" NEW", data, (ms->b & VIA1B_vADBInt) ? "+" : "-");
+ qemu_irq_raise(v1s->adb_data_ready);
+ break;
+
+ case ADB_STATE_EVEN:
+ case ADB_STATE_ODD:
+ ms->b |= VIA1B_vADBInt;
+ v1s->adb_data_out[v1s->adb_data_out_index++] = data;
+
+ trace_via1_adb_send(state == ADB_STATE_EVEN ? "EVEN" : " ODD",
+ data, (ms->b & VIA1B_vADBInt) ? "+" : "-");
+ qemu_irq_raise(v1s->adb_data_ready);
+ break;
+
+ case ADB_STATE_IDLE:
+ return;
+ }
+
+ /* If the command is complete, execute it */
+ if (v1s->adb_data_out_index == adb_via_send_len(v1s->adb_data_out[0])) {
+ v1s->adb_data_in_size = adb_request(adb_bus, v1s->adb_data_in,
+ v1s->adb_data_out,
+ v1s->adb_data_out_index);
+ v1s->adb_data_in_index = 0;
+
+ if (adb_bus->status & ADB_STATUS_BUSTIMEOUT) {
+ /*
+ * Bus timeout (but allow first EVEN and ODD byte to indicate
+ * timeout via vADBInt and SRQ status)
+ */
+ v1s->adb_data_in[0] = 0xff;
+ v1s->adb_data_in[1] = 0xff;
+ v1s->adb_data_in_size = 2;
+ }
+
+ /*
+ * If last command is TALK, store it for use by autopoll and adjust
+ * the autopoll mask accordingly
+ */
+ if ((v1s->adb_data_out[0] & 0xc) == 0xc) {
+ v1s->adb_autopoll_cmd = v1s->adb_data_out[0];
+
+ autopoll_mask = 1 << (v1s->adb_autopoll_cmd >> 4);
+ adb_set_autopoll_mask(adb_bus, autopoll_mask);
+ }
+ }
+}
+
+static void adb_via_receive(MOS6522Q800VIA1State *v1s, int state, uint8_t *data)
+{
+ MOS6522State *ms = MOS6522(v1s);
+ ADBBusState *adb_bus = &v1s->adb_bus;
+ uint16_t pending;
+
+ switch (state) {
+ case ADB_STATE_NEW:
+ ms->b |= VIA1B_vADBInt;
+ return;
+
+ case ADB_STATE_IDLE:
+ ms->b |= VIA1B_vADBInt;
+ adb_autopoll_unblock(adb_bus);
+
+ trace_via1_adb_receive("IDLE", *data,
+ (ms->b & VIA1B_vADBInt) ? "+" : "-", adb_bus->status,
+ v1s->adb_data_in_index, v1s->adb_data_in_size);
+
+ break;
+
+ case ADB_STATE_EVEN:
+ case ADB_STATE_ODD:
+ switch (v1s->adb_data_in_index) {
+ case 0:
+ /* First EVEN byte: vADBInt indicates bus timeout */
+ *data = v1s->adb_data_in[v1s->adb_data_in_index];
+ if (adb_bus->status & ADB_STATUS_BUSTIMEOUT) {
+ ms->b &= ~VIA1B_vADBInt;
+ } else {
+ ms->b |= VIA1B_vADBInt;
+ }
+
+ trace_via1_adb_receive(state == ADB_STATE_EVEN ? "EVEN" : " ODD",
+ *data, (ms->b & VIA1B_vADBInt) ? "+" : "-",
+ adb_bus->status, v1s->adb_data_in_index,
+ v1s->adb_data_in_size);
+
+ v1s->adb_data_in_index++;
+ break;
+
+ case 1:
+ /* First ODD byte: vADBInt indicates SRQ */
+ *data = v1s->adb_data_in[v1s->adb_data_in_index];
+ pending = adb_bus->pending & ~(1 << (v1s->adb_autopoll_cmd >> 4));
+ if (pending) {
+ ms->b &= ~VIA1B_vADBInt;
+ } else {
+ ms->b |= VIA1B_vADBInt;
+ }
+
+ trace_via1_adb_receive(state == ADB_STATE_EVEN ? "EVEN" : " ODD",
+ *data, (ms->b & VIA1B_vADBInt) ? "+" : "-",
+ adb_bus->status, v1s->adb_data_in_index,
+ v1s->adb_data_in_size);
+
+ v1s->adb_data_in_index++;
+ break;
+
+ default:
+ /*
+ * Otherwise vADBInt indicates end of data. Note that Linux
+ * specifically checks for the sequence 0x0 0xff to confirm the
+ * end of the poll reply, so provide these extra bytes below to
+ * keep it happy
+ */
+ if (v1s->adb_data_in_index < v1s->adb_data_in_size) {
+ /* Next data byte */
+ *data = v1s->adb_data_in[v1s->adb_data_in_index];
+ ms->b |= VIA1B_vADBInt;
+ } else if (v1s->adb_data_in_index == v1s->adb_data_in_size) {
+ if (adb_bus->status & ADB_STATUS_BUSTIMEOUT) {
+ /* Bus timeout (no more data) */
+ *data = 0xff;
+ } else {
+ /* Return 0x0 after reply */
+ *data = 0;
+ }
+ ms->b &= ~VIA1B_vADBInt;
+ } else {
+ /* Bus timeout (no more data) */
+ *data = 0xff;
+ ms->b &= ~VIA1B_vADBInt;
+ adb_bus->status = 0;
+ adb_autopoll_unblock(adb_bus);
+ }
+
+ trace_via1_adb_receive(state == ADB_STATE_EVEN ? "EVEN" : " ODD",
+ *data, (ms->b & VIA1B_vADBInt) ? "+" : "-",
+ adb_bus->status, v1s->adb_data_in_index,
+ v1s->adb_data_in_size);
+
+ if (v1s->adb_data_in_index <= v1s->adb_data_in_size) {
+ v1s->adb_data_in_index++;
+ }
+ break;
+ }
+
+ qemu_irq_raise(v1s->adb_data_ready);
+ break;
+ }
+}
+
+static void via1_adb_update(MOS6522Q800VIA1State *v1s)
+{
+ MOS6522State *s = MOS6522(v1s);
+ int oldstate, state;
+
+ oldstate = (v1s->last_b & VIA1B_vADB_StateMask) >> VIA1B_vADB_StateShift;
+ state = (s->b & VIA1B_vADB_StateMask) >> VIA1B_vADB_StateShift;
+
+ if (state != oldstate) {
+ if (s->acr & VIA1ACR_vShiftOut) {
+ /* output mode */
+ adb_via_send(v1s, state, s->sr);
+ } else {
+ /* input mode */
+ adb_via_receive(v1s, state, &s->sr);
+ }
+ }
+}
+
+static void via1_auxmode_update(MOS6522Q800VIA1State *v1s)
+{
+ MOS6522State *s = MOS6522(v1s);
+ int oldirq, irq;
+
+ oldirq = (v1s->last_b & VIA1B_vMystery) ? 1 : 0;
+ irq = (s->b & VIA1B_vMystery) ? 1 : 0;
+
+ /* Check to see if the A/UX mode bit has changed */
+ if (irq != oldirq) {
+ trace_via1_auxmode(irq);
+ qemu_set_irq(v1s->auxmode_irq, irq);
+ }
+}
+
+static uint64_t mos6522_q800_via1_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MOS6522Q800VIA1State *s = MOS6522_Q800_VIA1(opaque);
+ MOS6522State *ms = MOS6522(s);
+
+ addr = (addr >> 9) & 0xf;
+ return mos6522_read(ms, addr, size);
+}
+
+static void mos6522_q800_via1_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(opaque);
+ MOS6522State *ms = MOS6522(v1s);
+
+ addr = (addr >> 9) & 0xf;
+ mos6522_write(ms, addr, val, size);
+
+ switch (addr) {
+ case VIA_REG_B:
+ via1_rtc_update(v1s);
+ via1_adb_update(v1s);
+ via1_auxmode_update(v1s);
+
+ v1s->last_b = ms->b;
+ break;
+ }
+}
+
+static const MemoryRegionOps mos6522_q800_via1_ops = {
+ .read = mos6522_q800_via1_read,
+ .write = mos6522_q800_via1_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t mos6522_q800_via2_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MOS6522Q800VIA2State *s = MOS6522_Q800_VIA2(opaque);
+ MOS6522State *ms = MOS6522(s);
+
+ addr = (addr >> 9) & 0xf;
+ return mos6522_read(ms, addr, size);
+}
+
+static void mos6522_q800_via2_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ MOS6522Q800VIA2State *s = MOS6522_Q800_VIA2(opaque);
+ MOS6522State *ms = MOS6522(s);
+
+ addr = (addr >> 9) & 0xf;
+ mos6522_write(ms, addr, val, size);
+}
+
+static const MemoryRegionOps mos6522_q800_via2_ops = {
+ .read = mos6522_q800_via2_read,
+ .write = mos6522_q800_via2_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static void via1_postload_update_cb(void *opaque, bool running, RunState state)
+{
+ MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(opaque);
+
+ qemu_del_vm_change_state_handler(v1s->vmstate);
+ v1s->vmstate = NULL;
+
+ pram_update(v1s);
+}
+
+static int via1_post_load(void *opaque, int version_id)
+{
+ MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(opaque);
+
+ if (v1s->blk) {
+ v1s->vmstate = qemu_add_vm_change_state_handler(
+ via1_postload_update_cb, v1s);
+ }
+
+ return 0;
+}
+
+/* VIA 1 */
+static void mos6522_q800_via1_reset(DeviceState *dev)
+{
+ MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(dev);
+ MOS6522State *ms = MOS6522(v1s);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms);
+ ADBBusState *adb_bus = &v1s->adb_bus;
+
+ mdc->parent_reset(dev);
+
+ ms->timers[0].frequency = VIA_TIMER_FREQ;
+ ms->timers[1].frequency = VIA_TIMER_FREQ;
+
+ ms->b = VIA1B_vADB_StateMask | VIA1B_vADBInt | VIA1B_vRTCEnb;
+
+ /* ADB/RTC */
+ adb_set_autopoll_enabled(adb_bus, true);
+ v1s->cmd = REG_EMPTY;
+ v1s->alt = REG_EMPTY;
+}
+
+static void mos6522_q800_via1_realize(DeviceState *dev, Error **errp)
+{
+ MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(dev);
+ ADBBusState *adb_bus = &v1s->adb_bus;
+ struct tm tm;
+ int ret;
+
+ v1s->one_second_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, via1_one_second,
+ v1s);
+ via1_one_second_update(v1s);
+ v1s->sixty_hz_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, via1_sixty_hz,
+ v1s);
+ via1_sixty_hz_update(v1s);
+
+ qemu_get_timedate(&tm, 0);
+ v1s->tick_offset = (uint32_t)mktimegm(&tm) + RTC_OFFSET;
+
+ adb_register_autopoll_callback(adb_bus, adb_via_poll, v1s);
+ v1s->adb_data_ready = qdev_get_gpio_in(dev, VIA1_IRQ_ADB_READY_BIT);
+
+ if (v1s->blk) {
+ int64_t len = blk_getlength(v1s->blk);
+ if (len < 0) {
+ error_setg_errno(errp, -len,
+ "could not get length of backing image");
+ return;
+ }
+ ret = blk_set_perm(v1s->blk,
+ BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE,
+ BLK_PERM_ALL, errp);
+ if (ret < 0) {
+ return;
+ }
+
+ len = blk_pread(v1s->blk, 0, v1s->PRAM, sizeof(v1s->PRAM));
+ if (len != sizeof(v1s->PRAM)) {
+ error_setg(errp, "can't read PRAM contents");
+ return;
+ }
+ }
+}
+
+static void mos6522_q800_via1_init(Object *obj)
+{
+ MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(v1s);
+
+ memory_region_init_io(&v1s->via_mem, obj, &mos6522_q800_via1_ops, v1s,
+ "via1", VIA_SIZE);
+ sysbus_init_mmio(sbd, &v1s->via_mem);
+
+ /* ADB */
+ qbus_init((BusState *)&v1s->adb_bus, sizeof(v1s->adb_bus),
+ TYPE_ADB_BUS, DEVICE(v1s), "adb.0");
+
+ qdev_init_gpio_in(DEVICE(obj), via1_irq_request, VIA1_IRQ_NB);
+
+ /* A/UX mode */
+ qdev_init_gpio_out(DEVICE(obj), &v1s->auxmode_irq, 1);
+}
+
+static const VMStateDescription vmstate_q800_via1 = {
+ .name = "q800-via1",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = via1_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(parent_obj, MOS6522Q800VIA1State, 0, vmstate_mos6522,
+ MOS6522State),
+ VMSTATE_UINT8(last_b, MOS6522Q800VIA1State),
+ /* RTC */
+ VMSTATE_BUFFER(PRAM, MOS6522Q800VIA1State),
+ VMSTATE_UINT32(tick_offset, MOS6522Q800VIA1State),
+ VMSTATE_UINT8(data_out, MOS6522Q800VIA1State),
+ VMSTATE_INT32(data_out_cnt, MOS6522Q800VIA1State),
+ VMSTATE_UINT8(data_in, MOS6522Q800VIA1State),
+ VMSTATE_UINT8(data_in_cnt, MOS6522Q800VIA1State),
+ VMSTATE_UINT8(cmd, MOS6522Q800VIA1State),
+ VMSTATE_INT32(wprotect, MOS6522Q800VIA1State),
+ VMSTATE_INT32(alt, MOS6522Q800VIA1State),
+ /* ADB */
+ VMSTATE_INT32(adb_data_in_size, MOS6522Q800VIA1State),
+ VMSTATE_INT32(adb_data_in_index, MOS6522Q800VIA1State),
+ VMSTATE_INT32(adb_data_out_index, MOS6522Q800VIA1State),
+ VMSTATE_BUFFER(adb_data_in, MOS6522Q800VIA1State),
+ VMSTATE_BUFFER(adb_data_out, MOS6522Q800VIA1State),
+ VMSTATE_UINT8(adb_autopoll_cmd, MOS6522Q800VIA1State),
+ /* Timers */
+ VMSTATE_TIMER_PTR(one_second_timer, MOS6522Q800VIA1State),
+ VMSTATE_INT64(next_second, MOS6522Q800VIA1State),
+ VMSTATE_TIMER_PTR(sixty_hz_timer, MOS6522Q800VIA1State),
+ VMSTATE_INT64(next_sixty_hz, MOS6522Q800VIA1State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property mos6522_q800_via1_properties[] = {
+ DEFINE_PROP_DRIVE("drive", MOS6522Q800VIA1State, blk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mos6522_q800_via1_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = mos6522_q800_via1_realize;
+ dc->reset = mos6522_q800_via1_reset;
+ dc->vmsd = &vmstate_q800_via1;
+ device_class_set_props(dc, mos6522_q800_via1_properties);
+}
+
+static const TypeInfo mos6522_q800_via1_type_info = {
+ .name = TYPE_MOS6522_Q800_VIA1,
+ .parent = TYPE_MOS6522,
+ .instance_size = sizeof(MOS6522Q800VIA1State),
+ .instance_init = mos6522_q800_via1_init,
+ .class_init = mos6522_q800_via1_class_init,
+};
+
+/* VIA 2 */
+static void mos6522_q800_via2_portB_write(MOS6522State *s)
+{
+ if (s->dirb & VIA2B_vPower && (s->b & VIA2B_vPower) == 0) {
+ /* shutdown */
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ }
+}
+
+static void mos6522_q800_via2_reset(DeviceState *dev)
+{
+ MOS6522State *ms = MOS6522(dev);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms);
+
+ mdc->parent_reset(dev);
+
+ ms->timers[0].frequency = VIA_TIMER_FREQ;
+ ms->timers[1].frequency = VIA_TIMER_FREQ;
+
+ ms->dirb = 0;
+ ms->b = 0;
+ ms->dira = 0;
+ ms->a = 0x7f;
+}
+
+static void via2_nubus_irq_request(void *opaque, int irq, int level)
+{
+ MOS6522Q800VIA2State *v2s = opaque;
+ MOS6522State *s = MOS6522(v2s);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(s);
+
+ if (level) {
+ /* Port A nubus IRQ inputs are active LOW */
+ s->a &= ~(1 << irq);
+ s->ifr |= 1 << VIA2_IRQ_NUBUS_BIT;
+ } else {
+ s->a |= (1 << irq);
+ s->ifr &= ~(1 << VIA2_IRQ_NUBUS_BIT);
+ }
+
+ mdc->update_irq(s);
+}
+
+static void mos6522_q800_via2_init(Object *obj)
+{
+ MOS6522Q800VIA2State *v2s = MOS6522_Q800_VIA2(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(v2s);
+
+ memory_region_init_io(&v2s->via_mem, obj, &mos6522_q800_via2_ops, v2s,
+ "via2", VIA_SIZE);
+ sysbus_init_mmio(sbd, &v2s->via_mem);
+
+ qdev_init_gpio_in(DEVICE(obj), via2_irq_request, VIA2_IRQ_NB);
+
+ qdev_init_gpio_in_named(DEVICE(obj), via2_nubus_irq_request, "nubus-irq",
+ VIA2_NUBUS_IRQ_NB);
+}
+
+static const VMStateDescription vmstate_q800_via2 = {
+ .name = "q800-via2",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(parent_obj, MOS6522Q800VIA2State, 0, vmstate_mos6522,
+ MOS6522State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mos6522_q800_via2_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ MOS6522DeviceClass *mdc = MOS6522_CLASS(oc);
+
+ dc->reset = mos6522_q800_via2_reset;
+ dc->vmsd = &vmstate_q800_via2;
+ mdc->portB_write = mos6522_q800_via2_portB_write;
+}
+
+static const TypeInfo mos6522_q800_via2_type_info = {
+ .name = TYPE_MOS6522_Q800_VIA2,
+ .parent = TYPE_MOS6522,
+ .instance_size = sizeof(MOS6522Q800VIA2State),
+ .instance_init = mos6522_q800_via2_init,
+ .class_init = mos6522_q800_via2_class_init,
+};
+
+static void mac_via_register_types(void)
+{
+ type_register_static(&mos6522_q800_via1_type_info);
+ type_register_static(&mos6522_q800_via2_type_info);
+}
+
+type_init(mac_via_register_types);
diff --git a/hw/misc/macio/Kconfig b/hw/misc/macio/Kconfig
new file mode 100644
index 000000000..c6caeb672
--- /dev/null
+++ b/hw/misc/macio/Kconfig
@@ -0,0 +1,11 @@
+config CUDA
+ bool
+
+config MAC_PMU
+ bool
+
+config MAC_DBDMA
+ bool
+
+config MACIO_GPIO
+ bool
diff --git a/hw/misc/macio/cuda.c b/hw/misc/macio/cuda.c
new file mode 100644
index 000000000..e917a6a09
--- /dev/null
+++ b/hw/misc/macio/cuda.c
@@ -0,0 +1,629 @@
+/*
+ * QEMU PowerMac CUDA device support
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "hw/ppc/mac.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/input/adb.h"
+#include "hw/misc/mos6522.h"
+#include "hw/misc/macio/cuda.h"
+#include "qapi/error.h"
+#include "qemu/timer.h"
+#include "sysemu/runstate.h"
+#include "qapi/error.h"
+#include "qemu/cutils.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+/* Bits in B data register: all active low */
+#define TREQ 0x08 /* Transfer request (input) */
+#define TACK 0x10 /* Transfer acknowledge (output) */
+#define TIP 0x20 /* Transfer in progress (output) */
+
+/* commands (1st byte) */
+#define ADB_PACKET 0
+#define CUDA_PACKET 1
+#define ERROR_PACKET 2
+#define TIMER_PACKET 3
+#define POWER_PACKET 4
+#define MACIIC_PACKET 5
+#define PMU_PACKET 6
+
+#define CUDA_TIMER_FREQ (4700000 / 6)
+
+/* CUDA returns time_t's offset from Jan 1, 1904, not 1970 */
+#define RTC_OFFSET 2082844800
+
+static void cuda_receive_packet_from_host(CUDAState *s,
+ const uint8_t *data, int len);
+
+/* MacOS uses timer 1 for calibration on startup, so we use
+ * the timebase frequency and cuda_get_counter_value() with
+ * cuda_get_load_time() to steer MacOS to calculate calibrate its timers
+ * correctly for both TCG and KVM (see commit b981289c49 "PPC: Cuda: Use cuda
+ * timer to expose tbfreq to guest" for more information) */
+
+static uint64_t cuda_get_counter_value(MOS6522State *s, MOS6522Timer *ti)
+{
+ MOS6522CUDAState *mcs = container_of(s, MOS6522CUDAState, parent_obj);
+ CUDAState *cs = container_of(mcs, CUDAState, mos6522_cuda);
+
+ /* Reverse of the tb calculation algorithm that Mac OS X uses on bootup */
+ uint64_t tb_diff = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ cs->tb_frequency, NANOSECONDS_PER_SECOND) -
+ ti->load_time;
+
+ return (tb_diff * 0xBF401675E5DULL) / (cs->tb_frequency << 24);
+}
+
+static uint64_t cuda_get_load_time(MOS6522State *s, MOS6522Timer *ti)
+{
+ MOS6522CUDAState *mcs = container_of(s, MOS6522CUDAState, parent_obj);
+ CUDAState *cs = container_of(mcs, CUDAState, mos6522_cuda);
+
+ uint64_t load_time = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ cs->tb_frequency, NANOSECONDS_PER_SECOND);
+ return load_time;
+}
+
+static void cuda_set_sr_int(void *opaque)
+{
+ CUDAState *s = opaque;
+ MOS6522CUDAState *mcs = &s->mos6522_cuda;
+ MOS6522State *ms = MOS6522(mcs);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms);
+
+ mdc->set_sr_int(ms);
+}
+
+static void cuda_delay_set_sr_int(CUDAState *s)
+{
+ int64_t expire;
+
+ trace_cuda_delay_set_sr_int();
+
+ expire = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->sr_delay_ns;
+ timer_mod(s->sr_delay_timer, expire);
+}
+
+/* NOTE: TIP and TREQ are negated */
+static void cuda_update(CUDAState *s)
+{
+ MOS6522CUDAState *mcs = &s->mos6522_cuda;
+ MOS6522State *ms = MOS6522(mcs);
+ ADBBusState *adb_bus = &s->adb_bus;
+ int packet_received, len;
+
+ packet_received = 0;
+ if (!(ms->b & TIP)) {
+ /* transfer requested from host */
+
+ if (ms->acr & SR_OUT) {
+ /* data output */
+ if ((ms->b & (TACK | TIP)) != (s->last_b & (TACK | TIP))) {
+ if (s->data_out_index < sizeof(s->data_out)) {
+ if (s->data_out_index == 0) {
+ adb_autopoll_block(adb_bus);
+ }
+ trace_cuda_data_send(ms->sr);
+ s->data_out[s->data_out_index++] = ms->sr;
+ cuda_delay_set_sr_int(s);
+ }
+ }
+ } else {
+ if (s->data_in_index < s->data_in_size) {
+ /* data input */
+ if ((ms->b & (TACK | TIP)) != (s->last_b & (TACK | TIP))) {
+ ms->sr = s->data_in[s->data_in_index++];
+ trace_cuda_data_recv(ms->sr);
+ /* indicate end of transfer */
+ if (s->data_in_index >= s->data_in_size) {
+ ms->b = (ms->b | TREQ);
+ adb_autopoll_unblock(adb_bus);
+ }
+ cuda_delay_set_sr_int(s);
+ }
+ }
+ }
+ } else {
+ /* no transfer requested: handle sync case */
+ if ((s->last_b & TIP) && (ms->b & TACK) != (s->last_b & TACK)) {
+ /* update TREQ state each time TACK change state */
+ if (ms->b & TACK) {
+ ms->b = (ms->b | TREQ);
+ } else {
+ ms->b = (ms->b & ~TREQ);
+ }
+ cuda_delay_set_sr_int(s);
+ } else {
+ if (!(s->last_b & TIP)) {
+ /* handle end of host to cuda transfer */
+ packet_received = (s->data_out_index > 0);
+ /* always an IRQ at the end of transfer */
+ cuda_delay_set_sr_int(s);
+ }
+ /* signal if there is data to read */
+ if (s->data_in_index < s->data_in_size) {
+ ms->b = (ms->b & ~TREQ);
+ }
+ }
+ }
+
+ s->last_acr = ms->acr;
+ s->last_b = ms->b;
+
+ /* NOTE: cuda_receive_packet_from_host() can call cuda_update()
+ recursively */
+ if (packet_received) {
+ len = s->data_out_index;
+ s->data_out_index = 0;
+ cuda_receive_packet_from_host(s, s->data_out, len);
+ }
+}
+
+static void cuda_send_packet_to_host(CUDAState *s,
+ const uint8_t *data, int len)
+{
+ int i;
+
+ trace_cuda_packet_send(len);
+ for (i = 0; i < len; i++) {
+ trace_cuda_packet_send_data(i, data[i]);
+ }
+
+ memcpy(s->data_in, data, len);
+ s->data_in_size = len;
+ s->data_in_index = 0;
+ cuda_update(s);
+ cuda_delay_set_sr_int(s);
+}
+
+static void cuda_adb_poll(void *opaque)
+{
+ CUDAState *s = opaque;
+ ADBBusState *adb_bus = &s->adb_bus;
+ uint8_t obuf[ADB_MAX_OUT_LEN + 2];
+ int olen;
+
+ olen = adb_poll(adb_bus, obuf + 2, adb_bus->autopoll_mask);
+ if (olen > 0) {
+ obuf[0] = ADB_PACKET;
+ obuf[1] = 0x40; /* polled data */
+ cuda_send_packet_to_host(s, obuf, olen + 2);
+ }
+}
+
+/* description of commands */
+typedef struct CudaCommand {
+ uint8_t command;
+ const char *name;
+ bool (*handler)(CUDAState *s,
+ const uint8_t *in_args, int in_len,
+ uint8_t *out_args, int *out_len);
+} CudaCommand;
+
+static bool cuda_cmd_autopoll(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ ADBBusState *adb_bus = &s->adb_bus;
+ bool autopoll;
+
+ if (in_len != 1) {
+ return false;
+ }
+
+ autopoll = (in_data[0] != 0) ? true : false;
+
+ adb_set_autopoll_enabled(adb_bus, autopoll);
+ return true;
+}
+
+static bool cuda_cmd_set_autorate(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ ADBBusState *adb_bus = &s->adb_bus;
+
+ if (in_len != 1) {
+ return false;
+ }
+
+ /* we don't want a period of 0 ms */
+ /* FIXME: check what real hardware does */
+ if (in_data[0] == 0) {
+ return false;
+ }
+
+ adb_set_autopoll_rate_ms(adb_bus, in_data[0]);
+ return true;
+}
+
+static bool cuda_cmd_set_device_list(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ ADBBusState *adb_bus = &s->adb_bus;
+ uint16_t mask;
+
+ if (in_len != 2) {
+ return false;
+ }
+
+ mask = (((uint16_t)in_data[0]) << 8) | in_data[1];
+
+ adb_set_autopoll_mask(adb_bus, mask);
+ return true;
+}
+
+static bool cuda_cmd_powerdown(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ if (in_len != 0) {
+ return false;
+ }
+
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ return true;
+}
+
+static bool cuda_cmd_reset_system(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ if (in_len != 0) {
+ return false;
+ }
+
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ return true;
+}
+
+static bool cuda_cmd_set_file_server_flag(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ if (in_len != 1) {
+ return false;
+ }
+
+ qemu_log_mask(LOG_UNIMP,
+ "CUDA: unimplemented command FILE_SERVER_FLAG %d\n",
+ in_data[0]);
+ return true;
+}
+
+static bool cuda_cmd_set_power_message(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ if (in_len != 1) {
+ return false;
+ }
+
+ qemu_log_mask(LOG_UNIMP,
+ "CUDA: unimplemented command SET_POWER_MESSAGE %d\n",
+ in_data[0]);
+ return true;
+}
+
+static bool cuda_cmd_get_time(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ uint32_t ti;
+
+ if (in_len != 0) {
+ return false;
+ }
+
+ ti = s->tick_offset + (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
+ / NANOSECONDS_PER_SECOND);
+ out_data[0] = ti >> 24;
+ out_data[1] = ti >> 16;
+ out_data[2] = ti >> 8;
+ out_data[3] = ti;
+ *out_len = 4;
+ return true;
+}
+
+static bool cuda_cmd_set_time(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ uint32_t ti;
+
+ if (in_len != 4) {
+ return false;
+ }
+
+ ti = (((uint32_t)in_data[0]) << 24) + (((uint32_t)in_data[1]) << 16)
+ + (((uint32_t)in_data[2]) << 8) + in_data[3];
+ s->tick_offset = ti - (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
+ / NANOSECONDS_PER_SECOND);
+ return true;
+}
+
+static const CudaCommand handlers[] = {
+ { CUDA_AUTOPOLL, "AUTOPOLL", cuda_cmd_autopoll },
+ { CUDA_SET_AUTO_RATE, "SET_AUTO_RATE", cuda_cmd_set_autorate },
+ { CUDA_SET_DEVICE_LIST, "SET_DEVICE_LIST", cuda_cmd_set_device_list },
+ { CUDA_POWERDOWN, "POWERDOWN", cuda_cmd_powerdown },
+ { CUDA_RESET_SYSTEM, "RESET_SYSTEM", cuda_cmd_reset_system },
+ { CUDA_FILE_SERVER_FLAG, "FILE_SERVER_FLAG",
+ cuda_cmd_set_file_server_flag },
+ { CUDA_SET_POWER_MESSAGES, "SET_POWER_MESSAGES",
+ cuda_cmd_set_power_message },
+ { CUDA_GET_TIME, "GET_TIME", cuda_cmd_get_time },
+ { CUDA_SET_TIME, "SET_TIME", cuda_cmd_set_time },
+};
+
+static void cuda_receive_packet(CUDAState *s,
+ const uint8_t *data, int len)
+{
+ uint8_t obuf[16] = { CUDA_PACKET, 0, data[0] };
+ int i, out_len = 0;
+
+ for (i = 0; i < ARRAY_SIZE(handlers); i++) {
+ const CudaCommand *desc = &handlers[i];
+ if (desc->command == data[0]) {
+ trace_cuda_receive_packet_cmd(desc->name);
+ out_len = 0;
+ if (desc->handler(s, data + 1, len - 1, obuf + 3, &out_len)) {
+ cuda_send_packet_to_host(s, obuf, 3 + out_len);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "CUDA: %s: wrong parameters %d\n",
+ desc->name, len);
+ obuf[0] = ERROR_PACKET;
+ obuf[1] = 0x5; /* bad parameters */
+ obuf[2] = CUDA_PACKET;
+ obuf[3] = data[0];
+ cuda_send_packet_to_host(s, obuf, 4);
+ }
+ return;
+ }
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR, "CUDA: unknown command 0x%02x\n", data[0]);
+ obuf[0] = ERROR_PACKET;
+ obuf[1] = 0x2; /* unknown command */
+ obuf[2] = CUDA_PACKET;
+ obuf[3] = data[0];
+ cuda_send_packet_to_host(s, obuf, 4);
+}
+
+static void cuda_receive_packet_from_host(CUDAState *s,
+ const uint8_t *data, int len)
+{
+ int i;
+
+ trace_cuda_packet_receive(len);
+ for (i = 0; i < len; i++) {
+ trace_cuda_packet_receive_data(i, data[i]);
+ }
+
+ switch(data[0]) {
+ case ADB_PACKET:
+ {
+ uint8_t obuf[ADB_MAX_OUT_LEN + 3];
+ int olen;
+ olen = adb_request(&s->adb_bus, obuf + 2, data + 1, len - 1);
+ if (olen > 0) {
+ obuf[0] = ADB_PACKET;
+ obuf[1] = 0x00;
+ cuda_send_packet_to_host(s, obuf, olen + 2);
+ } else {
+ /* error */
+ obuf[0] = ADB_PACKET;
+ obuf[1] = -olen;
+ obuf[2] = data[1];
+ olen = 0;
+ cuda_send_packet_to_host(s, obuf, olen + 3);
+ }
+ }
+ break;
+ case CUDA_PACKET:
+ cuda_receive_packet(s, data + 1, len - 1);
+ break;
+ }
+}
+
+static uint64_t mos6522_cuda_read(void *opaque, hwaddr addr, unsigned size)
+{
+ CUDAState *s = opaque;
+ MOS6522CUDAState *mcs = &s->mos6522_cuda;
+ MOS6522State *ms = MOS6522(mcs);
+
+ addr = (addr >> 9) & 0xf;
+ return mos6522_read(ms, addr, size);
+}
+
+static void mos6522_cuda_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ CUDAState *s = opaque;
+ MOS6522CUDAState *mcs = &s->mos6522_cuda;
+ MOS6522State *ms = MOS6522(mcs);
+
+ addr = (addr >> 9) & 0xf;
+ mos6522_write(ms, addr, val, size);
+}
+
+static const MemoryRegionOps mos6522_cuda_ops = {
+ .read = mos6522_cuda_read,
+ .write = mos6522_cuda_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const VMStateDescription vmstate_cuda = {
+ .name = "cuda",
+ .version_id = 6,
+ .minimum_version_id = 6,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(mos6522_cuda.parent_obj, CUDAState, 0, vmstate_mos6522,
+ MOS6522State),
+ VMSTATE_UINT8(last_b, CUDAState),
+ VMSTATE_UINT8(last_acr, CUDAState),
+ VMSTATE_INT32(data_in_size, CUDAState),
+ VMSTATE_INT32(data_in_index, CUDAState),
+ VMSTATE_INT32(data_out_index, CUDAState),
+ VMSTATE_BUFFER(data_in, CUDAState),
+ VMSTATE_BUFFER(data_out, CUDAState),
+ VMSTATE_UINT32(tick_offset, CUDAState),
+ VMSTATE_TIMER_PTR(sr_delay_timer, CUDAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cuda_reset(DeviceState *dev)
+{
+ CUDAState *s = CUDA(dev);
+ ADBBusState *adb_bus = &s->adb_bus;
+
+ s->data_in_size = 0;
+ s->data_in_index = 0;
+ s->data_out_index = 0;
+
+ adb_set_autopoll_enabled(adb_bus, false);
+}
+
+static void cuda_realize(DeviceState *dev, Error **errp)
+{
+ CUDAState *s = CUDA(dev);
+ SysBusDevice *sbd;
+ ADBBusState *adb_bus = &s->adb_bus;
+ struct tm tm;
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->mos6522_cuda), errp)) {
+ return;
+ }
+
+ /* Pass IRQ from 6522 */
+ sbd = SYS_BUS_DEVICE(s);
+ sysbus_pass_irq(sbd, SYS_BUS_DEVICE(&s->mos6522_cuda));
+
+ qemu_get_timedate(&tm, 0);
+ s->tick_offset = (uint32_t)mktimegm(&tm) + RTC_OFFSET;
+
+ s->sr_delay_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, cuda_set_sr_int, s);
+ s->sr_delay_ns = 20 * SCALE_US;
+
+ adb_register_autopoll_callback(adb_bus, cuda_adb_poll, s);
+}
+
+static void cuda_init(Object *obj)
+{
+ CUDAState *s = CUDA(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ object_initialize_child(obj, "mos6522-cuda", &s->mos6522_cuda,
+ TYPE_MOS6522_CUDA);
+
+ memory_region_init_io(&s->mem, obj, &mos6522_cuda_ops, s, "cuda", 0x2000);
+ sysbus_init_mmio(sbd, &s->mem);
+
+ qbus_init(&s->adb_bus, sizeof(s->adb_bus), TYPE_ADB_BUS,
+ DEVICE(obj), "adb.0");
+}
+
+static Property cuda_properties[] = {
+ DEFINE_PROP_UINT64("timebase-frequency", CUDAState, tb_frequency, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void cuda_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = cuda_realize;
+ dc->reset = cuda_reset;
+ dc->vmsd = &vmstate_cuda;
+ device_class_set_props(dc, cuda_properties);
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo cuda_type_info = {
+ .name = TYPE_CUDA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(CUDAState),
+ .instance_init = cuda_init,
+ .class_init = cuda_class_init,
+};
+
+static void mos6522_cuda_portB_write(MOS6522State *s)
+{
+ MOS6522CUDAState *mcs = container_of(s, MOS6522CUDAState, parent_obj);
+ CUDAState *cs = container_of(mcs, CUDAState, mos6522_cuda);
+
+ cuda_update(cs);
+}
+
+static void mos6522_cuda_reset(DeviceState *dev)
+{
+ MOS6522State *ms = MOS6522(dev);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms);
+
+ mdc->parent_reset(dev);
+
+ ms->timers[0].frequency = CUDA_TIMER_FREQ;
+ ms->timers[1].frequency = (SCALE_US * 6000) / 4700;
+}
+
+static void mos6522_cuda_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ MOS6522DeviceClass *mdc = MOS6522_CLASS(oc);
+
+ dc->reset = mos6522_cuda_reset;
+ mdc->portB_write = mos6522_cuda_portB_write;
+ mdc->get_timer1_counter_value = cuda_get_counter_value;
+ mdc->get_timer2_counter_value = cuda_get_counter_value;
+ mdc->get_timer1_load_time = cuda_get_load_time;
+ mdc->get_timer2_load_time = cuda_get_load_time;
+}
+
+static const TypeInfo mos6522_cuda_type_info = {
+ .name = TYPE_MOS6522_CUDA,
+ .parent = TYPE_MOS6522,
+ .instance_size = sizeof(MOS6522CUDAState),
+ .class_init = mos6522_cuda_class_init,
+};
+
+static void cuda_register_types(void)
+{
+ type_register_static(&mos6522_cuda_type_info);
+ type_register_static(&cuda_type_info);
+}
+
+type_init(cuda_register_types)
diff --git a/hw/misc/macio/gpio.c b/hw/misc/macio/gpio.c
new file mode 100644
index 000000000..b1bcf830c
--- /dev/null
+++ b/hw/misc/macio/gpio.c
@@ -0,0 +1,219 @@
+/*
+ * PowerMac NewWorld MacIO GPIO emulation
+ *
+ * Copyright (c) 2016 Benjamin Herrenschmidt
+ * Copyright (c) 2018 Mark Cave-Ayland
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/ppc/mac.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/misc/macio/macio.h"
+#include "hw/misc/macio/gpio.h"
+#include "hw/nmi.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+
+void macio_set_gpio(MacIOGPIOState *s, uint32_t gpio, bool state)
+{
+ uint8_t new_reg;
+
+ trace_macio_set_gpio(gpio, state);
+
+ if (s->gpio_regs[gpio] & 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "GPIO: Setting GPIO %d while it's an output\n", gpio);
+ }
+
+ new_reg = s->gpio_regs[gpio] & ~2;
+ if (state) {
+ new_reg |= 2;
+ }
+
+ if (new_reg == s->gpio_regs[gpio]) {
+ return;
+ }
+
+ s->gpio_regs[gpio] = new_reg;
+
+ /*
+ * Note that we probably need to get access to the MPIC config to
+ * decode polarity since qemu always use "raise" regardless.
+ *
+ * For now, we hard wire known GPIOs
+ */
+
+ switch (gpio) {
+ case 1:
+ /* Level low */
+ if (!state) {
+ trace_macio_gpio_irq_assert(gpio);
+ qemu_irq_raise(s->gpio_extirqs[gpio]);
+ } else {
+ trace_macio_gpio_irq_deassert(gpio);
+ qemu_irq_lower(s->gpio_extirqs[gpio]);
+ }
+ break;
+
+ case 9:
+ /* Edge, triggered by NMI below */
+ if (state) {
+ trace_macio_gpio_irq_assert(gpio);
+ qemu_irq_raise(s->gpio_extirqs[gpio]);
+ } else {
+ trace_macio_gpio_irq_deassert(gpio);
+ qemu_irq_lower(s->gpio_extirqs[gpio]);
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "GPIO: setting unimplemented GPIO %d", gpio);
+ }
+}
+
+static void macio_gpio_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MacIOGPIOState *s = opaque;
+ uint8_t ibit;
+
+ trace_macio_gpio_write(addr, value);
+
+ /* Levels regs are read-only */
+ if (addr < 8) {
+ return;
+ }
+
+ addr -= 8;
+ if (addr < 36) {
+ value &= ~2;
+
+ if (value & 4) {
+ ibit = (value & 1) << 1;
+ } else {
+ ibit = s->gpio_regs[addr] & 2;
+ }
+
+ s->gpio_regs[addr] = value | ibit;
+ }
+}
+
+static uint64_t macio_gpio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MacIOGPIOState *s = opaque;
+ uint64_t val = 0;
+
+ /* Levels regs */
+ if (addr < 8) {
+ val = s->gpio_levels[addr];
+ } else {
+ addr -= 8;
+
+ if (addr < 36) {
+ val = s->gpio_regs[addr];
+ }
+ }
+
+ trace_macio_gpio_write(addr, val);
+ return val;
+}
+
+static const MemoryRegionOps macio_gpio_ops = {
+ .read = macio_gpio_read,
+ .write = macio_gpio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void macio_gpio_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ MacIOGPIOState *s = MACIO_GPIO(obj);
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ sysbus_init_irq(sbd, &s->gpio_extirqs[i]);
+ }
+
+ memory_region_init_io(&s->gpiomem, OBJECT(s), &macio_gpio_ops, obj,
+ "gpio", 0x30);
+ sysbus_init_mmio(sbd, &s->gpiomem);
+}
+
+static const VMStateDescription vmstate_macio_gpio = {
+ .name = "macio_gpio",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(gpio_levels, MacIOGPIOState, 8),
+ VMSTATE_UINT8_ARRAY(gpio_regs, MacIOGPIOState, 36),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void macio_gpio_reset(DeviceState *dev)
+{
+ MacIOGPIOState *s = MACIO_GPIO(dev);
+
+ /* GPIO 1 is up by default */
+ macio_set_gpio(s, 1, true);
+}
+
+static void macio_gpio_nmi(NMIState *n, int cpu_index, Error **errp)
+{
+ macio_set_gpio(MACIO_GPIO(n), 9, true);
+ macio_set_gpio(MACIO_GPIO(n), 9, false);
+}
+
+static void macio_gpio_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ NMIClass *nc = NMI_CLASS(oc);
+
+ dc->reset = macio_gpio_reset;
+ dc->vmsd = &vmstate_macio_gpio;
+ nc->nmi_monitor_handler = macio_gpio_nmi;
+}
+
+static const TypeInfo macio_gpio_init_info = {
+ .name = TYPE_MACIO_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MacIOGPIOState),
+ .instance_init = macio_gpio_init,
+ .class_init = macio_gpio_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_NMI },
+ { }
+ },
+};
+
+static void macio_gpio_register_types(void)
+{
+ type_register_static(&macio_gpio_init_info);
+}
+
+type_init(macio_gpio_register_types)
diff --git a/hw/misc/macio/mac_dbdma.c b/hw/misc/macio/mac_dbdma.c
new file mode 100644
index 000000000..e220f1a92
--- /dev/null
+++ b/hw/misc/macio/mac_dbdma.c
@@ -0,0 +1,940 @@
+/*
+ * PowerMac descriptor-based DMA emulation
+ *
+ * Copyright (c) 2005-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ * Copyright (c) 2009 Laurent Vivier
+ *
+ * some parts from linux-2.6.28, arch/powerpc/include/asm/dbdma.h
+ *
+ * Definitions for using the Apple Descriptor-Based DMA controller
+ * in Power Macintosh computers.
+ *
+ * Copyright (C) 1996 Paul Mackerras.
+ *
+ * some parts from mol 0.9.71
+ *
+ * Descriptor based DMA emulation
+ *
+ * Copyright (C) 1998-2004 Samuel Rydh (samuel@ibrium.se)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/ppc/mac_dbdma.h"
+#include "migration/vmstate.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "sysemu/dma.h"
+
+/* debug DBDMA */
+#define DEBUG_DBDMA 0
+#define DEBUG_DBDMA_CHANMASK ((1ull << DBDMA_CHANNELS) - 1)
+
+#define DBDMA_DPRINTF(fmt, ...) do { \
+ if (DEBUG_DBDMA) { \
+ printf("DBDMA: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0)
+
+#define DBDMA_DPRINTFCH(ch, fmt, ...) do { \
+ if (DEBUG_DBDMA) { \
+ if ((1ul << (ch)->channel) & DEBUG_DBDMA_CHANMASK) { \
+ printf("DBDMA[%02x]: " fmt , (ch)->channel, ## __VA_ARGS__); \
+ } \
+ } \
+} while (0)
+
+/*
+ */
+
+static DBDMAState *dbdma_from_ch(DBDMA_channel *ch)
+{
+ return container_of(ch, DBDMAState, channels[ch->channel]);
+}
+
+#if DEBUG_DBDMA
+static void dump_dbdma_cmd(DBDMA_channel *ch, dbdma_cmd *cmd)
+{
+ DBDMA_DPRINTFCH(ch, "dbdma_cmd %p\n", cmd);
+ DBDMA_DPRINTFCH(ch, " req_count 0x%04x\n", le16_to_cpu(cmd->req_count));
+ DBDMA_DPRINTFCH(ch, " command 0x%04x\n", le16_to_cpu(cmd->command));
+ DBDMA_DPRINTFCH(ch, " phy_addr 0x%08x\n", le32_to_cpu(cmd->phy_addr));
+ DBDMA_DPRINTFCH(ch, " cmd_dep 0x%08x\n", le32_to_cpu(cmd->cmd_dep));
+ DBDMA_DPRINTFCH(ch, " res_count 0x%04x\n", le16_to_cpu(cmd->res_count));
+ DBDMA_DPRINTFCH(ch, " xfer_status 0x%04x\n",
+ le16_to_cpu(cmd->xfer_status));
+}
+#else
+static void dump_dbdma_cmd(DBDMA_channel *ch, dbdma_cmd *cmd)
+{
+}
+#endif
+static void dbdma_cmdptr_load(DBDMA_channel *ch)
+{
+ DBDMA_DPRINTFCH(ch, "dbdma_cmdptr_load 0x%08x\n",
+ ch->regs[DBDMA_CMDPTR_LO]);
+ dma_memory_read(&address_space_memory, ch->regs[DBDMA_CMDPTR_LO],
+ &ch->current, sizeof(dbdma_cmd));
+}
+
+static void dbdma_cmdptr_save(DBDMA_channel *ch)
+{
+ DBDMA_DPRINTFCH(ch, "-> update 0x%08x stat=0x%08x, res=0x%04x\n",
+ ch->regs[DBDMA_CMDPTR_LO],
+ le16_to_cpu(ch->current.xfer_status),
+ le16_to_cpu(ch->current.res_count));
+ dma_memory_write(&address_space_memory, ch->regs[DBDMA_CMDPTR_LO],
+ &ch->current, sizeof(dbdma_cmd));
+}
+
+static void kill_channel(DBDMA_channel *ch)
+{
+ DBDMA_DPRINTFCH(ch, "kill_channel\n");
+
+ ch->regs[DBDMA_STATUS] |= DEAD;
+ ch->regs[DBDMA_STATUS] &= ~ACTIVE;
+
+ qemu_irq_raise(ch->irq);
+}
+
+static void conditional_interrupt(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t intr;
+ uint16_t sel_mask, sel_value;
+ uint32_t status;
+ int cond;
+
+ DBDMA_DPRINTFCH(ch, "%s\n", __func__);
+
+ intr = le16_to_cpu(current->command) & INTR_MASK;
+
+ switch(intr) {
+ case INTR_NEVER: /* don't interrupt */
+ return;
+ case INTR_ALWAYS: /* always interrupt */
+ qemu_irq_raise(ch->irq);
+ DBDMA_DPRINTFCH(ch, "%s: raise\n", __func__);
+ return;
+ }
+
+ status = ch->regs[DBDMA_STATUS] & DEVSTAT;
+
+ sel_mask = (ch->regs[DBDMA_INTR_SEL] >> 16) & 0x0f;
+ sel_value = ch->regs[DBDMA_INTR_SEL] & 0x0f;
+
+ cond = (status & sel_mask) == (sel_value & sel_mask);
+
+ switch(intr) {
+ case INTR_IFSET: /* intr if condition bit is 1 */
+ if (cond) {
+ qemu_irq_raise(ch->irq);
+ DBDMA_DPRINTFCH(ch, "%s: raise\n", __func__);
+ }
+ return;
+ case INTR_IFCLR: /* intr if condition bit is 0 */
+ if (!cond) {
+ qemu_irq_raise(ch->irq);
+ DBDMA_DPRINTFCH(ch, "%s: raise\n", __func__);
+ }
+ return;
+ }
+}
+
+static int conditional_wait(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t wait;
+ uint16_t sel_mask, sel_value;
+ uint32_t status;
+ int cond;
+ int res = 0;
+
+ wait = le16_to_cpu(current->command) & WAIT_MASK;
+ switch(wait) {
+ case WAIT_NEVER: /* don't wait */
+ return 0;
+ case WAIT_ALWAYS: /* always wait */
+ DBDMA_DPRINTFCH(ch, " [WAIT_ALWAYS]\n");
+ return 1;
+ }
+
+ status = ch->regs[DBDMA_STATUS] & DEVSTAT;
+
+ sel_mask = (ch->regs[DBDMA_WAIT_SEL] >> 16) & 0x0f;
+ sel_value = ch->regs[DBDMA_WAIT_SEL] & 0x0f;
+
+ cond = (status & sel_mask) == (sel_value & sel_mask);
+
+ switch(wait) {
+ case WAIT_IFSET: /* wait if condition bit is 1 */
+ if (cond) {
+ res = 1;
+ }
+ DBDMA_DPRINTFCH(ch, " [WAIT_IFSET=%d]\n", res);
+ break;
+ case WAIT_IFCLR: /* wait if condition bit is 0 */
+ if (!cond) {
+ res = 1;
+ }
+ DBDMA_DPRINTFCH(ch, " [WAIT_IFCLR=%d]\n", res);
+ break;
+ }
+ return res;
+}
+
+static void next(DBDMA_channel *ch)
+{
+ uint32_t cp;
+
+ ch->regs[DBDMA_STATUS] &= ~BT;
+
+ cp = ch->regs[DBDMA_CMDPTR_LO];
+ ch->regs[DBDMA_CMDPTR_LO] = cp + sizeof(dbdma_cmd);
+ dbdma_cmdptr_load(ch);
+}
+
+static void branch(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+
+ ch->regs[DBDMA_CMDPTR_LO] = le32_to_cpu(current->cmd_dep);
+ ch->regs[DBDMA_STATUS] |= BT;
+ dbdma_cmdptr_load(ch);
+}
+
+static void conditional_branch(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t br;
+ uint16_t sel_mask, sel_value;
+ uint32_t status;
+ int cond;
+
+ /* check if we must branch */
+
+ br = le16_to_cpu(current->command) & BR_MASK;
+
+ switch(br) {
+ case BR_NEVER: /* don't branch */
+ next(ch);
+ return;
+ case BR_ALWAYS: /* always branch */
+ DBDMA_DPRINTFCH(ch, " [BR_ALWAYS]\n");
+ branch(ch);
+ return;
+ }
+
+ status = ch->regs[DBDMA_STATUS] & DEVSTAT;
+
+ sel_mask = (ch->regs[DBDMA_BRANCH_SEL] >> 16) & 0x0f;
+ sel_value = ch->regs[DBDMA_BRANCH_SEL] & 0x0f;
+
+ cond = (status & sel_mask) == (sel_value & sel_mask);
+
+ switch(br) {
+ case BR_IFSET: /* branch if condition bit is 1 */
+ if (cond) {
+ DBDMA_DPRINTFCH(ch, " [BR_IFSET = 1]\n");
+ branch(ch);
+ } else {
+ DBDMA_DPRINTFCH(ch, " [BR_IFSET = 0]\n");
+ next(ch);
+ }
+ return;
+ case BR_IFCLR: /* branch if condition bit is 0 */
+ if (!cond) {
+ DBDMA_DPRINTFCH(ch, " [BR_IFCLR = 1]\n");
+ branch(ch);
+ } else {
+ DBDMA_DPRINTFCH(ch, " [BR_IFCLR = 0]\n");
+ next(ch);
+ }
+ return;
+ }
+}
+
+static void channel_run(DBDMA_channel *ch);
+
+static void dbdma_end(DBDMA_io *io)
+{
+ DBDMA_channel *ch = io->channel;
+ dbdma_cmd *current = &ch->current;
+
+ DBDMA_DPRINTFCH(ch, "%s\n", __func__);
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ current->res_count = cpu_to_le16(io->len);
+ dbdma_cmdptr_save(ch);
+ if (io->is_last)
+ ch->regs[DBDMA_STATUS] &= ~FLUSH;
+
+ conditional_interrupt(ch);
+ conditional_branch(ch);
+
+wait:
+ /* Indicate that we're ready for a new DMA round */
+ ch->io.processing = false;
+
+ if ((ch->regs[DBDMA_STATUS] & RUN) &&
+ (ch->regs[DBDMA_STATUS] & ACTIVE))
+ channel_run(ch);
+}
+
+static void start_output(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t req_count, int is_last)
+{
+ DBDMA_DPRINTFCH(ch, "start_output\n");
+
+ /* KEY_REGS, KEY_DEVICE and KEY_STREAM
+ * are not implemented in the mac-io chip
+ */
+
+ DBDMA_DPRINTFCH(ch, "addr 0x%x key 0x%x\n", addr, key);
+ if (!addr || key > KEY_STREAM3) {
+ kill_channel(ch);
+ return;
+ }
+
+ ch->io.addr = addr;
+ ch->io.len = req_count;
+ ch->io.is_last = is_last;
+ ch->io.dma_end = dbdma_end;
+ ch->io.is_dma_out = 1;
+ ch->io.processing = true;
+ if (ch->rw) {
+ ch->rw(&ch->io);
+ }
+}
+
+static void start_input(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t req_count, int is_last)
+{
+ DBDMA_DPRINTFCH(ch, "start_input\n");
+
+ /* KEY_REGS, KEY_DEVICE and KEY_STREAM
+ * are not implemented in the mac-io chip
+ */
+
+ DBDMA_DPRINTFCH(ch, "addr 0x%x key 0x%x\n", addr, key);
+ if (!addr || key > KEY_STREAM3) {
+ kill_channel(ch);
+ return;
+ }
+
+ ch->io.addr = addr;
+ ch->io.len = req_count;
+ ch->io.is_last = is_last;
+ ch->io.dma_end = dbdma_end;
+ ch->io.is_dma_out = 0;
+ ch->io.processing = true;
+ if (ch->rw) {
+ ch->rw(&ch->io);
+ }
+}
+
+static void load_word(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t len)
+{
+ dbdma_cmd *current = &ch->current;
+
+ DBDMA_DPRINTFCH(ch, "load_word %d bytes, addr=%08x\n", len, addr);
+
+ /* only implements KEY_SYSTEM */
+
+ if (key != KEY_SYSTEM) {
+ printf("DBDMA: LOAD_WORD, unimplemented key %x\n", key);
+ kill_channel(ch);
+ return;
+ }
+
+ dma_memory_read(&address_space_memory, addr, &current->cmd_dep, len);
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ dbdma_cmdptr_save(ch);
+ ch->regs[DBDMA_STATUS] &= ~FLUSH;
+
+ conditional_interrupt(ch);
+ next(ch);
+
+wait:
+ DBDMA_kick(dbdma_from_ch(ch));
+}
+
+static void store_word(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t len)
+{
+ dbdma_cmd *current = &ch->current;
+
+ DBDMA_DPRINTFCH(ch, "store_word %d bytes, addr=%08x pa=%x\n",
+ len, addr, le32_to_cpu(current->cmd_dep));
+
+ /* only implements KEY_SYSTEM */
+
+ if (key != KEY_SYSTEM) {
+ printf("DBDMA: STORE_WORD, unimplemented key %x\n", key);
+ kill_channel(ch);
+ return;
+ }
+
+ dma_memory_write(&address_space_memory, addr, &current->cmd_dep, len);
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ dbdma_cmdptr_save(ch);
+ ch->regs[DBDMA_STATUS] &= ~FLUSH;
+
+ conditional_interrupt(ch);
+ next(ch);
+
+wait:
+ DBDMA_kick(dbdma_from_ch(ch));
+}
+
+static void nop(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ dbdma_cmdptr_save(ch);
+
+ conditional_interrupt(ch);
+ conditional_branch(ch);
+
+wait:
+ DBDMA_kick(dbdma_from_ch(ch));
+}
+
+static void stop(DBDMA_channel *ch)
+{
+ ch->regs[DBDMA_STATUS] &= ~(ACTIVE);
+
+ /* the stop command does not increment command pointer */
+}
+
+static void channel_run(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t cmd, key;
+ uint16_t req_count;
+ uint32_t phy_addr;
+
+ DBDMA_DPRINTFCH(ch, "channel_run\n");
+ dump_dbdma_cmd(ch, current);
+
+ /* clear WAKE flag at command fetch */
+
+ ch->regs[DBDMA_STATUS] &= ~WAKE;
+
+ cmd = le16_to_cpu(current->command) & COMMAND_MASK;
+
+ switch (cmd) {
+ case DBDMA_NOP:
+ nop(ch);
+ return;
+
+ case DBDMA_STOP:
+ stop(ch);
+ return;
+ }
+
+ key = le16_to_cpu(current->command) & 0x0700;
+ req_count = le16_to_cpu(current->req_count);
+ phy_addr = le32_to_cpu(current->phy_addr);
+
+ if (key == KEY_STREAM4) {
+ printf("command %x, invalid key 4\n", cmd);
+ kill_channel(ch);
+ return;
+ }
+
+ switch (cmd) {
+ case OUTPUT_MORE:
+ DBDMA_DPRINTFCH(ch, "* OUTPUT_MORE *\n");
+ start_output(ch, key, phy_addr, req_count, 0);
+ return;
+
+ case OUTPUT_LAST:
+ DBDMA_DPRINTFCH(ch, "* OUTPUT_LAST *\n");
+ start_output(ch, key, phy_addr, req_count, 1);
+ return;
+
+ case INPUT_MORE:
+ DBDMA_DPRINTFCH(ch, "* INPUT_MORE *\n");
+ start_input(ch, key, phy_addr, req_count, 0);
+ return;
+
+ case INPUT_LAST:
+ DBDMA_DPRINTFCH(ch, "* INPUT_LAST *\n");
+ start_input(ch, key, phy_addr, req_count, 1);
+ return;
+ }
+
+ if (key < KEY_REGS) {
+ printf("command %x, invalid key %x\n", cmd, key);
+ key = KEY_SYSTEM;
+ }
+
+ /* for LOAD_WORD and STORE_WORD, req_count is on 3 bits
+ * and BRANCH is invalid
+ */
+
+ req_count = req_count & 0x0007;
+ if (req_count & 0x4) {
+ req_count = 4;
+ phy_addr &= ~3;
+ } else if (req_count & 0x2) {
+ req_count = 2;
+ phy_addr &= ~1;
+ } else
+ req_count = 1;
+
+ switch (cmd) {
+ case LOAD_WORD:
+ DBDMA_DPRINTFCH(ch, "* LOAD_WORD *\n");
+ load_word(ch, key, phy_addr, req_count);
+ return;
+
+ case STORE_WORD:
+ DBDMA_DPRINTFCH(ch, "* STORE_WORD *\n");
+ store_word(ch, key, phy_addr, req_count);
+ return;
+ }
+}
+
+static void DBDMA_run(DBDMAState *s)
+{
+ int channel;
+
+ for (channel = 0; channel < DBDMA_CHANNELS; channel++) {
+ DBDMA_channel *ch = &s->channels[channel];
+ uint32_t status = ch->regs[DBDMA_STATUS];
+ if (!ch->io.processing && (status & RUN) && (status & ACTIVE)) {
+ channel_run(ch);
+ }
+ }
+}
+
+static void DBDMA_run_bh(void *opaque)
+{
+ DBDMAState *s = opaque;
+
+ DBDMA_DPRINTF("-> DBDMA_run_bh\n");
+ DBDMA_run(s);
+ DBDMA_DPRINTF("<- DBDMA_run_bh\n");
+}
+
+void DBDMA_kick(DBDMAState *dbdma)
+{
+ qemu_bh_schedule(dbdma->bh);
+}
+
+void DBDMA_register_channel(void *dbdma, int nchan, qemu_irq irq,
+ DBDMA_rw rw, DBDMA_flush flush,
+ void *opaque)
+{
+ DBDMAState *s = dbdma;
+ DBDMA_channel *ch = &s->channels[nchan];
+
+ DBDMA_DPRINTFCH(ch, "DBDMA_register_channel 0x%x\n", nchan);
+
+ assert(rw);
+ assert(flush);
+
+ ch->irq = irq;
+ ch->rw = rw;
+ ch->flush = flush;
+ ch->io.opaque = opaque;
+}
+
+static void dbdma_control_write(DBDMA_channel *ch)
+{
+ uint16_t mask, value;
+ uint32_t status;
+ bool do_flush = false;
+
+ mask = (ch->regs[DBDMA_CONTROL] >> 16) & 0xffff;
+ value = ch->regs[DBDMA_CONTROL] & 0xffff;
+
+ /* This is the status register which we'll update
+ * appropriately and store back
+ */
+ status = ch->regs[DBDMA_STATUS];
+
+ /* RUN and PAUSE are bits under SW control only
+ * FLUSH and WAKE are set by SW and cleared by HW
+ * DEAD, ACTIVE and BT are only under HW control
+ *
+ * We handle ACTIVE separately at the end of the
+ * logic to ensure all cases are covered.
+ */
+
+ /* Setting RUN will tentatively activate the channel
+ */
+ if ((mask & RUN) && (value & RUN)) {
+ status |= RUN;
+ DBDMA_DPRINTFCH(ch, " Setting RUN !\n");
+ }
+
+ /* Clearing RUN 1->0 will stop the channel */
+ if ((mask & RUN) && !(value & RUN)) {
+ /* This has the side effect of clearing the DEAD bit */
+ status &= ~(DEAD | RUN);
+ DBDMA_DPRINTFCH(ch, " Clearing RUN !\n");
+ }
+
+ /* Setting WAKE wakes up an idle channel if it's running
+ *
+ * Note: The doc doesn't say so but assume that only works
+ * on a channel whose RUN bit is set.
+ *
+ * We set WAKE in status, it's not terribly useful as it will
+ * be cleared on the next command fetch but it seems to mimmic
+ * the HW behaviour and is useful for the way we handle
+ * ACTIVE further down.
+ */
+ if ((mask & WAKE) && (value & WAKE) && (status & RUN)) {
+ status |= WAKE;
+ DBDMA_DPRINTFCH(ch, " Setting WAKE !\n");
+ }
+
+ /* PAUSE being set will deactivate (or prevent activation)
+ * of the channel. We just copy it over for now, ACTIVE will
+ * be re-evaluated later.
+ */
+ if (mask & PAUSE) {
+ status = (status & ~PAUSE) | (value & PAUSE);
+ DBDMA_DPRINTFCH(ch, " %sing PAUSE !\n",
+ (value & PAUSE) ? "sett" : "clear");
+ }
+
+ /* FLUSH is its own thing */
+ if ((mask & FLUSH) && (value & FLUSH)) {
+ DBDMA_DPRINTFCH(ch, " Setting FLUSH !\n");
+ /* We set flush directly in the status register, we do *NOT*
+ * set it in "status" so that it gets naturally cleared when
+ * we update the status register further down. That way it
+ * will be set only during the HW flush operation so it is
+ * visible to any completions happening during that time.
+ */
+ ch->regs[DBDMA_STATUS] |= FLUSH;
+ do_flush = true;
+ }
+
+ /* If either RUN or PAUSE is clear, so should ACTIVE be,
+ * otherwise, ACTIVE will be set if we modified RUN, PAUSE or
+ * set WAKE. That means that PAUSE was just cleared, RUN was
+ * just set or WAKE was just set.
+ */
+ if ((status & PAUSE) || !(status & RUN)) {
+ status &= ~ACTIVE;
+ DBDMA_DPRINTFCH(ch, " -> ACTIVE down !\n");
+
+ /* We stopped processing, we want the underlying HW command
+ * to complete *before* we clear the ACTIVE bit. Otherwise
+ * we can get into a situation where the command status will
+ * have RUN or ACTIVE not set which is going to confuse the
+ * MacOS driver.
+ */
+ do_flush = true;
+ } else if (mask & (RUN | PAUSE)) {
+ status |= ACTIVE;
+ DBDMA_DPRINTFCH(ch, " -> ACTIVE up !\n");
+ } else if ((mask & WAKE) && (value & WAKE)) {
+ status |= ACTIVE;
+ DBDMA_DPRINTFCH(ch, " -> ACTIVE up !\n");
+ }
+
+ DBDMA_DPRINTFCH(ch, " new status=0x%08x\n", status);
+
+ /* If we need to flush the underlying HW, do it now, this happens
+ * both on FLUSH commands and when stopping the channel for safety.
+ */
+ if (do_flush && ch->flush) {
+ ch->flush(&ch->io);
+ }
+
+ /* Finally update the status register image */
+ ch->regs[DBDMA_STATUS] = status;
+
+ /* If active, make sure the BH gets to run */
+ if (status & ACTIVE) {
+ DBDMA_kick(dbdma_from_ch(ch));
+ }
+}
+
+static void dbdma_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ int channel = addr >> DBDMA_CHANNEL_SHIFT;
+ DBDMAState *s = opaque;
+ DBDMA_channel *ch = &s->channels[channel];
+ int reg = (addr - (channel << DBDMA_CHANNEL_SHIFT)) >> 2;
+
+ DBDMA_DPRINTFCH(ch, "writel 0x" TARGET_FMT_plx " <= 0x%08"PRIx64"\n",
+ addr, value);
+ DBDMA_DPRINTFCH(ch, "channel 0x%x reg 0x%x\n",
+ (uint32_t)addr >> DBDMA_CHANNEL_SHIFT, reg);
+
+ /* cmdptr cannot be modified if channel is ACTIVE */
+
+ if (reg == DBDMA_CMDPTR_LO && (ch->regs[DBDMA_STATUS] & ACTIVE)) {
+ return;
+ }
+
+ ch->regs[reg] = value;
+
+ switch(reg) {
+ case DBDMA_CONTROL:
+ dbdma_control_write(ch);
+ break;
+ case DBDMA_CMDPTR_LO:
+ /* 16-byte aligned */
+ ch->regs[DBDMA_CMDPTR_LO] &= ~0xf;
+ dbdma_cmdptr_load(ch);
+ break;
+ case DBDMA_STATUS:
+ case DBDMA_INTR_SEL:
+ case DBDMA_BRANCH_SEL:
+ case DBDMA_WAIT_SEL:
+ /* nothing to do */
+ break;
+ case DBDMA_XFER_MODE:
+ case DBDMA_CMDPTR_HI:
+ case DBDMA_DATA2PTR_HI:
+ case DBDMA_DATA2PTR_LO:
+ case DBDMA_ADDRESS_HI:
+ case DBDMA_BRANCH_ADDR_HI:
+ case DBDMA_RES1:
+ case DBDMA_RES2:
+ case DBDMA_RES3:
+ case DBDMA_RES4:
+ /* unused */
+ break;
+ }
+}
+
+static uint64_t dbdma_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint32_t value;
+ int channel = addr >> DBDMA_CHANNEL_SHIFT;
+ DBDMAState *s = opaque;
+ DBDMA_channel *ch = &s->channels[channel];
+ int reg = (addr - (channel << DBDMA_CHANNEL_SHIFT)) >> 2;
+
+ value = ch->regs[reg];
+
+ switch(reg) {
+ case DBDMA_CONTROL:
+ value = ch->regs[DBDMA_STATUS];
+ break;
+ case DBDMA_STATUS:
+ case DBDMA_CMDPTR_LO:
+ case DBDMA_INTR_SEL:
+ case DBDMA_BRANCH_SEL:
+ case DBDMA_WAIT_SEL:
+ /* nothing to do */
+ break;
+ case DBDMA_XFER_MODE:
+ case DBDMA_CMDPTR_HI:
+ case DBDMA_DATA2PTR_HI:
+ case DBDMA_DATA2PTR_LO:
+ case DBDMA_ADDRESS_HI:
+ case DBDMA_BRANCH_ADDR_HI:
+ /* unused */
+ value = 0;
+ break;
+ case DBDMA_RES1:
+ case DBDMA_RES2:
+ case DBDMA_RES3:
+ case DBDMA_RES4:
+ /* reserved */
+ break;
+ }
+
+ DBDMA_DPRINTFCH(ch, "readl 0x" TARGET_FMT_plx " => 0x%08x\n", addr, value);
+ DBDMA_DPRINTFCH(ch, "channel 0x%x reg 0x%x\n",
+ (uint32_t)addr >> DBDMA_CHANNEL_SHIFT, reg);
+
+ return value;
+}
+
+static const MemoryRegionOps dbdma_ops = {
+ .read = dbdma_read,
+ .write = dbdma_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const VMStateDescription vmstate_dbdma_io = {
+ .name = "dbdma_io",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(addr, struct DBDMA_io),
+ VMSTATE_INT32(len, struct DBDMA_io),
+ VMSTATE_INT32(is_last, struct DBDMA_io),
+ VMSTATE_INT32(is_dma_out, struct DBDMA_io),
+ VMSTATE_BOOL(processing, struct DBDMA_io),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_dbdma_cmd = {
+ .name = "dbdma_cmd",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(req_count, dbdma_cmd),
+ VMSTATE_UINT16(command, dbdma_cmd),
+ VMSTATE_UINT32(phy_addr, dbdma_cmd),
+ VMSTATE_UINT32(cmd_dep, dbdma_cmd),
+ VMSTATE_UINT16(res_count, dbdma_cmd),
+ VMSTATE_UINT16(xfer_status, dbdma_cmd),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_dbdma_channel = {
+ .name = "dbdma_channel",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, struct DBDMA_channel, DBDMA_REGS),
+ VMSTATE_STRUCT(io, struct DBDMA_channel, 0, vmstate_dbdma_io, DBDMA_io),
+ VMSTATE_STRUCT(current, struct DBDMA_channel, 0, vmstate_dbdma_cmd,
+ dbdma_cmd),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_dbdma = {
+ .name = "dbdma",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(channels, DBDMAState, DBDMA_CHANNELS, 1,
+ vmstate_dbdma_channel, DBDMA_channel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mac_dbdma_reset(DeviceState *d)
+{
+ DBDMAState *s = MAC_DBDMA(d);
+ int i;
+
+ for (i = 0; i < DBDMA_CHANNELS; i++) {
+ memset(s->channels[i].regs, 0, DBDMA_SIZE);
+ }
+}
+
+static void dbdma_unassigned_rw(DBDMA_io *io)
+{
+ DBDMA_channel *ch = io->channel;
+ dbdma_cmd *current = &ch->current;
+ uint16_t cmd;
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: use of unassigned channel %d\n",
+ __func__, ch->channel);
+ ch->io.processing = false;
+
+ cmd = le16_to_cpu(current->command) & COMMAND_MASK;
+ if (cmd == OUTPUT_MORE || cmd == OUTPUT_LAST ||
+ cmd == INPUT_MORE || cmd == INPUT_LAST) {
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ current->res_count = cpu_to_le16(io->len);
+ dbdma_cmdptr_save(ch);
+ }
+}
+
+static void dbdma_unassigned_flush(DBDMA_io *io)
+{
+ DBDMA_channel *ch = io->channel;
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: use of unassigned channel %d\n",
+ __func__, ch->channel);
+}
+
+static void mac_dbdma_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ DBDMAState *s = MAC_DBDMA(obj);
+ int i;
+
+ for (i = 0; i < DBDMA_CHANNELS; i++) {
+ DBDMA_channel *ch = &s->channels[i];
+
+ ch->rw = dbdma_unassigned_rw;
+ ch->flush = dbdma_unassigned_flush;
+ ch->channel = i;
+ ch->io.channel = ch;
+ }
+
+ memory_region_init_io(&s->mem, obj, &dbdma_ops, s, "dbdma", 0x1000);
+ sysbus_init_mmio(sbd, &s->mem);
+}
+
+static void mac_dbdma_realize(DeviceState *dev, Error **errp)
+{
+ DBDMAState *s = MAC_DBDMA(dev);
+
+ s->bh = qemu_bh_new(DBDMA_run_bh, s);
+}
+
+static void mac_dbdma_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = mac_dbdma_realize;
+ dc->reset = mac_dbdma_reset;
+ dc->vmsd = &vmstate_dbdma;
+}
+
+static const TypeInfo mac_dbdma_type_info = {
+ .name = TYPE_MAC_DBDMA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(DBDMAState),
+ .instance_init = mac_dbdma_init,
+ .class_init = mac_dbdma_class_init
+};
+
+static void mac_dbdma_register_types(void)
+{
+ type_register_static(&mac_dbdma_type_info);
+}
+
+type_init(mac_dbdma_register_types)
diff --git a/hw/misc/macio/macio.c b/hw/misc/macio/macio.c
new file mode 100644
index 000000000..c1fad43f6
--- /dev/null
+++ b/hw/misc/macio/macio.c
@@ -0,0 +1,504 @@
+/*
+ * PowerMac MacIO device emulation
+ *
+ * Copyright (c) 2005-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "hw/ppc/mac.h"
+#include "hw/misc/macio/cuda.h"
+#include "hw/pci/pci.h"
+#include "hw/ppc/mac_dbdma.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/char/escc.h"
+#include "hw/misc/macio/macio.h"
+#include "hw/intc/heathrow_pic.h"
+#include "trace.h"
+
+/* Note: this code is strongly inspirated from the corresponding code
+ * in PearPC */
+
+/*
+ * The mac-io has two interfaces to the ESCC. One is called "escc-legacy",
+ * while the other one is the normal, current ESCC interface.
+ *
+ * The magic below creates memory aliases to spawn the escc-legacy device
+ * purely by rerouting the respective registers to our escc region. This
+ * works because the only difference between the two memory regions is the
+ * register layout, not their semantics.
+ *
+ * Reference: ftp://ftp.software.ibm.com/rs6000/technology/spec/chrp/inwork/CHRP_IORef_1.0.pdf
+ */
+static void macio_escc_legacy_setup(MacIOState *s)
+{
+ ESCCState *escc = ESCC(&s->escc);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(escc);
+ MemoryRegion *escc_legacy = g_new(MemoryRegion, 1);
+ MemoryRegion *bar = &s->bar;
+ int i;
+ static const int maps[] = {
+ 0x00, 0x00, /* Command B */
+ 0x02, 0x20, /* Command A */
+ 0x04, 0x10, /* Data B */
+ 0x06, 0x30, /* Data A */
+ 0x08, 0x40, /* Enhancement B */
+ 0x0A, 0x50, /* Enhancement A */
+ 0x80, 0x80, /* Recovery count */
+ 0x90, 0x90, /* Start A */
+ 0xa0, 0xa0, /* Start B */
+ 0xb0, 0xb0, /* Detect AB */
+ };
+
+ memory_region_init(escc_legacy, OBJECT(s), "escc-legacy", 256);
+ for (i = 0; i < ARRAY_SIZE(maps); i += 2) {
+ MemoryRegion *port = g_new(MemoryRegion, 1);
+ memory_region_init_alias(port, OBJECT(s), "escc-legacy-port",
+ sysbus_mmio_get_region(sbd, 0),
+ maps[i + 1], 0x2);
+ memory_region_add_subregion(escc_legacy, maps[i], port);
+ }
+
+ memory_region_add_subregion(bar, 0x12000, escc_legacy);
+}
+
+static void macio_bar_setup(MacIOState *s)
+{
+ ESCCState *escc = ESCC(&s->escc);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(escc);
+ MemoryRegion *bar = &s->bar;
+
+ memory_region_add_subregion(bar, 0x13000, sysbus_mmio_get_region(sbd, 0));
+ macio_escc_legacy_setup(s);
+}
+
+static void macio_common_realize(PCIDevice *d, Error **errp)
+{
+ MacIOState *s = MACIO(d);
+ SysBusDevice *sysbus_dev;
+
+ if (!qdev_realize(DEVICE(&s->dbdma), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&s->dbdma);
+ memory_region_add_subregion(&s->bar, 0x08000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+
+ qdev_prop_set_uint32(DEVICE(&s->escc), "disabled", 0);
+ qdev_prop_set_uint32(DEVICE(&s->escc), "frequency", ESCC_CLOCK);
+ qdev_prop_set_uint32(DEVICE(&s->escc), "it_shift", 4);
+ qdev_prop_set_uint32(DEVICE(&s->escc), "chnBtype", escc_serial);
+ qdev_prop_set_uint32(DEVICE(&s->escc), "chnAtype", escc_serial);
+ if (!qdev_realize(DEVICE(&s->escc), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+
+ macio_bar_setup(s);
+ pci_register_bar(d, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar);
+}
+
+static void macio_realize_ide(MacIOState *s, MACIOIDEState *ide,
+ qemu_irq irq0, qemu_irq irq1, int dmaid,
+ Error **errp)
+{
+ SysBusDevice *sysbus_dev;
+
+ sysbus_dev = SYS_BUS_DEVICE(ide);
+ sysbus_connect_irq(sysbus_dev, 0, irq0);
+ sysbus_connect_irq(sysbus_dev, 1, irq1);
+ qdev_prop_set_uint32(DEVICE(ide), "channel", dmaid);
+ object_property_set_link(OBJECT(ide), "dbdma", OBJECT(&s->dbdma),
+ &error_abort);
+ macio_ide_register_dma(ide);
+
+ qdev_realize(DEVICE(ide), BUS(&s->macio_bus), errp);
+}
+
+static void macio_oldworld_realize(PCIDevice *d, Error **errp)
+{
+ MacIOState *s = MACIO(d);
+ OldWorldMacIOState *os = OLDWORLD_MACIO(d);
+ DeviceState *pic_dev = DEVICE(&os->pic);
+ Error *err = NULL;
+ SysBusDevice *sysbus_dev;
+
+ macio_common_realize(d, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ /* Heathrow PIC */
+ if (!qdev_realize(DEVICE(&os->pic), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&os->pic);
+ memory_region_add_subregion(&s->bar, 0x0,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+
+ qdev_prop_set_uint64(DEVICE(&s->cuda), "timebase-frequency",
+ s->frequency);
+ if (!qdev_realize(DEVICE(&s->cuda), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&s->cuda);
+ memory_region_add_subregion(&s->bar, 0x16000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+ sysbus_connect_irq(sysbus_dev, 0, qdev_get_gpio_in(pic_dev,
+ OLDWORLD_CUDA_IRQ));
+
+ sysbus_dev = SYS_BUS_DEVICE(&s->escc);
+ sysbus_connect_irq(sysbus_dev, 0, qdev_get_gpio_in(pic_dev,
+ OLDWORLD_ESCCB_IRQ));
+ sysbus_connect_irq(sysbus_dev, 1, qdev_get_gpio_in(pic_dev,
+ OLDWORLD_ESCCA_IRQ));
+
+ if (!qdev_realize(DEVICE(&os->nvram), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&os->nvram);
+ memory_region_add_subregion(&s->bar, 0x60000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+ pmac_format_nvram_partition(&os->nvram, os->nvram.size);
+
+ /* IDE buses */
+ macio_realize_ide(s, &os->ide[0],
+ qdev_get_gpio_in(pic_dev, OLDWORLD_IDE0_IRQ),
+ qdev_get_gpio_in(pic_dev, OLDWORLD_IDE0_DMA_IRQ),
+ 0x16, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ macio_realize_ide(s, &os->ide[1],
+ qdev_get_gpio_in(pic_dev, OLDWORLD_IDE1_IRQ),
+ qdev_get_gpio_in(pic_dev, OLDWORLD_IDE1_DMA_IRQ),
+ 0x1a, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+}
+
+static void macio_init_ide(MacIOState *s, MACIOIDEState *ide, int index)
+{
+ gchar *name = g_strdup_printf("ide[%i]", index);
+ uint32_t addr = 0x1f000 + ((index + 1) * 0x1000);
+
+ object_initialize_child(OBJECT(s), name, ide, TYPE_MACIO_IDE);
+ qdev_prop_set_uint32(DEVICE(ide), "addr", addr);
+ memory_region_add_subregion(&s->bar, addr, &ide->mem);
+ g_free(name);
+}
+
+static void macio_oldworld_init(Object *obj)
+{
+ MacIOState *s = MACIO(obj);
+ OldWorldMacIOState *os = OLDWORLD_MACIO(obj);
+ DeviceState *dev;
+ int i;
+
+ object_initialize_child(OBJECT(s), "pic", &os->pic, TYPE_HEATHROW);
+
+ object_initialize_child(OBJECT(s), "cuda", &s->cuda, TYPE_CUDA);
+
+ object_initialize_child(OBJECT(s), "nvram", &os->nvram, TYPE_MACIO_NVRAM);
+ dev = DEVICE(&os->nvram);
+ qdev_prop_set_uint32(dev, "size", 0x2000);
+ qdev_prop_set_uint32(dev, "it_shift", 4);
+
+ for (i = 0; i < 2; i++) {
+ macio_init_ide(s, &os->ide[i], i);
+ }
+}
+
+static void timer_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ trace_macio_timer_write(addr, size, value);
+}
+
+static uint64_t timer_read(void *opaque, hwaddr addr, unsigned size)
+{
+ uint32_t value = 0;
+ uint64_t systime = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ uint64_t kltime;
+
+ kltime = muldiv64(systime, 4194300, NANOSECONDS_PER_SECOND * 4);
+ kltime = muldiv64(kltime, 18432000, 1048575);
+
+ switch (addr) {
+ case 0x38:
+ value = kltime;
+ break;
+ case 0x3c:
+ value = kltime >> 32;
+ break;
+ }
+
+ trace_macio_timer_read(addr, size, value);
+ return value;
+}
+
+static const MemoryRegionOps timer_ops = {
+ .read = timer_read,
+ .write = timer_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void macio_newworld_realize(PCIDevice *d, Error **errp)
+{
+ MacIOState *s = MACIO(d);
+ NewWorldMacIOState *ns = NEWWORLD_MACIO(d);
+ DeviceState *pic_dev = DEVICE(&ns->pic);
+ Error *err = NULL;
+ SysBusDevice *sysbus_dev;
+ MemoryRegion *timer_memory = NULL;
+
+ macio_common_realize(d, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ /* OpenPIC */
+ qdev_prop_set_uint32(pic_dev, "model", OPENPIC_MODEL_KEYLARGO);
+ sysbus_dev = SYS_BUS_DEVICE(&ns->pic);
+ sysbus_realize_and_unref(sysbus_dev, &error_fatal);
+ memory_region_add_subregion(&s->bar, 0x40000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+
+ sysbus_dev = SYS_BUS_DEVICE(&s->escc);
+ sysbus_connect_irq(sysbus_dev, 0, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_ESCCB_IRQ));
+ sysbus_connect_irq(sysbus_dev, 1, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_ESCCA_IRQ));
+
+ /* IDE buses */
+ macio_realize_ide(s, &ns->ide[0],
+ qdev_get_gpio_in(pic_dev, NEWWORLD_IDE0_IRQ),
+ qdev_get_gpio_in(pic_dev, NEWWORLD_IDE0_DMA_IRQ),
+ 0x16, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ macio_realize_ide(s, &ns->ide[1],
+ qdev_get_gpio_in(pic_dev, NEWWORLD_IDE1_IRQ),
+ qdev_get_gpio_in(pic_dev, NEWWORLD_IDE1_DMA_IRQ),
+ 0x1a, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ /* Timer */
+ timer_memory = g_new(MemoryRegion, 1);
+ memory_region_init_io(timer_memory, OBJECT(s), &timer_ops, NULL, "timer",
+ 0x1000);
+ memory_region_add_subregion(&s->bar, 0x15000, timer_memory);
+
+ if (ns->has_pmu) {
+ /* GPIOs */
+ if (!qdev_realize(DEVICE(&ns->gpio), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&ns->gpio);
+ sysbus_connect_irq(sysbus_dev, 1, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_EXTING_GPIO1));
+ sysbus_connect_irq(sysbus_dev, 9, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_EXTING_GPIO9));
+ memory_region_add_subregion(&s->bar, 0x50,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+
+ /* PMU */
+ object_initialize_child(OBJECT(s), "pmu", &s->pmu, TYPE_VIA_PMU);
+ object_property_set_link(OBJECT(&s->pmu), "gpio", OBJECT(sysbus_dev),
+ &error_abort);
+ qdev_prop_set_bit(DEVICE(&s->pmu), "has-adb", ns->has_adb);
+ if (!qdev_realize(DEVICE(&s->pmu), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&s->pmu);
+ sysbus_connect_irq(sysbus_dev, 0, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_PMU_IRQ));
+ memory_region_add_subregion(&s->bar, 0x16000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+ } else {
+ object_unparent(OBJECT(&ns->gpio));
+
+ /* CUDA */
+ object_initialize_child(OBJECT(s), "cuda", &s->cuda, TYPE_CUDA);
+ qdev_prop_set_uint64(DEVICE(&s->cuda), "timebase-frequency",
+ s->frequency);
+
+ if (!qdev_realize(DEVICE(&s->cuda), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&s->cuda);
+ sysbus_connect_irq(sysbus_dev, 0, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_CUDA_IRQ));
+ memory_region_add_subregion(&s->bar, 0x16000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+ }
+}
+
+static void macio_newworld_init(Object *obj)
+{
+ MacIOState *s = MACIO(obj);
+ NewWorldMacIOState *ns = NEWWORLD_MACIO(obj);
+ int i;
+
+ object_initialize_child(OBJECT(s), "pic", &ns->pic, TYPE_OPENPIC);
+
+ object_initialize_child(OBJECT(s), "gpio", &ns->gpio, TYPE_MACIO_GPIO);
+
+ for (i = 0; i < 2; i++) {
+ macio_init_ide(s, &ns->ide[i], i);
+ }
+}
+
+static void macio_instance_init(Object *obj)
+{
+ MacIOState *s = MACIO(obj);
+
+ memory_region_init(&s->bar, obj, "macio", 0x80000);
+
+ qbus_init(&s->macio_bus, sizeof(s->macio_bus), TYPE_MACIO_BUS,
+ DEVICE(obj), "macio.0");
+
+ object_initialize_child(OBJECT(s), "dbdma", &s->dbdma, TYPE_MAC_DBDMA);
+
+ object_initialize_child(OBJECT(s), "escc", &s->escc, TYPE_ESCC);
+}
+
+static const VMStateDescription vmstate_macio_oldworld = {
+ .name = "macio-oldworld",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj.parent, OldWorldMacIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void macio_oldworld_class_init(ObjectClass *oc, void *data)
+{
+ PCIDeviceClass *pdc = PCI_DEVICE_CLASS(oc);
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ pdc->realize = macio_oldworld_realize;
+ pdc->device_id = PCI_DEVICE_ID_APPLE_343S1201;
+ dc->vmsd = &vmstate_macio_oldworld;
+}
+
+static const VMStateDescription vmstate_macio_newworld = {
+ .name = "macio-newworld",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj.parent, NewWorldMacIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property macio_newworld_properties[] = {
+ DEFINE_PROP_BOOL("has-pmu", NewWorldMacIOState, has_pmu, false),
+ DEFINE_PROP_BOOL("has-adb", NewWorldMacIOState, has_adb, false),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void macio_newworld_class_init(ObjectClass *oc, void *data)
+{
+ PCIDeviceClass *pdc = PCI_DEVICE_CLASS(oc);
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ pdc->realize = macio_newworld_realize;
+ pdc->device_id = PCI_DEVICE_ID_APPLE_UNI_N_KEYL;
+ dc->vmsd = &vmstate_macio_newworld;
+ device_class_set_props(dc, macio_newworld_properties);
+}
+
+static Property macio_properties[] = {
+ DEFINE_PROP_UINT64("frequency", MacIOState, frequency, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void macio_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->vendor_id = PCI_VENDOR_ID_APPLE;
+ k->class_id = PCI_CLASS_OTHERS << 8;
+ device_class_set_props(dc, macio_properties);
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo macio_bus_info = {
+ .name = TYPE_MACIO_BUS,
+ .parent = TYPE_SYSTEM_BUS,
+ .instance_size = sizeof(MacIOBusState),
+};
+
+static const TypeInfo macio_oldworld_type_info = {
+ .name = TYPE_OLDWORLD_MACIO,
+ .parent = TYPE_MACIO,
+ .instance_size = sizeof(OldWorldMacIOState),
+ .instance_init = macio_oldworld_init,
+ .class_init = macio_oldworld_class_init,
+};
+
+static const TypeInfo macio_newworld_type_info = {
+ .name = TYPE_NEWWORLD_MACIO,
+ .parent = TYPE_MACIO,
+ .instance_size = sizeof(NewWorldMacIOState),
+ .instance_init = macio_newworld_init,
+ .class_init = macio_newworld_class_init,
+};
+
+static const TypeInfo macio_type_info = {
+ .name = TYPE_MACIO,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(MacIOState),
+ .instance_init = macio_instance_init,
+ .abstract = true,
+ .class_init = macio_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void macio_register_types(void)
+{
+ type_register_static(&macio_bus_info);
+ type_register_static(&macio_type_info);
+ type_register_static(&macio_oldworld_type_info);
+ type_register_static(&macio_newworld_type_info);
+}
+
+type_init(macio_register_types)
diff --git a/hw/misc/macio/meson.build b/hw/misc/macio/meson.build
new file mode 100644
index 000000000..17282da20
--- /dev/null
+++ b/hw/misc/macio/meson.build
@@ -0,0 +1,8 @@
+macio_ss = ss.source_set()
+macio_ss.add(files('macio.c'))
+macio_ss.add(when: 'CONFIG_CUDA', if_true: files('cuda.c'))
+macio_ss.add(when: 'CONFIG_MACIO_GPIO', if_true: files('gpio.c'))
+macio_ss.add(when: 'CONFIG_MAC_DBDMA', if_true: files('mac_dbdma.c'))
+macio_ss.add(when: 'CONFIG_MAC_PMU', if_true: files('pmu.c'))
+
+softmmu_ss.add_all(when: 'CONFIG_MACIO', if_true: macio_ss)
diff --git a/hw/misc/macio/pmu.c b/hw/misc/macio/pmu.c
new file mode 100644
index 000000000..eb39c6469
--- /dev/null
+++ b/hw/misc/macio/pmu.c
@@ -0,0 +1,871 @@
+/*
+ * QEMU PowerMac PMU device support
+ *
+ * Copyright (c) 2016 Benjamin Herrenschmidt, IBM Corp.
+ * Copyright (c) 2018 Mark Cave-Ayland
+ *
+ * Based on the CUDA device by:
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "hw/ppc/mac.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/input/adb.h"
+#include "hw/irq.h"
+#include "hw/misc/mos6522.h"
+#include "hw/misc/macio/gpio.h"
+#include "hw/misc/macio/pmu.h"
+#include "qapi/error.h"
+#include "qemu/timer.h"
+#include "sysemu/runstate.h"
+#include "qapi/error.h"
+#include "qemu/cutils.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+
+/* Bits in B data register: all active low */
+#define TACK 0x08 /* Transfer request (input) */
+#define TREQ 0x10 /* Transfer acknowledge (output) */
+
+/* PMU returns time_t's offset from Jan 1, 1904, not 1970 */
+#define RTC_OFFSET 2082844800
+
+#define VIA_TIMER_FREQ (4700000 / 6)
+
+static void via_update_irq(PMUState *s)
+{
+ MOS6522PMUState *mps = MOS6522_PMU(&s->mos6522_pmu);
+ MOS6522State *ms = MOS6522(mps);
+
+ bool new_state = !!(ms->ifr & ms->ier & (SR_INT | T1_INT | T2_INT));
+
+ if (new_state != s->via_irq_state) {
+ s->via_irq_state = new_state;
+ qemu_set_irq(s->via_irq, new_state);
+ }
+}
+
+static void via_set_sr_int(void *opaque)
+{
+ PMUState *s = opaque;
+ MOS6522PMUState *mps = MOS6522_PMU(&s->mos6522_pmu);
+ MOS6522State *ms = MOS6522(mps);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms);
+
+ mdc->set_sr_int(ms);
+}
+
+static void pmu_update_extirq(PMUState *s)
+{
+ if ((s->intbits & s->intmask) != 0) {
+ macio_set_gpio(s->gpio, 1, false);
+ } else {
+ macio_set_gpio(s->gpio, 1, true);
+ }
+}
+
+static void pmu_adb_poll(void *opaque)
+{
+ PMUState *s = opaque;
+ ADBBusState *adb_bus = &s->adb_bus;
+ int olen;
+
+ if (!(s->intbits & PMU_INT_ADB)) {
+ olen = adb_poll(adb_bus, s->adb_reply, adb_bus->autopoll_mask);
+ trace_pmu_adb_poll(olen);
+
+ if (olen > 0) {
+ s->adb_reply_size = olen;
+ s->intbits |= PMU_INT_ADB | PMU_INT_ADB_AUTO;
+ pmu_update_extirq(s);
+ }
+ }
+}
+
+static void pmu_one_sec_timer(void *opaque)
+{
+ PMUState *s = opaque;
+
+ trace_pmu_one_sec_timer();
+
+ s->intbits |= PMU_INT_TICK;
+ pmu_update_extirq(s);
+ s->one_sec_target += 1000;
+
+ timer_mod(s->one_sec_timer, s->one_sec_target);
+}
+
+static void pmu_cmd_int_ack(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len != 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: INT_ACK command, invalid len: %d want: 0\n",
+ in_len);
+ return;
+ }
+
+ /* Make appropriate reply packet */
+ if (s->intbits & PMU_INT_ADB) {
+ if (!s->adb_reply_size) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "Odd, PMU_INT_ADB set with no reply in buffer\n");
+ }
+
+ memcpy(out_data + 1, s->adb_reply, s->adb_reply_size);
+ out_data[0] = s->intbits & (PMU_INT_ADB | PMU_INT_ADB_AUTO);
+ *out_len = s->adb_reply_size + 1;
+ s->intbits &= ~(PMU_INT_ADB | PMU_INT_ADB_AUTO);
+ s->adb_reply_size = 0;
+ } else {
+ out_data[0] = s->intbits;
+ s->intbits = 0;
+ *out_len = 1;
+ }
+
+ pmu_update_extirq(s);
+}
+
+static void pmu_cmd_set_int_mask(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: SET_INT_MASK command, invalid len: %d want: 1\n",
+ in_len);
+ return;
+ }
+
+ trace_pmu_cmd_set_int_mask(s->intmask);
+ s->intmask = in_data[0];
+
+ pmu_update_extirq(s);
+}
+
+static void pmu_cmd_set_adb_autopoll(PMUState *s, uint16_t mask)
+{
+ ADBBusState *adb_bus = &s->adb_bus;
+
+ trace_pmu_cmd_set_adb_autopoll(mask);
+
+ if (mask) {
+ adb_set_autopoll_mask(adb_bus, mask);
+ adb_set_autopoll_enabled(adb_bus, true);
+ } else {
+ adb_set_autopoll_enabled(adb_bus, false);
+ }
+}
+
+static void pmu_cmd_adb(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ int len, adblen;
+ uint8_t adb_cmd[255];
+
+ if (in_len < 2) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: ADB PACKET, invalid len: %d want at least 2\n",
+ in_len);
+ return;
+ }
+
+ *out_len = 0;
+
+ if (!s->has_adb) {
+ trace_pmu_cmd_adb_nobus();
+ return;
+ }
+
+ /* Set autopoll is a special form of the command */
+ if (in_data[0] == 0 && in_data[1] == 0x86) {
+ uint16_t mask = in_data[2];
+ mask = (mask << 8) | in_data[3];
+ if (in_len != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: ADB Autopoll requires 4 bytes, got %d\n",
+ in_len);
+ return;
+ }
+
+ pmu_cmd_set_adb_autopoll(s, mask);
+ return;
+ }
+
+ trace_pmu_cmd_adb_request(in_len, in_data[0], in_data[1], in_data[2],
+ in_data[3], in_data[4]);
+
+ *out_len = 0;
+
+ /* Check ADB len */
+ adblen = in_data[2];
+ if (adblen > (in_len - 3)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: ADB len is %d > %d (in_len -3)...erroring\n",
+ adblen, in_len - 3);
+ len = -1;
+ } else if (adblen > 252) {
+ qemu_log_mask(LOG_GUEST_ERROR, "PMU: ADB command too big!\n");
+ len = -1;
+ } else {
+ /* Format command */
+ adb_cmd[0] = in_data[0];
+ memcpy(&adb_cmd[1], &in_data[3], in_len - 3);
+ len = adb_request(&s->adb_bus, s->adb_reply + 2, adb_cmd, in_len - 2);
+
+ trace_pmu_cmd_adb_reply(len);
+ }
+
+ if (len > 0) {
+ /* XXX Check this */
+ s->adb_reply_size = len + 2;
+ s->adb_reply[0] = 0x01;
+ s->adb_reply[1] = len;
+ } else {
+ /* XXX Check this */
+ s->adb_reply_size = 1;
+ s->adb_reply[0] = 0x00;
+ }
+
+ s->intbits |= PMU_INT_ADB;
+ pmu_update_extirq(s);
+}
+
+static void pmu_cmd_adb_poll_off(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ ADBBusState *adb_bus = &s->adb_bus;
+
+ if (in_len != 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: ADB POLL OFF command, invalid len: %d want: 0\n",
+ in_len);
+ return;
+ }
+
+ if (s->has_adb) {
+ adb_set_autopoll_enabled(adb_bus, false);
+ }
+}
+
+static void pmu_cmd_shutdown(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: SHUTDOWN command, invalid len: %d want: 4\n",
+ in_len);
+ return;
+ }
+
+ *out_len = 1;
+ out_data[0] = 0;
+
+ if (in_data[0] != 'M' || in_data[1] != 'A' || in_data[2] != 'T' ||
+ in_data[3] != 'T') {
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: SHUTDOWN command, Bad MATT signature\n");
+ return;
+ }
+
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+}
+
+static void pmu_cmd_reset(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len != 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: RESET command, invalid len: %d want: 0\n",
+ in_len);
+ return;
+ }
+
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+}
+
+static void pmu_cmd_get_rtc(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ uint32_t ti;
+
+ if (in_len != 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: GET_RTC command, invalid len: %d want: 0\n",
+ in_len);
+ return;
+ }
+
+ ti = s->tick_offset + (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
+ / NANOSECONDS_PER_SECOND);
+ out_data[0] = ti >> 24;
+ out_data[1] = ti >> 16;
+ out_data[2] = ti >> 8;
+ out_data[3] = ti;
+ *out_len = 4;
+}
+
+static void pmu_cmd_set_rtc(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ uint32_t ti;
+
+ if (in_len != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: SET_RTC command, invalid len: %d want: 4\n",
+ in_len);
+ return;
+ }
+
+ ti = (((uint32_t)in_data[0]) << 24) + (((uint32_t)in_data[1]) << 16)
+ + (((uint32_t)in_data[2]) << 8) + in_data[3];
+
+ s->tick_offset = ti - (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
+ / NANOSECONDS_PER_SECOND);
+}
+
+static void pmu_cmd_system_ready(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ /* Do nothing */
+}
+
+static void pmu_cmd_get_version(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ *out_len = 1;
+ *out_data = 1; /* ??? Check what Apple does */
+}
+
+static void pmu_cmd_power_events(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len < 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: POWER EVENTS command, invalid len %d, want at least 1\n",
+ in_len);
+ return;
+ }
+
+ switch (in_data[0]) {
+ /* Dummies for now */
+ case PMU_PWR_GET_POWERUP_EVENTS:
+ *out_len = 2;
+ out_data[0] = 0;
+ out_data[1] = 0;
+ break;
+ case PMU_PWR_SET_POWERUP_EVENTS:
+ case PMU_PWR_CLR_POWERUP_EVENTS:
+ break;
+ case PMU_PWR_GET_WAKEUP_EVENTS:
+ *out_len = 2;
+ out_data[0] = 0;
+ out_data[1] = 0;
+ break;
+ case PMU_PWR_SET_WAKEUP_EVENTS:
+ case PMU_PWR_CLR_WAKEUP_EVENTS:
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: POWER EVENTS unknown subcommand 0x%02x\n",
+ in_data[0]);
+ }
+}
+
+static void pmu_cmd_get_cover(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ /* Not 100% sure here, will have to check what a real Mac
+ * returns other than byte 0 bit 0 is LID closed on laptops
+ */
+ *out_len = 1;
+ *out_data = 0x00;
+}
+
+static void pmu_cmd_download_status(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ /* This has to do with PMU firmware updates as far as I can tell.
+ *
+ * We return 0x62 which is what OpenPMU expects
+ */
+ *out_len = 1;
+ *out_data = 0x62;
+}
+
+static void pmu_cmd_read_pmu_ram(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len < 3) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: READ_PMU_RAM command, invalid len %d, expected 3\n",
+ in_len);
+ return;
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: Unsupported READ_PMU_RAM, args: %02x %02x %02x\n",
+ in_data[0], in_data[1], in_data[2]);
+
+ *out_len = 0;
+}
+
+/* description of commands */
+typedef struct PMUCmdHandler {
+ uint8_t command;
+ const char *name;
+ void (*handler)(PMUState *s,
+ const uint8_t *in_args, uint8_t in_len,
+ uint8_t *out_args, uint8_t *out_len);
+} PMUCmdHandler;
+
+static const PMUCmdHandler PMUCmdHandlers[] = {
+ { PMU_INT_ACK, "INT ACK", pmu_cmd_int_ack },
+ { PMU_SET_INTR_MASK, "SET INT MASK", pmu_cmd_set_int_mask },
+ { PMU_ADB_CMD, "ADB COMMAND", pmu_cmd_adb },
+ { PMU_ADB_POLL_OFF, "ADB POLL OFF", pmu_cmd_adb_poll_off },
+ { PMU_RESET, "REBOOT", pmu_cmd_reset },
+ { PMU_SHUTDOWN, "SHUTDOWN", pmu_cmd_shutdown },
+ { PMU_READ_RTC, "GET RTC", pmu_cmd_get_rtc },
+ { PMU_SET_RTC, "SET RTC", pmu_cmd_set_rtc },
+ { PMU_SYSTEM_READY, "SYSTEM READY", pmu_cmd_system_ready },
+ { PMU_GET_VERSION, "GET VERSION", pmu_cmd_get_version },
+ { PMU_POWER_EVENTS, "POWER EVENTS", pmu_cmd_power_events },
+ { PMU_GET_COVER, "GET_COVER", pmu_cmd_get_cover },
+ { PMU_DOWNLOAD_STATUS, "DOWNLOAD STATUS", pmu_cmd_download_status },
+ { PMU_READ_PMU_RAM, "READ PMGR RAM", pmu_cmd_read_pmu_ram },
+};
+
+static void pmu_dispatch_cmd(PMUState *s)
+{
+ unsigned int i;
+
+ /* No response by default */
+ s->cmd_rsp_sz = 0;
+
+ for (i = 0; i < ARRAY_SIZE(PMUCmdHandlers); i++) {
+ const PMUCmdHandler *desc = &PMUCmdHandlers[i];
+
+ if (desc->command != s->cmd) {
+ continue;
+ }
+
+ trace_pmu_dispatch_cmd(desc->name);
+ desc->handler(s, s->cmd_buf, s->cmd_buf_pos,
+ s->cmd_rsp, &s->cmd_rsp_sz);
+
+ if (s->rsplen != -1 && s->rsplen != s->cmd_rsp_sz) {
+ trace_pmu_debug_protocol_string("QEMU internal cmd resp mismatch!");
+ } else {
+ trace_pmu_debug_protocol_resp_size(s->cmd_rsp_sz);
+ }
+
+ return;
+ }
+
+ trace_pmu_dispatch_unknown_cmd(s->cmd);
+
+ /* Manufacture fake response with 0's */
+ if (s->rsplen == -1) {
+ s->cmd_rsp_sz = 0;
+ } else {
+ s->cmd_rsp_sz = s->rsplen;
+ memset(s->cmd_rsp, 0, s->rsplen);
+ }
+}
+
+static void pmu_update(PMUState *s)
+{
+ MOS6522PMUState *mps = &s->mos6522_pmu;
+ MOS6522State *ms = MOS6522(mps);
+ ADBBusState *adb_bus = &s->adb_bus;
+
+ /* Only react to changes in reg B */
+ if (ms->b == s->last_b) {
+ return;
+ }
+ s->last_b = ms->b;
+
+ /* Check the TREQ / TACK state */
+ switch (ms->b & (TREQ | TACK)) {
+ case TREQ:
+ /* This is an ack release, handle it and bail out */
+ ms->b |= TACK;
+ s->last_b = ms->b;
+
+ trace_pmu_debug_protocol_string("handshake: TREQ high, setting TACK");
+ return;
+ case TACK:
+ /* This is a valid request, handle below */
+ break;
+ case TREQ | TACK:
+ /* This is an idle state */
+ return;
+ default:
+ /* Invalid state, log and ignore */
+ trace_pmu_debug_protocol_error(ms->b);
+ return;
+ }
+
+ /* If we wanted to handle commands asynchronously, this is where
+ * we would delay the clearing of TACK until we are ready to send
+ * the response
+ */
+
+ /* We have a request, handshake TACK so we don't stay in
+ * an invalid state. If we were concurrent with the OS we
+ * should only do this after we grabbed the SR but that isn't
+ * a problem here.
+ */
+
+ trace_pmu_debug_protocol_clear_treq(s->cmd_state);
+
+ ms->b &= ~TACK;
+ s->last_b = ms->b;
+
+ /* Act according to state */
+ switch (s->cmd_state) {
+ case pmu_state_idle:
+ if (!(ms->acr & SR_OUT)) {
+ trace_pmu_debug_protocol_string("protocol error! "
+ "state idle, ACR reading");
+ break;
+ }
+
+ s->cmd = ms->sr;
+ via_set_sr_int(s);
+ s->cmdlen = pmu_data_len[s->cmd][0];
+ s->rsplen = pmu_data_len[s->cmd][1];
+ s->cmd_buf_pos = 0;
+ s->cmd_rsp_pos = 0;
+ s->cmd_state = pmu_state_cmd;
+
+ adb_autopoll_block(adb_bus);
+ trace_pmu_debug_protocol_cmd(s->cmd, s->cmdlen, s->rsplen);
+ break;
+
+ case pmu_state_cmd:
+ if (!(ms->acr & SR_OUT)) {
+ trace_pmu_debug_protocol_string("protocol error! "
+ "state cmd, ACR reading");
+ break;
+ }
+
+ if (s->cmdlen == -1) {
+ trace_pmu_debug_protocol_cmdlen(ms->sr);
+
+ s->cmdlen = ms->sr;
+ if (s->cmdlen > sizeof(s->cmd_buf)) {
+ trace_pmu_debug_protocol_cmd_toobig(s->cmdlen);
+ }
+ } else if (s->cmd_buf_pos < sizeof(s->cmd_buf)) {
+ s->cmd_buf[s->cmd_buf_pos++] = ms->sr;
+ }
+
+ via_set_sr_int(s);
+ break;
+
+ case pmu_state_rsp:
+ if (ms->acr & SR_OUT) {
+ trace_pmu_debug_protocol_string("protocol error! "
+ "state resp, ACR writing");
+ break;
+ }
+
+ if (s->rsplen == -1) {
+ trace_pmu_debug_protocol_cmd_send_resp_size(s->cmd_rsp_sz);
+
+ ms->sr = s->cmd_rsp_sz;
+ s->rsplen = s->cmd_rsp_sz;
+ } else if (s->cmd_rsp_pos < s->cmd_rsp_sz) {
+ trace_pmu_debug_protocol_cmd_send_resp(s->cmd_rsp_pos, s->rsplen);
+
+ ms->sr = s->cmd_rsp[s->cmd_rsp_pos++];
+ }
+
+ via_set_sr_int(s);
+ break;
+ }
+
+ /* Check for state completion */
+ if (s->cmd_state == pmu_state_cmd && s->cmdlen == s->cmd_buf_pos) {
+ trace_pmu_debug_protocol_string("Command reception complete, "
+ "dispatching...");
+
+ pmu_dispatch_cmd(s);
+ s->cmd_state = pmu_state_rsp;
+ }
+
+ if (s->cmd_state == pmu_state_rsp && s->rsplen == s->cmd_rsp_pos) {
+ trace_pmu_debug_protocol_cmd_resp_complete(ms->ier);
+
+ adb_autopoll_unblock(adb_bus);
+ s->cmd_state = pmu_state_idle;
+ }
+}
+
+static uint64_t mos6522_pmu_read(void *opaque, hwaddr addr, unsigned size)
+{
+ PMUState *s = opaque;
+ MOS6522PMUState *mps = &s->mos6522_pmu;
+ MOS6522State *ms = MOS6522(mps);
+
+ addr = (addr >> 9) & 0xf;
+ return mos6522_read(ms, addr, size);
+}
+
+static void mos6522_pmu_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ PMUState *s = opaque;
+ MOS6522PMUState *mps = &s->mos6522_pmu;
+ MOS6522State *ms = MOS6522(mps);
+
+ addr = (addr >> 9) & 0xf;
+ mos6522_write(ms, addr, val, size);
+}
+
+static const MemoryRegionOps mos6522_pmu_ops = {
+ .read = mos6522_pmu_read,
+ .write = mos6522_pmu_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static bool pmu_adb_state_needed(void *opaque)
+{
+ PMUState *s = opaque;
+
+ return s->has_adb;
+}
+
+static const VMStateDescription vmstate_pmu_adb = {
+ .name = "pmu/adb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = pmu_adb_state_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(adb_reply_size, PMUState),
+ VMSTATE_BUFFER(adb_reply, PMUState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pmu = {
+ .name = "pmu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(mos6522_pmu.parent_obj, PMUState, 0, vmstate_mos6522,
+ MOS6522State),
+ VMSTATE_UINT8(last_b, PMUState),
+ VMSTATE_UINT8(cmd, PMUState),
+ VMSTATE_UINT32(cmdlen, PMUState),
+ VMSTATE_UINT32(rsplen, PMUState),
+ VMSTATE_UINT8(cmd_buf_pos, PMUState),
+ VMSTATE_BUFFER(cmd_buf, PMUState),
+ VMSTATE_UINT8(cmd_rsp_pos, PMUState),
+ VMSTATE_UINT8(cmd_rsp_sz, PMUState),
+ VMSTATE_BUFFER(cmd_rsp, PMUState),
+ VMSTATE_UINT8(intbits, PMUState),
+ VMSTATE_UINT8(intmask, PMUState),
+ VMSTATE_UINT32(tick_offset, PMUState),
+ VMSTATE_TIMER_PTR(one_sec_timer, PMUState),
+ VMSTATE_INT64(one_sec_target, PMUState),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription * []) {
+ &vmstate_pmu_adb,
+ NULL
+ }
+};
+
+static void pmu_reset(DeviceState *dev)
+{
+ PMUState *s = VIA_PMU(dev);
+
+ /* OpenBIOS needs to do this? MacOS 9 needs it */
+ s->intmask = PMU_INT_ADB | PMU_INT_TICK;
+ s->intbits = 0;
+
+ s->cmd_state = pmu_state_idle;
+}
+
+static void pmu_realize(DeviceState *dev, Error **errp)
+{
+ PMUState *s = VIA_PMU(dev);
+ SysBusDevice *sbd;
+ ADBBusState *adb_bus = &s->adb_bus;
+ struct tm tm;
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->mos6522_pmu), errp)) {
+ return;
+ }
+
+ /* Pass IRQ from 6522 */
+ sbd = SYS_BUS_DEVICE(s);
+ sysbus_pass_irq(sbd, SYS_BUS_DEVICE(&s->mos6522_pmu));
+
+ qemu_get_timedate(&tm, 0);
+ s->tick_offset = (uint32_t)mktimegm(&tm) + RTC_OFFSET;
+ s->one_sec_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, pmu_one_sec_timer, s);
+ s->one_sec_target = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000;
+ timer_mod(s->one_sec_timer, s->one_sec_target);
+
+ if (s->has_adb) {
+ qbus_init(&s->adb_bus, sizeof(s->adb_bus), TYPE_ADB_BUS,
+ dev, "adb.0");
+ adb_register_autopoll_callback(adb_bus, pmu_adb_poll, s);
+ }
+}
+
+static void pmu_init(Object *obj)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(obj);
+ PMUState *s = VIA_PMU(obj);
+
+ object_property_add_link(obj, "gpio", TYPE_MACIO_GPIO,
+ (Object **) &s->gpio,
+ qdev_prop_allow_set_link_before_realize,
+ 0);
+
+ object_initialize_child(obj, "mos6522-pmu", &s->mos6522_pmu,
+ TYPE_MOS6522_PMU);
+
+ memory_region_init_io(&s->mem, obj, &mos6522_pmu_ops, s, "via-pmu",
+ 0x2000);
+ sysbus_init_mmio(d, &s->mem);
+}
+
+static Property pmu_properties[] = {
+ DEFINE_PROP_BOOL("has-adb", PMUState, has_adb, true),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void pmu_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = pmu_realize;
+ dc->reset = pmu_reset;
+ dc->vmsd = &vmstate_pmu;
+ device_class_set_props(dc, pmu_properties);
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo pmu_type_info = {
+ .name = TYPE_VIA_PMU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PMUState),
+ .instance_init = pmu_init,
+ .class_init = pmu_class_init,
+};
+
+static void mos6522_pmu_portB_write(MOS6522State *s)
+{
+ MOS6522PMUState *mps = container_of(s, MOS6522PMUState, parent_obj);
+ PMUState *ps = container_of(mps, PMUState, mos6522_pmu);
+
+ if ((s->pcr & 0xe0) == 0x20 || (s->pcr & 0xe0) == 0x60) {
+ s->ifr &= ~CB2_INT;
+ }
+ s->ifr &= ~CB1_INT;
+
+ via_update_irq(ps);
+ pmu_update(ps);
+}
+
+static void mos6522_pmu_portA_write(MOS6522State *s)
+{
+ MOS6522PMUState *mps = container_of(s, MOS6522PMUState, parent_obj);
+ PMUState *ps = container_of(mps, PMUState, mos6522_pmu);
+
+ if ((s->pcr & 0x0e) == 0x02 || (s->pcr & 0x0e) == 0x06) {
+ s->ifr &= ~CA2_INT;
+ }
+ s->ifr &= ~CA1_INT;
+
+ via_update_irq(ps);
+}
+
+static void mos6522_pmu_reset(DeviceState *dev)
+{
+ MOS6522State *ms = MOS6522(dev);
+ MOS6522PMUState *mps = container_of(ms, MOS6522PMUState, parent_obj);
+ PMUState *s = container_of(mps, PMUState, mos6522_pmu);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms);
+
+ mdc->parent_reset(dev);
+
+ ms->timers[0].frequency = VIA_TIMER_FREQ;
+ ms->timers[1].frequency = (SCALE_US * 6000) / 4700;
+
+ s->last_b = ms->b = TACK | TREQ;
+}
+
+static void mos6522_pmu_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ MOS6522DeviceClass *mdc = MOS6522_CLASS(oc);
+
+ dc->reset = mos6522_pmu_reset;
+ mdc->portB_write = mos6522_pmu_portB_write;
+ mdc->portA_write = mos6522_pmu_portA_write;
+}
+
+static const TypeInfo mos6522_pmu_type_info = {
+ .name = TYPE_MOS6522_PMU,
+ .parent = TYPE_MOS6522,
+ .instance_size = sizeof(MOS6522PMUState),
+ .class_init = mos6522_pmu_class_init,
+};
+
+static void pmu_register_types(void)
+{
+ type_register_static(&pmu_type_info);
+ type_register_static(&mos6522_pmu_type_info);
+}
+
+type_init(pmu_register_types)
diff --git a/hw/misc/macio/trace-events b/hw/misc/macio/trace-events
new file mode 100644
index 000000000..ad4b9d1c0
--- /dev/null
+++ b/hw/misc/macio/trace-events
@@ -0,0 +1,42 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# cuda.c
+cuda_delay_set_sr_int(void) ""
+cuda_data_send(uint8_t data) "send: 0x%02x"
+cuda_data_recv(uint8_t data) "recv: 0x%02x"
+cuda_receive_packet_cmd(const char *cmd) "handling command %s"
+cuda_packet_receive(int len) "length %d"
+cuda_packet_receive_data(int i, const uint8_t data) "[%d] 0x%02x"
+cuda_packet_send(int len) "length %d"
+cuda_packet_send_data(int i, const uint8_t data) "[%d] 0x%02x"
+
+# macio.c
+macio_timer_write(uint64_t addr, unsigned len, uint64_t val) "write addr 0x%"PRIx64 " len %d val 0x%"PRIx64
+macio_timer_read(uint64_t addr, unsigned len, uint32_t val) "read addr 0x%"PRIx64 " len %d val 0x%"PRIx32
+
+# gpio.c
+macio_set_gpio(int gpio, bool state) "setting GPIO %d to %d"
+macio_gpio_irq_assert(int gpio) "asserting GPIO %d"
+macio_gpio_irq_deassert(int gpio) "deasserting GPIO %d"
+macio_gpio_write(uint64_t addr, uint64_t val) "addr: 0x%"PRIx64" value: 0x%"PRIx64
+
+# pmu.c
+pmu_adb_poll(int olen) "ADB autopoll, olen=%d"
+pmu_one_sec_timer(void) "PMU one sec..."
+pmu_cmd_set_int_mask(int intmask) "Setting PMU int mask to 0x%02x"
+pmu_cmd_set_adb_autopoll(int mask) "ADB set autopoll, mask=0x%04x"
+pmu_cmd_adb_nobus(void) "ADB PACKET with no ADB bus!"
+pmu_cmd_adb_request(int inlen, int indata0, int indata1, int indata2, int indata3, int indata4) "ADB request: len=%d, cmd=0x%02x, pflags=0x%02x, adblen=%d: 0x%02x 0x%02x..."
+pmu_cmd_adb_reply(int len) "ADB reply is %d bytes"
+pmu_dispatch_cmd(const char *name) "handling command %s"
+pmu_dispatch_unknown_cmd(int cmd) "Unknown PMU command 0x%02x"
+pmu_debug_protocol_string(const char *str) "%s"
+pmu_debug_protocol_resp_size(int size) "sending %d resp bytes"
+pmu_debug_protocol_error(int portB) "protocol error! portB=0x%02x"
+pmu_debug_protocol_clear_treq(int state) "TREQ cleared, clearing TACK, state: %d"
+pmu_debug_protocol_cmd(int cmd, int cmdlen, int rsplen) "Got command byte 0x%02x, clen=%d, rlen=%d"
+pmu_debug_protocol_cmdlen(int len) "got cmd length byte: %d"
+pmu_debug_protocol_cmd_toobig(int len) "command too big (%d bytes)"
+pmu_debug_protocol_cmd_send_resp_size(int len) "sending length byte: %d"
+pmu_debug_protocol_cmd_send_resp(int pos, int len) "sending byte: %d/%d"
+pmu_debug_protocol_cmd_resp_complete(int ier) "Response send complete. IER=0x%02x"
diff --git a/hw/misc/macio/trace.h b/hw/misc/macio/trace.h
new file mode 100644
index 000000000..34a3cf1b4
--- /dev/null
+++ b/hw/misc/macio/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_misc_macio.h"
diff --git a/hw/misc/mchp_pfsoc_dmc.c b/hw/misc/mchp_pfsoc_dmc.c
new file mode 100644
index 000000000..43d8e970a
--- /dev/null
+++ b/hw/misc/mchp_pfsoc_dmc.c
@@ -0,0 +1,215 @@
+/*
+ * Microchip PolarFire SoC DDR Memory Controller module emulation
+ *
+ * Copyright (c) 2020 Wind River Systems, Inc.
+ *
+ * Author:
+ * Bin Meng <bin.meng@windriver.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 or
+ * (at your option) version 3 of the License.
+ *
+ * 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/bitops.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/misc/mchp_pfsoc_dmc.h"
+
+/* DDR SGMII PHY module */
+
+#define SGMII_PHY_IOC_REG1 0x208
+#define SGMII_PHY_TRAINING_STATUS 0x814
+#define SGMII_PHY_DQ_DQS_ERR_DONE 0x834
+#define SGMII_PHY_DQDQS_STATUS1 0x84c
+#define SGMII_PHY_PVT_STAT 0xc20
+
+static uint64_t mchp_pfsoc_ddr_sgmii_phy_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t val = 0;
+ static int training_status_bit;
+
+ switch (offset) {
+ case SGMII_PHY_IOC_REG1:
+ /* See ddr_pvt_calibration() in HSS */
+ val = BIT(4) | BIT(2);
+ break;
+ case SGMII_PHY_TRAINING_STATUS:
+ /*
+ * The codes logic emulates the training status change from
+ * DDR_TRAINING_IP_SM_BCLKSCLK to DDR_TRAINING_IP_SM_DQ_DQS.
+ *
+ * See ddr_setup() in mss_ddr.c in the HSS source codes.
+ */
+ val = 1 << training_status_bit;
+ training_status_bit = (training_status_bit + 1) % 5;
+ break;
+ case SGMII_PHY_DQ_DQS_ERR_DONE:
+ /*
+ * DDR_TRAINING_IP_SM_VERIFY state in ddr_setup(),
+ * check that DQ/DQS training passed without error.
+ */
+ val = 8;
+ break;
+ case SGMII_PHY_DQDQS_STATUS1:
+ /*
+ * DDR_TRAINING_IP_SM_VERIFY state in ddr_setup(),
+ * check that DQ/DQS calculated window is above 5 taps.
+ */
+ val = 0xff;
+ break;
+ case SGMII_PHY_PVT_STAT:
+ /* See sgmii_channel_setup() in HSS */
+ val = BIT(14) | BIT(6);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
+ "(size %d, offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, offset);
+ break;
+ }
+
+ return val;
+}
+
+static void mchp_pfsoc_ddr_sgmii_phy_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
+ "(size %d, value 0x%" PRIx64
+ ", offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, value, offset);
+}
+
+static const MemoryRegionOps mchp_pfsoc_ddr_sgmii_phy_ops = {
+ .read = mchp_pfsoc_ddr_sgmii_phy_read,
+ .write = mchp_pfsoc_ddr_sgmii_phy_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void mchp_pfsoc_ddr_sgmii_phy_realize(DeviceState *dev, Error **errp)
+{
+ MchpPfSoCDdrSgmiiPhyState *s = MCHP_PFSOC_DDR_SGMII_PHY(dev);
+
+ memory_region_init_io(&s->sgmii_phy, OBJECT(dev),
+ &mchp_pfsoc_ddr_sgmii_phy_ops, s,
+ "mchp.pfsoc.ddr_sgmii_phy",
+ MCHP_PFSOC_DDR_SGMII_PHY_REG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->sgmii_phy);
+}
+
+static void mchp_pfsoc_ddr_sgmii_phy_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "Microchip PolarFire SoC DDR SGMII PHY module";
+ dc->realize = mchp_pfsoc_ddr_sgmii_phy_realize;
+}
+
+static const TypeInfo mchp_pfsoc_ddr_sgmii_phy_info = {
+ .name = TYPE_MCHP_PFSOC_DDR_SGMII_PHY,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MchpPfSoCDdrSgmiiPhyState),
+ .class_init = mchp_pfsoc_ddr_sgmii_phy_class_init,
+};
+
+static void mchp_pfsoc_ddr_sgmii_phy_register_types(void)
+{
+ type_register_static(&mchp_pfsoc_ddr_sgmii_phy_info);
+}
+
+type_init(mchp_pfsoc_ddr_sgmii_phy_register_types)
+
+/* DDR CFG module */
+
+#define CFG_MT_DONE_ACK 0x4428
+#define CFG_STAT_DFI_INIT_COMPLETE 0x10034
+#define CFG_STAT_DFI_TRAINING_COMPLETE 0x10038
+
+static uint64_t mchp_pfsoc_ddr_cfg_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t val = 0;
+
+ switch (offset) {
+ case CFG_MT_DONE_ACK:
+ /* memory test in MTC_test() */
+ val = BIT(0);
+ break;
+ case CFG_STAT_DFI_INIT_COMPLETE:
+ /* DDR_TRAINING_IP_SM_START_CHECK state in ddr_setup() */
+ val = BIT(0);
+ break;
+ case CFG_STAT_DFI_TRAINING_COMPLETE:
+ /* DDR_TRAINING_IP_SM_VERIFY state in ddr_setup() */
+ val = BIT(0);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
+ "(size %d, offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, offset);
+ break;
+ }
+
+ return val;
+}
+
+static void mchp_pfsoc_ddr_cfg_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
+ "(size %d, value 0x%" PRIx64
+ ", offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, value, offset);
+}
+
+static const MemoryRegionOps mchp_pfsoc_ddr_cfg_ops = {
+ .read = mchp_pfsoc_ddr_cfg_read,
+ .write = mchp_pfsoc_ddr_cfg_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void mchp_pfsoc_ddr_cfg_realize(DeviceState *dev, Error **errp)
+{
+ MchpPfSoCDdrCfgState *s = MCHP_PFSOC_DDR_CFG(dev);
+
+ memory_region_init_io(&s->cfg, OBJECT(dev),
+ &mchp_pfsoc_ddr_cfg_ops, s,
+ "mchp.pfsoc.ddr_cfg",
+ MCHP_PFSOC_DDR_CFG_REG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->cfg);
+}
+
+static void mchp_pfsoc_ddr_cfg_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "Microchip PolarFire SoC DDR CFG module";
+ dc->realize = mchp_pfsoc_ddr_cfg_realize;
+}
+
+static const TypeInfo mchp_pfsoc_ddr_cfg_info = {
+ .name = TYPE_MCHP_PFSOC_DDR_CFG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MchpPfSoCDdrCfgState),
+ .class_init = mchp_pfsoc_ddr_cfg_class_init,
+};
+
+static void mchp_pfsoc_ddr_cfg_register_types(void)
+{
+ type_register_static(&mchp_pfsoc_ddr_cfg_info);
+}
+
+type_init(mchp_pfsoc_ddr_cfg_register_types)
diff --git a/hw/misc/mchp_pfsoc_ioscb.c b/hw/misc/mchp_pfsoc_ioscb.c
new file mode 100644
index 000000000..f4fd55a0e
--- /dev/null
+++ b/hw/misc/mchp_pfsoc_ioscb.c
@@ -0,0 +1,241 @@
+/*
+ * Microchip PolarFire SoC IOSCB module emulation
+ *
+ * Copyright (c) 2020 Wind River Systems, Inc.
+ *
+ * Author:
+ * Bin Meng <bin.meng@windriver.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 or
+ * (at your option) version 3 of the License.
+ *
+ * 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/bitops.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/misc/mchp_pfsoc_ioscb.h"
+
+/*
+ * The whole IOSCB module registers map into the system address at 0x3000_0000,
+ * named as "System Port 0 (AXI-D0)".
+ */
+#define IOSCB_WHOLE_REG_SIZE 0x10000000
+#define IOSCB_SUBMOD_REG_SIZE 0x1000
+
+/*
+ * There are many sub-modules in the IOSCB module.
+ * See Microchip PolarFire SoC documentation (Register_Map.zip),
+ * Register Map/PF_SoC_RegMap_V1_1/MPFS250T/mpfs250t_ioscb_memmap_dri.htm
+ *
+ * The following are sub-modules offsets that are of concern.
+ */
+#define IOSCB_LANE01_BASE 0x06500000
+#define IOSCB_LANE23_BASE 0x06510000
+#define IOSCB_CTRL_BASE 0x07020000
+#define IOSCB_CFG_BASE 0x07080000
+#define IOSCB_PLL_MSS_BASE 0x0E001000
+#define IOSCB_CFM_MSS_BASE 0x0E002000
+#define IOSCB_PLL_DDR_BASE 0x0E010000
+#define IOSCB_BC_DDR_BASE 0x0E020000
+#define IOSCB_IO_CALIB_DDR_BASE 0x0E040000
+#define IOSCB_PLL_SGMII_BASE 0x0E080000
+#define IOSCB_DLL_SGMII_BASE 0x0E100000
+#define IOSCB_CFM_SGMII_BASE 0x0E200000
+#define IOSCB_BC_SGMII_BASE 0x0E400000
+#define IOSCB_IO_CALIB_SGMII_BASE 0x0E800000
+
+static uint64_t mchp_pfsoc_dummy_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
+ "(size %d, offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, offset);
+
+ return 0;
+}
+
+static void mchp_pfsoc_dummy_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
+ "(size %d, value 0x%" PRIx64
+ ", offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, value, offset);
+}
+
+static const MemoryRegionOps mchp_pfsoc_dummy_ops = {
+ .read = mchp_pfsoc_dummy_read,
+ .write = mchp_pfsoc_dummy_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/* All PLL modules in IOSCB have the same register layout */
+
+#define PLL_CTRL 0x04
+
+static uint64_t mchp_pfsoc_pll_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t val = 0;
+
+ switch (offset) {
+ case PLL_CTRL:
+ /* PLL is locked */
+ val = BIT(25);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
+ "(size %d, offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, offset);
+ break;
+ }
+
+ return val;
+}
+
+static const MemoryRegionOps mchp_pfsoc_pll_ops = {
+ .read = mchp_pfsoc_pll_read,
+ .write = mchp_pfsoc_dummy_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/* IO_CALIB_DDR submodule */
+
+#define IO_CALIB_DDR_IOC_REG1 0x08
+
+static uint64_t mchp_pfsoc_io_calib_ddr_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t val = 0;
+
+ switch (offset) {
+ case IO_CALIB_DDR_IOC_REG1:
+ /* calibration completed */
+ val = BIT(2);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
+ "(size %d, offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, offset);
+ break;
+ }
+
+ return val;
+}
+
+static const MemoryRegionOps mchp_pfsoc_io_calib_ddr_ops = {
+ .read = mchp_pfsoc_io_calib_ddr_read,
+ .write = mchp_pfsoc_dummy_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void mchp_pfsoc_ioscb_realize(DeviceState *dev, Error **errp)
+{
+ MchpPfSoCIoscbState *s = MCHP_PFSOC_IOSCB(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init(&s->container, OBJECT(s),
+ "mchp.pfsoc.ioscb", IOSCB_WHOLE_REG_SIZE);
+ sysbus_init_mmio(sbd, &s->container);
+
+ /* add subregions for all sub-modules in IOSCB */
+
+ memory_region_init_io(&s->lane01, OBJECT(s), &mchp_pfsoc_dummy_ops, s,
+ "mchp.pfsoc.ioscb.lane01", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_LANE01_BASE, &s->lane01);
+
+ memory_region_init_io(&s->lane23, OBJECT(s), &mchp_pfsoc_dummy_ops, s,
+ "mchp.pfsoc.ioscb.lane23", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_LANE23_BASE, &s->lane23);
+
+ memory_region_init_io(&s->ctrl, OBJECT(s), &mchp_pfsoc_dummy_ops, s,
+ "mchp.pfsoc.ioscb.ctrl", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_CTRL_BASE, &s->ctrl);
+
+ memory_region_init_io(&s->cfg, OBJECT(s), &mchp_pfsoc_dummy_ops, s,
+ "mchp.pfsoc.ioscb.cfg", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_CFG_BASE, &s->cfg);
+
+ memory_region_init_io(&s->pll_mss, OBJECT(s), &mchp_pfsoc_pll_ops, s,
+ "mchp.pfsoc.ioscb.pll_mss", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_PLL_MSS_BASE, &s->pll_mss);
+
+ memory_region_init_io(&s->cfm_mss, OBJECT(s), &mchp_pfsoc_dummy_ops, s,
+ "mchp.pfsoc.ioscb.cfm_mss", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_CFM_MSS_BASE, &s->cfm_mss);
+
+ memory_region_init_io(&s->pll_ddr, OBJECT(s), &mchp_pfsoc_pll_ops, s,
+ "mchp.pfsoc.ioscb.pll_ddr", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_PLL_DDR_BASE, &s->pll_ddr);
+
+ memory_region_init_io(&s->bc_ddr, OBJECT(s), &mchp_pfsoc_dummy_ops, s,
+ "mchp.pfsoc.ioscb.bc_ddr", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_BC_DDR_BASE, &s->bc_ddr);
+
+ memory_region_init_io(&s->io_calib_ddr, OBJECT(s),
+ &mchp_pfsoc_io_calib_ddr_ops, s,
+ "mchp.pfsoc.ioscb.io_calib_ddr",
+ IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_IO_CALIB_DDR_BASE,
+ &s->io_calib_ddr);
+
+ memory_region_init_io(&s->pll_sgmii, OBJECT(s), &mchp_pfsoc_pll_ops, s,
+ "mchp.pfsoc.ioscb.pll_sgmii", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_PLL_SGMII_BASE,
+ &s->pll_sgmii);
+
+ memory_region_init_io(&s->dll_sgmii, OBJECT(s), &mchp_pfsoc_dummy_ops, s,
+ "mchp.pfsoc.ioscb.dll_sgmii", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_DLL_SGMII_BASE,
+ &s->dll_sgmii);
+
+ memory_region_init_io(&s->cfm_sgmii, OBJECT(s), &mchp_pfsoc_dummy_ops, s,
+ "mchp.pfsoc.ioscb.cfm_sgmii", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_CFM_SGMII_BASE,
+ &s->cfm_sgmii);
+
+ memory_region_init_io(&s->bc_sgmii, OBJECT(s), &mchp_pfsoc_dummy_ops, s,
+ "mchp.pfsoc.ioscb.bc_sgmii", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_BC_SGMII_BASE,
+ &s->bc_sgmii);
+
+ memory_region_init_io(&s->io_calib_sgmii, OBJECT(s), &mchp_pfsoc_dummy_ops,
+ s, "mchp.pfsoc.ioscb.io_calib_sgmii",
+ IOSCB_SUBMOD_REG_SIZE);
+ memory_region_add_subregion(&s->container, IOSCB_IO_CALIB_SGMII_BASE,
+ &s->io_calib_sgmii);
+}
+
+static void mchp_pfsoc_ioscb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "Microchip PolarFire SoC IOSCB modules";
+ dc->realize = mchp_pfsoc_ioscb_realize;
+}
+
+static const TypeInfo mchp_pfsoc_ioscb_info = {
+ .name = TYPE_MCHP_PFSOC_IOSCB,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MchpPfSoCIoscbState),
+ .class_init = mchp_pfsoc_ioscb_class_init,
+};
+
+static void mchp_pfsoc_ioscb_register_types(void)
+{
+ type_register_static(&mchp_pfsoc_ioscb_info);
+}
+
+type_init(mchp_pfsoc_ioscb_register_types)
diff --git a/hw/misc/mchp_pfsoc_sysreg.c b/hw/misc/mchp_pfsoc_sysreg.c
new file mode 100644
index 000000000..89571eded
--- /dev/null
+++ b/hw/misc/mchp_pfsoc_sysreg.c
@@ -0,0 +1,98 @@
+/*
+ * Microchip PolarFire SoC SYSREG module emulation
+ *
+ * Copyright (c) 2020 Wind River Systems, Inc.
+ *
+ * Author:
+ * Bin Meng <bin.meng@windriver.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 or
+ * (at your option) version 3 of the License.
+ *
+ * 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/bitops.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/misc/mchp_pfsoc_sysreg.h"
+
+#define ENVM_CR 0xb8
+
+static uint64_t mchp_pfsoc_sysreg_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ uint32_t val = 0;
+
+ switch (offset) {
+ case ENVM_CR:
+ /* Indicate the eNVM is running at the configured divider rate */
+ val = BIT(6);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
+ "(size %d, offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, offset);
+ break;
+ }
+
+ return val;
+}
+
+static void mchp_pfsoc_sysreg_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
+ "(size %d, value 0x%" PRIx64
+ ", offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, value, offset);
+}
+
+static const MemoryRegionOps mchp_pfsoc_sysreg_ops = {
+ .read = mchp_pfsoc_sysreg_read,
+ .write = mchp_pfsoc_sysreg_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void mchp_pfsoc_sysreg_realize(DeviceState *dev, Error **errp)
+{
+ MchpPfSoCSysregState *s = MCHP_PFSOC_SYSREG(dev);
+
+ memory_region_init_io(&s->sysreg, OBJECT(dev),
+ &mchp_pfsoc_sysreg_ops, s,
+ "mchp.pfsoc.sysreg",
+ MCHP_PFSOC_SYSREG_REG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->sysreg);
+}
+
+static void mchp_pfsoc_sysreg_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "Microchip PolarFire SoC SYSREG module";
+ dc->realize = mchp_pfsoc_sysreg_realize;
+}
+
+static const TypeInfo mchp_pfsoc_sysreg_info = {
+ .name = TYPE_MCHP_PFSOC_SYSREG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MchpPfSoCSysregState),
+ .class_init = mchp_pfsoc_sysreg_class_init,
+};
+
+static void mchp_pfsoc_sysreg_register_types(void)
+{
+ type_register_static(&mchp_pfsoc_sysreg_info);
+}
+
+type_init(mchp_pfsoc_sysreg_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
new file mode 100644
index 000000000..3f41a3a5b
--- /dev/null
+++ b/hw/misc/meson.build
@@ -0,0 +1,128 @@
+softmmu_ss.add(when: 'CONFIG_APPLESMC', if_true: files('applesmc.c'))
+softmmu_ss.add(when: 'CONFIG_EDU', if_true: files('edu.c'))
+softmmu_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
+softmmu_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
+softmmu_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
+softmmu_ss.add(when: 'CONFIG_PCA9552', if_true: files('pca9552.c'))
+softmmu_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
+softmmu_ss.add(when: 'CONFIG_SGA', if_true: files('sga.c'))
+softmmu_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
+softmmu_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
+softmmu_ss.add(when: 'CONFIG_LED', if_true: files('led.c'))
+softmmu_ss.add(when: 'CONFIG_PVPANIC_COMMON', if_true: files('pvpanic.c'))
+
+# ARM devices
+softmmu_ss.add(when: 'CONFIG_PL310', if_true: files('arm_l2x0.c'))
+softmmu_ss.add(when: 'CONFIG_INTEGRATOR_DEBUG', if_true: files('arm_integrator_debug.c'))
+softmmu_ss.add(when: 'CONFIG_A9SCU', if_true: files('a9scu.c'))
+softmmu_ss.add(when: 'CONFIG_ARM11SCU', if_true: files('arm11scu.c'))
+
+softmmu_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.c'))
+
+# Mac devices
+softmmu_ss.add(when: 'CONFIG_MOS6522', if_true: files('mos6522.c'))
+
+# virt devices
+softmmu_ss.add(when: 'CONFIG_VIRT_CTRL', if_true: files('virt_ctrl.c'))
+
+# RISC-V devices
+softmmu_ss.add(when: 'CONFIG_MCHP_PFSOC_DMC', if_true: files('mchp_pfsoc_dmc.c'))
+softmmu_ss.add(when: 'CONFIG_MCHP_PFSOC_IOSCB', if_true: files('mchp_pfsoc_ioscb.c'))
+softmmu_ss.add(when: 'CONFIG_MCHP_PFSOC_SYSREG', if_true: files('mchp_pfsoc_sysreg.c'))
+softmmu_ss.add(when: 'CONFIG_SIFIVE_TEST', if_true: files('sifive_test.c'))
+softmmu_ss.add(when: 'CONFIG_SIFIVE_E_PRCI', if_true: files('sifive_e_prci.c'))
+softmmu_ss.add(when: 'CONFIG_SIFIVE_U_OTP', if_true: files('sifive_u_otp.c'))
+softmmu_ss.add(when: 'CONFIG_SIFIVE_U_PRCI', if_true: files('sifive_u_prci.c'))
+
+subdir('macio')
+
+softmmu_ss.add(when: 'CONFIG_IVSHMEM_DEVICE', if_true: files('ivshmem.c'))
+
+softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-ccu.c'))
+specific_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-cpucfg.c'))
+softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-dramc.c'))
+softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-sysctrl.c'))
+softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sid.c'))
+softmmu_ss.add(when: 'CONFIG_REALVIEW', if_true: files('arm_sysctl.c'))
+softmmu_ss.add(when: 'CONFIG_NSERIES', if_true: files('cbus.c'))
+softmmu_ss.add(when: 'CONFIG_ECCMEMCTL', if_true: files('eccmemctl.c'))
+softmmu_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_pmu.c', 'exynos4210_clk.c', 'exynos4210_rng.c'))
+softmmu_ss.add(when: 'CONFIG_IMX', if_true: files(
+ 'imx25_ccm.c',
+ 'imx31_ccm.c',
+ 'imx6_ccm.c',
+ 'imx6ul_ccm.c',
+ 'imx7_ccm.c',
+ 'imx7_gpr.c',
+ 'imx7_snvs.c',
+ 'imx_ccm.c',
+ 'imx_rngc.c',
+))
+softmmu_ss.add(when: 'CONFIG_MAINSTONE', if_true: files('mst_fpga.c'))
+softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files(
+ 'npcm7xx_clk.c',
+ 'npcm7xx_gcr.c',
+ 'npcm7xx_mft.c',
+ 'npcm7xx_pwm.c',
+ 'npcm7xx_rng.c',
+))
+softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files(
+ 'omap_clk.c',
+ 'omap_gpmc.c',
+ 'omap_l4.c',
+ 'omap_sdrc.c',
+ 'omap_tap.c',
+))
+softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files(
+ 'bcm2835_mbox.c',
+ 'bcm2835_mphi.c',
+ 'bcm2835_property.c',
+ 'bcm2835_rng.c',
+ 'bcm2835_thermal.c',
+ 'bcm2835_cprman.c',
+ 'bcm2835_powermgt.c',
+))
+softmmu_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
+softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
+softmmu_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-xramc.c'))
+softmmu_ss.add(when: 'CONFIG_STM32F2XX_SYSCFG', if_true: files('stm32f2xx_syscfg.c'))
+softmmu_ss.add(when: 'CONFIG_STM32F4XX_SYSCFG', if_true: files('stm32f4xx_syscfg.c'))
+softmmu_ss.add(when: 'CONFIG_STM32F4XX_EXTI', if_true: files('stm32f4xx_exti.c'))
+softmmu_ss.add(when: 'CONFIG_MPS2_FPGAIO', if_true: files('mps2-fpgaio.c'))
+softmmu_ss.add(when: 'CONFIG_MPS2_SCC', if_true: files('mps2-scc.c'))
+
+softmmu_ss.add(when: 'CONFIG_TZ_MPC', if_true: files('tz-mpc.c'))
+softmmu_ss.add(when: 'CONFIG_TZ_MSC', if_true: files('tz-msc.c'))
+softmmu_ss.add(when: 'CONFIG_TZ_PPC', if_true: files('tz-ppc.c'))
+softmmu_ss.add(when: 'CONFIG_IOTKIT_SECCTL', if_true: files('iotkit-secctl.c'))
+softmmu_ss.add(when: 'CONFIG_IOTKIT_SYSINFO', if_true: files('iotkit-sysinfo.c'))
+softmmu_ss.add(when: 'CONFIG_ARMSSE_CPU_PWRCTRL', if_true: files('armsse-cpu-pwrctrl.c'))
+softmmu_ss.add(when: 'CONFIG_ARMSSE_CPUID', if_true: files('armsse-cpuid.c'))
+softmmu_ss.add(when: 'CONFIG_ARMSSE_MHU', if_true: files('armsse-mhu.c'))
+
+softmmu_ss.add(when: 'CONFIG_PVPANIC_ISA', if_true: files('pvpanic-isa.c'))
+softmmu_ss.add(when: 'CONFIG_PVPANIC_PCI', if_true: files('pvpanic-pci.c'))
+softmmu_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c'))
+softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
+ 'aspeed_hace.c',
+ 'aspeed_lpc.c',
+ 'aspeed_scu.c',
+ 'aspeed_sdmc.c',
+ 'aspeed_xdma.c'))
+
+softmmu_ss.add(when: 'CONFIG_MSF2', if_true: files('msf2-sysreg.c'))
+softmmu_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_rng.c'))
+
+softmmu_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_ahb_apb_pnp.c'))
+
+specific_ss.add(when: 'CONFIG_AVR_POWER', if_true: files('avr_power.c'))
+
+specific_ss.add(when: 'CONFIG_IMX', if_true: files('imx6_src.c'))
+specific_ss.add(when: 'CONFIG_IOTKIT_SYSCTL', if_true: files('iotkit-sysctl.c'))
+
+specific_ss.add(when: 'CONFIG_MAC_VIA', if_true: files('mac_via.c'))
+
+specific_ss.add(when: 'CONFIG_MIPS_CPS', if_true: files('mips_cmgcr.c', 'mips_cpc.c'))
+specific_ss.add(when: 'CONFIG_MIPS_ITU', if_true: files('mips_itu.c'))
+
+specific_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
diff --git a/hw/misc/mips_cmgcr.c b/hw/misc/mips_cmgcr.c
new file mode 100644
index 000000000..3c8b37f70
--- /dev/null
+++ b/hw/misc/mips_cmgcr.c
@@ -0,0 +1,255 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved.
+ * Authors: Sanjay Lal <sanjayl@kymasys.com>
+ *
+ * Copyright (C) 2015 Imagination Technologies
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/misc/mips_cmgcr.h"
+#include "hw/misc/mips_cpc.h"
+#include "hw/qdev-properties.h"
+#include "hw/intc/mips_gic.h"
+
+static inline bool is_cpc_connected(MIPSGCRState *s)
+{
+ return s->cpc_mr != NULL;
+}
+
+static inline bool is_gic_connected(MIPSGCRState *s)
+{
+ return s->gic_mr != NULL;
+}
+
+static inline void update_gcr_base(MIPSGCRState *gcr, uint64_t val)
+{
+ CPUState *cpu;
+ MIPSCPU *mips_cpu;
+
+ gcr->gcr_base = val & GCR_BASE_GCRBASE_MSK;
+ memory_region_set_address(&gcr->iomem, gcr->gcr_base);
+
+ CPU_FOREACH(cpu) {
+ mips_cpu = MIPS_CPU(cpu);
+ mips_cpu->env.CP0_CMGCRBase = gcr->gcr_base >> 4;
+ }
+}
+
+static inline void update_cpc_base(MIPSGCRState *gcr, uint64_t val)
+{
+ if (is_cpc_connected(gcr)) {
+ gcr->cpc_base = val & GCR_CPC_BASE_MSK;
+ memory_region_transaction_begin();
+ memory_region_set_address(gcr->cpc_mr,
+ gcr->cpc_base & GCR_CPC_BASE_CPCBASE_MSK);
+ memory_region_set_enabled(gcr->cpc_mr,
+ gcr->cpc_base & GCR_CPC_BASE_CPCEN_MSK);
+ memory_region_transaction_commit();
+ }
+}
+
+static inline void update_gic_base(MIPSGCRState *gcr, uint64_t val)
+{
+ if (is_gic_connected(gcr)) {
+ gcr->gic_base = val & GCR_GIC_BASE_MSK;
+ memory_region_transaction_begin();
+ memory_region_set_address(gcr->gic_mr,
+ gcr->gic_base & GCR_GIC_BASE_GICBASE_MSK);
+ memory_region_set_enabled(gcr->gic_mr,
+ gcr->gic_base & GCR_GIC_BASE_GICEN_MSK);
+ memory_region_transaction_commit();
+ }
+}
+
+/* Read GCR registers */
+static uint64_t gcr_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MIPSGCRState *gcr = (MIPSGCRState *) opaque;
+ MIPSGCRVPState *current_vps = &gcr->vps[current_cpu->cpu_index];
+ MIPSGCRVPState *other_vps = &gcr->vps[current_vps->other];
+
+ switch (addr) {
+ /* Global Control Block Register */
+ case GCR_CONFIG_OFS:
+ /* Set PCORES to 0 */
+ return 0;
+ case GCR_BASE_OFS:
+ return gcr->gcr_base;
+ case GCR_REV_OFS:
+ return gcr->gcr_rev;
+ case GCR_GIC_BASE_OFS:
+ return gcr->gic_base;
+ case GCR_CPC_BASE_OFS:
+ return gcr->cpc_base;
+ case GCR_GIC_STATUS_OFS:
+ return is_gic_connected(gcr);
+ case GCR_CPC_STATUS_OFS:
+ return is_cpc_connected(gcr);
+ case GCR_L2_CONFIG_OFS:
+ /* L2 BYPASS */
+ return GCR_L2_CONFIG_BYPASS_MSK;
+ /* Core-Local and Core-Other Control Blocks */
+ case MIPS_CLCB_OFS + GCR_CL_CONFIG_OFS:
+ case MIPS_COCB_OFS + GCR_CL_CONFIG_OFS:
+ /* Set PVP to # of VPs - 1 */
+ return gcr->num_vps - 1;
+ case MIPS_CLCB_OFS + GCR_CL_RESETBASE_OFS:
+ return current_vps->reset_base;
+ case MIPS_COCB_OFS + GCR_CL_RESETBASE_OFS:
+ return other_vps->reset_base;
+ case MIPS_CLCB_OFS + GCR_CL_OTHER_OFS:
+ return current_vps->other;
+ case MIPS_COCB_OFS + GCR_CL_OTHER_OFS:
+ return other_vps->other;
+ default:
+ qemu_log_mask(LOG_UNIMP, "Read %d bytes at GCR offset 0x%" HWADDR_PRIx
+ "\n", size, addr);
+ return 0;
+ }
+ return 0;
+}
+
+static inline target_ulong get_exception_base(MIPSGCRVPState *vps)
+{
+ /* TODO: BEV_BASE and SELECT_BEV */
+ return (int32_t)(vps->reset_base & GCR_CL_RESET_BASE_RESETBASE_MSK);
+}
+
+/* Write GCR registers */
+static void gcr_write(void *opaque, hwaddr addr, uint64_t data, unsigned size)
+{
+ MIPSGCRState *gcr = (MIPSGCRState *)opaque;
+ MIPSGCRVPState *current_vps = &gcr->vps[current_cpu->cpu_index];
+ MIPSGCRVPState *other_vps = &gcr->vps[current_vps->other];
+
+ switch (addr) {
+ case GCR_BASE_OFS:
+ update_gcr_base(gcr, data);
+ break;
+ case GCR_GIC_BASE_OFS:
+ update_gic_base(gcr, data);
+ break;
+ case GCR_CPC_BASE_OFS:
+ update_cpc_base(gcr, data);
+ break;
+ case MIPS_CLCB_OFS + GCR_CL_RESETBASE_OFS:
+ current_vps->reset_base = data & GCR_CL_RESET_BASE_MSK;
+ cpu_set_exception_base(current_cpu->cpu_index,
+ get_exception_base(current_vps));
+ break;
+ case MIPS_COCB_OFS + GCR_CL_RESETBASE_OFS:
+ other_vps->reset_base = data & GCR_CL_RESET_BASE_MSK;
+ cpu_set_exception_base(current_vps->other,
+ get_exception_base(other_vps));
+ break;
+ case MIPS_CLCB_OFS + GCR_CL_OTHER_OFS:
+ if ((data & GCR_CL_OTHER_MSK) < gcr->num_vps) {
+ current_vps->other = data & GCR_CL_OTHER_MSK;
+ }
+ break;
+ case MIPS_COCB_OFS + GCR_CL_OTHER_OFS:
+ if ((data & GCR_CL_OTHER_MSK) < gcr->num_vps) {
+ other_vps->other = data & GCR_CL_OTHER_MSK;
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "Write %d bytes at GCR offset 0x%" HWADDR_PRIx
+ " 0x%" PRIx64 "\n", size, addr, data);
+ break;
+ }
+}
+
+static const MemoryRegionOps gcr_ops = {
+ .read = gcr_read,
+ .write = gcr_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .max_access_size = 8,
+ },
+};
+
+static void mips_gcr_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ MIPSGCRState *s = MIPS_GCR(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &gcr_ops, s,
+ "mips-gcr", GCR_ADDRSPACE_SZ);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void mips_gcr_reset(DeviceState *dev)
+{
+ MIPSGCRState *s = MIPS_GCR(dev);
+ int i;
+
+ update_gic_base(s, 0);
+ update_cpc_base(s, 0);
+
+ for (i = 0; i < s->num_vps; i++) {
+ s->vps[i].other = 0;
+ s->vps[i].reset_base = 0xBFC00000 & GCR_CL_RESET_BASE_MSK;
+ cpu_set_exception_base(i, get_exception_base(&s->vps[i]));
+ }
+}
+
+static const VMStateDescription vmstate_mips_gcr = {
+ .name = "mips-gcr",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(cpc_base, MIPSGCRState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static Property mips_gcr_properties[] = {
+ DEFINE_PROP_INT32("num-vp", MIPSGCRState, num_vps, 1),
+ DEFINE_PROP_INT32("gcr-rev", MIPSGCRState, gcr_rev, 0x800),
+ DEFINE_PROP_UINT64("gcr-base", MIPSGCRState, gcr_base, GCR_BASE_ADDR),
+ DEFINE_PROP_LINK("gic", MIPSGCRState, gic_mr, TYPE_MEMORY_REGION,
+ MemoryRegion *),
+ DEFINE_PROP_LINK("cpc", MIPSGCRState, cpc_mr, TYPE_MEMORY_REGION,
+ MemoryRegion *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mips_gcr_realize(DeviceState *dev, Error **errp)
+{
+ MIPSGCRState *s = MIPS_GCR(dev);
+
+ /* Create local set of registers for each VP */
+ s->vps = g_new(MIPSGCRVPState, s->num_vps);
+}
+
+static void mips_gcr_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ device_class_set_props(dc, mips_gcr_properties);
+ dc->vmsd = &vmstate_mips_gcr;
+ dc->reset = mips_gcr_reset;
+ dc->realize = mips_gcr_realize;
+}
+
+static const TypeInfo mips_gcr_info = {
+ .name = TYPE_MIPS_GCR,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MIPSGCRState),
+ .instance_init = mips_gcr_init,
+ .class_init = mips_gcr_class_init,
+};
+
+static void mips_gcr_register_types(void)
+{
+ type_register_static(&mips_gcr_info);
+}
+
+type_init(mips_gcr_register_types)
diff --git a/hw/misc/mips_cpc.c b/hw/misc/mips_cpc.c
new file mode 100644
index 000000000..4a94c8705
--- /dev/null
+++ b/hw/misc/mips_cpc.c
@@ -0,0 +1,195 @@
+/*
+ * Cluster Power Controller emulation
+ *
+ * Copyright (c) 2016 Imagination Technologies
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "cpu.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+
+#include "hw/misc/mips_cpc.h"
+#include "hw/qdev-properties.h"
+
+static inline uint64_t cpc_vp_run_mask(MIPSCPCState *cpc)
+{
+ return (1ULL << cpc->num_vp) - 1;
+}
+
+static void mips_cpu_reset_async_work(CPUState *cs, run_on_cpu_data data)
+{
+ MIPSCPCState *cpc = (MIPSCPCState *) data.host_ptr;
+
+ cpu_reset(cs);
+ cs->halted = 0;
+ cpc->vp_running |= 1ULL << cs->cpu_index;
+}
+
+static void cpc_run_vp(MIPSCPCState *cpc, uint64_t vp_run)
+{
+ CPUState *cs = first_cpu;
+
+ CPU_FOREACH(cs) {
+ uint64_t i = 1ULL << cs->cpu_index;
+ if (i & vp_run & ~cpc->vp_running) {
+ /*
+ * To avoid racing with a CPU we are just kicking off.
+ * We do the final bit of preparation for the work in
+ * the target CPUs context.
+ */
+ async_safe_run_on_cpu(cs, mips_cpu_reset_async_work,
+ RUN_ON_CPU_HOST_PTR(cpc));
+ }
+ }
+}
+
+static void cpc_stop_vp(MIPSCPCState *cpc, uint64_t vp_stop)
+{
+ CPUState *cs = first_cpu;
+
+ CPU_FOREACH(cs) {
+ uint64_t i = 1ULL << cs->cpu_index;
+ if (i & vp_stop & cpc->vp_running) {
+ cpu_interrupt(cs, CPU_INTERRUPT_HALT);
+ cpc->vp_running &= ~i;
+ }
+ }
+}
+
+static void cpc_write(void *opaque, hwaddr offset, uint64_t data,
+ unsigned size)
+{
+ MIPSCPCState *s = opaque;
+
+ switch (offset) {
+ case CPC_CL_BASE_OFS + CPC_VP_RUN_OFS:
+ case CPC_CO_BASE_OFS + CPC_VP_RUN_OFS:
+ cpc_run_vp(s, data & cpc_vp_run_mask(s));
+ break;
+ case CPC_CL_BASE_OFS + CPC_VP_STOP_OFS:
+ case CPC_CO_BASE_OFS + CPC_VP_STOP_OFS:
+ cpc_stop_vp(s, data & cpc_vp_run_mask(s));
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ break;
+ }
+
+ return;
+}
+
+static uint64_t cpc_read(void *opaque, hwaddr offset, unsigned size)
+{
+ MIPSCPCState *s = opaque;
+
+ switch (offset) {
+ case CPC_CL_BASE_OFS + CPC_VP_RUNNING_OFS:
+ case CPC_CO_BASE_OFS + CPC_VP_RUNNING_OFS:
+ return s->vp_running;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ return 0;
+ }
+}
+
+static const MemoryRegionOps cpc_ops = {
+ .read = cpc_read,
+ .write = cpc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .max_access_size = 8,
+ },
+};
+
+static void mips_cpc_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ MIPSCPCState *s = MIPS_CPC(obj);
+
+ memory_region_init_io(&s->mr, OBJECT(s), &cpc_ops, s, "mips-cpc",
+ CPC_ADDRSPACE_SZ);
+ sysbus_init_mmio(sbd, &s->mr);
+}
+
+static void mips_cpc_realize(DeviceState *dev, Error **errp)
+{
+ MIPSCPCState *s = MIPS_CPC(dev);
+
+ if (s->vp_start_running > cpc_vp_run_mask(s)) {
+ error_setg(errp,
+ "incorrect vp_start_running 0x%" PRIx64 " for num_vp = %d",
+ s->vp_running, s->num_vp);
+ return;
+ }
+}
+
+static void mips_cpc_reset(DeviceState *dev)
+{
+ MIPSCPCState *s = MIPS_CPC(dev);
+
+ /* Reflect the fact that all VPs are halted on reset */
+ s->vp_running = 0;
+
+ /* Put selected VPs into run state */
+ cpc_run_vp(s, s->vp_start_running);
+}
+
+static const VMStateDescription vmstate_mips_cpc = {
+ .name = "mips-cpc",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(vp_running, MIPSCPCState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static Property mips_cpc_properties[] = {
+ DEFINE_PROP_UINT32("num-vp", MIPSCPCState, num_vp, 0x1),
+ DEFINE_PROP_UINT64("vp-start-running", MIPSCPCState, vp_start_running, 0x1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mips_cpc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = mips_cpc_realize;
+ dc->reset = mips_cpc_reset;
+ dc->vmsd = &vmstate_mips_cpc;
+ device_class_set_props(dc, mips_cpc_properties);
+}
+
+static const TypeInfo mips_cpc_info = {
+ .name = TYPE_MIPS_CPC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MIPSCPCState),
+ .instance_init = mips_cpc_init,
+ .class_init = mips_cpc_class_init,
+};
+
+static void mips_cpc_register_types(void)
+{
+ type_register_static(&mips_cpc_info);
+}
+
+type_init(mips_cpc_register_types)
diff --git a/hw/misc/mips_itu.c b/hw/misc/mips_itu.c
new file mode 100644
index 000000000..80683fed3
--- /dev/null
+++ b/hw/misc/mips_itu.c
@@ -0,0 +1,581 @@
+/*
+ * Inter-Thread Communication Unit emulation.
+ *
+ * Copyright (c) 2016 Imagination Technologies
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "exec/exec-all.h"
+#include "hw/misc/mips_itu.h"
+#include "hw/qdev-properties.h"
+
+#define ITC_TAG_ADDRSPACE_SZ (ITC_ADDRESSMAP_NUM * 8)
+/* Initialize as 4kB area to fit all 32 cells with default 128B grain.
+ Storage may be resized by the software. */
+#define ITC_STORAGE_ADDRSPACE_SZ 0x1000
+
+#define ITC_FIFO_NUM_MAX 16
+#define ITC_SEMAPH_NUM_MAX 16
+#define ITC_AM1_NUMENTRIES_OFS 20
+
+#define ITC_CELL_PV_MAX_VAL 0xFFFF
+
+#define ITC_CELL_TAG_FIFO_DEPTH 28
+#define ITC_CELL_TAG_FIFO_PTR 18
+#define ITC_CELL_TAG_FIFO 17
+#define ITC_CELL_TAG_T 16
+#define ITC_CELL_TAG_F 1
+#define ITC_CELL_TAG_E 0
+
+#define ITC_AM0_BASE_ADDRESS_MASK 0xFFFFFC00ULL
+#define ITC_AM0_EN_MASK 0x1
+
+#define ITC_AM1_ADDR_MASK_MASK 0x1FC00
+#define ITC_AM1_ENTRY_GRAIN_MASK 0x7
+
+typedef enum ITCView {
+ ITCVIEW_BYPASS = 0,
+ ITCVIEW_CONTROL = 1,
+ ITCVIEW_EF_SYNC = 2,
+ ITCVIEW_EF_TRY = 3,
+ ITCVIEW_PV_SYNC = 4,
+ ITCVIEW_PV_TRY = 5,
+ ITCVIEW_PV_ICR0 = 15,
+} ITCView;
+
+#define ITC_ICR0_CELL_NUM 16
+#define ITC_ICR0_BLK_GRAIN 8
+#define ITC_ICR0_BLK_GRAIN_MASK 0x7
+#define ITC_ICR0_ERR_AXI 2
+#define ITC_ICR0_ERR_PARITY 1
+#define ITC_ICR0_ERR_EXEC 0
+
+MemoryRegion *mips_itu_get_tag_region(MIPSITUState *itu)
+{
+ return &itu->tag_io;
+}
+
+static uint64_t itc_tag_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MIPSITUState *tag = (MIPSITUState *)opaque;
+ uint64_t index = addr >> 3;
+
+ if (index >= ITC_ADDRESSMAP_NUM) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Read 0x%" PRIx64 "\n", addr);
+ return 0;
+ }
+
+ return tag->ITCAddressMap[index];
+}
+
+void itc_reconfigure(MIPSITUState *tag)
+{
+ uint64_t *am = &tag->ITCAddressMap[0];
+ MemoryRegion *mr = &tag->storage_io;
+ hwaddr address = am[0] & ITC_AM0_BASE_ADDRESS_MASK;
+ uint64_t size = (1 * KiB) + (am[1] & ITC_AM1_ADDR_MASK_MASK);
+ bool is_enabled = (am[0] & ITC_AM0_EN_MASK) != 0;
+
+ if (tag->saar_present) {
+ address = ((*(uint64_t *) tag->saar) & 0xFFFFFFFFE000ULL) << 4;
+ size = 1ULL << ((*(uint64_t *) tag->saar >> 1) & 0x1f);
+ is_enabled = *(uint64_t *) tag->saar & 1;
+ }
+
+ memory_region_transaction_begin();
+ if (!(size & (size - 1))) {
+ memory_region_set_size(mr, size);
+ }
+ memory_region_set_address(mr, address);
+ memory_region_set_enabled(mr, is_enabled);
+ memory_region_transaction_commit();
+}
+
+static void itc_tag_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ MIPSITUState *tag = (MIPSITUState *)opaque;
+ uint64_t *am = &tag->ITCAddressMap[0];
+ uint64_t am_old, mask;
+ uint64_t index = addr >> 3;
+
+ switch (index) {
+ case 0:
+ mask = ITC_AM0_BASE_ADDRESS_MASK | ITC_AM0_EN_MASK;
+ break;
+ case 1:
+ mask = ITC_AM1_ADDR_MASK_MASK | ITC_AM1_ENTRY_GRAIN_MASK;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "Bad write 0x%" PRIx64 "\n", addr);
+ return;
+ }
+
+ am_old = am[index];
+ am[index] = (data & mask) | (am_old & ~mask);
+ if (am_old != am[index]) {
+ itc_reconfigure(tag);
+ }
+}
+
+static const MemoryRegionOps itc_tag_ops = {
+ .read = itc_tag_read,
+ .write = itc_tag_write,
+ .impl = {
+ .max_access_size = 8,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static inline uint32_t get_num_cells(MIPSITUState *s)
+{
+ return s->num_fifo + s->num_semaphores;
+}
+
+static inline ITCView get_itc_view(hwaddr addr)
+{
+ return (addr >> 3) & 0xf;
+}
+
+static inline int get_cell_stride_shift(const MIPSITUState *s)
+{
+ /* Minimum interval (for EntryGain = 0) is 128 B */
+ if (s->saar_present) {
+ return 7 + ((s->icr0 >> ITC_ICR0_BLK_GRAIN) &
+ ITC_ICR0_BLK_GRAIN_MASK);
+ } else {
+ return 7 + (s->ITCAddressMap[1] & ITC_AM1_ENTRY_GRAIN_MASK);
+ }
+}
+
+static inline ITCStorageCell *get_cell(MIPSITUState *s,
+ hwaddr addr)
+{
+ uint32_t cell_idx = addr >> get_cell_stride_shift(s);
+ uint32_t num_cells = get_num_cells(s);
+
+ if (cell_idx >= num_cells) {
+ cell_idx = num_cells - 1;
+ }
+
+ return &s->cell[cell_idx];
+}
+
+static void wake_blocked_threads(ITCStorageCell *c)
+{
+ CPUState *cs;
+ CPU_FOREACH(cs) {
+ if (cs->halted && (c->blocked_threads & (1ULL << cs->cpu_index))) {
+ cpu_interrupt(cs, CPU_INTERRUPT_WAKE);
+ }
+ }
+ c->blocked_threads = 0;
+}
+
+static void QEMU_NORETURN block_thread_and_exit(ITCStorageCell *c)
+{
+ c->blocked_threads |= 1ULL << current_cpu->cpu_index;
+ current_cpu->halted = 1;
+ current_cpu->exception_index = EXCP_HLT;
+ cpu_loop_exit_restore(current_cpu, current_cpu->mem_io_pc);
+}
+
+/* ITC Bypass View */
+
+static inline uint64_t view_bypass_read(ITCStorageCell *c)
+{
+ if (c->tag.FIFO) {
+ return c->data[c->fifo_out];
+ } else {
+ return c->data[0];
+ }
+}
+
+static inline void view_bypass_write(ITCStorageCell *c, uint64_t val)
+{
+ if (c->tag.FIFO && (c->tag.FIFOPtr > 0)) {
+ int idx = (c->fifo_out + c->tag.FIFOPtr - 1) % ITC_CELL_DEPTH;
+ c->data[idx] = val;
+ }
+
+ /* ignore a write to the semaphore cell */
+}
+
+/* ITC Control View */
+
+static inline uint64_t view_control_read(ITCStorageCell *c)
+{
+ return ((uint64_t)c->tag.FIFODepth << ITC_CELL_TAG_FIFO_DEPTH) |
+ (c->tag.FIFOPtr << ITC_CELL_TAG_FIFO_PTR) |
+ (c->tag.FIFO << ITC_CELL_TAG_FIFO) |
+ (c->tag.T << ITC_CELL_TAG_T) |
+ (c->tag.E << ITC_CELL_TAG_E) |
+ (c->tag.F << ITC_CELL_TAG_F);
+}
+
+static inline void view_control_write(ITCStorageCell *c, uint64_t val)
+{
+ c->tag.T = (val >> ITC_CELL_TAG_T) & 1;
+ c->tag.E = (val >> ITC_CELL_TAG_E) & 1;
+ c->tag.F = (val >> ITC_CELL_TAG_F) & 1;
+
+ if (c->tag.E) {
+ c->tag.FIFOPtr = 0;
+ }
+}
+
+/* ITC Empty/Full View */
+
+static uint64_t view_ef_common_read(ITCStorageCell *c, bool blocking)
+{
+ uint64_t ret = 0;
+
+ if (!c->tag.FIFO) {
+ return 0;
+ }
+
+ c->tag.F = 0;
+
+ if (blocking && c->tag.E) {
+ block_thread_and_exit(c);
+ }
+
+ if (c->blocked_threads) {
+ wake_blocked_threads(c);
+ }
+
+ if (c->tag.FIFOPtr > 0) {
+ ret = c->data[c->fifo_out];
+ c->fifo_out = (c->fifo_out + 1) % ITC_CELL_DEPTH;
+ c->tag.FIFOPtr--;
+ }
+
+ if (c->tag.FIFOPtr == 0) {
+ c->tag.E = 1;
+ }
+
+ return ret;
+}
+
+static uint64_t view_ef_sync_read(ITCStorageCell *c)
+{
+ return view_ef_common_read(c, true);
+}
+
+static uint64_t view_ef_try_read(ITCStorageCell *c)
+{
+ return view_ef_common_read(c, false);
+}
+
+static inline void view_ef_common_write(ITCStorageCell *c, uint64_t val,
+ bool blocking)
+{
+ if (!c->tag.FIFO) {
+ return;
+ }
+
+ c->tag.E = 0;
+
+ if (blocking && c->tag.F) {
+ block_thread_and_exit(c);
+ }
+
+ if (c->blocked_threads) {
+ wake_blocked_threads(c);
+ }
+
+ if (c->tag.FIFOPtr < ITC_CELL_DEPTH) {
+ int idx = (c->fifo_out + c->tag.FIFOPtr) % ITC_CELL_DEPTH;
+ c->data[idx] = val;
+ c->tag.FIFOPtr++;
+ }
+
+ if (c->tag.FIFOPtr == ITC_CELL_DEPTH) {
+ c->tag.F = 1;
+ }
+}
+
+static void view_ef_sync_write(ITCStorageCell *c, uint64_t val)
+{
+ view_ef_common_write(c, val, true);
+}
+
+static void view_ef_try_write(ITCStorageCell *c, uint64_t val)
+{
+ view_ef_common_write(c, val, false);
+}
+
+/* ITC P/V View */
+
+static uint64_t view_pv_common_read(ITCStorageCell *c, bool blocking)
+{
+ uint64_t ret = c->data[0];
+
+ if (c->tag.FIFO) {
+ return 0;
+ }
+
+ if (c->data[0] > 0) {
+ c->data[0]--;
+ } else if (blocking) {
+ block_thread_and_exit(c);
+ }
+
+ return ret;
+}
+
+static uint64_t view_pv_sync_read(ITCStorageCell *c)
+{
+ return view_pv_common_read(c, true);
+}
+
+static uint64_t view_pv_try_read(ITCStorageCell *c)
+{
+ return view_pv_common_read(c, false);
+}
+
+static inline void view_pv_common_write(ITCStorageCell *c)
+{
+ if (c->tag.FIFO) {
+ return;
+ }
+
+ if (c->data[0] < ITC_CELL_PV_MAX_VAL) {
+ c->data[0]++;
+ }
+
+ if (c->blocked_threads) {
+ wake_blocked_threads(c);
+ }
+}
+
+static void view_pv_sync_write(ITCStorageCell *c)
+{
+ view_pv_common_write(c);
+}
+
+static void view_pv_try_write(ITCStorageCell *c)
+{
+ view_pv_common_write(c);
+}
+
+static void raise_exception(int excp)
+{
+ current_cpu->exception_index = excp;
+ cpu_loop_exit(current_cpu);
+}
+
+static uint64_t itc_storage_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MIPSITUState *s = (MIPSITUState *)opaque;
+ ITCStorageCell *cell = get_cell(s, addr);
+ ITCView view = get_itc_view(addr);
+ uint64_t ret = -1;
+
+ switch (size) {
+ case 1:
+ case 2:
+ s->icr0 |= 1 << ITC_ICR0_ERR_AXI;
+ raise_exception(EXCP_DBE);
+ return 0;
+ }
+
+ switch (view) {
+ case ITCVIEW_BYPASS:
+ ret = view_bypass_read(cell);
+ break;
+ case ITCVIEW_CONTROL:
+ ret = view_control_read(cell);
+ break;
+ case ITCVIEW_EF_SYNC:
+ ret = view_ef_sync_read(cell);
+ break;
+ case ITCVIEW_EF_TRY:
+ ret = view_ef_try_read(cell);
+ break;
+ case ITCVIEW_PV_SYNC:
+ ret = view_pv_sync_read(cell);
+ break;
+ case ITCVIEW_PV_TRY:
+ ret = view_pv_try_read(cell);
+ break;
+ case ITCVIEW_PV_ICR0:
+ ret = s->icr0;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "itc_storage_read: Bad ITC View %d\n", (int)view);
+ break;
+ }
+
+ return ret;
+}
+
+static void itc_storage_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned size)
+{
+ MIPSITUState *s = (MIPSITUState *)opaque;
+ ITCStorageCell *cell = get_cell(s, addr);
+ ITCView view = get_itc_view(addr);
+
+ switch (size) {
+ case 1:
+ case 2:
+ s->icr0 |= 1 << ITC_ICR0_ERR_AXI;
+ raise_exception(EXCP_DBE);
+ return;
+ }
+
+ switch (view) {
+ case ITCVIEW_BYPASS:
+ view_bypass_write(cell, data);
+ break;
+ case ITCVIEW_CONTROL:
+ view_control_write(cell, data);
+ break;
+ case ITCVIEW_EF_SYNC:
+ view_ef_sync_write(cell, data);
+ break;
+ case ITCVIEW_EF_TRY:
+ view_ef_try_write(cell, data);
+ break;
+ case ITCVIEW_PV_SYNC:
+ view_pv_sync_write(cell);
+ break;
+ case ITCVIEW_PV_TRY:
+ view_pv_try_write(cell);
+ break;
+ case ITCVIEW_PV_ICR0:
+ if (data & 0x7) {
+ /* clear ERROR bits */
+ s->icr0 &= ~(data & 0x7);
+ }
+ /* set BLK_GRAIN */
+ s->icr0 &= ~0x700;
+ s->icr0 |= data & 0x700;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "itc_storage_write: Bad ITC View %d\n", (int)view);
+ break;
+ }
+
+}
+
+static const MemoryRegionOps itc_storage_ops = {
+ .read = itc_storage_read,
+ .write = itc_storage_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void itc_reset_cells(MIPSITUState *s)
+{
+ int i;
+
+ memset(s->cell, 0, get_num_cells(s) * sizeof(s->cell[0]));
+
+ for (i = 0; i < s->num_fifo; i++) {
+ s->cell[i].tag.E = 1;
+ s->cell[i].tag.FIFO = 1;
+ s->cell[i].tag.FIFODepth = ITC_CELL_DEPTH_SHIFT;
+ }
+}
+
+static void mips_itu_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ MIPSITUState *s = MIPS_ITU(obj);
+
+ memory_region_init_io(&s->storage_io, OBJECT(s), &itc_storage_ops, s,
+ "mips-itc-storage", ITC_STORAGE_ADDRSPACE_SZ);
+ sysbus_init_mmio(sbd, &s->storage_io);
+
+ memory_region_init_io(&s->tag_io, OBJECT(s), &itc_tag_ops, s,
+ "mips-itc-tag", ITC_TAG_ADDRSPACE_SZ);
+}
+
+static void mips_itu_realize(DeviceState *dev, Error **errp)
+{
+ MIPSITUState *s = MIPS_ITU(dev);
+
+ if (s->num_fifo > ITC_FIFO_NUM_MAX) {
+ error_setg(errp, "Exceed maximum number of FIFO cells: %d",
+ s->num_fifo);
+ return;
+ }
+ if (s->num_semaphores > ITC_SEMAPH_NUM_MAX) {
+ error_setg(errp, "Exceed maximum number of Semaphore cells: %d",
+ s->num_semaphores);
+ return;
+ }
+
+ s->cell = g_new(ITCStorageCell, get_num_cells(s));
+}
+
+static void mips_itu_reset(DeviceState *dev)
+{
+ MIPSITUState *s = MIPS_ITU(dev);
+
+ if (s->saar_present) {
+ *(uint64_t *) s->saar = 0x11 << 1;
+ s->icr0 = get_num_cells(s) << ITC_ICR0_CELL_NUM;
+ } else {
+ s->ITCAddressMap[0] = 0;
+ s->ITCAddressMap[1] =
+ ((ITC_STORAGE_ADDRSPACE_SZ - 1) & ITC_AM1_ADDR_MASK_MASK) |
+ (get_num_cells(s) << ITC_AM1_NUMENTRIES_OFS);
+ }
+ itc_reconfigure(s);
+
+ itc_reset_cells(s);
+}
+
+static Property mips_itu_properties[] = {
+ DEFINE_PROP_INT32("num-fifo", MIPSITUState, num_fifo,
+ ITC_FIFO_NUM_MAX),
+ DEFINE_PROP_INT32("num-semaphores", MIPSITUState, num_semaphores,
+ ITC_SEMAPH_NUM_MAX),
+ DEFINE_PROP_BOOL("saar-present", MIPSITUState, saar_present, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mips_itu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, mips_itu_properties);
+ dc->realize = mips_itu_realize;
+ dc->reset = mips_itu_reset;
+}
+
+static const TypeInfo mips_itu_info = {
+ .name = TYPE_MIPS_ITU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MIPSITUState),
+ .instance_init = mips_itu_init,
+ .class_init = mips_itu_class_init,
+};
+
+static void mips_itu_register_types(void)
+{
+ type_register_static(&mips_itu_info);
+}
+
+type_init(mips_itu_register_types)
diff --git a/hw/misc/mos6522.c b/hw/misc/mos6522.c
new file mode 100644
index 000000000..1c57332b4
--- /dev/null
+++ b/hw/misc/mos6522.c
@@ -0,0 +1,541 @@
+/*
+ * QEMU MOS6522 VIA emulation
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ * Copyright (c) 2018 Mark Cave-Ayland
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/input/adb.h"
+#include "hw/irq.h"
+#include "hw/misc/mos6522.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/timer.h"
+#include "qemu/cutils.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+/* XXX: implement all timer modes */
+
+static void mos6522_timer1_update(MOS6522State *s, MOS6522Timer *ti,
+ int64_t current_time);
+static void mos6522_timer2_update(MOS6522State *s, MOS6522Timer *ti,
+ int64_t current_time);
+
+static void mos6522_update_irq(MOS6522State *s)
+{
+ if (s->ifr & s->ier) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static uint64_t get_counter_value(MOS6522State *s, MOS6522Timer *ti)
+{
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(s);
+
+ if (ti->index == 0) {
+ return mdc->get_timer1_counter_value(s, ti);
+ } else {
+ return mdc->get_timer2_counter_value(s, ti);
+ }
+}
+
+static uint64_t get_load_time(MOS6522State *s, MOS6522Timer *ti)
+{
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(s);
+
+ if (ti->index == 0) {
+ return mdc->get_timer1_load_time(s, ti);
+ } else {
+ return mdc->get_timer2_load_time(s, ti);
+ }
+}
+
+static unsigned int get_counter(MOS6522State *s, MOS6522Timer *ti)
+{
+ int64_t d;
+ unsigned int counter;
+
+ d = get_counter_value(s, ti);
+
+ if (ti->index == 0) {
+ /* the timer goes down from latch to -1 (period of latch + 2) */
+ if (d <= (ti->counter_value + 1)) {
+ counter = (ti->counter_value - d) & 0xffff;
+ } else {
+ counter = (d - (ti->counter_value + 1)) % (ti->latch + 2);
+ counter = (ti->latch - counter) & 0xffff;
+ }
+ } else {
+ counter = (ti->counter_value - d) & 0xffff;
+ }
+ return counter;
+}
+
+static void set_counter(MOS6522State *s, MOS6522Timer *ti, unsigned int val)
+{
+ trace_mos6522_set_counter(1 + ti->index, val);
+ ti->load_time = get_load_time(s, ti);
+ ti->counter_value = val;
+ if (ti->index == 0) {
+ mos6522_timer1_update(s, ti, ti->load_time);
+ } else {
+ mos6522_timer2_update(s, ti, ti->load_time);
+ }
+}
+
+static int64_t get_next_irq_time(MOS6522State *s, MOS6522Timer *ti,
+ int64_t current_time)
+{
+ int64_t d, next_time;
+ unsigned int counter;
+
+ if (ti->frequency == 0) {
+ return INT64_MAX;
+ }
+
+ /* current counter value */
+ d = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - ti->load_time,
+ ti->frequency, NANOSECONDS_PER_SECOND);
+
+ /* the timer goes down from latch to -1 (period of latch + 2) */
+ if (d <= (ti->counter_value + 1)) {
+ counter = (ti->counter_value - d) & 0xffff;
+ } else {
+ counter = (d - (ti->counter_value + 1)) % (ti->latch + 2);
+ counter = (ti->latch - counter) & 0xffff;
+ }
+
+ /* Note: we consider the irq is raised on 0 */
+ if (counter == 0xffff) {
+ next_time = d + ti->latch + 1;
+ } else if (counter == 0) {
+ next_time = d + ti->latch + 2;
+ } else {
+ next_time = d + counter;
+ }
+ trace_mos6522_get_next_irq_time(ti->latch, d, next_time - d);
+ next_time = muldiv64(next_time, NANOSECONDS_PER_SECOND, ti->frequency) +
+ ti->load_time;
+
+ if (next_time <= current_time) {
+ next_time = current_time + 1;
+ }
+ return next_time;
+}
+
+static void mos6522_timer1_update(MOS6522State *s, MOS6522Timer *ti,
+ int64_t current_time)
+{
+ if (!ti->timer) {
+ return;
+ }
+ ti->next_irq_time = get_next_irq_time(s, ti, current_time);
+ if ((s->ier & T1_INT) == 0 || (s->acr & T1MODE) != T1MODE_CONT) {
+ timer_del(ti->timer);
+ } else {
+ timer_mod(ti->timer, ti->next_irq_time);
+ }
+}
+
+static void mos6522_timer2_update(MOS6522State *s, MOS6522Timer *ti,
+ int64_t current_time)
+{
+ if (!ti->timer) {
+ return;
+ }
+ ti->next_irq_time = get_next_irq_time(s, ti, current_time);
+ if ((s->ier & T2_INT) == 0) {
+ timer_del(ti->timer);
+ } else {
+ timer_mod(ti->timer, ti->next_irq_time);
+ }
+}
+
+static void mos6522_timer1(void *opaque)
+{
+ MOS6522State *s = opaque;
+ MOS6522Timer *ti = &s->timers[0];
+
+ mos6522_timer1_update(s, ti, ti->next_irq_time);
+ s->ifr |= T1_INT;
+ mos6522_update_irq(s);
+}
+
+static void mos6522_timer2(void *opaque)
+{
+ MOS6522State *s = opaque;
+ MOS6522Timer *ti = &s->timers[1];
+
+ mos6522_timer2_update(s, ti, ti->next_irq_time);
+ s->ifr |= T2_INT;
+ mos6522_update_irq(s);
+}
+
+static void mos6522_set_sr_int(MOS6522State *s)
+{
+ trace_mos6522_set_sr_int();
+ s->ifr |= SR_INT;
+ mos6522_update_irq(s);
+}
+
+static uint64_t mos6522_get_counter_value(MOS6522State *s, MOS6522Timer *ti)
+{
+ return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - ti->load_time,
+ ti->frequency, NANOSECONDS_PER_SECOND);
+}
+
+static uint64_t mos6522_get_load_time(MOS6522State *s, MOS6522Timer *ti)
+{
+ uint64_t load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ return load_time;
+}
+
+static void mos6522_portA_write(MOS6522State *s)
+{
+ qemu_log_mask(LOG_UNIMP, "portA_write unimplemented\n");
+}
+
+static void mos6522_portB_write(MOS6522State *s)
+{
+ qemu_log_mask(LOG_UNIMP, "portB_write unimplemented\n");
+}
+
+uint64_t mos6522_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MOS6522State *s = opaque;
+ uint32_t val;
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ if (now >= s->timers[0].next_irq_time) {
+ mos6522_timer1_update(s, &s->timers[0], now);
+ s->ifr |= T1_INT;
+ }
+ if (now >= s->timers[1].next_irq_time) {
+ mos6522_timer2_update(s, &s->timers[1], now);
+ s->ifr |= T2_INT;
+ }
+ switch (addr) {
+ case VIA_REG_B:
+ val = s->b;
+ break;
+ case VIA_REG_A:
+ qemu_log_mask(LOG_UNIMP, "Read access to register A with handshake");
+ /* fall through */
+ case VIA_REG_ANH:
+ val = s->a;
+ break;
+ case VIA_REG_DIRB:
+ val = s->dirb;
+ break;
+ case VIA_REG_DIRA:
+ val = s->dira;
+ break;
+ case VIA_REG_T1CL:
+ val = get_counter(s, &s->timers[0]) & 0xff;
+ s->ifr &= ~T1_INT;
+ mos6522_update_irq(s);
+ break;
+ case VIA_REG_T1CH:
+ val = get_counter(s, &s->timers[0]) >> 8;
+ mos6522_update_irq(s);
+ break;
+ case VIA_REG_T1LL:
+ val = s->timers[0].latch & 0xff;
+ break;
+ case VIA_REG_T1LH:
+ /* XXX: check this */
+ val = (s->timers[0].latch >> 8) & 0xff;
+ break;
+ case VIA_REG_T2CL:
+ val = get_counter(s, &s->timers[1]) & 0xff;
+ s->ifr &= ~T2_INT;
+ mos6522_update_irq(s);
+ break;
+ case VIA_REG_T2CH:
+ val = get_counter(s, &s->timers[1]) >> 8;
+ break;
+ case VIA_REG_SR:
+ val = s->sr;
+ s->ifr &= ~SR_INT;
+ mos6522_update_irq(s);
+ break;
+ case VIA_REG_ACR:
+ val = s->acr;
+ break;
+ case VIA_REG_PCR:
+ val = s->pcr;
+ break;
+ case VIA_REG_IFR:
+ val = s->ifr;
+ if (s->ifr & s->ier) {
+ val |= 0x80;
+ }
+ break;
+ case VIA_REG_IER:
+ val = s->ier | 0x80;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ if (addr != VIA_REG_IFR || val != 0) {
+ trace_mos6522_read(addr, val);
+ }
+
+ return val;
+}
+
+void mos6522_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+ MOS6522State *s = opaque;
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(s);
+
+ trace_mos6522_write(addr, val);
+
+ switch (addr) {
+ case VIA_REG_B:
+ s->b = (s->b & ~s->dirb) | (val & s->dirb);
+ mdc->portB_write(s);
+ break;
+ case VIA_REG_A:
+ qemu_log_mask(LOG_UNIMP, "Write access to register A with handshake");
+ /* fall through */
+ case VIA_REG_ANH:
+ s->a = (s->a & ~s->dira) | (val & s->dira);
+ mdc->portA_write(s);
+ break;
+ case VIA_REG_DIRB:
+ s->dirb = val;
+ break;
+ case VIA_REG_DIRA:
+ s->dira = val;
+ break;
+ case VIA_REG_T1CL:
+ s->timers[0].latch = (s->timers[0].latch & 0xff00) | val;
+ mos6522_timer1_update(s, &s->timers[0],
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ break;
+ case VIA_REG_T1CH:
+ s->timers[0].latch = (s->timers[0].latch & 0xff) | (val << 8);
+ s->ifr &= ~T1_INT;
+ set_counter(s, &s->timers[0], s->timers[0].latch);
+ break;
+ case VIA_REG_T1LL:
+ s->timers[0].latch = (s->timers[0].latch & 0xff00) | val;
+ mos6522_timer1_update(s, &s->timers[0],
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ break;
+ case VIA_REG_T1LH:
+ s->timers[0].latch = (s->timers[0].latch & 0xff) | (val << 8);
+ s->ifr &= ~T1_INT;
+ mos6522_timer1_update(s, &s->timers[0],
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ break;
+ case VIA_REG_T2CL:
+ s->timers[1].latch = (s->timers[1].latch & 0xff00) | val;
+ break;
+ case VIA_REG_T2CH:
+ /* To ensure T2 generates an interrupt on zero crossing with the
+ common timer code, write the value directly from the latch to
+ the counter */
+ s->timers[1].latch = (s->timers[1].latch & 0xff) | (val << 8);
+ s->ifr &= ~T2_INT;
+ set_counter(s, &s->timers[1], s->timers[1].latch);
+ break;
+ case VIA_REG_SR:
+ s->sr = val;
+ break;
+ case VIA_REG_ACR:
+ s->acr = val;
+ mos6522_timer1_update(s, &s->timers[0],
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ break;
+ case VIA_REG_PCR:
+ s->pcr = val;
+ break;
+ case VIA_REG_IFR:
+ /* reset bits */
+ s->ifr &= ~val;
+ mos6522_update_irq(s);
+ break;
+ case VIA_REG_IER:
+ if (val & IER_SET) {
+ /* set bits */
+ s->ier |= val & 0x7f;
+ } else {
+ /* reset bits */
+ s->ier &= ~val;
+ }
+ mos6522_update_irq(s);
+ /* if IER is modified starts needed timers */
+ mos6522_timer1_update(s, &s->timers[0],
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ mos6522_timer2_update(s, &s->timers[1],
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static const MemoryRegionOps mos6522_ops = {
+ .read = mos6522_read,
+ .write = mos6522_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const VMStateDescription vmstate_mos6522_timer = {
+ .name = "mos6522_timer",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(latch, MOS6522Timer),
+ VMSTATE_UINT16(counter_value, MOS6522Timer),
+ VMSTATE_INT64(load_time, MOS6522Timer),
+ VMSTATE_INT64(next_irq_time, MOS6522Timer),
+ VMSTATE_TIMER_PTR(timer, MOS6522Timer),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_mos6522 = {
+ .name = "mos6522",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(a, MOS6522State),
+ VMSTATE_UINT8(b, MOS6522State),
+ VMSTATE_UINT8(dira, MOS6522State),
+ VMSTATE_UINT8(dirb, MOS6522State),
+ VMSTATE_UINT8(sr, MOS6522State),
+ VMSTATE_UINT8(acr, MOS6522State),
+ VMSTATE_UINT8(pcr, MOS6522State),
+ VMSTATE_UINT8(ifr, MOS6522State),
+ VMSTATE_UINT8(ier, MOS6522State),
+ VMSTATE_STRUCT_ARRAY(timers, MOS6522State, 2, 0,
+ vmstate_mos6522_timer, MOS6522Timer),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mos6522_reset(DeviceState *dev)
+{
+ MOS6522State *s = MOS6522(dev);
+
+ s->b = 0;
+ s->a = 0;
+ s->dirb = 0xff;
+ s->dira = 0;
+ s->sr = 0;
+ s->acr = 0;
+ s->pcr = 0;
+ s->ifr = 0;
+ s->ier = 0;
+ /* s->ier = T1_INT | SR_INT; */
+
+ s->timers[0].frequency = s->frequency;
+ s->timers[0].latch = 0xffff;
+ set_counter(s, &s->timers[0], 0xffff);
+ timer_del(s->timers[0].timer);
+
+ s->timers[1].frequency = s->frequency;
+ s->timers[1].latch = 0xffff;
+ timer_del(s->timers[1].timer);
+}
+
+static void mos6522_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ MOS6522State *s = MOS6522(obj);
+ int i;
+
+ memory_region_init_io(&s->mem, obj, &mos6522_ops, s, "mos6522", 0x10);
+ sysbus_init_mmio(sbd, &s->mem);
+ sysbus_init_irq(sbd, &s->irq);
+
+ for (i = 0; i < ARRAY_SIZE(s->timers); i++) {
+ s->timers[i].index = i;
+ }
+
+ s->timers[0].timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, mos6522_timer1, s);
+ s->timers[1].timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, mos6522_timer2, s);
+}
+
+static void mos6522_finalize(Object *obj)
+{
+ MOS6522State *s = MOS6522(obj);
+
+ timer_free(s->timers[0].timer);
+ timer_free(s->timers[1].timer);
+}
+
+static Property mos6522_properties[] = {
+ DEFINE_PROP_UINT64("frequency", MOS6522State, frequency, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void mos6522_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ MOS6522DeviceClass *mdc = MOS6522_CLASS(oc);
+
+ dc->reset = mos6522_reset;
+ dc->vmsd = &vmstate_mos6522;
+ device_class_set_props(dc, mos6522_properties);
+ mdc->parent_reset = dc->reset;
+ mdc->set_sr_int = mos6522_set_sr_int;
+ mdc->portB_write = mos6522_portB_write;
+ mdc->portA_write = mos6522_portA_write;
+ mdc->update_irq = mos6522_update_irq;
+ mdc->get_timer1_counter_value = mos6522_get_counter_value;
+ mdc->get_timer2_counter_value = mos6522_get_counter_value;
+ mdc->get_timer1_load_time = mos6522_get_load_time;
+ mdc->get_timer2_load_time = mos6522_get_load_time;
+}
+
+static const TypeInfo mos6522_type_info = {
+ .name = TYPE_MOS6522,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MOS6522State),
+ .instance_init = mos6522_init,
+ .instance_finalize = mos6522_finalize,
+ .abstract = true,
+ .class_size = sizeof(MOS6522DeviceClass),
+ .class_init = mos6522_class_init,
+};
+
+static void mos6522_register_types(void)
+{
+ type_register_static(&mos6522_type_info);
+}
+
+type_init(mos6522_register_types)
diff --git a/hw/misc/mps2-fpgaio.c b/hw/misc/mps2-fpgaio.c
new file mode 100644
index 000000000..07b8cbdad
--- /dev/null
+++ b/hw/misc/mps2-fpgaio.c
@@ -0,0 +1,355 @@
+/*
+ * ARM MPS2 AN505 FPGAIO emulation
+ *
+ * Copyright (c) 2018 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+/* This is a model of the "FPGA system control and I/O" block found
+ * in the AN505 FPGA image for the MPS2 devboard.
+ * It is documented in AN505:
+ * https://developer.arm.com/documentation/dai0505/latest/
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/registerfields.h"
+#include "hw/misc/mps2-fpgaio.h"
+#include "hw/misc/led.h"
+#include "hw/qdev-properties.h"
+#include "qemu/timer.h"
+
+REG32(LED0, 0)
+REG32(DBGCTRL, 4)
+REG32(BUTTON, 8)
+REG32(CLK1HZ, 0x10)
+REG32(CLK100HZ, 0x14)
+REG32(COUNTER, 0x18)
+REG32(PRESCALE, 0x1c)
+REG32(PSCNTR, 0x20)
+REG32(SWITCH, 0x28)
+REG32(MISC, 0x4c)
+
+static uint32_t counter_from_tickoff(int64_t now, int64_t tick_offset, int frq)
+{
+ return muldiv64(now - tick_offset, frq, NANOSECONDS_PER_SECOND);
+}
+
+static int64_t tickoff_from_counter(int64_t now, uint32_t count, int frq)
+{
+ return now - muldiv64(count, NANOSECONDS_PER_SECOND, frq);
+}
+
+static void resync_counter(MPS2FPGAIO *s)
+{
+ /*
+ * Update s->counter and s->pscntr to their true current values
+ * by calculating how many times PSCNTR has ticked since the
+ * last time we did a resync.
+ */
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ int64_t elapsed = now - s->pscntr_sync_ticks;
+
+ /*
+ * Round elapsed down to a whole number of PSCNTR ticks, so we don't
+ * lose time if we do multiple resyncs in a single tick.
+ */
+ uint64_t ticks = muldiv64(elapsed, s->prescale_clk, NANOSECONDS_PER_SECOND);
+
+ /*
+ * Work out what PSCNTR and COUNTER have moved to. We assume that
+ * PSCNTR reloads from PRESCALE one tick-period after it hits zero,
+ * and that COUNTER increments at the same moment.
+ */
+ if (ticks == 0) {
+ /* We haven't ticked since the last time we were asked */
+ return;
+ } else if (ticks < s->pscntr) {
+ /* We haven't yet reached zero, just reduce the PSCNTR */
+ s->pscntr -= ticks;
+ } else {
+ if (s->prescale == 0) {
+ /*
+ * If the reload value is zero then the PSCNTR will stick
+ * at zero once it reaches it, and so we will increment
+ * COUNTER every tick after that.
+ */
+ s->counter += ticks - s->pscntr;
+ s->pscntr = 0;
+ } else {
+ /*
+ * This is the complicated bit. This ASCII art diagram gives an
+ * example with PRESCALE==5 PSCNTR==7:
+ *
+ * ticks 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
+ * PSCNTR 7 6 5 4 3 2 1 0 5 4 3 2 1 0 5
+ * cinc 1 2
+ * y 0 1 2 3 4 5 6 7 8 9 10 11 12
+ * x 0 1 2 3 4 5 0 1 2 3 4 5 0
+ *
+ * where x = y % (s->prescale + 1)
+ * and so PSCNTR = s->prescale - x
+ * and COUNTER is incremented by y / (s->prescale + 1)
+ *
+ * The case where PSCNTR < PRESCALE works out the same,
+ * though we must be careful to calculate y as 64-bit unsigned
+ * for all parts of the expression.
+ * y < 0 is not possible because that implies ticks < s->pscntr.
+ */
+ uint64_t y = ticks - s->pscntr + s->prescale;
+ s->pscntr = s->prescale - (y % (s->prescale + 1));
+ s->counter += y / (s->prescale + 1);
+ }
+ }
+
+ /*
+ * Only advance the sync time to the timestamp of the last PSCNTR tick,
+ * not all the way to 'now', so we don't lose time if we do multiple
+ * resyncs in a single tick.
+ */
+ s->pscntr_sync_ticks += muldiv64(ticks, NANOSECONDS_PER_SECOND,
+ s->prescale_clk);
+}
+
+static uint64_t mps2_fpgaio_read(void *opaque, hwaddr offset, unsigned size)
+{
+ MPS2FPGAIO *s = MPS2_FPGAIO(opaque);
+ uint64_t r;
+ int64_t now;
+
+ switch (offset) {
+ case A_LED0:
+ r = s->led0;
+ break;
+ case A_DBGCTRL:
+ if (!s->has_dbgctrl) {
+ goto bad_offset;
+ }
+ r = s->dbgctrl;
+ break;
+ case A_BUTTON:
+ /* User-pressable board buttons. We don't model that, so just return
+ * zeroes.
+ */
+ r = 0;
+ break;
+ case A_PRESCALE:
+ r = s->prescale;
+ break;
+ case A_MISC:
+ r = s->misc;
+ break;
+ case A_CLK1HZ:
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ r = counter_from_tickoff(now, s->clk1hz_tick_offset, 1);
+ break;
+ case A_CLK100HZ:
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ r = counter_from_tickoff(now, s->clk100hz_tick_offset, 100);
+ break;
+ case A_COUNTER:
+ resync_counter(s);
+ r = s->counter;
+ break;
+ case A_PSCNTR:
+ resync_counter(s);
+ r = s->pscntr;
+ break;
+ case A_SWITCH:
+ if (!s->has_switches) {
+ goto bad_offset;
+ }
+ /* User-togglable board switches. We don't model that, so report 0. */
+ r = 0;
+ break;
+ default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MPS2 FPGAIO read: bad offset %x\n", (int) offset);
+ r = 0;
+ break;
+ }
+
+ trace_mps2_fpgaio_read(offset, r, size);
+ return r;
+}
+
+static void mps2_fpgaio_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ MPS2FPGAIO *s = MPS2_FPGAIO(opaque);
+ int64_t now;
+
+ trace_mps2_fpgaio_write(offset, value, size);
+
+ switch (offset) {
+ case A_LED0:
+ if (s->num_leds != 0) {
+ uint32_t i;
+
+ s->led0 = value & MAKE_64BIT_MASK(0, s->num_leds);
+ for (i = 0; i < s->num_leds; i++) {
+ led_set_state(s->led[i], value & (1 << i));
+ }
+ }
+ break;
+ case A_DBGCTRL:
+ if (!s->has_dbgctrl) {
+ goto bad_offset;
+ }
+ qemu_log_mask(LOG_UNIMP,
+ "MPS2 FPGAIO: DBGCTRL unimplemented\n");
+ s->dbgctrl = value;
+ break;
+ case A_PRESCALE:
+ resync_counter(s);
+ s->prescale = value;
+ break;
+ case A_MISC:
+ /* These are control bits for some of the other devices on the
+ * board (SPI, CLCD, etc). We don't implement that yet, so just
+ * make the bits read as written.
+ */
+ qemu_log_mask(LOG_UNIMP,
+ "MPS2 FPGAIO: MISC control bits unimplemented\n");
+ s->misc = value;
+ break;
+ case A_CLK1HZ:
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->clk1hz_tick_offset = tickoff_from_counter(now, value, 1);
+ break;
+ case A_CLK100HZ:
+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ s->clk100hz_tick_offset = tickoff_from_counter(now, value, 100);
+ break;
+ case A_COUNTER:
+ resync_counter(s);
+ s->counter = value;
+ break;
+ case A_PSCNTR:
+ resync_counter(s);
+ s->pscntr = value;
+ break;
+ default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MPS2 FPGAIO write: bad offset 0x%x\n", (int) offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps mps2_fpgaio_ops = {
+ .read = mps2_fpgaio_read,
+ .write = mps2_fpgaio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void mps2_fpgaio_reset(DeviceState *dev)
+{
+ MPS2FPGAIO *s = MPS2_FPGAIO(dev);
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ trace_mps2_fpgaio_reset();
+ s->led0 = 0;
+ s->prescale = 0;
+ s->misc = 0;
+ s->clk1hz_tick_offset = tickoff_from_counter(now, 0, 1);
+ s->clk100hz_tick_offset = tickoff_from_counter(now, 0, 100);
+ s->counter = 0;
+ s->pscntr = 0;
+ s->pscntr_sync_ticks = now;
+
+ for (size_t i = 0; i < s->num_leds; i++) {
+ device_cold_reset(DEVICE(s->led[i]));
+ }
+}
+
+static void mps2_fpgaio_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ MPS2FPGAIO *s = MPS2_FPGAIO(obj);
+
+ memory_region_init_io(&s->iomem, obj, &mps2_fpgaio_ops, s,
+ "mps2-fpgaio", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void mps2_fpgaio_realize(DeviceState *dev, Error **errp)
+{
+ MPS2FPGAIO *s = MPS2_FPGAIO(dev);
+ uint32_t i;
+
+ if (s->num_leds > MPS2FPGAIO_MAX_LEDS) {
+ error_setg(errp, "num-leds cannot be greater than %d",
+ MPS2FPGAIO_MAX_LEDS);
+ return;
+ }
+
+ for (i = 0; i < s->num_leds; i++) {
+ g_autofree char *ledname = g_strdup_printf("USERLED%d", i);
+ s->led[i] = led_create_simple(OBJECT(dev), GPIO_POLARITY_ACTIVE_HIGH,
+ LED_COLOR_GREEN, ledname);
+ }
+}
+
+static const VMStateDescription mps2_fpgaio_vmstate = {
+ .name = "mps2-fpgaio",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(led0, MPS2FPGAIO),
+ VMSTATE_UINT32(prescale, MPS2FPGAIO),
+ VMSTATE_UINT32(misc, MPS2FPGAIO),
+ VMSTATE_UINT32(dbgctrl, MPS2FPGAIO),
+ VMSTATE_INT64(clk1hz_tick_offset, MPS2FPGAIO),
+ VMSTATE_INT64(clk100hz_tick_offset, MPS2FPGAIO),
+ VMSTATE_UINT32(counter, MPS2FPGAIO),
+ VMSTATE_UINT32(pscntr, MPS2FPGAIO),
+ VMSTATE_INT64(pscntr_sync_ticks, MPS2FPGAIO),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static Property mps2_fpgaio_properties[] = {
+ /* Frequency of the prescale counter */
+ DEFINE_PROP_UINT32("prescale-clk", MPS2FPGAIO, prescale_clk, 20000000),
+ /* Number of LEDs controlled by LED0 register */
+ DEFINE_PROP_UINT32("num-leds", MPS2FPGAIO, num_leds, 2),
+ DEFINE_PROP_BOOL("has-switches", MPS2FPGAIO, has_switches, false),
+ DEFINE_PROP_BOOL("has-dbgctrl", MPS2FPGAIO, has_dbgctrl, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mps2_fpgaio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &mps2_fpgaio_vmstate;
+ dc->realize = mps2_fpgaio_realize;
+ dc->reset = mps2_fpgaio_reset;
+ device_class_set_props(dc, mps2_fpgaio_properties);
+}
+
+static const TypeInfo mps2_fpgaio_info = {
+ .name = TYPE_MPS2_FPGAIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MPS2FPGAIO),
+ .instance_init = mps2_fpgaio_init,
+ .class_init = mps2_fpgaio_class_init,
+};
+
+static void mps2_fpgaio_register_types(void)
+{
+ type_register_static(&mps2_fpgaio_info);
+}
+
+type_init(mps2_fpgaio_register_types);
diff --git a/hw/misc/mps2-scc.c b/hw/misc/mps2-scc.c
new file mode 100644
index 000000000..b3b42a792
--- /dev/null
+++ b/hw/misc/mps2-scc.c
@@ -0,0 +1,396 @@
+/*
+ * ARM MPS2 SCC emulation
+ *
+ * Copyright (c) 2017 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+/* This is a model of the SCC (Serial Communication Controller)
+ * found in the FPGA images of MPS2 development boards.
+ *
+ * Documentation of it can be found in the MPS2 TRM:
+ * https://developer.arm.com/documentation/100112/latest/
+ * and also in the Application Notes documenting individual FPGA images.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/bitops.h"
+#include "trace.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "hw/registerfields.h"
+#include "hw/misc/mps2-scc.h"
+#include "hw/misc/led.h"
+#include "hw/qdev-properties.h"
+
+REG32(CFG0, 0)
+REG32(CFG1, 4)
+REG32(CFG2, 8)
+REG32(CFG3, 0xc)
+REG32(CFG4, 0x10)
+REG32(CFG5, 0x14)
+REG32(CFG6, 0x18)
+REG32(CFGDATA_RTN, 0xa0)
+REG32(CFGDATA_OUT, 0xa4)
+REG32(CFGCTRL, 0xa8)
+ FIELD(CFGCTRL, DEVICE, 0, 12)
+ FIELD(CFGCTRL, RES1, 12, 8)
+ FIELD(CFGCTRL, FUNCTION, 20, 6)
+ FIELD(CFGCTRL, RES2, 26, 4)
+ FIELD(CFGCTRL, WRITE, 30, 1)
+ FIELD(CFGCTRL, START, 31, 1)
+REG32(CFGSTAT, 0xac)
+ FIELD(CFGSTAT, DONE, 0, 1)
+ FIELD(CFGSTAT, ERROR, 1, 1)
+REG32(DLL, 0x100)
+REG32(AID, 0xFF8)
+REG32(ID, 0xFFC)
+
+static int scc_partno(MPS2SCC *s)
+{
+ /* Return the partno field of the SCC_ID (0x524, 0x511, etc) */
+ return extract32(s->id, 4, 8);
+}
+
+/* Handle a write via the SYS_CFG channel to the specified function/device.
+ * Return false on error (reported to guest via SYS_CFGCTRL ERROR bit).
+ */
+static bool scc_cfg_write(MPS2SCC *s, unsigned function,
+ unsigned device, uint32_t value)
+{
+ trace_mps2_scc_cfg_write(function, device, value);
+
+ if (function != 1 || device >= s->num_oscclk) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MPS2 SCC config write: bad function %d device %d\n",
+ function, device);
+ return false;
+ }
+
+ s->oscclk[device] = value;
+ return true;
+}
+
+/* Handle a read via the SYS_CFG channel to the specified function/device.
+ * Return false on error (reported to guest via SYS_CFGCTRL ERROR bit),
+ * or set *value on success.
+ */
+static bool scc_cfg_read(MPS2SCC *s, unsigned function,
+ unsigned device, uint32_t *value)
+{
+ if (function != 1 || device >= s->num_oscclk) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MPS2 SCC config read: bad function %d device %d\n",
+ function, device);
+ return false;
+ }
+
+ *value = s->oscclk[device];
+
+ trace_mps2_scc_cfg_read(function, device, *value);
+ return true;
+}
+
+static uint64_t mps2_scc_read(void *opaque, hwaddr offset, unsigned size)
+{
+ MPS2SCC *s = MPS2_SCC(opaque);
+ uint64_t r;
+
+ switch (offset) {
+ case A_CFG0:
+ r = s->cfg0;
+ break;
+ case A_CFG1:
+ r = s->cfg1;
+ break;
+ case A_CFG2:
+ if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) {
+ /* CFG2 reserved on other boards */
+ goto bad_offset;
+ }
+ r = s->cfg2;
+ break;
+ case A_CFG3:
+ if (scc_partno(s) == 0x524 && scc_partno(s) == 0x547) {
+ /* CFG3 reserved on AN524 */
+ goto bad_offset;
+ }
+ /* These are user-settable DIP switches on the board. We don't
+ * model that, so just return zeroes.
+ */
+ r = 0;
+ break;
+ case A_CFG4:
+ r = s->cfg4;
+ break;
+ case A_CFG5:
+ if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) {
+ /* CFG5 reserved on other boards */
+ goto bad_offset;
+ }
+ r = s->cfg5;
+ break;
+ case A_CFG6:
+ if (scc_partno(s) != 0x524) {
+ /* CFG6 reserved on other boards */
+ goto bad_offset;
+ }
+ r = s->cfg6;
+ break;
+ case A_CFGDATA_RTN:
+ r = s->cfgdata_rtn;
+ break;
+ case A_CFGDATA_OUT:
+ r = s->cfgdata_out;
+ break;
+ case A_CFGCTRL:
+ r = s->cfgctrl;
+ break;
+ case A_CFGSTAT:
+ r = s->cfgstat;
+ break;
+ case A_DLL:
+ r = s->dll;
+ break;
+ case A_AID:
+ r = s->aid;
+ break;
+ case A_ID:
+ r = s->id;
+ break;
+ default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MPS2 SCC read: bad offset %x\n", (int) offset);
+ r = 0;
+ break;
+ }
+
+ trace_mps2_scc_read(offset, r, size);
+ return r;
+}
+
+static void mps2_scc_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ MPS2SCC *s = MPS2_SCC(opaque);
+
+ trace_mps2_scc_write(offset, value, size);
+
+ switch (offset) {
+ case A_CFG0:
+ /*
+ * On some boards bit 0 controls board-specific remapping;
+ * we always reflect bit 0 in the 'remap' GPIO output line,
+ * and let the board wire it up or not as it chooses.
+ * TODO on some boards bit 1 is CPU_WAIT.
+ */
+ s->cfg0 = value;
+ qemu_set_irq(s->remap, s->cfg0 & 1);
+ break;
+ case A_CFG1:
+ s->cfg1 = value;
+ for (size_t i = 0; i < ARRAY_SIZE(s->led); i++) {
+ led_set_state(s->led[i], extract32(value, i, 1));
+ }
+ break;
+ case A_CFG2:
+ if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) {
+ /* CFG2 reserved on other boards */
+ goto bad_offset;
+ }
+ /* AN524: QSPI Select signal */
+ s->cfg2 = value;
+ break;
+ case A_CFG5:
+ if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) {
+ /* CFG5 reserved on other boards */
+ goto bad_offset;
+ }
+ /* AN524: ACLK frequency in Hz */
+ s->cfg5 = value;
+ break;
+ case A_CFG6:
+ if (scc_partno(s) != 0x524) {
+ /* CFG6 reserved on other boards */
+ goto bad_offset;
+ }
+ /* AN524: Clock divider for BRAM */
+ s->cfg6 = value;
+ break;
+ case A_CFGDATA_OUT:
+ s->cfgdata_out = value;
+ break;
+ case A_CFGCTRL:
+ /* Writing to CFGCTRL clears SYS_CFGSTAT */
+ s->cfgstat = 0;
+ s->cfgctrl = value & ~(R_CFGCTRL_RES1_MASK |
+ R_CFGCTRL_RES2_MASK |
+ R_CFGCTRL_START_MASK);
+
+ if (value & R_CFGCTRL_START_MASK) {
+ /* Start bit set -- do a read or write (instantaneously) */
+ int device = extract32(s->cfgctrl, R_CFGCTRL_DEVICE_SHIFT,
+ R_CFGCTRL_DEVICE_LENGTH);
+ int function = extract32(s->cfgctrl, R_CFGCTRL_FUNCTION_SHIFT,
+ R_CFGCTRL_FUNCTION_LENGTH);
+
+ s->cfgstat = R_CFGSTAT_DONE_MASK;
+ if (s->cfgctrl & R_CFGCTRL_WRITE_MASK) {
+ if (!scc_cfg_write(s, function, device, s->cfgdata_out)) {
+ s->cfgstat |= R_CFGSTAT_ERROR_MASK;
+ }
+ } else {
+ uint32_t result;
+ if (!scc_cfg_read(s, function, device, &result)) {
+ s->cfgstat |= R_CFGSTAT_ERROR_MASK;
+ } else {
+ s->cfgdata_rtn = result;
+ }
+ }
+ }
+ break;
+ case A_DLL:
+ /* DLL stands for Digital Locked Loop.
+ * Bits [31:24] (DLL_LOCK_MASK) are writable, and indicate a
+ * mask of which of the DLL_LOCKED bits [16:23] should be ORed
+ * together to determine the ALL_UNMASKED_DLLS_LOCKED bit [0].
+ * For QEMU, our DLLs are always locked, so we can leave bit 0
+ * as 1 always and don't need to recalculate it.
+ */
+ s->dll = deposit32(s->dll, 24, 8, extract32(value, 24, 8));
+ break;
+ default:
+ bad_offset:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MPS2 SCC write: bad offset 0x%x\n", (int) offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps mps2_scc_ops = {
+ .read = mps2_scc_read,
+ .write = mps2_scc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void mps2_scc_reset(DeviceState *dev)
+{
+ MPS2SCC *s = MPS2_SCC(dev);
+ int i;
+
+ trace_mps2_scc_reset();
+ s->cfg0 = s->cfg0_reset;
+ s->cfg1 = 0;
+ s->cfg2 = 0;
+ s->cfg5 = 0;
+ s->cfg6 = 0;
+ s->cfgdata_rtn = 0;
+ s->cfgdata_out = 0;
+ s->cfgctrl = 0x100000;
+ s->cfgstat = 0;
+ s->dll = 0xffff0001;
+ for (i = 0; i < s->num_oscclk; i++) {
+ s->oscclk[i] = s->oscclk_reset[i];
+ }
+ for (i = 0; i < ARRAY_SIZE(s->led); i++) {
+ device_cold_reset(DEVICE(s->led[i]));
+ }
+}
+
+static void mps2_scc_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ MPS2SCC *s = MPS2_SCC(obj);
+
+ memory_region_init_io(&s->iomem, obj, &mps2_scc_ops, s, "mps2-scc", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ qdev_init_gpio_out_named(DEVICE(obj), &s->remap, "remap", 1);
+}
+
+static void mps2_scc_realize(DeviceState *dev, Error **errp)
+{
+ MPS2SCC *s = MPS2_SCC(dev);
+
+ for (size_t i = 0; i < ARRAY_SIZE(s->led); i++) {
+ char *name = g_strdup_printf("SCC LED%zu", i);
+ s->led[i] = led_create_simple(OBJECT(dev), GPIO_POLARITY_ACTIVE_HIGH,
+ LED_COLOR_GREEN, name);
+ g_free(name);
+ }
+
+ s->oscclk = g_new0(uint32_t, s->num_oscclk);
+}
+
+static const VMStateDescription mps2_scc_vmstate = {
+ .name = "mps2-scc",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cfg0, MPS2SCC),
+ VMSTATE_UINT32(cfg1, MPS2SCC),
+ VMSTATE_UINT32(cfg2, MPS2SCC),
+ /* cfg3, cfg4 are read-only so need not be migrated */
+ VMSTATE_UINT32(cfg5, MPS2SCC),
+ VMSTATE_UINT32(cfg6, MPS2SCC),
+ VMSTATE_UINT32(cfgdata_rtn, MPS2SCC),
+ VMSTATE_UINT32(cfgdata_out, MPS2SCC),
+ VMSTATE_UINT32(cfgctrl, MPS2SCC),
+ VMSTATE_UINT32(cfgstat, MPS2SCC),
+ VMSTATE_UINT32(dll, MPS2SCC),
+ VMSTATE_VARRAY_UINT32(oscclk, MPS2SCC, num_oscclk,
+ 0, vmstate_info_uint32, uint32_t),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property mps2_scc_properties[] = {
+ /* Values for various read-only ID registers (which are specific
+ * to the board model or FPGA image)
+ */
+ DEFINE_PROP_UINT32("scc-cfg4", MPS2SCC, cfg4, 0),
+ DEFINE_PROP_UINT32("scc-aid", MPS2SCC, aid, 0),
+ DEFINE_PROP_UINT32("scc-id", MPS2SCC, id, 0),
+ /* Reset value for CFG0 register */
+ DEFINE_PROP_UINT32("scc-cfg0", MPS2SCC, cfg0_reset, 0),
+ /*
+ * These are the initial settings for the source clocks on the board.
+ * In hardware they can be configured via a config file read by the
+ * motherboard configuration controller to suit the FPGA image.
+ */
+ DEFINE_PROP_ARRAY("oscclk", MPS2SCC, num_oscclk, oscclk_reset,
+ qdev_prop_uint32, uint32_t),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mps2_scc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = mps2_scc_realize;
+ dc->vmsd = &mps2_scc_vmstate;
+ dc->reset = mps2_scc_reset;
+ device_class_set_props(dc, mps2_scc_properties);
+}
+
+static const TypeInfo mps2_scc_info = {
+ .name = TYPE_MPS2_SCC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MPS2SCC),
+ .instance_init = mps2_scc_init,
+ .class_init = mps2_scc_class_init,
+};
+
+static void mps2_scc_register_types(void)
+{
+ type_register_static(&mps2_scc_info);
+}
+
+type_init(mps2_scc_register_types);
diff --git a/hw/misc/msf2-sysreg.c b/hw/misc/msf2-sysreg.c
new file mode 100644
index 000000000..2dce55c36
--- /dev/null
+++ b/hw/misc/msf2-sysreg.c
@@ -0,0 +1,163 @@
+/*
+ * System Register block model of Microsemi SmartFusion2.
+ *
+ * Copyright (c) 2017 Subbaraya Sundeep <sundeep.lkml@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.
+ *
+ * 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 "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/misc/msf2-sysreg.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/error-report.h"
+#include "trace.h"
+
+static inline int msf2_divbits(uint32_t div)
+{
+ int r = ctz32(div);
+
+ return (div < 8) ? r : r + 1;
+}
+
+static void msf2_sysreg_reset(DeviceState *d)
+{
+ MSF2SysregState *s = MSF2_SYSREG(d);
+
+ s->regs[MSSDDR_PLL_STATUS_LOW_CR] = 0x021A2358;
+ s->regs[MSSDDR_PLL_STATUS] = 0x3;
+ s->regs[MSSDDR_FACC1_CR] = msf2_divbits(s->apb0div) << 5 |
+ msf2_divbits(s->apb1div) << 2;
+}
+
+static uint64_t msf2_sysreg_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ MSF2SysregState *s = opaque;
+ uint32_t ret = 0;
+
+ offset >>= 2;
+ if (offset < ARRAY_SIZE(s->regs)) {
+ ret = s->regs[offset];
+ trace_msf2_sysreg_read(offset << 2, ret);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%08" HWADDR_PRIx "\n", __func__,
+ offset << 2);
+ }
+
+ return ret;
+}
+
+static void msf2_sysreg_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ MSF2SysregState *s = opaque;
+ uint32_t newval = val;
+
+ offset >>= 2;
+
+ switch (offset) {
+ case MSSDDR_PLL_STATUS:
+ trace_msf2_sysreg_write_pll_status();
+ break;
+
+ case ESRAM_CR:
+ case DDR_CR:
+ case ENVM_REMAP_BASE_CR:
+ if (newval != s->regs[offset]) {
+ qemu_log_mask(LOG_UNIMP,
+ TYPE_MSF2_SYSREG": remapping not supported\n");
+ }
+ break;
+
+ default:
+ if (offset < ARRAY_SIZE(s->regs)) {
+ trace_msf2_sysreg_write(offset << 2, newval, s->regs[offset]);
+ s->regs[offset] = newval;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%08" HWADDR_PRIx "\n", __func__,
+ offset << 2);
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps sysreg_ops = {
+ .read = msf2_sysreg_read,
+ .write = msf2_sysreg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void msf2_sysreg_init(Object *obj)
+{
+ MSF2SysregState *s = MSF2_SYSREG(obj);
+
+ memory_region_init_io(&s->iomem, obj, &sysreg_ops, s, TYPE_MSF2_SYSREG,
+ MSF2_SYSREG_MMIO_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription vmstate_msf2_sysreg = {
+ .name = TYPE_MSF2_SYSREG,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MSF2SysregState, MSF2_SYSREG_MMIO_SIZE / 4),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property msf2_sysreg_properties[] = {
+ /* default divisors in Libero GUI */
+ DEFINE_PROP_UINT8("apb0divisor", MSF2SysregState, apb0div, 2),
+ DEFINE_PROP_UINT8("apb1divisor", MSF2SysregState, apb1div, 2),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void msf2_sysreg_realize(DeviceState *dev, Error **errp)
+{
+ MSF2SysregState *s = MSF2_SYSREG(dev);
+
+ if ((s->apb0div > 32 || !is_power_of_2(s->apb0div))
+ || (s->apb1div > 32 || !is_power_of_2(s->apb1div))) {
+ error_setg(errp, "Invalid apb divisor value");
+ error_append_hint(errp, "apb divisor must be a power of 2"
+ " and maximum value is 32\n");
+ }
+}
+
+static void msf2_sysreg_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_msf2_sysreg;
+ dc->reset = msf2_sysreg_reset;
+ device_class_set_props(dc, msf2_sysreg_properties);
+ dc->realize = msf2_sysreg_realize;
+}
+
+static const TypeInfo msf2_sysreg_info = {
+ .name = TYPE_MSF2_SYSREG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .class_init = msf2_sysreg_class_init,
+ .instance_size = sizeof(MSF2SysregState),
+ .instance_init = msf2_sysreg_init,
+};
+
+static void msf2_sysreg_register_types(void)
+{
+ type_register_static(&msf2_sysreg_info);
+}
+
+type_init(msf2_sysreg_register_types)
diff --git a/hw/misc/mst_fpga.c b/hw/misc/mst_fpga.c
new file mode 100644
index 000000000..2aaadfa96
--- /dev/null
+++ b/hw/misc/mst_fpga.c
@@ -0,0 +1,269 @@
+/*
+ * PXA270-based Intel Mainstone platforms.
+ * FPGA driver
+ *
+ * Copyright (c) 2007 by Armin Kuster <akuster@kama-aina.net> or
+ * <akuster@mvista.com>
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+/* Mainstone FPGA for extern irqs */
+#define FPGA_GPIO_PIN 0
+#define MST_NUM_IRQS 16
+#define MST_LEDDAT1 0x10
+#define MST_LEDDAT2 0x14
+#define MST_LEDCTRL 0x40
+#define MST_GPSWR 0x60
+#define MST_MSCWR1 0x80
+#define MST_MSCWR2 0x84
+#define MST_MSCWR3 0x88
+#define MST_MSCRD 0x90
+#define MST_INTMSKENA 0xc0
+#define MST_INTSETCLR 0xd0
+#define MST_PCMCIA0 0xe0
+#define MST_PCMCIA1 0xe4
+
+#define MST_PCMCIAx_READY (1 << 10)
+#define MST_PCMCIAx_nCD (1 << 5)
+
+#define MST_PCMCIA_CD0_IRQ 9
+#define MST_PCMCIA_CD1_IRQ 13
+
+#define TYPE_MAINSTONE_FPGA "mainstone-fpga"
+OBJECT_DECLARE_SIMPLE_TYPE(mst_irq_state, MAINSTONE_FPGA)
+
+struct mst_irq_state {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ qemu_irq parent;
+
+ uint32_t prev_level;
+ uint32_t leddat1;
+ uint32_t leddat2;
+ uint32_t ledctrl;
+ uint32_t gpswr;
+ uint32_t mscwr1;
+ uint32_t mscwr2;
+ uint32_t mscwr3;
+ uint32_t mscrd;
+ uint32_t intmskena;
+ uint32_t intsetclr;
+ uint32_t pcmcia0;
+ uint32_t pcmcia1;
+};
+
+static void
+mst_fpga_set_irq(void *opaque, int irq, int level)
+{
+ mst_irq_state *s = (mst_irq_state *)opaque;
+ uint32_t oldint = s->intsetclr & s->intmskena;
+
+ if (level)
+ s->prev_level |= 1u << irq;
+ else
+ s->prev_level &= ~(1u << irq);
+
+ switch(irq) {
+ case MST_PCMCIA_CD0_IRQ:
+ if (level)
+ s->pcmcia0 &= ~MST_PCMCIAx_nCD;
+ else
+ s->pcmcia0 |= MST_PCMCIAx_nCD;
+ break;
+ case MST_PCMCIA_CD1_IRQ:
+ if (level)
+ s->pcmcia1 &= ~MST_PCMCIAx_nCD;
+ else
+ s->pcmcia1 |= MST_PCMCIAx_nCD;
+ break;
+ }
+
+ if ((s->intmskena & (1u << irq)) && level)
+ s->intsetclr |= 1u << irq;
+
+ if (oldint != (s->intsetclr & s->intmskena))
+ qemu_set_irq(s->parent, s->intsetclr & s->intmskena);
+}
+
+
+static uint64_t
+mst_fpga_readb(void *opaque, hwaddr addr, unsigned size)
+{
+ mst_irq_state *s = (mst_irq_state *) opaque;
+
+ switch (addr) {
+ case MST_LEDDAT1:
+ return s->leddat1;
+ case MST_LEDDAT2:
+ return s->leddat2;
+ case MST_LEDCTRL:
+ return s->ledctrl;
+ case MST_GPSWR:
+ return s->gpswr;
+ case MST_MSCWR1:
+ return s->mscwr1;
+ case MST_MSCWR2:
+ return s->mscwr2;
+ case MST_MSCWR3:
+ return s->mscwr3;
+ case MST_MSCRD:
+ return s->mscrd;
+ case MST_INTMSKENA:
+ return s->intmskena;
+ case MST_INTSETCLR:
+ return s->intsetclr;
+ case MST_PCMCIA0:
+ return s->pcmcia0;
+ case MST_PCMCIA1:
+ return s->pcmcia1;
+ default:
+ printf("Mainstone - mst_fpga_readb: Bad register offset "
+ "0x" TARGET_FMT_plx "\n", addr);
+ }
+ return 0;
+}
+
+static void
+mst_fpga_writeb(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ mst_irq_state *s = (mst_irq_state *) opaque;
+ value &= 0xffffffff;
+
+ switch (addr) {
+ case MST_LEDDAT1:
+ s->leddat1 = value;
+ break;
+ case MST_LEDDAT2:
+ s->leddat2 = value;
+ break;
+ case MST_LEDCTRL:
+ s->ledctrl = value;
+ break;
+ case MST_GPSWR:
+ s->gpswr = value;
+ break;
+ case MST_MSCWR1:
+ s->mscwr1 = value;
+ break;
+ case MST_MSCWR2:
+ s->mscwr2 = value;
+ break;
+ case MST_MSCWR3:
+ s->mscwr3 = value;
+ break;
+ case MST_MSCRD:
+ s->mscrd = value;
+ break;
+ case MST_INTMSKENA: /* Mask interrupt */
+ s->intmskena = (value & 0xFEEFF);
+ qemu_set_irq(s->parent, s->intsetclr & s->intmskena);
+ break;
+ case MST_INTSETCLR: /* clear or set interrupt */
+ s->intsetclr = (value & 0xFEEFF);
+ qemu_set_irq(s->parent, s->intsetclr & s->intmskena);
+ break;
+ /* For PCMCIAx allow the to change only power and reset */
+ case MST_PCMCIA0:
+ s->pcmcia0 = (value & 0x1f) | (s->pcmcia0 & ~0x1f);
+ break;
+ case MST_PCMCIA1:
+ s->pcmcia1 = (value & 0x1f) | (s->pcmcia1 & ~0x1f);
+ break;
+ default:
+ printf("Mainstone - mst_fpga_writeb: Bad register offset "
+ "0x" TARGET_FMT_plx "\n", addr);
+ }
+}
+
+static const MemoryRegionOps mst_fpga_ops = {
+ .read = mst_fpga_readb,
+ .write = mst_fpga_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int mst_fpga_post_load(void *opaque, int version_id)
+{
+ mst_irq_state *s = (mst_irq_state *) opaque;
+
+ qemu_set_irq(s->parent, s->intsetclr & s->intmskena);
+ return 0;
+}
+
+static void mst_fpga_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ mst_irq_state *s = MAINSTONE_FPGA(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ s->pcmcia0 = MST_PCMCIAx_READY | MST_PCMCIAx_nCD;
+ s->pcmcia1 = MST_PCMCIAx_READY | MST_PCMCIAx_nCD;
+
+ sysbus_init_irq(sbd, &s->parent);
+
+ /* alloc the external 16 irqs */
+ qdev_init_gpio_in(dev, mst_fpga_set_irq, MST_NUM_IRQS);
+
+ memory_region_init_io(&s->iomem, obj, &mst_fpga_ops, s,
+ "fpga", 0x00100000);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription vmstate_mst_fpga_regs = {
+ .name = "mainstone_fpga",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = mst_fpga_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(prev_level, mst_irq_state),
+ VMSTATE_UINT32(leddat1, mst_irq_state),
+ VMSTATE_UINT32(leddat2, mst_irq_state),
+ VMSTATE_UINT32(ledctrl, mst_irq_state),
+ VMSTATE_UINT32(gpswr, mst_irq_state),
+ VMSTATE_UINT32(mscwr1, mst_irq_state),
+ VMSTATE_UINT32(mscwr2, mst_irq_state),
+ VMSTATE_UINT32(mscwr3, mst_irq_state),
+ VMSTATE_UINT32(mscrd, mst_irq_state),
+ VMSTATE_UINT32(intmskena, mst_irq_state),
+ VMSTATE_UINT32(intsetclr, mst_irq_state),
+ VMSTATE_UINT32(pcmcia0, mst_irq_state),
+ VMSTATE_UINT32(pcmcia1, mst_irq_state),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void mst_fpga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "Mainstone II FPGA";
+ dc->vmsd = &vmstate_mst_fpga_regs;
+}
+
+static const TypeInfo mst_fpga_info = {
+ .name = TYPE_MAINSTONE_FPGA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(mst_irq_state),
+ .instance_init = mst_fpga_init,
+ .class_init = mst_fpga_class_init,
+};
+
+static void mst_fpga_register_types(void)
+{
+ type_register_static(&mst_fpga_info);
+}
+
+type_init(mst_fpga_register_types)
diff --git a/hw/misc/npcm7xx_clk.c b/hw/misc/npcm7xx_clk.c
new file mode 100644
index 000000000..0b61070c5
--- /dev/null
+++ b/hw/misc/npcm7xx_clk.c
@@ -0,0 +1,1095 @@
+/*
+ * Nuvoton NPCM7xx Clock Control Registers.
+ *
+ * Copyright 2020 Google LLC
+ *
+ * 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.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/misc/npcm7xx_clk.h"
+#include "hw/timer/npcm7xx_timer.h"
+#include "hw/qdev-clock.h"
+#include "migration/vmstate.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qemu/units.h"
+#include "trace.h"
+#include "sysemu/watchdog.h"
+
+/*
+ * The reference clock hz, and the SECCNT and CNTR25M registers in this module,
+ * is always 25 MHz.
+ */
+#define NPCM7XX_CLOCK_REF_HZ (25000000)
+
+/* Register Field Definitions */
+#define NPCM7XX_CLK_WDRCR_CA9C BIT(0) /* Cortex-A9 Cores */
+
+#define PLLCON_LOKI BIT(31)
+#define PLLCON_LOKS BIT(30)
+#define PLLCON_PWDEN BIT(12)
+#define PLLCON_FBDV(con) extract32((con), 16, 12)
+#define PLLCON_OTDV2(con) extract32((con), 13, 3)
+#define PLLCON_OTDV1(con) extract32((con), 8, 3)
+#define PLLCON_INDV(con) extract32((con), 0, 6)
+
+enum NPCM7xxCLKRegisters {
+ NPCM7XX_CLK_CLKEN1,
+ NPCM7XX_CLK_CLKSEL,
+ NPCM7XX_CLK_CLKDIV1,
+ NPCM7XX_CLK_PLLCON0,
+ NPCM7XX_CLK_PLLCON1,
+ NPCM7XX_CLK_SWRSTR,
+ NPCM7XX_CLK_IPSRST1 = 0x20 / sizeof(uint32_t),
+ NPCM7XX_CLK_IPSRST2,
+ NPCM7XX_CLK_CLKEN2,
+ NPCM7XX_CLK_CLKDIV2,
+ NPCM7XX_CLK_CLKEN3,
+ NPCM7XX_CLK_IPSRST3,
+ NPCM7XX_CLK_WD0RCR,
+ NPCM7XX_CLK_WD1RCR,
+ NPCM7XX_CLK_WD2RCR,
+ NPCM7XX_CLK_SWRSTC1,
+ NPCM7XX_CLK_SWRSTC2,
+ NPCM7XX_CLK_SWRSTC3,
+ NPCM7XX_CLK_SWRSTC4,
+ NPCM7XX_CLK_PLLCON2,
+ NPCM7XX_CLK_CLKDIV3,
+ NPCM7XX_CLK_CORSTC,
+ NPCM7XX_CLK_PLLCONG,
+ NPCM7XX_CLK_AHBCKFI,
+ NPCM7XX_CLK_SECCNT,
+ NPCM7XX_CLK_CNTR25M,
+ NPCM7XX_CLK_REGS_END,
+};
+
+/*
+ * These reset values were taken from version 0.91 of the NPCM750R data sheet.
+ *
+ * All are loaded on power-up reset. CLKENx and SWRSTR should also be loaded on
+ * core domain reset, but this reset type is not yet supported by QEMU.
+ */
+static const uint32_t cold_reset_values[NPCM7XX_CLK_NR_REGS] = {
+ [NPCM7XX_CLK_CLKEN1] = 0xffffffff,
+ [NPCM7XX_CLK_CLKSEL] = 0x004aaaaa,
+ [NPCM7XX_CLK_CLKDIV1] = 0x5413f855,
+ [NPCM7XX_CLK_PLLCON0] = 0x00222101 | PLLCON_LOKI,
+ [NPCM7XX_CLK_PLLCON1] = 0x00202101 | PLLCON_LOKI,
+ [NPCM7XX_CLK_IPSRST1] = 0x00001000,
+ [NPCM7XX_CLK_IPSRST2] = 0x80000000,
+ [NPCM7XX_CLK_CLKEN2] = 0xffffffff,
+ [NPCM7XX_CLK_CLKDIV2] = 0xaa4f8f9f,
+ [NPCM7XX_CLK_CLKEN3] = 0xffffffff,
+ [NPCM7XX_CLK_IPSRST3] = 0x03000000,
+ [NPCM7XX_CLK_WD0RCR] = 0xffffffff,
+ [NPCM7XX_CLK_WD1RCR] = 0xffffffff,
+ [NPCM7XX_CLK_WD2RCR] = 0xffffffff,
+ [NPCM7XX_CLK_SWRSTC1] = 0x00000003,
+ [NPCM7XX_CLK_PLLCON2] = 0x00c02105 | PLLCON_LOKI,
+ [NPCM7XX_CLK_CORSTC] = 0x04000003,
+ [NPCM7XX_CLK_PLLCONG] = 0x01228606 | PLLCON_LOKI,
+ [NPCM7XX_CLK_AHBCKFI] = 0x000000c8,
+};
+
+/* The number of watchdogs that can trigger a reset. */
+#define NPCM7XX_NR_WATCHDOGS (3)
+
+/* Clock converter functions */
+
+#define TYPE_NPCM7XX_CLOCK_PLL "npcm7xx-clock-pll"
+#define NPCM7XX_CLOCK_PLL(obj) OBJECT_CHECK(NPCM7xxClockPLLState, \
+ (obj), TYPE_NPCM7XX_CLOCK_PLL)
+#define TYPE_NPCM7XX_CLOCK_SEL "npcm7xx-clock-sel"
+#define NPCM7XX_CLOCK_SEL(obj) OBJECT_CHECK(NPCM7xxClockSELState, \
+ (obj), TYPE_NPCM7XX_CLOCK_SEL)
+#define TYPE_NPCM7XX_CLOCK_DIVIDER "npcm7xx-clock-divider"
+#define NPCM7XX_CLOCK_DIVIDER(obj) OBJECT_CHECK(NPCM7xxClockDividerState, \
+ (obj), TYPE_NPCM7XX_CLOCK_DIVIDER)
+
+static void npcm7xx_clk_update_pll(void *opaque)
+{
+ NPCM7xxClockPLLState *s = opaque;
+ uint32_t con = s->clk->regs[s->reg];
+ uint64_t freq;
+
+ /* The PLL is grounded if it is not locked yet. */
+ if (con & PLLCON_LOKI) {
+ freq = clock_get_hz(s->clock_in);
+ freq *= PLLCON_FBDV(con);
+ freq /= PLLCON_INDV(con) * PLLCON_OTDV1(con) * PLLCON_OTDV2(con);
+ } else {
+ freq = 0;
+ }
+
+ clock_update_hz(s->clock_out, freq);
+}
+
+static void npcm7xx_clk_update_sel(void *opaque)
+{
+ NPCM7xxClockSELState *s = opaque;
+ uint32_t index = extract32(s->clk->regs[NPCM7XX_CLK_CLKSEL], s->offset,
+ s->len);
+
+ if (index >= s->input_size) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: SEL index: %u out of range\n",
+ __func__, index);
+ index = 0;
+ }
+ clock_update_hz(s->clock_out, clock_get_hz(s->clock_in[index]));
+}
+
+static void npcm7xx_clk_update_divider(void *opaque)
+{
+ NPCM7xxClockDividerState *s = opaque;
+ uint32_t freq;
+
+ freq = s->divide(s);
+ clock_update_hz(s->clock_out, freq);
+}
+
+static uint32_t divide_by_constant(NPCM7xxClockDividerState *s)
+{
+ return clock_get_hz(s->clock_in) / s->divisor;
+}
+
+static uint32_t divide_by_reg_divisor(NPCM7xxClockDividerState *s)
+{
+ return clock_get_hz(s->clock_in) /
+ (extract32(s->clk->regs[s->reg], s->offset, s->len) + 1);
+}
+
+static uint32_t divide_by_reg_divisor_times_2(NPCM7xxClockDividerState *s)
+{
+ return divide_by_reg_divisor(s) / 2;
+}
+
+static uint32_t shift_by_reg_divisor(NPCM7xxClockDividerState *s)
+{
+ return clock_get_hz(s->clock_in) >>
+ extract32(s->clk->regs[s->reg], s->offset, s->len);
+}
+
+static NPCM7xxClockPLL find_pll_by_reg(enum NPCM7xxCLKRegisters reg)
+{
+ switch (reg) {
+ case NPCM7XX_CLK_PLLCON0:
+ return NPCM7XX_CLOCK_PLL0;
+ case NPCM7XX_CLK_PLLCON1:
+ return NPCM7XX_CLOCK_PLL1;
+ case NPCM7XX_CLK_PLLCON2:
+ return NPCM7XX_CLOCK_PLL2;
+ case NPCM7XX_CLK_PLLCONG:
+ return NPCM7XX_CLOCK_PLLG;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void npcm7xx_clk_update_all_plls(NPCM7xxCLKState *clk)
+{
+ int i;
+
+ for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) {
+ npcm7xx_clk_update_pll(&clk->plls[i]);
+ }
+}
+
+static void npcm7xx_clk_update_all_sels(NPCM7xxCLKState *clk)
+{
+ int i;
+
+ for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) {
+ npcm7xx_clk_update_sel(&clk->sels[i]);
+ }
+}
+
+static void npcm7xx_clk_update_all_dividers(NPCM7xxCLKState *clk)
+{
+ int i;
+
+ for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) {
+ npcm7xx_clk_update_divider(&clk->dividers[i]);
+ }
+}
+
+static void npcm7xx_clk_update_all_clocks(NPCM7xxCLKState *clk)
+{
+ clock_update_hz(clk->clkref, NPCM7XX_CLOCK_REF_HZ);
+ npcm7xx_clk_update_all_plls(clk);
+ npcm7xx_clk_update_all_sels(clk);
+ npcm7xx_clk_update_all_dividers(clk);
+}
+
+/* Types of clock sources. */
+typedef enum ClockSrcType {
+ CLKSRC_REF,
+ CLKSRC_PLL,
+ CLKSRC_SEL,
+ CLKSRC_DIV,
+} ClockSrcType;
+
+typedef struct PLLInitInfo {
+ const char *name;
+ ClockSrcType src_type;
+ int src_index;
+ int reg;
+ const char *public_name;
+} PLLInitInfo;
+
+typedef struct SELInitInfo {
+ const char *name;
+ uint8_t input_size;
+ ClockSrcType src_type[NPCM7XX_CLK_SEL_MAX_INPUT];
+ int src_index[NPCM7XX_CLK_SEL_MAX_INPUT];
+ int offset;
+ int len;
+ const char *public_name;
+} SELInitInfo;
+
+typedef struct DividerInitInfo {
+ const char *name;
+ ClockSrcType src_type;
+ int src_index;
+ uint32_t (*divide)(NPCM7xxClockDividerState *s);
+ int reg; /* not used when type == CONSTANT */
+ int offset; /* not used when type == CONSTANT */
+ int len; /* not used when type == CONSTANT */
+ int divisor; /* used only when type == CONSTANT */
+ const char *public_name;
+} DividerInitInfo;
+
+static const PLLInitInfo pll_init_info_list[] = {
+ [NPCM7XX_CLOCK_PLL0] = {
+ .name = "pll0",
+ .src_type = CLKSRC_REF,
+ .reg = NPCM7XX_CLK_PLLCON0,
+ },
+ [NPCM7XX_CLOCK_PLL1] = {
+ .name = "pll1",
+ .src_type = CLKSRC_REF,
+ .reg = NPCM7XX_CLK_PLLCON1,
+ },
+ [NPCM7XX_CLOCK_PLL2] = {
+ .name = "pll2",
+ .src_type = CLKSRC_REF,
+ .reg = NPCM7XX_CLK_PLLCON2,
+ },
+ [NPCM7XX_CLOCK_PLLG] = {
+ .name = "pllg",
+ .src_type = CLKSRC_REF,
+ .reg = NPCM7XX_CLK_PLLCONG,
+ },
+};
+
+static const SELInitInfo sel_init_info_list[] = {
+ [NPCM7XX_CLOCK_PIXCKSEL] = {
+ .name = "pixcksel",
+ .input_size = 2,
+ .src_type = {CLKSRC_PLL, CLKSRC_REF},
+ .src_index = {NPCM7XX_CLOCK_PLLG, 0},
+ .offset = 5,
+ .len = 1,
+ .public_name = "pixel-clock",
+ },
+ [NPCM7XX_CLOCK_MCCKSEL] = {
+ .name = "mccksel",
+ .input_size = 4,
+ .src_type = {CLKSRC_DIV, CLKSRC_REF, CLKSRC_REF,
+ /*MCBPCK, shouldn't be used in normal operation*/
+ CLKSRC_REF},
+ .src_index = {NPCM7XX_CLOCK_PLL1D2, 0, 0, 0},
+ .offset = 12,
+ .len = 2,
+ .public_name = "mc-phy-clock",
+ },
+ [NPCM7XX_CLOCK_CPUCKSEL] = {
+ .name = "cpucksel",
+ .input_size = 4,
+ .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF,
+ /*SYSBPCK, shouldn't be used in normal operation*/
+ CLKSRC_REF},
+ .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0, 0},
+ .offset = 0,
+ .len = 2,
+ .public_name = "system-clock",
+ },
+ [NPCM7XX_CLOCK_CLKOUTSEL] = {
+ .name = "clkoutsel",
+ .input_size = 5,
+ .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF,
+ CLKSRC_PLL, CLKSRC_DIV},
+ .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0,
+ NPCM7XX_CLOCK_PLLG, NPCM7XX_CLOCK_PLL2D2},
+ .offset = 18,
+ .len = 3,
+ .public_name = "tock",
+ },
+ [NPCM7XX_CLOCK_UARTCKSEL] = {
+ .name = "uartcksel",
+ .input_size = 4,
+ .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV},
+ .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0,
+ NPCM7XX_CLOCK_PLL2D2},
+ .offset = 8,
+ .len = 2,
+ },
+ [NPCM7XX_CLOCK_TIMCKSEL] = {
+ .name = "timcksel",
+ .input_size = 4,
+ .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV},
+ .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0,
+ NPCM7XX_CLOCK_PLL2D2},
+ .offset = 14,
+ .len = 2,
+ },
+ [NPCM7XX_CLOCK_SDCKSEL] = {
+ .name = "sdcksel",
+ .input_size = 4,
+ .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV},
+ .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0,
+ NPCM7XX_CLOCK_PLL2D2},
+ .offset = 6,
+ .len = 2,
+ },
+ [NPCM7XX_CLOCK_GFXMSEL] = {
+ .name = "gfxmksel",
+ .input_size = 2,
+ .src_type = {CLKSRC_REF, CLKSRC_PLL},
+ .src_index = {0, NPCM7XX_CLOCK_PLL2},
+ .offset = 21,
+ .len = 1,
+ },
+ [NPCM7XX_CLOCK_SUCKSEL] = {
+ .name = "sucksel",
+ .input_size = 4,
+ .src_type = {CLKSRC_PLL, CLKSRC_DIV, CLKSRC_REF, CLKSRC_DIV},
+ .src_index = {NPCM7XX_CLOCK_PLL0, NPCM7XX_CLOCK_PLL1D2, 0,
+ NPCM7XX_CLOCK_PLL2D2},
+ .offset = 10,
+ .len = 2,
+ },
+};
+
+static const DividerInitInfo divider_init_info_list[] = {
+ [NPCM7XX_CLOCK_PLL1D2] = {
+ .name = "pll1d2",
+ .src_type = CLKSRC_PLL,
+ .src_index = NPCM7XX_CLOCK_PLL1,
+ .divide = divide_by_constant,
+ .divisor = 2,
+ },
+ [NPCM7XX_CLOCK_PLL2D2] = {
+ .name = "pll2d2",
+ .src_type = CLKSRC_PLL,
+ .src_index = NPCM7XX_CLOCK_PLL2,
+ .divide = divide_by_constant,
+ .divisor = 2,
+ },
+ [NPCM7XX_CLOCK_MC_DIVIDER] = {
+ .name = "mc-divider",
+ .src_type = CLKSRC_SEL,
+ .src_index = NPCM7XX_CLOCK_MCCKSEL,
+ .divide = divide_by_constant,
+ .divisor = 2,
+ .public_name = "mc-clock"
+ },
+ [NPCM7XX_CLOCK_AXI_DIVIDER] = {
+ .name = "axi-divider",
+ .src_type = CLKSRC_SEL,
+ .src_index = NPCM7XX_CLOCK_CPUCKSEL,
+ .divide = shift_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV1,
+ .offset = 0,
+ .len = 1,
+ .public_name = "clk2"
+ },
+ [NPCM7XX_CLOCK_AHB_DIVIDER] = {
+ .name = "ahb-divider",
+ .src_type = CLKSRC_DIV,
+ .src_index = NPCM7XX_CLOCK_AXI_DIVIDER,
+ .divide = divide_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV1,
+ .offset = 26,
+ .len = 2,
+ .public_name = "clk4"
+ },
+ [NPCM7XX_CLOCK_AHB3_DIVIDER] = {
+ .name = "ahb3-divider",
+ .src_type = CLKSRC_DIV,
+ .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+ .divide = divide_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV1,
+ .offset = 6,
+ .len = 5,
+ .public_name = "ahb3-spi3-clock"
+ },
+ [NPCM7XX_CLOCK_SPI0_DIVIDER] = {
+ .name = "spi0-divider",
+ .src_type = CLKSRC_DIV,
+ .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+ .divide = divide_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV3,
+ .offset = 6,
+ .len = 5,
+ .public_name = "spi0-clock",
+ },
+ [NPCM7XX_CLOCK_SPIX_DIVIDER] = {
+ .name = "spix-divider",
+ .src_type = CLKSRC_DIV,
+ .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+ .divide = divide_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV3,
+ .offset = 1,
+ .len = 5,
+ .public_name = "spix-clock",
+ },
+ [NPCM7XX_CLOCK_APB1_DIVIDER] = {
+ .name = "apb1-divider",
+ .src_type = CLKSRC_DIV,
+ .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+ .divide = shift_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV2,
+ .offset = 24,
+ .len = 2,
+ .public_name = "apb1-clock",
+ },
+ [NPCM7XX_CLOCK_APB2_DIVIDER] = {
+ .name = "apb2-divider",
+ .src_type = CLKSRC_DIV,
+ .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+ .divide = shift_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV2,
+ .offset = 26,
+ .len = 2,
+ .public_name = "apb2-clock",
+ },
+ [NPCM7XX_CLOCK_APB3_DIVIDER] = {
+ .name = "apb3-divider",
+ .src_type = CLKSRC_DIV,
+ .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+ .divide = shift_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV2,
+ .offset = 28,
+ .len = 2,
+ .public_name = "apb3-clock",
+ },
+ [NPCM7XX_CLOCK_APB4_DIVIDER] = {
+ .name = "apb4-divider",
+ .src_type = CLKSRC_DIV,
+ .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+ .divide = shift_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV2,
+ .offset = 30,
+ .len = 2,
+ .public_name = "apb4-clock",
+ },
+ [NPCM7XX_CLOCK_APB5_DIVIDER] = {
+ .name = "apb5-divider",
+ .src_type = CLKSRC_DIV,
+ .src_index = NPCM7XX_CLOCK_AHB_DIVIDER,
+ .divide = shift_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV2,
+ .offset = 22,
+ .len = 2,
+ .public_name = "apb5-clock",
+ },
+ [NPCM7XX_CLOCK_CLKOUT_DIVIDER] = {
+ .name = "clkout-divider",
+ .src_type = CLKSRC_SEL,
+ .src_index = NPCM7XX_CLOCK_CLKOUTSEL,
+ .divide = divide_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV2,
+ .offset = 16,
+ .len = 5,
+ .public_name = "clkout",
+ },
+ [NPCM7XX_CLOCK_UART_DIVIDER] = {
+ .name = "uart-divider",
+ .src_type = CLKSRC_SEL,
+ .src_index = NPCM7XX_CLOCK_UARTCKSEL,
+ .divide = divide_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV1,
+ .offset = 16,
+ .len = 5,
+ .public_name = "uart-clock",
+ },
+ [NPCM7XX_CLOCK_TIMER_DIVIDER] = {
+ .name = "timer-divider",
+ .src_type = CLKSRC_SEL,
+ .src_index = NPCM7XX_CLOCK_TIMCKSEL,
+ .divide = divide_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV1,
+ .offset = 21,
+ .len = 5,
+ .public_name = "timer-clock",
+ },
+ [NPCM7XX_CLOCK_ADC_DIVIDER] = {
+ .name = "adc-divider",
+ .src_type = CLKSRC_DIV,
+ .src_index = NPCM7XX_CLOCK_TIMER_DIVIDER,
+ .divide = shift_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV1,
+ .offset = 28,
+ .len = 3,
+ .public_name = "adc-clock",
+ },
+ [NPCM7XX_CLOCK_MMC_DIVIDER] = {
+ .name = "mmc-divider",
+ .src_type = CLKSRC_SEL,
+ .src_index = NPCM7XX_CLOCK_SDCKSEL,
+ .divide = divide_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV1,
+ .offset = 11,
+ .len = 5,
+ .public_name = "mmc-clock",
+ },
+ [NPCM7XX_CLOCK_SDHC_DIVIDER] = {
+ .name = "sdhc-divider",
+ .src_type = CLKSRC_SEL,
+ .src_index = NPCM7XX_CLOCK_SDCKSEL,
+ .divide = divide_by_reg_divisor_times_2,
+ .reg = NPCM7XX_CLK_CLKDIV2,
+ .offset = 0,
+ .len = 4,
+ .public_name = "sdhc-clock",
+ },
+ [NPCM7XX_CLOCK_GFXM_DIVIDER] = {
+ .name = "gfxm-divider",
+ .src_type = CLKSRC_SEL,
+ .src_index = NPCM7XX_CLOCK_GFXMSEL,
+ .divide = divide_by_constant,
+ .divisor = 3,
+ .public_name = "gfxm-clock",
+ },
+ [NPCM7XX_CLOCK_UTMI_DIVIDER] = {
+ .name = "utmi-divider",
+ .src_type = CLKSRC_SEL,
+ .src_index = NPCM7XX_CLOCK_SUCKSEL,
+ .divide = divide_by_reg_divisor,
+ .reg = NPCM7XX_CLK_CLKDIV2,
+ .offset = 8,
+ .len = 5,
+ .public_name = "utmi-clock",
+ },
+};
+
+static void npcm7xx_clk_update_pll_cb(void *opaque, ClockEvent event)
+{
+ npcm7xx_clk_update_pll(opaque);
+}
+
+static void npcm7xx_clk_pll_init(Object *obj)
+{
+ NPCM7xxClockPLLState *pll = NPCM7XX_CLOCK_PLL(obj);
+
+ pll->clock_in = qdev_init_clock_in(DEVICE(pll), "clock-in",
+ npcm7xx_clk_update_pll_cb, pll,
+ ClockUpdate);
+ pll->clock_out = qdev_init_clock_out(DEVICE(pll), "clock-out");
+}
+
+static void npcm7xx_clk_update_sel_cb(void *opaque, ClockEvent event)
+{
+ npcm7xx_clk_update_sel(opaque);
+}
+
+static void npcm7xx_clk_sel_init(Object *obj)
+{
+ int i;
+ NPCM7xxClockSELState *sel = NPCM7XX_CLOCK_SEL(obj);
+
+ for (i = 0; i < NPCM7XX_CLK_SEL_MAX_INPUT; ++i) {
+ sel->clock_in[i] = qdev_init_clock_in(DEVICE(sel),
+ g_strdup_printf("clock-in[%d]", i),
+ npcm7xx_clk_update_sel_cb, sel, ClockUpdate);
+ }
+ sel->clock_out = qdev_init_clock_out(DEVICE(sel), "clock-out");
+}
+
+static void npcm7xx_clk_update_divider_cb(void *opaque, ClockEvent event)
+{
+ npcm7xx_clk_update_divider(opaque);
+}
+
+static void npcm7xx_clk_divider_init(Object *obj)
+{
+ NPCM7xxClockDividerState *div = NPCM7XX_CLOCK_DIVIDER(obj);
+
+ div->clock_in = qdev_init_clock_in(DEVICE(div), "clock-in",
+ npcm7xx_clk_update_divider_cb,
+ div, ClockUpdate);
+ div->clock_out = qdev_init_clock_out(DEVICE(div), "clock-out");
+}
+
+static void npcm7xx_init_clock_pll(NPCM7xxClockPLLState *pll,
+ NPCM7xxCLKState *clk, const PLLInitInfo *init_info)
+{
+ pll->name = init_info->name;
+ pll->clk = clk;
+ pll->reg = init_info->reg;
+ if (init_info->public_name != NULL) {
+ qdev_alias_clock(DEVICE(pll), "clock-out", DEVICE(clk),
+ init_info->public_name);
+ }
+}
+
+static void npcm7xx_init_clock_sel(NPCM7xxClockSELState *sel,
+ NPCM7xxCLKState *clk, const SELInitInfo *init_info)
+{
+ int input_size = init_info->input_size;
+
+ sel->name = init_info->name;
+ sel->clk = clk;
+ sel->input_size = init_info->input_size;
+ g_assert(input_size <= NPCM7XX_CLK_SEL_MAX_INPUT);
+ sel->offset = init_info->offset;
+ sel->len = init_info->len;
+ if (init_info->public_name != NULL) {
+ qdev_alias_clock(DEVICE(sel), "clock-out", DEVICE(clk),
+ init_info->public_name);
+ }
+}
+
+static void npcm7xx_init_clock_divider(NPCM7xxClockDividerState *div,
+ NPCM7xxCLKState *clk, const DividerInitInfo *init_info)
+{
+ div->name = init_info->name;
+ div->clk = clk;
+
+ div->divide = init_info->divide;
+ if (div->divide == divide_by_constant) {
+ div->divisor = init_info->divisor;
+ } else {
+ div->reg = init_info->reg;
+ div->offset = init_info->offset;
+ div->len = init_info->len;
+ }
+ if (init_info->public_name != NULL) {
+ qdev_alias_clock(DEVICE(div), "clock-out", DEVICE(clk),
+ init_info->public_name);
+ }
+}
+
+static Clock *npcm7xx_get_clock(NPCM7xxCLKState *clk, ClockSrcType type,
+ int index)
+{
+ switch (type) {
+ case CLKSRC_REF:
+ return clk->clkref;
+ case CLKSRC_PLL:
+ return clk->plls[index].clock_out;
+ case CLKSRC_SEL:
+ return clk->sels[index].clock_out;
+ case CLKSRC_DIV:
+ return clk->dividers[index].clock_out;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void npcm7xx_connect_clocks(NPCM7xxCLKState *clk)
+{
+ int i, j;
+ Clock *src;
+
+ for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) {
+ src = npcm7xx_get_clock(clk, pll_init_info_list[i].src_type,
+ pll_init_info_list[i].src_index);
+ clock_set_source(clk->plls[i].clock_in, src);
+ }
+ for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) {
+ for (j = 0; j < sel_init_info_list[i].input_size; ++j) {
+ src = npcm7xx_get_clock(clk, sel_init_info_list[i].src_type[j],
+ sel_init_info_list[i].src_index[j]);
+ clock_set_source(clk->sels[i].clock_in[j], src);
+ }
+ }
+ for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) {
+ src = npcm7xx_get_clock(clk, divider_init_info_list[i].src_type,
+ divider_init_info_list[i].src_index);
+ clock_set_source(clk->dividers[i].clock_in, src);
+ }
+}
+
+static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint32_t reg = offset / sizeof(uint32_t);
+ NPCM7xxCLKState *s = opaque;
+ int64_t now_ns;
+ uint32_t value = 0;
+
+ if (reg >= NPCM7XX_CLK_NR_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: offset 0x%04" HWADDR_PRIx " out of range\n",
+ __func__, offset);
+ return 0;
+ }
+
+ switch (reg) {
+ case NPCM7XX_CLK_SWRSTR:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: register @ 0x%04" HWADDR_PRIx " is write-only\n",
+ __func__, offset);
+ break;
+
+ case NPCM7XX_CLK_SECCNT:
+ now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ value = (now_ns - s->ref_ns) / NANOSECONDS_PER_SECOND;
+ break;
+
+ case NPCM7XX_CLK_CNTR25M:
+ now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ /*
+ * This register counts 25 MHz cycles, updating every 640 ns. It rolls
+ * over to zero every second.
+ *
+ * The 4 LSBs are always zero: (1e9 / 640) << 4 = 25000000.
+ */
+ value = (((now_ns - s->ref_ns) / 640) << 4) % NPCM7XX_CLOCK_REF_HZ;
+ break;
+
+ default:
+ value = s->regs[reg];
+ break;
+ };
+
+ trace_npcm7xx_clk_read(offset, value);
+
+ return value;
+}
+
+static void npcm7xx_clk_write(void *opaque, hwaddr offset,
+ uint64_t v, unsigned size)
+{
+ uint32_t reg = offset / sizeof(uint32_t);
+ NPCM7xxCLKState *s = opaque;
+ uint32_t value = v;
+
+ trace_npcm7xx_clk_write(offset, value);
+
+ if (reg >= NPCM7XX_CLK_NR_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: offset 0x%04" HWADDR_PRIx " out of range\n",
+ __func__, offset);
+ return;
+ }
+
+ switch (reg) {
+ case NPCM7XX_CLK_SWRSTR:
+ qemu_log_mask(LOG_UNIMP, "%s: SW reset not implemented: 0x%02x\n",
+ __func__, value);
+ value = 0;
+ break;
+
+ case NPCM7XX_CLK_PLLCON0:
+ case NPCM7XX_CLK_PLLCON1:
+ case NPCM7XX_CLK_PLLCON2:
+ case NPCM7XX_CLK_PLLCONG:
+ if (value & PLLCON_PWDEN) {
+ /* Power down -- clear lock and indicate loss of lock */
+ value &= ~PLLCON_LOKI;
+ value |= PLLCON_LOKS;
+ } else {
+ /* Normal mode -- assume always locked */
+ value |= PLLCON_LOKI;
+ /* Keep LOKS unchanged unless cleared by writing 1 */
+ if (value & PLLCON_LOKS) {
+ value &= ~PLLCON_LOKS;
+ } else {
+ value |= (value & PLLCON_LOKS);
+ }
+ }
+ /* Only update PLL when it is locked. */
+ if (value & PLLCON_LOKI) {
+ npcm7xx_clk_update_pll(&s->plls[find_pll_by_reg(reg)]);
+ }
+ break;
+
+ case NPCM7XX_CLK_CLKSEL:
+ npcm7xx_clk_update_all_sels(s);
+ break;
+
+ case NPCM7XX_CLK_CLKDIV1:
+ case NPCM7XX_CLK_CLKDIV2:
+ case NPCM7XX_CLK_CLKDIV3:
+ npcm7xx_clk_update_all_dividers(s);
+ break;
+
+ case NPCM7XX_CLK_CNTR25M:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
+ __func__, offset);
+ return;
+ }
+
+ s->regs[reg] = value;
+}
+
+/* Perform reset action triggered by a watchdog */
+static void npcm7xx_clk_perform_watchdog_reset(void *opaque, int n,
+ int level)
+{
+ NPCM7xxCLKState *clk = NPCM7XX_CLK(opaque);
+ uint32_t rcr;
+
+ g_assert(n >= 0 && n <= NPCM7XX_NR_WATCHDOGS);
+ rcr = clk->regs[NPCM7XX_CLK_WD0RCR + n];
+ if (rcr & NPCM7XX_CLK_WDRCR_CA9C) {
+ watchdog_perform_action();
+ } else {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: only CPU reset is implemented. (requested 0x%" PRIx32")\n",
+ __func__, rcr);
+ }
+}
+
+static const struct MemoryRegionOps npcm7xx_clk_ops = {
+ .read = npcm7xx_clk_read,
+ .write = npcm7xx_clk_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void npcm7xx_clk_enter_reset(Object *obj, ResetType type)
+{
+ NPCM7xxCLKState *s = NPCM7XX_CLK(obj);
+
+ QEMU_BUILD_BUG_ON(sizeof(s->regs) != sizeof(cold_reset_values));
+
+ switch (type) {
+ case RESET_TYPE_COLD:
+ memcpy(s->regs, cold_reset_values, sizeof(cold_reset_values));
+ s->ref_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ npcm7xx_clk_update_all_clocks(s);
+ return;
+ }
+
+ /*
+ * A small number of registers need to be reset on a core domain reset,
+ * but no such reset type exists yet.
+ */
+ qemu_log_mask(LOG_UNIMP, "%s: reset type %d not implemented.",
+ __func__, type);
+}
+
+static void npcm7xx_clk_init_clock_hierarchy(NPCM7xxCLKState *s)
+{
+ int i;
+
+ s->clkref = qdev_init_clock_in(DEVICE(s), "clkref", NULL, NULL, 0);
+
+ /* First pass: init all converter modules */
+ QEMU_BUILD_BUG_ON(ARRAY_SIZE(pll_init_info_list) != NPCM7XX_CLOCK_NR_PLLS);
+ QEMU_BUILD_BUG_ON(ARRAY_SIZE(sel_init_info_list) != NPCM7XX_CLOCK_NR_SELS);
+ QEMU_BUILD_BUG_ON(ARRAY_SIZE(divider_init_info_list)
+ != NPCM7XX_CLOCK_NR_DIVIDERS);
+ for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) {
+ object_initialize_child(OBJECT(s), pll_init_info_list[i].name,
+ &s->plls[i], TYPE_NPCM7XX_CLOCK_PLL);
+ npcm7xx_init_clock_pll(&s->plls[i], s,
+ &pll_init_info_list[i]);
+ }
+ for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) {
+ object_initialize_child(OBJECT(s), sel_init_info_list[i].name,
+ &s->sels[i], TYPE_NPCM7XX_CLOCK_SEL);
+ npcm7xx_init_clock_sel(&s->sels[i], s,
+ &sel_init_info_list[i]);
+ }
+ for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) {
+ object_initialize_child(OBJECT(s), divider_init_info_list[i].name,
+ &s->dividers[i], TYPE_NPCM7XX_CLOCK_DIVIDER);
+ npcm7xx_init_clock_divider(&s->dividers[i], s,
+ &divider_init_info_list[i]);
+ }
+
+ /* Second pass: connect converter modules */
+ npcm7xx_connect_clocks(s);
+
+ clock_update_hz(s->clkref, NPCM7XX_CLOCK_REF_HZ);
+}
+
+static void npcm7xx_clk_init(Object *obj)
+{
+ NPCM7xxCLKState *s = NPCM7XX_CLK(obj);
+
+ memory_region_init_io(&s->iomem, obj, &npcm7xx_clk_ops, s,
+ TYPE_NPCM7XX_CLK, 4 * KiB);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+}
+
+static int npcm7xx_clk_post_load(void *opaque, int version_id)
+{
+ if (version_id >= 1) {
+ NPCM7xxCLKState *clk = opaque;
+
+ npcm7xx_clk_update_all_clocks(clk);
+ }
+
+ return 0;
+}
+
+static void npcm7xx_clk_realize(DeviceState *dev, Error **errp)
+{
+ int i;
+ NPCM7xxCLKState *s = NPCM7XX_CLK(dev);
+
+ qdev_init_gpio_in_named(DEVICE(s), npcm7xx_clk_perform_watchdog_reset,
+ NPCM7XX_WATCHDOG_RESET_GPIO_IN, NPCM7XX_NR_WATCHDOGS);
+ npcm7xx_clk_init_clock_hierarchy(s);
+
+ /* Realize child devices */
+ for (i = 0; i < NPCM7XX_CLOCK_NR_PLLS; ++i) {
+ if (!qdev_realize(DEVICE(&s->plls[i]), NULL, errp)) {
+ return;
+ }
+ }
+ for (i = 0; i < NPCM7XX_CLOCK_NR_SELS; ++i) {
+ if (!qdev_realize(DEVICE(&s->sels[i]), NULL, errp)) {
+ return;
+ }
+ }
+ for (i = 0; i < NPCM7XX_CLOCK_NR_DIVIDERS; ++i) {
+ if (!qdev_realize(DEVICE(&s->dividers[i]), NULL, errp)) {
+ return;
+ }
+ }
+}
+
+static const VMStateDescription vmstate_npcm7xx_clk_pll = {
+ .name = "npcm7xx-clock-pll",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_CLOCK(clock_in, NPCM7xxClockPLLState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static const VMStateDescription vmstate_npcm7xx_clk_sel = {
+ .name = "npcm7xx-clock-sel",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_ARRAY_OF_POINTER_TO_STRUCT(clock_in, NPCM7xxClockSELState,
+ NPCM7XX_CLK_SEL_MAX_INPUT, 0, vmstate_clock, Clock),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static const VMStateDescription vmstate_npcm7xx_clk_divider = {
+ .name = "npcm7xx-clock-divider",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_CLOCK(clock_in, NPCM7xxClockDividerState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static const VMStateDescription vmstate_npcm7xx_clk = {
+ .name = "npcm7xx-clk",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = npcm7xx_clk_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, NPCM7xxCLKState, NPCM7XX_CLK_NR_REGS),
+ VMSTATE_INT64(ref_ns, NPCM7xxCLKState),
+ VMSTATE_CLOCK(clkref, NPCM7xxCLKState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void npcm7xx_clk_pll_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "NPCM7xx Clock PLL Module";
+ dc->vmsd = &vmstate_npcm7xx_clk_pll;
+}
+
+static void npcm7xx_clk_sel_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "NPCM7xx Clock SEL Module";
+ dc->vmsd = &vmstate_npcm7xx_clk_sel;
+}
+
+static void npcm7xx_clk_divider_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "NPCM7xx Clock Divider Module";
+ dc->vmsd = &vmstate_npcm7xx_clk_divider;
+}
+
+static void npcm7xx_clk_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ QEMU_BUILD_BUG_ON(NPCM7XX_CLK_REGS_END > NPCM7XX_CLK_NR_REGS);
+
+ dc->desc = "NPCM7xx Clock Control Registers";
+ dc->vmsd = &vmstate_npcm7xx_clk;
+ dc->realize = npcm7xx_clk_realize;
+ rc->phases.enter = npcm7xx_clk_enter_reset;
+}
+
+static const TypeInfo npcm7xx_clk_pll_info = {
+ .name = TYPE_NPCM7XX_CLOCK_PLL,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(NPCM7xxClockPLLState),
+ .instance_init = npcm7xx_clk_pll_init,
+ .class_init = npcm7xx_clk_pll_class_init,
+};
+
+static const TypeInfo npcm7xx_clk_sel_info = {
+ .name = TYPE_NPCM7XX_CLOCK_SEL,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(NPCM7xxClockSELState),
+ .instance_init = npcm7xx_clk_sel_init,
+ .class_init = npcm7xx_clk_sel_class_init,
+};
+
+static const TypeInfo npcm7xx_clk_divider_info = {
+ .name = TYPE_NPCM7XX_CLOCK_DIVIDER,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(NPCM7xxClockDividerState),
+ .instance_init = npcm7xx_clk_divider_init,
+ .class_init = npcm7xx_clk_divider_class_init,
+};
+
+static const TypeInfo npcm7xx_clk_info = {
+ .name = TYPE_NPCM7XX_CLK,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NPCM7xxCLKState),
+ .instance_init = npcm7xx_clk_init,
+ .class_init = npcm7xx_clk_class_init,
+};
+
+static void npcm7xx_clk_register_type(void)
+{
+ type_register_static(&npcm7xx_clk_pll_info);
+ type_register_static(&npcm7xx_clk_sel_info);
+ type_register_static(&npcm7xx_clk_divider_info);
+ type_register_static(&npcm7xx_clk_info);
+}
+type_init(npcm7xx_clk_register_type);
diff --git a/hw/misc/npcm7xx_gcr.c b/hw/misc/npcm7xx_gcr.c
new file mode 100644
index 000000000..eace9e196
--- /dev/null
+++ b/hw/misc/npcm7xx_gcr.c
@@ -0,0 +1,269 @@
+/*
+ * Nuvoton NPCM7xx System Global Control Registers.
+ *
+ * Copyright 2020 Google LLC
+ *
+ * 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.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/misc/npcm7xx_gcr.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qemu/cutils.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+
+#include "trace.h"
+
+#define NPCM7XX_GCR_MIN_DRAM_SIZE (128 * MiB)
+#define NPCM7XX_GCR_MAX_DRAM_SIZE (2 * GiB)
+
+enum NPCM7xxGCRRegisters {
+ NPCM7XX_GCR_PDID,
+ NPCM7XX_GCR_PWRON,
+ NPCM7XX_GCR_MFSEL1 = 0x0c / sizeof(uint32_t),
+ NPCM7XX_GCR_MFSEL2,
+ NPCM7XX_GCR_MISCPE,
+ NPCM7XX_GCR_SPSWC = 0x038 / sizeof(uint32_t),
+ NPCM7XX_GCR_INTCR,
+ NPCM7XX_GCR_INTSR,
+ NPCM7XX_GCR_HIFCR = 0x050 / sizeof(uint32_t),
+ NPCM7XX_GCR_INTCR2 = 0x060 / sizeof(uint32_t),
+ NPCM7XX_GCR_MFSEL3,
+ NPCM7XX_GCR_SRCNT,
+ NPCM7XX_GCR_RESSR,
+ NPCM7XX_GCR_RLOCKR1,
+ NPCM7XX_GCR_FLOCKR1,
+ NPCM7XX_GCR_DSCNT,
+ NPCM7XX_GCR_MDLR,
+ NPCM7XX_GCR_SCRPAD3,
+ NPCM7XX_GCR_SCRPAD2,
+ NPCM7XX_GCR_DAVCLVLR = 0x098 / sizeof(uint32_t),
+ NPCM7XX_GCR_INTCR3,
+ NPCM7XX_GCR_VSINTR = 0x0ac / sizeof(uint32_t),
+ NPCM7XX_GCR_MFSEL4,
+ NPCM7XX_GCR_CPBPNTR = 0x0c4 / sizeof(uint32_t),
+ NPCM7XX_GCR_CPCTL = 0x0d0 / sizeof(uint32_t),
+ NPCM7XX_GCR_CP2BST,
+ NPCM7XX_GCR_B2CPNT,
+ NPCM7XX_GCR_CPPCTL,
+ NPCM7XX_GCR_I2CSEGSEL,
+ NPCM7XX_GCR_I2CSEGCTL,
+ NPCM7XX_GCR_VSRCR,
+ NPCM7XX_GCR_MLOCKR,
+ NPCM7XX_GCR_SCRPAD = 0x013c / sizeof(uint32_t),
+ NPCM7XX_GCR_USB1PHYCTL,
+ NPCM7XX_GCR_USB2PHYCTL,
+ NPCM7XX_GCR_REGS_END,
+};
+
+static const uint32_t cold_reset_values[NPCM7XX_GCR_NR_REGS] = {
+ [NPCM7XX_GCR_PDID] = 0x04a92750, /* Poleg A1 */
+ [NPCM7XX_GCR_MISCPE] = 0x0000ffff,
+ [NPCM7XX_GCR_SPSWC] = 0x00000003,
+ [NPCM7XX_GCR_INTCR] = 0x0000035e,
+ [NPCM7XX_GCR_HIFCR] = 0x0000004e,
+ [NPCM7XX_GCR_INTCR2] = (1U << 19), /* DDR initialized */
+ [NPCM7XX_GCR_RESSR] = 0x80000000,
+ [NPCM7XX_GCR_DSCNT] = 0x000000c0,
+ [NPCM7XX_GCR_DAVCLVLR] = 0x5a00f3cf,
+ [NPCM7XX_GCR_SCRPAD] = 0x00000008,
+ [NPCM7XX_GCR_USB1PHYCTL] = 0x034730e4,
+ [NPCM7XX_GCR_USB2PHYCTL] = 0x034730e4,
+};
+
+static uint64_t npcm7xx_gcr_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint32_t reg = offset / sizeof(uint32_t);
+ NPCM7xxGCRState *s = opaque;
+
+ if (reg >= NPCM7XX_GCR_NR_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: offset 0x%04" HWADDR_PRIx " out of range\n",
+ __func__, offset);
+ return 0;
+ }
+
+ trace_npcm7xx_gcr_read(offset, s->regs[reg]);
+
+ return s->regs[reg];
+}
+
+static void npcm7xx_gcr_write(void *opaque, hwaddr offset,
+ uint64_t v, unsigned size)
+{
+ uint32_t reg = offset / sizeof(uint32_t);
+ NPCM7xxGCRState *s = opaque;
+ uint32_t value = v;
+
+ trace_npcm7xx_gcr_write(offset, value);
+
+ if (reg >= NPCM7XX_GCR_NR_REGS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: offset 0x%04" HWADDR_PRIx " out of range\n",
+ __func__, offset);
+ return;
+ }
+
+ switch (reg) {
+ case NPCM7XX_GCR_PDID:
+ case NPCM7XX_GCR_PWRON:
+ case NPCM7XX_GCR_INTSR:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
+ __func__, offset);
+ return;
+
+ case NPCM7XX_GCR_RESSR:
+ case NPCM7XX_GCR_CP2BST:
+ /* Write 1 to clear */
+ value = s->regs[reg] & ~value;
+ break;
+
+ case NPCM7XX_GCR_RLOCKR1:
+ case NPCM7XX_GCR_MDLR:
+ /* Write 1 to set */
+ value |= s->regs[reg];
+ break;
+ };
+
+ s->regs[reg] = value;
+}
+
+static const struct MemoryRegionOps npcm7xx_gcr_ops = {
+ .read = npcm7xx_gcr_read,
+ .write = npcm7xx_gcr_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void npcm7xx_gcr_enter_reset(Object *obj, ResetType type)
+{
+ NPCM7xxGCRState *s = NPCM7XX_GCR(obj);
+
+ QEMU_BUILD_BUG_ON(sizeof(s->regs) != sizeof(cold_reset_values));
+
+ switch (type) {
+ case RESET_TYPE_COLD:
+ memcpy(s->regs, cold_reset_values, sizeof(s->regs));
+ s->regs[NPCM7XX_GCR_PWRON] = s->reset_pwron;
+ s->regs[NPCM7XX_GCR_MDLR] = s->reset_mdlr;
+ s->regs[NPCM7XX_GCR_INTCR3] = s->reset_intcr3;
+ break;
+ }
+}
+
+static void npcm7xx_gcr_realize(DeviceState *dev, Error **errp)
+{
+ ERRP_GUARD();
+ NPCM7xxGCRState *s = NPCM7XX_GCR(dev);
+ uint64_t dram_size;
+ Object *obj;
+
+ obj = object_property_get_link(OBJECT(dev), "dram-mr", errp);
+ if (!obj) {
+ error_prepend(errp, "%s: required dram-mr link not found: ", __func__);
+ return;
+ }
+ dram_size = memory_region_size(MEMORY_REGION(obj));
+ if (!is_power_of_2(dram_size) ||
+ dram_size < NPCM7XX_GCR_MIN_DRAM_SIZE ||
+ dram_size > NPCM7XX_GCR_MAX_DRAM_SIZE) {
+ g_autofree char *sz = size_to_str(dram_size);
+ g_autofree char *min_sz = size_to_str(NPCM7XX_GCR_MIN_DRAM_SIZE);
+ g_autofree char *max_sz = size_to_str(NPCM7XX_GCR_MAX_DRAM_SIZE);
+ error_setg(errp, "%s: unsupported DRAM size %s", __func__, sz);
+ error_append_hint(errp,
+ "DRAM size must be a power of two between %s and %s,"
+ " inclusive.\n", min_sz, max_sz);
+ return;
+ }
+
+ /* Power-on reset value */
+ s->reset_intcr3 = 0x00001002;
+
+ /*
+ * The GMMAP (Graphics Memory Map) field is used by u-boot to detect the
+ * DRAM size, and is normally initialized by the boot block as part of DRAM
+ * training. However, since we don't have a complete emulation of the
+ * memory controller and try to make it look like it has already been
+ * initialized, the boot block will skip this initialization, and we need
+ * to make sure this field is set correctly up front.
+ *
+ * WARNING: some versions of u-boot only looks at bits 8 and 9, so 2 GiB of
+ * DRAM will be interpreted as 128 MiB.
+ *
+ * https://github.com/Nuvoton-Israel/u-boot/blob/2aef993bd2aafeb5408dbaad0f3ce099ee40c4aa/board/nuvoton/poleg/poleg.c#L244
+ */
+ s->reset_intcr3 |= ctz64(dram_size / NPCM7XX_GCR_MIN_DRAM_SIZE) << 8;
+}
+
+static void npcm7xx_gcr_init(Object *obj)
+{
+ NPCM7xxGCRState *s = NPCM7XX_GCR(obj);
+
+ memory_region_init_io(&s->iomem, obj, &npcm7xx_gcr_ops, s,
+ TYPE_NPCM7XX_GCR, 4 * KiB);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+}
+
+static const VMStateDescription vmstate_npcm7xx_gcr = {
+ .name = "npcm7xx-gcr",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, NPCM7xxGCRState, NPCM7XX_GCR_NR_REGS),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static Property npcm7xx_gcr_properties[] = {
+ DEFINE_PROP_UINT32("disabled-modules", NPCM7xxGCRState, reset_mdlr, 0),
+ DEFINE_PROP_UINT32("power-on-straps", NPCM7xxGCRState, reset_pwron, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void npcm7xx_gcr_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ QEMU_BUILD_BUG_ON(NPCM7XX_GCR_REGS_END > NPCM7XX_GCR_NR_REGS);
+
+ dc->desc = "NPCM7xx System Global Control Registers";
+ dc->realize = npcm7xx_gcr_realize;
+ dc->vmsd = &vmstate_npcm7xx_gcr;
+ rc->phases.enter = npcm7xx_gcr_enter_reset;
+
+ device_class_set_props(dc, npcm7xx_gcr_properties);
+}
+
+static const TypeInfo npcm7xx_gcr_info = {
+ .name = TYPE_NPCM7XX_GCR,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NPCM7xxGCRState),
+ .instance_init = npcm7xx_gcr_init,
+ .class_init = npcm7xx_gcr_class_init,
+};
+
+static void npcm7xx_gcr_register_type(void)
+{
+ type_register_static(&npcm7xx_gcr_info);
+}
+type_init(npcm7xx_gcr_register_type);
diff --git a/hw/misc/npcm7xx_mft.c b/hw/misc/npcm7xx_mft.c
new file mode 100644
index 000000000..a30583a1b
--- /dev/null
+++ b/hw/misc/npcm7xx_mft.c
@@ -0,0 +1,540 @@
+/*
+ * Nuvoton NPCM7xx MFT Module
+ *
+ * Copyright 2021 Google LLC
+ *
+ * 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.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/qdev-clock.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/npcm7xx_mft.h"
+#include "hw/misc/npcm7xx_pwm.h"
+#include "hw/registerfields.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/bitops.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qemu/units.h"
+#include "trace.h"
+
+/*
+ * Some of the registers can only accessed via 16-bit ops and some can only
+ * be accessed via 8-bit ops. However we mark all of them using REG16 to
+ * simplify implementation. npcm7xx_mft_check_mem_op checks the access length
+ * of memory operations.
+ */
+REG16(NPCM7XX_MFT_CNT1, 0x00);
+REG16(NPCM7XX_MFT_CRA, 0x02);
+REG16(NPCM7XX_MFT_CRB, 0x04);
+REG16(NPCM7XX_MFT_CNT2, 0x06);
+REG16(NPCM7XX_MFT_PRSC, 0x08);
+REG16(NPCM7XX_MFT_CKC, 0x0a);
+REG16(NPCM7XX_MFT_MCTRL, 0x0c);
+REG16(NPCM7XX_MFT_ICTRL, 0x0e);
+REG16(NPCM7XX_MFT_ICLR, 0x10);
+REG16(NPCM7XX_MFT_IEN, 0x12);
+REG16(NPCM7XX_MFT_CPA, 0x14);
+REG16(NPCM7XX_MFT_CPB, 0x16);
+REG16(NPCM7XX_MFT_CPCFG, 0x18);
+REG16(NPCM7XX_MFT_INASEL, 0x1a);
+REG16(NPCM7XX_MFT_INBSEL, 0x1c);
+
+/* Register Fields */
+#define NPCM7XX_MFT_CKC_C2CSEL BIT(3)
+#define NPCM7XX_MFT_CKC_C1CSEL BIT(0)
+
+#define NPCM7XX_MFT_MCTRL_TBEN BIT(6)
+#define NPCM7XX_MFT_MCTRL_TAEN BIT(5)
+#define NPCM7XX_MFT_MCTRL_TBEDG BIT(4)
+#define NPCM7XX_MFT_MCTRL_TAEDG BIT(3)
+#define NPCM7XX_MFT_MCTRL_MODE5 BIT(2)
+
+#define NPCM7XX_MFT_ICTRL_TFPND BIT(5)
+#define NPCM7XX_MFT_ICTRL_TEPND BIT(4)
+#define NPCM7XX_MFT_ICTRL_TDPND BIT(3)
+#define NPCM7XX_MFT_ICTRL_TCPND BIT(2)
+#define NPCM7XX_MFT_ICTRL_TBPND BIT(1)
+#define NPCM7XX_MFT_ICTRL_TAPND BIT(0)
+
+#define NPCM7XX_MFT_ICLR_TFCLR BIT(5)
+#define NPCM7XX_MFT_ICLR_TECLR BIT(4)
+#define NPCM7XX_MFT_ICLR_TDCLR BIT(3)
+#define NPCM7XX_MFT_ICLR_TCCLR BIT(2)
+#define NPCM7XX_MFT_ICLR_TBCLR BIT(1)
+#define NPCM7XX_MFT_ICLR_TACLR BIT(0)
+
+#define NPCM7XX_MFT_IEN_TFIEN BIT(5)
+#define NPCM7XX_MFT_IEN_TEIEN BIT(4)
+#define NPCM7XX_MFT_IEN_TDIEN BIT(3)
+#define NPCM7XX_MFT_IEN_TCIEN BIT(2)
+#define NPCM7XX_MFT_IEN_TBIEN BIT(1)
+#define NPCM7XX_MFT_IEN_TAIEN BIT(0)
+
+#define NPCM7XX_MFT_CPCFG_GET_B(rv) extract8((rv), 4, 4)
+#define NPCM7XX_MFT_CPCFG_GET_A(rv) extract8((rv), 0, 4)
+#define NPCM7XX_MFT_CPCFG_HIEN BIT(3)
+#define NPCM7XX_MFT_CPCFG_EQEN BIT(2)
+#define NPCM7XX_MFT_CPCFG_LOEN BIT(1)
+#define NPCM7XX_MFT_CPCFG_CPSEL BIT(0)
+
+#define NPCM7XX_MFT_INASEL_SELA BIT(0)
+#define NPCM7XX_MFT_INBSEL_SELB BIT(0)
+
+/* Max CNT values of the module. The CNT value is a countdown from it. */
+#define NPCM7XX_MFT_MAX_CNT 0xFFFF
+
+/* Each fan revolution should generated 2 pulses */
+#define NPCM7XX_MFT_PULSE_PER_REVOLUTION 2
+
+typedef enum NPCM7xxMFTCaptureState {
+ /* capture succeeded with a valid CNT value. */
+ NPCM7XX_CAPTURE_SUCCEED,
+ /* capture stopped prematurely due to reaching CPCFG condition. */
+ NPCM7XX_CAPTURE_COMPARE_HIT,
+ /* capture fails since it reaches underflow condition for CNT. */
+ NPCM7XX_CAPTURE_UNDERFLOW,
+} NPCM7xxMFTCaptureState;
+
+static void npcm7xx_mft_reset(NPCM7xxMFTState *s)
+{
+ int i;
+
+ /* Only registers PRSC ~ INBSEL need to be reset. */
+ for (i = R_NPCM7XX_MFT_PRSC; i <= R_NPCM7XX_MFT_INBSEL; ++i) {
+ s->regs[i] = 0;
+ }
+}
+
+static void npcm7xx_mft_clear_interrupt(NPCM7xxMFTState *s, uint8_t iclr)
+{
+ /*
+ * Clear bits in ICTRL where corresponding bits in iclr is 1.
+ * Both iclr and ictrl are 8-bit regs. (See npcm7xx_mft_check_mem_op)
+ */
+ s->regs[R_NPCM7XX_MFT_ICTRL] &= ~iclr;
+}
+
+/*
+ * If the CPCFG's condition should be triggered during count down from
+ * NPCM7XX_MFT_MAX_CNT to src if compared to tgt, return the count when
+ * the condition is triggered.
+ * Otherwise return -1.
+ * Since tgt is uint16_t it must always <= NPCM7XX_MFT_MAX_CNT.
+ */
+static int npcm7xx_mft_compare(int32_t src, uint16_t tgt, uint8_t cpcfg)
+{
+ if (cpcfg & NPCM7XX_MFT_CPCFG_HIEN) {
+ return NPCM7XX_MFT_MAX_CNT;
+ }
+ if ((cpcfg & NPCM7XX_MFT_CPCFG_EQEN) && (src <= tgt)) {
+ return tgt;
+ }
+ if ((cpcfg & NPCM7XX_MFT_CPCFG_LOEN) && (tgt > 0) && (src < tgt)) {
+ return tgt - 1;
+ }
+
+ return -1;
+}
+
+/* Compute CNT according to corresponding fan's RPM. */
+static NPCM7xxMFTCaptureState npcm7xx_mft_compute_cnt(
+ Clock *clock, uint32_t max_rpm, uint32_t duty, uint16_t tgt,
+ uint8_t cpcfg, uint16_t *cnt)
+{
+ uint32_t rpm = (uint64_t)max_rpm * (uint64_t)duty / NPCM7XX_PWM_MAX_DUTY;
+ int32_t count;
+ int stopped;
+ NPCM7xxMFTCaptureState state;
+
+ if (rpm == 0) {
+ /*
+ * If RPM = 0, capture won't happen. CNT will continue count down.
+ * So it's effective equivalent to have a cnt > NPCM7XX_MFT_MAX_CNT
+ */
+ count = NPCM7XX_MFT_MAX_CNT + 1;
+ } else {
+ /*
+ * RPM = revolution/min. The time for one revlution (in ns) is
+ * MINUTE_TO_NANOSECOND / RPM.
+ */
+ count = clock_ns_to_ticks(clock, (60 * NANOSECONDS_PER_SECOND) /
+ (rpm * NPCM7XX_MFT_PULSE_PER_REVOLUTION));
+ }
+
+ if (count > NPCM7XX_MFT_MAX_CNT) {
+ count = -1;
+ } else {
+ /* The CNT is a countdown value from NPCM7XX_MFT_MAX_CNT. */
+ count = NPCM7XX_MFT_MAX_CNT - count;
+ }
+ stopped = npcm7xx_mft_compare(count, tgt, cpcfg);
+ if (stopped == -1) {
+ if (count == -1) {
+ /* Underflow */
+ state = NPCM7XX_CAPTURE_UNDERFLOW;
+ } else {
+ state = NPCM7XX_CAPTURE_SUCCEED;
+ }
+ } else {
+ count = stopped;
+ state = NPCM7XX_CAPTURE_COMPARE_HIT;
+ }
+
+ if (count != -1) {
+ *cnt = count;
+ }
+ trace_npcm7xx_mft_rpm(clock->canonical_path, clock_get_hz(clock),
+ state, count, rpm, duty);
+ return state;
+}
+
+/*
+ * Capture Fan RPM and update CNT and CR registers accordingly.
+ * Raise IRQ if certain contidions are met in IEN.
+ */
+static void npcm7xx_mft_capture(NPCM7xxMFTState *s)
+{
+ int irq_level = 0;
+ NPCM7xxMFTCaptureState state;
+ int sel;
+ uint8_t cpcfg;
+
+ /*
+ * If not mode 5, the behavior is undefined. We just do nothing in this
+ * case.
+ */
+ if (!(s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_MODE5)) {
+ return;
+ }
+
+ /* Capture input A. */
+ if (s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_TAEN &&
+ s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C1CSEL) {
+ sel = s->regs[R_NPCM7XX_MFT_INASEL] & NPCM7XX_MFT_INASEL_SELA;
+ cpcfg = NPCM7XX_MFT_CPCFG_GET_A(s->regs[R_NPCM7XX_MFT_CPCFG]);
+ state = npcm7xx_mft_compute_cnt(s->clock_1,
+ sel ? s->max_rpm[2] : s->max_rpm[0],
+ sel ? s->duty[2] : s->duty[0],
+ s->regs[R_NPCM7XX_MFT_CPA],
+ cpcfg,
+ &s->regs[R_NPCM7XX_MFT_CNT1]);
+ switch (state) {
+ case NPCM7XX_CAPTURE_SUCCEED:
+ /* Interrupt on input capture on TAn transition - TAPND */
+ s->regs[R_NPCM7XX_MFT_CRA] = s->regs[R_NPCM7XX_MFT_CNT1];
+ s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TAPND;
+ if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TAIEN) {
+ irq_level = 1;
+ }
+ break;
+
+ case NPCM7XX_CAPTURE_COMPARE_HIT:
+ /* Compare Hit - TEPND */
+ s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TEPND;
+ if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TEIEN) {
+ irq_level = 1;
+ }
+ break;
+
+ case NPCM7XX_CAPTURE_UNDERFLOW:
+ /* Underflow - TCPND */
+ s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TCPND;
+ if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TCIEN) {
+ irq_level = 1;
+ }
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+ /* Capture input B. */
+ if (s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_TBEN &&
+ s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C2CSEL) {
+ sel = s->regs[R_NPCM7XX_MFT_INBSEL] & NPCM7XX_MFT_INBSEL_SELB;
+ cpcfg = NPCM7XX_MFT_CPCFG_GET_B(s->regs[R_NPCM7XX_MFT_CPCFG]);
+ state = npcm7xx_mft_compute_cnt(s->clock_2,
+ sel ? s->max_rpm[3] : s->max_rpm[1],
+ sel ? s->duty[3] : s->duty[1],
+ s->regs[R_NPCM7XX_MFT_CPB],
+ cpcfg,
+ &s->regs[R_NPCM7XX_MFT_CNT2]);
+ switch (state) {
+ case NPCM7XX_CAPTURE_SUCCEED:
+ /* Interrupt on input capture on TBn transition - TBPND */
+ s->regs[R_NPCM7XX_MFT_CRB] = s->regs[R_NPCM7XX_MFT_CNT2];
+ s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TBPND;
+ if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TBIEN) {
+ irq_level = 1;
+ }
+ break;
+
+ case NPCM7XX_CAPTURE_COMPARE_HIT:
+ /* Compare Hit - TFPND */
+ s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TFPND;
+ if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TFIEN) {
+ irq_level = 1;
+ }
+ break;
+
+ case NPCM7XX_CAPTURE_UNDERFLOW:
+ /* Underflow - TDPND */
+ s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TDPND;
+ if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TDIEN) {
+ irq_level = 1;
+ }
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+ trace_npcm7xx_mft_capture(DEVICE(s)->canonical_path, irq_level);
+ qemu_set_irq(s->irq, irq_level);
+}
+
+/* Update clock for counters. */
+static void npcm7xx_mft_update_clock(void *opaque, ClockEvent event)
+{
+ NPCM7xxMFTState *s = NPCM7XX_MFT(opaque);
+ uint64_t prescaled_clock_period;
+
+ prescaled_clock_period = clock_get(s->clock_in) *
+ (s->regs[R_NPCM7XX_MFT_PRSC] + 1ULL);
+ trace_npcm7xx_mft_update_clock(s->clock_in->canonical_path,
+ s->regs[R_NPCM7XX_MFT_CKC],
+ clock_get(s->clock_in),
+ prescaled_clock_period);
+ /* Update clock 1 */
+ if (s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C1CSEL) {
+ /* Clock is prescaled. */
+ clock_update(s->clock_1, prescaled_clock_period);
+ } else {
+ /* Clock stopped. */
+ clock_update(s->clock_1, 0);
+ }
+ /* Update clock 2 */
+ if (s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C2CSEL) {
+ /* Clock is prescaled. */
+ clock_update(s->clock_2, prescaled_clock_period);
+ } else {
+ /* Clock stopped. */
+ clock_update(s->clock_2, 0);
+ }
+
+ npcm7xx_mft_capture(s);
+}
+
+static uint64_t npcm7xx_mft_read(void *opaque, hwaddr offset, unsigned size)
+{
+ NPCM7xxMFTState *s = NPCM7XX_MFT(opaque);
+ uint16_t value = 0;
+
+ switch (offset) {
+ case A_NPCM7XX_MFT_ICLR:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: register @ 0x%04" HWADDR_PRIx " is write-only\n",
+ __func__, offset);
+ break;
+
+ default:
+ value = s->regs[offset / 2];
+ }
+
+ trace_npcm7xx_mft_read(DEVICE(s)->canonical_path, offset, value);
+ return value;
+}
+
+static void npcm7xx_mft_write(void *opaque, hwaddr offset,
+ uint64_t v, unsigned size)
+{
+ NPCM7xxMFTState *s = NPCM7XX_MFT(opaque);
+
+ trace_npcm7xx_mft_write(DEVICE(s)->canonical_path, offset, v);
+ switch (offset) {
+ case A_NPCM7XX_MFT_ICLR:
+ npcm7xx_mft_clear_interrupt(s, v);
+ break;
+
+ case A_NPCM7XX_MFT_CKC:
+ case A_NPCM7XX_MFT_PRSC:
+ s->regs[offset / 2] = v;
+ npcm7xx_mft_update_clock(s, ClockUpdate);
+ break;
+
+ default:
+ s->regs[offset / 2] = v;
+ npcm7xx_mft_capture(s);
+ break;
+ }
+}
+
+static bool npcm7xx_mft_check_mem_op(void *opaque, hwaddr offset,
+ unsigned size, bool is_write,
+ MemTxAttrs attrs)
+{
+ switch (offset) {
+ /* 16-bit registers. Must be accessed with 16-bit read/write.*/
+ case A_NPCM7XX_MFT_CNT1:
+ case A_NPCM7XX_MFT_CRA:
+ case A_NPCM7XX_MFT_CRB:
+ case A_NPCM7XX_MFT_CNT2:
+ case A_NPCM7XX_MFT_CPA:
+ case A_NPCM7XX_MFT_CPB:
+ return size == 2;
+
+ /* 8-bit registers. Must be accessed with 8-bit read/write.*/
+ case A_NPCM7XX_MFT_PRSC:
+ case A_NPCM7XX_MFT_CKC:
+ case A_NPCM7XX_MFT_MCTRL:
+ case A_NPCM7XX_MFT_ICTRL:
+ case A_NPCM7XX_MFT_ICLR:
+ case A_NPCM7XX_MFT_IEN:
+ case A_NPCM7XX_MFT_CPCFG:
+ case A_NPCM7XX_MFT_INASEL:
+ case A_NPCM7XX_MFT_INBSEL:
+ return size == 1;
+
+ default:
+ /* Invalid registers. */
+ return false;
+ }
+}
+
+static void npcm7xx_mft_get_max_rpm(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ visit_type_uint32(v, name, (uint32_t *)opaque, errp);
+}
+
+static void npcm7xx_mft_set_max_rpm(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ NPCM7xxMFTState *s = NPCM7XX_MFT(obj);
+ uint32_t *max_rpm = opaque;
+ uint32_t value;
+
+ if (!visit_type_uint32(v, name, &value, errp)) {
+ return;
+ }
+
+ *max_rpm = value;
+ npcm7xx_mft_capture(s);
+}
+
+static void npcm7xx_mft_duty_handler(void *opaque, int n, int value)
+{
+ NPCM7xxMFTState *s = NPCM7XX_MFT(opaque);
+
+ trace_npcm7xx_mft_set_duty(DEVICE(s)->canonical_path, n, value);
+ s->duty[n] = value;
+ npcm7xx_mft_capture(s);
+}
+
+static const struct MemoryRegionOps npcm7xx_mft_ops = {
+ .read = npcm7xx_mft_read,
+ .write = npcm7xx_mft_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 2,
+ .unaligned = false,
+ .accepts = npcm7xx_mft_check_mem_op,
+ },
+};
+
+static void npcm7xx_mft_enter_reset(Object *obj, ResetType type)
+{
+ NPCM7xxMFTState *s = NPCM7XX_MFT(obj);
+
+ npcm7xx_mft_reset(s);
+}
+
+static void npcm7xx_mft_hold_reset(Object *obj)
+{
+ NPCM7xxMFTState *s = NPCM7XX_MFT(obj);
+
+ qemu_irq_lower(s->irq);
+}
+
+static void npcm7xx_mft_init(Object *obj)
+{
+ NPCM7xxMFTState *s = NPCM7XX_MFT(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &npcm7xx_mft_ops, s,
+ TYPE_NPCM7XX_MFT, 4 * KiB);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ s->clock_in = qdev_init_clock_in(dev, "clock-in", npcm7xx_mft_update_clock,
+ s, ClockUpdate);
+ s->clock_1 = qdev_init_clock_out(dev, "clock1");
+ s->clock_2 = qdev_init_clock_out(dev, "clock2");
+
+ for (int i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
+ object_property_add(obj, "max_rpm[*]", "uint32",
+ npcm7xx_mft_get_max_rpm,
+ npcm7xx_mft_set_max_rpm,
+ NULL, &s->max_rpm[i]);
+ }
+ qdev_init_gpio_in_named(dev, npcm7xx_mft_duty_handler, "duty",
+ NPCM7XX_MFT_FANIN_COUNT);
+}
+
+static const VMStateDescription vmstate_npcm7xx_mft = {
+ .name = "npcm7xx-mft-module",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_CLOCK(clock_in, NPCM7xxMFTState),
+ VMSTATE_CLOCK(clock_1, NPCM7xxMFTState),
+ VMSTATE_CLOCK(clock_2, NPCM7xxMFTState),
+ VMSTATE_UINT16_ARRAY(regs, NPCM7xxMFTState, NPCM7XX_MFT_NR_REGS),
+ VMSTATE_UINT32_ARRAY(max_rpm, NPCM7xxMFTState, NPCM7XX_MFT_FANIN_COUNT),
+ VMSTATE_UINT32_ARRAY(duty, NPCM7xxMFTState, NPCM7XX_MFT_FANIN_COUNT),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void npcm7xx_mft_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "NPCM7xx MFT Controller";
+ dc->vmsd = &vmstate_npcm7xx_mft;
+ rc->phases.enter = npcm7xx_mft_enter_reset;
+ rc->phases.hold = npcm7xx_mft_hold_reset;
+}
+
+static const TypeInfo npcm7xx_mft_info = {
+ .name = TYPE_NPCM7XX_MFT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NPCM7xxMFTState),
+ .class_init = npcm7xx_mft_class_init,
+ .instance_init = npcm7xx_mft_init,
+};
+
+static void npcm7xx_mft_register_type(void)
+{
+ type_register_static(&npcm7xx_mft_info);
+}
+type_init(npcm7xx_mft_register_type);
diff --git a/hw/misc/npcm7xx_pwm.c b/hw/misc/npcm7xx_pwm.c
new file mode 100644
index 000000000..2be5bd25c
--- /dev/null
+++ b/hw/misc/npcm7xx_pwm.c
@@ -0,0 +1,569 @@
+/*
+ * Nuvoton NPCM7xx PWM Module
+ *
+ * Copyright 2020 Google LLC
+ *
+ * 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.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/qdev-clock.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/npcm7xx_pwm.h"
+#include "hw/registerfields.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "trace.h"
+
+REG32(NPCM7XX_PWM_PPR, 0x00);
+REG32(NPCM7XX_PWM_CSR, 0x04);
+REG32(NPCM7XX_PWM_PCR, 0x08);
+REG32(NPCM7XX_PWM_CNR0, 0x0c);
+REG32(NPCM7XX_PWM_CMR0, 0x10);
+REG32(NPCM7XX_PWM_PDR0, 0x14);
+REG32(NPCM7XX_PWM_CNR1, 0x18);
+REG32(NPCM7XX_PWM_CMR1, 0x1c);
+REG32(NPCM7XX_PWM_PDR1, 0x20);
+REG32(NPCM7XX_PWM_CNR2, 0x24);
+REG32(NPCM7XX_PWM_CMR2, 0x28);
+REG32(NPCM7XX_PWM_PDR2, 0x2c);
+REG32(NPCM7XX_PWM_CNR3, 0x30);
+REG32(NPCM7XX_PWM_CMR3, 0x34);
+REG32(NPCM7XX_PWM_PDR3, 0x38);
+REG32(NPCM7XX_PWM_PIER, 0x3c);
+REG32(NPCM7XX_PWM_PIIR, 0x40);
+REG32(NPCM7XX_PWM_PWDR0, 0x44);
+REG32(NPCM7XX_PWM_PWDR1, 0x48);
+REG32(NPCM7XX_PWM_PWDR2, 0x4c);
+REG32(NPCM7XX_PWM_PWDR3, 0x50);
+
+/* Register field definitions. */
+#define NPCM7XX_PPR(rv, index) extract32((rv), npcm7xx_ppr_base[index], 8)
+#define NPCM7XX_CSR(rv, index) extract32((rv), npcm7xx_csr_base[index], 3)
+#define NPCM7XX_CH(rv, index) extract32((rv), npcm7xx_ch_base[index], 4)
+#define NPCM7XX_CH_EN BIT(0)
+#define NPCM7XX_CH_INV BIT(2)
+#define NPCM7XX_CH_MOD BIT(3)
+
+#define NPCM7XX_MAX_CMR 65535
+#define NPCM7XX_MAX_CNR 65535
+
+/* Offset of each PWM channel's prescaler in the PPR register. */
+static const int npcm7xx_ppr_base[] = { 0, 0, 8, 8 };
+/* Offset of each PWM channel's clock selector in the CSR register. */
+static const int npcm7xx_csr_base[] = { 0, 4, 8, 12 };
+/* Offset of each PWM channel's control variable in the PCR register. */
+static const int npcm7xx_ch_base[] = { 0, 8, 12, 16 };
+
+static uint32_t npcm7xx_pwm_calculate_freq(NPCM7xxPWM *p)
+{
+ uint32_t ppr;
+ uint32_t csr;
+ uint32_t freq;
+
+ if (!p->running) {
+ return 0;
+ }
+
+ csr = NPCM7XX_CSR(p->module->csr, p->index);
+ ppr = NPCM7XX_PPR(p->module->ppr, p->index);
+ freq = clock_get_hz(p->module->clock);
+ freq /= ppr + 1;
+ /* csr can only be 0~4 */
+ if (csr > 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid csr value %u\n",
+ __func__, csr);
+ csr = 4;
+ }
+ /* freq won't be changed if csr == 4. */
+ if (csr < 4) {
+ freq >>= csr + 1;
+ }
+
+ return freq / (p->cnr + 1);
+}
+
+static uint32_t npcm7xx_pwm_calculate_duty(NPCM7xxPWM *p)
+{
+ uint32_t duty;
+
+ if (p->running) {
+ if (p->cnr == 0) {
+ duty = 0;
+ } else if (p->cmr >= p->cnr) {
+ duty = NPCM7XX_PWM_MAX_DUTY;
+ } else {
+ duty = (uint64_t)NPCM7XX_PWM_MAX_DUTY * (p->cmr + 1) / (p->cnr + 1);
+ }
+ } else {
+ duty = 0;
+ }
+
+ if (p->inverted) {
+ duty = NPCM7XX_PWM_MAX_DUTY - duty;
+ }
+
+ return duty;
+}
+
+static void npcm7xx_pwm_update_freq(NPCM7xxPWM *p)
+{
+ uint32_t freq = npcm7xx_pwm_calculate_freq(p);
+
+ if (freq != p->freq) {
+ trace_npcm7xx_pwm_update_freq(DEVICE(p->module)->canonical_path,
+ p->index, p->freq, freq);
+ p->freq = freq;
+ }
+}
+
+static void npcm7xx_pwm_update_duty(NPCM7xxPWM *p)
+{
+ uint32_t duty = npcm7xx_pwm_calculate_duty(p);
+
+ if (duty != p->duty) {
+ trace_npcm7xx_pwm_update_duty(DEVICE(p->module)->canonical_path,
+ p->index, p->duty, duty);
+ p->duty = duty;
+ qemu_set_irq(p->module->duty_gpio_out[p->index], p->duty);
+ }
+}
+
+static void npcm7xx_pwm_update_output(NPCM7xxPWM *p)
+{
+ npcm7xx_pwm_update_freq(p);
+ npcm7xx_pwm_update_duty(p);
+}
+
+static void npcm7xx_pwm_write_ppr(NPCM7xxPWMState *s, uint32_t new_ppr)
+{
+ int i;
+ uint32_t old_ppr = s->ppr;
+
+ QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_ppr_base) != NPCM7XX_PWM_PER_MODULE);
+ s->ppr = new_ppr;
+ for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
+ if (NPCM7XX_PPR(old_ppr, i) != NPCM7XX_PPR(new_ppr, i)) {
+ npcm7xx_pwm_update_freq(&s->pwm[i]);
+ }
+ }
+}
+
+static void npcm7xx_pwm_write_csr(NPCM7xxPWMState *s, uint32_t new_csr)
+{
+ int i;
+ uint32_t old_csr = s->csr;
+
+ QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_csr_base) != NPCM7XX_PWM_PER_MODULE);
+ s->csr = new_csr;
+ for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
+ if (NPCM7XX_CSR(old_csr, i) != NPCM7XX_CSR(new_csr, i)) {
+ npcm7xx_pwm_update_freq(&s->pwm[i]);
+ }
+ }
+}
+
+static void npcm7xx_pwm_write_pcr(NPCM7xxPWMState *s, uint32_t new_pcr)
+{
+ int i;
+ bool inverted;
+ uint32_t pcr;
+ NPCM7xxPWM *p;
+
+ s->pcr = new_pcr;
+ QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_ch_base) != NPCM7XX_PWM_PER_MODULE);
+ for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
+ p = &s->pwm[i];
+ pcr = NPCM7XX_CH(new_pcr, i);
+ inverted = pcr & NPCM7XX_CH_INV;
+
+ /*
+ * We only run a PWM channel with toggle mode. Single-shot mode does not
+ * generate frequency and duty-cycle values.
+ */
+ if ((pcr & NPCM7XX_CH_EN) && (pcr & NPCM7XX_CH_MOD)) {
+ if (p->running) {
+ /* Re-run this PWM channel if inverted changed. */
+ if (p->inverted ^ inverted) {
+ p->inverted = inverted;
+ npcm7xx_pwm_update_duty(p);
+ }
+ } else {
+ /* Run this PWM channel. */
+ p->running = true;
+ p->inverted = inverted;
+ npcm7xx_pwm_update_output(p);
+ }
+ } else {
+ /* Clear this PWM channel. */
+ p->running = false;
+ p->inverted = inverted;
+ npcm7xx_pwm_update_output(p);
+ }
+ }
+
+}
+
+static hwaddr npcm7xx_cnr_index(hwaddr offset)
+{
+ switch (offset) {
+ case A_NPCM7XX_PWM_CNR0:
+ return 0;
+ case A_NPCM7XX_PWM_CNR1:
+ return 1;
+ case A_NPCM7XX_PWM_CNR2:
+ return 2;
+ case A_NPCM7XX_PWM_CNR3:
+ return 3;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static hwaddr npcm7xx_cmr_index(hwaddr offset)
+{
+ switch (offset) {
+ case A_NPCM7XX_PWM_CMR0:
+ return 0;
+ case A_NPCM7XX_PWM_CMR1:
+ return 1;
+ case A_NPCM7XX_PWM_CMR2:
+ return 2;
+ case A_NPCM7XX_PWM_CMR3:
+ return 3;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static hwaddr npcm7xx_pdr_index(hwaddr offset)
+{
+ switch (offset) {
+ case A_NPCM7XX_PWM_PDR0:
+ return 0;
+ case A_NPCM7XX_PWM_PDR1:
+ return 1;
+ case A_NPCM7XX_PWM_PDR2:
+ return 2;
+ case A_NPCM7XX_PWM_PDR3:
+ return 3;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static hwaddr npcm7xx_pwdr_index(hwaddr offset)
+{
+ switch (offset) {
+ case A_NPCM7XX_PWM_PWDR0:
+ return 0;
+ case A_NPCM7XX_PWM_PWDR1:
+ return 1;
+ case A_NPCM7XX_PWM_PWDR2:
+ return 2;
+ case A_NPCM7XX_PWM_PWDR3:
+ return 3;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static uint64_t npcm7xx_pwm_read(void *opaque, hwaddr offset, unsigned size)
+{
+ NPCM7xxPWMState *s = opaque;
+ uint64_t value = 0;
+
+ switch (offset) {
+ case A_NPCM7XX_PWM_CNR0:
+ case A_NPCM7XX_PWM_CNR1:
+ case A_NPCM7XX_PWM_CNR2:
+ case A_NPCM7XX_PWM_CNR3:
+ value = s->pwm[npcm7xx_cnr_index(offset)].cnr;
+ break;
+
+ case A_NPCM7XX_PWM_CMR0:
+ case A_NPCM7XX_PWM_CMR1:
+ case A_NPCM7XX_PWM_CMR2:
+ case A_NPCM7XX_PWM_CMR3:
+ value = s->pwm[npcm7xx_cmr_index(offset)].cmr;
+ break;
+
+ case A_NPCM7XX_PWM_PDR0:
+ case A_NPCM7XX_PWM_PDR1:
+ case A_NPCM7XX_PWM_PDR2:
+ case A_NPCM7XX_PWM_PDR3:
+ value = s->pwm[npcm7xx_pdr_index(offset)].pdr;
+ break;
+
+ case A_NPCM7XX_PWM_PWDR0:
+ case A_NPCM7XX_PWM_PWDR1:
+ case A_NPCM7XX_PWM_PWDR2:
+ case A_NPCM7XX_PWM_PWDR3:
+ value = s->pwm[npcm7xx_pwdr_index(offset)].pwdr;
+ break;
+
+ case A_NPCM7XX_PWM_PPR:
+ value = s->ppr;
+ break;
+
+ case A_NPCM7XX_PWM_CSR:
+ value = s->csr;
+ break;
+
+ case A_NPCM7XX_PWM_PCR:
+ value = s->pcr;
+ break;
+
+ case A_NPCM7XX_PWM_PIER:
+ value = s->pier;
+ break;
+
+ case A_NPCM7XX_PWM_PIIR:
+ value = s->piir;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
+ __func__, offset);
+ break;
+ }
+
+ trace_npcm7xx_pwm_read(DEVICE(s)->canonical_path, offset, value);
+ return value;
+}
+
+static void npcm7xx_pwm_write(void *opaque, hwaddr offset,
+ uint64_t v, unsigned size)
+{
+ NPCM7xxPWMState *s = opaque;
+ NPCM7xxPWM *p;
+ uint32_t value = v;
+
+ trace_npcm7xx_pwm_write(DEVICE(s)->canonical_path, offset, value);
+ switch (offset) {
+ case A_NPCM7XX_PWM_CNR0:
+ case A_NPCM7XX_PWM_CNR1:
+ case A_NPCM7XX_PWM_CNR2:
+ case A_NPCM7XX_PWM_CNR3:
+ p = &s->pwm[npcm7xx_cnr_index(offset)];
+ if (value > NPCM7XX_MAX_CNR) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid cnr value: %u", __func__, value);
+ p->cnr = NPCM7XX_MAX_CNR;
+ } else {
+ p->cnr = value;
+ }
+ npcm7xx_pwm_update_output(p);
+ break;
+
+ case A_NPCM7XX_PWM_CMR0:
+ case A_NPCM7XX_PWM_CMR1:
+ case A_NPCM7XX_PWM_CMR2:
+ case A_NPCM7XX_PWM_CMR3:
+ p = &s->pwm[npcm7xx_cmr_index(offset)];
+ if (value > NPCM7XX_MAX_CMR) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid cmr value: %u", __func__, value);
+ p->cmr = NPCM7XX_MAX_CMR;
+ } else {
+ p->cmr = value;
+ }
+ npcm7xx_pwm_update_output(p);
+ break;
+
+ case A_NPCM7XX_PWM_PDR0:
+ case A_NPCM7XX_PWM_PDR1:
+ case A_NPCM7XX_PWM_PDR2:
+ case A_NPCM7XX_PWM_PDR3:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
+ __func__, offset);
+ break;
+
+ case A_NPCM7XX_PWM_PWDR0:
+ case A_NPCM7XX_PWM_PWDR1:
+ case A_NPCM7XX_PWM_PWDR2:
+ case A_NPCM7XX_PWM_PWDR3:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n",
+ __func__, offset);
+ break;
+
+ case A_NPCM7XX_PWM_PPR:
+ npcm7xx_pwm_write_ppr(s, value);
+ break;
+
+ case A_NPCM7XX_PWM_CSR:
+ npcm7xx_pwm_write_csr(s, value);
+ break;
+
+ case A_NPCM7XX_PWM_PCR:
+ npcm7xx_pwm_write_pcr(s, value);
+ break;
+
+ case A_NPCM7XX_PWM_PIER:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n",
+ __func__, offset);
+ break;
+
+ case A_NPCM7XX_PWM_PIIR:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n",
+ __func__, offset);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
+ __func__, offset);
+ break;
+ }
+}
+
+static const struct MemoryRegionOps npcm7xx_pwm_ops = {
+ .read = npcm7xx_pwm_read,
+ .write = npcm7xx_pwm_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void npcm7xx_pwm_enter_reset(Object *obj, ResetType type)
+{
+ NPCM7xxPWMState *s = NPCM7XX_PWM(obj);
+ int i;
+
+ for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) {
+ NPCM7xxPWM *p = &s->pwm[i];
+
+ p->cnr = 0x00000000;
+ p->cmr = 0x00000000;
+ p->pdr = 0x00000000;
+ p->pwdr = 0x00000000;
+ }
+
+ s->ppr = 0x00000000;
+ s->csr = 0x00000000;
+ s->pcr = 0x00000000;
+ s->pier = 0x00000000;
+ s->piir = 0x00000000;
+}
+
+static void npcm7xx_pwm_hold_reset(Object *obj)
+{
+ NPCM7xxPWMState *s = NPCM7XX_PWM(obj);
+ int i;
+
+ for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) {
+ qemu_irq_lower(s->pwm[i].irq);
+ }
+}
+
+static void npcm7xx_pwm_init(Object *obj)
+{
+ NPCM7xxPWMState *s = NPCM7XX_PWM(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ int i;
+
+ QEMU_BUILD_BUG_ON(ARRAY_SIZE(s->pwm) != NPCM7XX_PWM_PER_MODULE);
+ for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) {
+ NPCM7xxPWM *p = &s->pwm[i];
+ p->module = s;
+ p->index = i;
+ sysbus_init_irq(sbd, &p->irq);
+ }
+
+ memory_region_init_io(&s->iomem, obj, &npcm7xx_pwm_ops, s,
+ TYPE_NPCM7XX_PWM, 4 * KiB);
+ sysbus_init_mmio(sbd, &s->iomem);
+ s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL, 0);
+
+ for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
+ object_property_add_uint32_ptr(obj, "freq[*]",
+ &s->pwm[i].freq, OBJ_PROP_FLAG_READ);
+ object_property_add_uint32_ptr(obj, "duty[*]",
+ &s->pwm[i].duty, OBJ_PROP_FLAG_READ);
+ }
+ qdev_init_gpio_out_named(DEVICE(s), s->duty_gpio_out,
+ "duty-gpio-out", NPCM7XX_PWM_PER_MODULE);
+}
+
+static const VMStateDescription vmstate_npcm7xx_pwm = {
+ .name = "npcm7xx-pwm",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(running, NPCM7xxPWM),
+ VMSTATE_BOOL(inverted, NPCM7xxPWM),
+ VMSTATE_UINT8(index, NPCM7xxPWM),
+ VMSTATE_UINT32(cnr, NPCM7xxPWM),
+ VMSTATE_UINT32(cmr, NPCM7xxPWM),
+ VMSTATE_UINT32(pdr, NPCM7xxPWM),
+ VMSTATE_UINT32(pwdr, NPCM7xxPWM),
+ VMSTATE_UINT32(freq, NPCM7xxPWM),
+ VMSTATE_UINT32(duty, NPCM7xxPWM),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static const VMStateDescription vmstate_npcm7xx_pwm_module = {
+ .name = "npcm7xx-pwm-module",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_CLOCK(clock, NPCM7xxPWMState),
+ VMSTATE_STRUCT_ARRAY(pwm, NPCM7xxPWMState,
+ NPCM7XX_PWM_PER_MODULE, 0, vmstate_npcm7xx_pwm,
+ NPCM7xxPWM),
+ VMSTATE_UINT32(ppr, NPCM7xxPWMState),
+ VMSTATE_UINT32(csr, NPCM7xxPWMState),
+ VMSTATE_UINT32(pcr, NPCM7xxPWMState),
+ VMSTATE_UINT32(pier, NPCM7xxPWMState),
+ VMSTATE_UINT32(piir, NPCM7xxPWMState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void npcm7xx_pwm_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "NPCM7xx PWM Controller";
+ dc->vmsd = &vmstate_npcm7xx_pwm_module;
+ rc->phases.enter = npcm7xx_pwm_enter_reset;
+ rc->phases.hold = npcm7xx_pwm_hold_reset;
+}
+
+static const TypeInfo npcm7xx_pwm_info = {
+ .name = TYPE_NPCM7XX_PWM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NPCM7xxPWMState),
+ .class_init = npcm7xx_pwm_class_init,
+ .instance_init = npcm7xx_pwm_init,
+};
+
+static void npcm7xx_pwm_register_type(void)
+{
+ type_register_static(&npcm7xx_pwm_info);
+}
+type_init(npcm7xx_pwm_register_type);
diff --git a/hw/misc/npcm7xx_rng.c b/hw/misc/npcm7xx_rng.c
new file mode 100644
index 000000000..b01df7cdb
--- /dev/null
+++ b/hw/misc/npcm7xx_rng.c
@@ -0,0 +1,180 @@
+/*
+ * Nuvoton NPCM7xx Random Number Generator.
+ *
+ * Copyright 2020 Google LLC
+ *
+ * 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.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/misc/npcm7xx_rng.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/guest-random.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+
+#include "trace.h"
+
+#define NPCM7XX_RNG_REGS_SIZE (4 * KiB)
+
+#define NPCM7XX_RNGCS (0x00)
+#define NPCM7XX_RNGCS_CLKP(rv) extract32(rv, 2, 4)
+#define NPCM7XX_RNGCS_DVALID BIT(1)
+#define NPCM7XX_RNGCS_RNGE BIT(0)
+
+#define NPCM7XX_RNGD (0x04)
+#define NPCM7XX_RNGMODE (0x08)
+#define NPCM7XX_RNGMODE_NORMAL (0x02)
+
+static bool npcm7xx_rng_is_enabled(NPCM7xxRNGState *s)
+{
+ return (s->rngcs & NPCM7XX_RNGCS_RNGE) &&
+ (s->rngmode == NPCM7XX_RNGMODE_NORMAL);
+}
+
+static uint64_t npcm7xx_rng_read(void *opaque, hwaddr offset, unsigned size)
+{
+ NPCM7xxRNGState *s = opaque;
+ uint64_t value = 0;
+
+ switch (offset) {
+ case NPCM7XX_RNGCS:
+ /*
+ * If the RNG is enabled, but we don't have any valid random data, try
+ * obtaining some and update the DVALID bit accordingly.
+ */
+ if (!npcm7xx_rng_is_enabled(s)) {
+ s->rngcs &= ~NPCM7XX_RNGCS_DVALID;
+ } else if (!(s->rngcs & NPCM7XX_RNGCS_DVALID)) {
+ uint8_t byte = 0;
+
+ if (qemu_guest_getrandom(&byte, sizeof(byte), NULL) == 0) {
+ s->rngd = byte;
+ s->rngcs |= NPCM7XX_RNGCS_DVALID;
+ }
+ }
+ value = s->rngcs;
+ break;
+ case NPCM7XX_RNGD:
+ if (npcm7xx_rng_is_enabled(s) && s->rngcs & NPCM7XX_RNGCS_DVALID) {
+ s->rngcs &= ~NPCM7XX_RNGCS_DVALID;
+ value = s->rngd;
+ s->rngd = 0;
+ }
+ break;
+ case NPCM7XX_RNGMODE:
+ value = s->rngmode;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: read from invalid offset 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, offset);
+ break;
+ }
+
+ trace_npcm7xx_rng_read(offset, value, size);
+
+ return value;
+}
+
+static void npcm7xx_rng_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ NPCM7xxRNGState *s = opaque;
+
+ trace_npcm7xx_rng_write(offset, value, size);
+
+ switch (offset) {
+ case NPCM7XX_RNGCS:
+ s->rngcs &= NPCM7XX_RNGCS_DVALID;
+ s->rngcs |= value & ~NPCM7XX_RNGCS_DVALID;
+ break;
+ case NPCM7XX_RNGD:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to read-only register @ 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, offset);
+ break;
+ case NPCM7XX_RNGMODE:
+ s->rngmode = value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to invalid offset 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps npcm7xx_rng_ops = {
+ .read = npcm7xx_rng_read,
+ .write = npcm7xx_rng_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void npcm7xx_rng_enter_reset(Object *obj, ResetType type)
+{
+ NPCM7xxRNGState *s = NPCM7XX_RNG(obj);
+
+ s->rngcs = 0;
+ s->rngd = 0;
+ s->rngmode = 0;
+}
+
+static void npcm7xx_rng_init(Object *obj)
+{
+ NPCM7xxRNGState *s = NPCM7XX_RNG(obj);
+
+ memory_region_init_io(&s->iomem, obj, &npcm7xx_rng_ops, s, "regs",
+ NPCM7XX_RNG_REGS_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+}
+
+static const VMStateDescription vmstate_npcm7xx_rng = {
+ .name = "npcm7xx-rng",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(rngcs, NPCM7xxRNGState),
+ VMSTATE_UINT8(rngd, NPCM7xxRNGState),
+ VMSTATE_UINT8(rngmode, NPCM7xxRNGState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void npcm7xx_rng_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "NPCM7xx Random Number Generator";
+ dc->vmsd = &vmstate_npcm7xx_rng;
+ rc->phases.enter = npcm7xx_rng_enter_reset;
+}
+
+static const TypeInfo npcm7xx_rng_types[] = {
+ {
+ .name = TYPE_NPCM7XX_RNG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NPCM7xxRNGState),
+ .class_init = npcm7xx_rng_class_init,
+ .instance_init = npcm7xx_rng_init,
+ },
+};
+DEFINE_TYPES(npcm7xx_rng_types);
diff --git a/hw/misc/nrf51_rng.c b/hw/misc/nrf51_rng.c
new file mode 100644
index 000000000..fc86e1b69
--- /dev/null
+++ b/hw/misc/nrf51_rng.c
@@ -0,0 +1,266 @@
+/*
+ * nRF51 Random Number Generator
+ *
+ * Reference Manual: http://infocenter.nordicsemi.com/pdf/nRF51_RM_v3.0.1.pdf
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "hw/arm/nrf51.h"
+#include "hw/irq.h"
+#include "hw/misc/nrf51_rng.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/guest-random.h"
+
+static void update_irq(NRF51RNGState *s)
+{
+ bool irq = s->interrupt_enabled && s->event_valrdy;
+ qemu_set_irq(s->irq, irq);
+}
+
+static uint64_t rng_read(void *opaque, hwaddr offset, unsigned int size)
+{
+ NRF51RNGState *s = NRF51_RNG(opaque);
+ uint64_t r = 0;
+
+ switch (offset) {
+ case NRF51_RNG_EVENT_VALRDY:
+ r = s->event_valrdy;
+ break;
+ case NRF51_RNG_REG_SHORTS:
+ r = s->shortcut_stop_on_valrdy;
+ break;
+ case NRF51_RNG_REG_INTEN:
+ case NRF51_RNG_REG_INTENSET:
+ case NRF51_RNG_REG_INTENCLR:
+ r = s->interrupt_enabled;
+ break;
+ case NRF51_RNG_REG_CONFIG:
+ r = s->filter_enabled;
+ break;
+ case NRF51_RNG_REG_VALUE:
+ r = s->value;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ }
+
+ return r;
+}
+
+static int64_t calc_next_timeout(NRF51RNGState *s)
+{
+ int64_t timeout = qemu_clock_get_us(QEMU_CLOCK_VIRTUAL);
+ if (s->filter_enabled) {
+ timeout += s->period_filtered_us;
+ } else {
+ timeout += s->period_unfiltered_us;
+ }
+
+ return timeout;
+}
+
+
+static void rng_update_timer(NRF51RNGState *s)
+{
+ if (s->active) {
+ timer_mod(&s->timer, calc_next_timeout(s));
+ } else {
+ timer_del(&s->timer);
+ }
+}
+
+
+static void rng_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned int size)
+{
+ NRF51RNGState *s = NRF51_RNG(opaque);
+
+ switch (offset) {
+ case NRF51_RNG_TASK_START:
+ if (value == NRF51_TRIGGER_TASK) {
+ s->active = 1;
+ rng_update_timer(s);
+ }
+ break;
+ case NRF51_RNG_TASK_STOP:
+ if (value == NRF51_TRIGGER_TASK) {
+ s->active = 0;
+ rng_update_timer(s);
+ }
+ break;
+ case NRF51_RNG_EVENT_VALRDY:
+ if (value == NRF51_EVENT_CLEAR) {
+ s->event_valrdy = 0;
+ }
+ break;
+ case NRF51_RNG_REG_SHORTS:
+ s->shortcut_stop_on_valrdy =
+ (value & BIT_MASK(NRF51_RNG_REG_SHORTS_VALRDY_STOP)) ? 1 : 0;
+ break;
+ case NRF51_RNG_REG_INTEN:
+ s->interrupt_enabled =
+ (value & BIT_MASK(NRF51_RNG_REG_INTEN_VALRDY)) ? 1 : 0;
+ break;
+ case NRF51_RNG_REG_INTENSET:
+ if (value & BIT_MASK(NRF51_RNG_REG_INTEN_VALRDY)) {
+ s->interrupt_enabled = 1;
+ }
+ break;
+ case NRF51_RNG_REG_INTENCLR:
+ if (value & BIT_MASK(NRF51_RNG_REG_INTEN_VALRDY)) {
+ s->interrupt_enabled = 0;
+ }
+ break;
+ case NRF51_RNG_REG_CONFIG:
+ s->filter_enabled =
+ (value & BIT_MASK(NRF51_RNG_REG_CONFIG_DECEN)) ? 1 : 0;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ }
+
+ update_irq(s);
+}
+
+static const MemoryRegionOps rng_ops = {
+ .read = rng_read,
+ .write = rng_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4
+};
+
+static void nrf51_rng_timer_expire(void *opaque)
+{
+ NRF51RNGState *s = NRF51_RNG(opaque);
+
+ qemu_guest_getrandom_nofail(&s->value, 1);
+
+ s->event_valrdy = 1;
+ qemu_set_irq(s->eep_valrdy, 1);
+
+ if (s->shortcut_stop_on_valrdy) {
+ s->active = 0;
+ }
+
+ rng_update_timer(s);
+ update_irq(s);
+}
+
+static void nrf51_rng_tep_start(void *opaque, int n, int level)
+{
+ NRF51RNGState *s = NRF51_RNG(opaque);
+
+ if (level) {
+ s->active = 1;
+ rng_update_timer(s);
+ }
+}
+
+static void nrf51_rng_tep_stop(void *opaque, int n, int level)
+{
+ NRF51RNGState *s = NRF51_RNG(opaque);
+
+ if (level) {
+ s->active = 0;
+ rng_update_timer(s);
+ }
+}
+
+
+static void nrf51_rng_init(Object *obj)
+{
+ NRF51RNGState *s = NRF51_RNG(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->mmio, obj, &rng_ops, s,
+ TYPE_NRF51_RNG, NRF51_RNG_SIZE);
+ sysbus_init_mmio(sbd, &s->mmio);
+
+ timer_init_us(&s->timer, QEMU_CLOCK_VIRTUAL, nrf51_rng_timer_expire, s);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ /* Tasks */
+ qdev_init_gpio_in_named(DEVICE(s), nrf51_rng_tep_start, "tep_start", 1);
+ qdev_init_gpio_in_named(DEVICE(s), nrf51_rng_tep_stop, "tep_stop", 1);
+
+ /* Events */
+ qdev_init_gpio_out_named(DEVICE(s), &s->eep_valrdy, "eep_valrdy", 1);
+}
+
+static void nrf51_rng_reset(DeviceState *dev)
+{
+ NRF51RNGState *s = NRF51_RNG(dev);
+
+ s->value = 0;
+ s->active = 0;
+ s->event_valrdy = 0;
+ s->shortcut_stop_on_valrdy = 0;
+ s->interrupt_enabled = 0;
+ s->filter_enabled = 0;
+
+ rng_update_timer(s);
+}
+
+
+static Property nrf51_rng_properties[] = {
+ DEFINE_PROP_UINT16("period_unfiltered_us", NRF51RNGState,
+ period_unfiltered_us, 167),
+ DEFINE_PROP_UINT16("period_filtered_us", NRF51RNGState,
+ period_filtered_us, 660),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_rng = {
+ .name = "nrf51_soc.rng",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(active, NRF51RNGState),
+ VMSTATE_UINT32(event_valrdy, NRF51RNGState),
+ VMSTATE_UINT32(shortcut_stop_on_valrdy, NRF51RNGState),
+ VMSTATE_UINT32(interrupt_enabled, NRF51RNGState),
+ VMSTATE_UINT32(filter_enabled, NRF51RNGState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void nrf51_rng_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, nrf51_rng_properties);
+ dc->vmsd = &vmstate_rng;
+ dc->reset = nrf51_rng_reset;
+}
+
+static const TypeInfo nrf51_rng_info = {
+ .name = TYPE_NRF51_RNG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NRF51RNGState),
+ .instance_init = nrf51_rng_init,
+ .class_init = nrf51_rng_class_init
+};
+
+static void nrf51_rng_register_types(void)
+{
+ type_register_static(&nrf51_rng_info);
+}
+
+type_init(nrf51_rng_register_types)
diff --git a/hw/misc/omap_clk.c b/hw/misc/omap_clk.c
new file mode 100644
index 000000000..c77ca2fc7
--- /dev/null
+++ b/hw/misc/omap_clk.c
@@ -0,0 +1,1267 @@
+/*
+ * OMAP clocks.
+ *
+ * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * Clocks data comes in part from arch/arm/mach-omap1/clock.h in Linux.
+ *
+ * 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 "hw/hw.h"
+#include "hw/irq.h"
+#include "hw/arm/omap.h"
+
+struct clk {
+ const char *name;
+ const char *alias;
+ struct clk *parent;
+ struct clk *child1;
+ struct clk *sibling;
+#define ALWAYS_ENABLED (1 << 0)
+#define CLOCK_IN_OMAP310 (1 << 10)
+#define CLOCK_IN_OMAP730 (1 << 11)
+#define CLOCK_IN_OMAP1510 (1 << 12)
+#define CLOCK_IN_OMAP16XX (1 << 13)
+#define CLOCK_IN_OMAP242X (1 << 14)
+#define CLOCK_IN_OMAP243X (1 << 15)
+#define CLOCK_IN_OMAP343X (1 << 16)
+ uint32_t flags;
+ int id;
+
+ int running; /* Is currently ticking */
+ int enabled; /* Is enabled, regardless of its input clk */
+ unsigned long rate; /* Current rate (if .running) */
+ unsigned int divisor; /* Rate relative to input (if .enabled) */
+ unsigned int multiplier; /* Rate relative to input (if .enabled) */
+ qemu_irq users[16]; /* Who to notify on change */
+ int usecount; /* Automatically idle when unused */
+};
+
+static struct clk xtal_osc12m = {
+ .name = "xtal_osc_12m",
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk xtal_osc32k = {
+ .name = "xtal_osc_32k",
+ .rate = 32768,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+};
+
+static struct clk ck_ref = {
+ .name = "ck_ref",
+ .alias = "clkin",
+ .parent = &xtal_osc12m,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+/* If a dpll is disabled it becomes a bypass, child clocks don't stop */
+static struct clk dpll1 = {
+ .name = "dpll1",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk dpll2 = {
+ .name = "dpll2",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+};
+
+static struct clk dpll3 = {
+ .name = "dpll3",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+};
+
+static struct clk dpll4 = {
+ .name = "dpll4",
+ .parent = &ck_ref,
+ .multiplier = 4,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk apll = {
+ .name = "apll",
+ .parent = &ck_ref,
+ .multiplier = 48,
+ .divisor = 12,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk ck_48m = {
+ .name = "ck_48m",
+ .parent = &dpll4, /* either dpll4 or apll */
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk ck_dpll1out = {
+ .name = "ck_dpll1out",
+ .parent = &dpll1,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk sossi_ck = {
+ .name = "ck_sossi",
+ .parent = &ck_dpll1out,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk clkm1 = {
+ .name = "clkm1",
+ .alias = "ck_gen1",
+ .parent = &dpll1,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk clkm2 = {
+ .name = "clkm2",
+ .alias = "ck_gen2",
+ .parent = &dpll1,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk clkm3 = {
+ .name = "clkm3",
+ .alias = "ck_gen3",
+ .parent = &dpll1, /* either dpll1 or ck_ref */
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk arm_ck = {
+ .name = "arm_ck",
+ .alias = "mpu_ck",
+ .parent = &clkm1,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk armper_ck = {
+ .name = "armper_ck",
+ .alias = "mpuper_ck",
+ .parent = &clkm1,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk arm_gpio_ck = {
+ .name = "arm_gpio_ck",
+ .alias = "mpu_gpio_ck",
+ .parent = &clkm1,
+ .divisor = 1,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk armxor_ck = {
+ .name = "armxor_ck",
+ .alias = "mpuxor_ck",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk armtim_ck = {
+ .name = "armtim_ck",
+ .alias = "mputim_ck",
+ .parent = &ck_ref, /* either CLKIN or DPLL1 */
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk armwdt_ck = {
+ .name = "armwdt_ck",
+ .alias = "mpuwd_ck",
+ .parent = &clkm1,
+ .divisor = 14,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk arminth_ck16xx = {
+ .name = "arminth_ck",
+ .parent = &arm_ck,
+ .flags = CLOCK_IN_OMAP16XX | ALWAYS_ENABLED,
+ /* Note: On 16xx the frequency can be divided by 2 by programming
+ * ARM_CKCTL:ARM_INTHCK_SEL(14) to 1
+ *
+ * 1510 version is in TC clocks.
+ */
+};
+
+static struct clk dsp_ck = {
+ .name = "dsp_ck",
+ .parent = &clkm2,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk dspmmu_ck = {
+ .name = "dspmmu_ck",
+ .parent = &clkm2,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX |
+ ALWAYS_ENABLED,
+};
+
+static struct clk dspper_ck = {
+ .name = "dspper_ck",
+ .parent = &clkm2,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk dspxor_ck = {
+ .name = "dspxor_ck",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk dsptim_ck = {
+ .name = "dsptim_ck",
+ .parent = &ck_ref,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk tc_ck = {
+ .name = "tc_ck",
+ .parent = &clkm3,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX |
+ CLOCK_IN_OMAP730 | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk arminth_ck15xx = {
+ .name = "arminth_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+ /* Note: On 1510 the frequency follows TC_CK
+ *
+ * 16xx version is in MPU clocks.
+ */
+};
+
+static struct clk tipb_ck = {
+ /* No-idle controlled by "tc_ck" */
+ .name = "tipb_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+};
+
+static struct clk l3_ocpi_ck = {
+ /* No-idle controlled by "tc_ck" */
+ .name = "l3_ocpi_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk tc1_ck = {
+ .name = "tc1_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk tc2_ck = {
+ .name = "tc2_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk dma_ck = {
+ /* No-idle controlled by "tc_ck" */
+ .name = "dma_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk dma_lcdfree_ck = {
+ .name = "dma_lcdfree_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX | ALWAYS_ENABLED,
+};
+
+static struct clk api_ck = {
+ .name = "api_ck",
+ .alias = "mpui_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk lb_ck = {
+ .name = "lb_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk lbfree_ck = {
+ .name = "lbfree_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk hsab_ck = {
+ .name = "hsab_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk rhea1_ck = {
+ .name = "rhea1_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX | ALWAYS_ENABLED,
+};
+
+static struct clk rhea2_ck = {
+ .name = "rhea2_ck",
+ .parent = &tc_ck,
+ .flags = CLOCK_IN_OMAP16XX | ALWAYS_ENABLED,
+};
+
+static struct clk lcd_ck_16xx = {
+ .name = "lcd_ck",
+ .parent = &clkm3,
+ .flags = CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP730,
+};
+
+static struct clk lcd_ck_1510 = {
+ .name = "lcd_ck",
+ .parent = &clkm3,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk uart1_1510 = {
+ .name = "uart1_ck",
+ /* Direct from ULPD, no real parent */
+ .parent = &armper_ck, /* either armper_ck or dpll4 */
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+};
+
+static struct clk uart1_16xx = {
+ .name = "uart1_ck",
+ /* Direct from ULPD, no real parent */
+ .parent = &armper_ck,
+ .rate = 48000000,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk uart2_ck = {
+ .name = "uart2_ck",
+ /* Direct from ULPD, no real parent */
+ .parent = &armper_ck, /* either armper_ck or dpll4 */
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+ ALWAYS_ENABLED,
+};
+
+static struct clk uart3_1510 = {
+ .name = "uart3_ck",
+ /* Direct from ULPD, no real parent */
+ .parent = &armper_ck, /* either armper_ck or dpll4 */
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310 | ALWAYS_ENABLED,
+};
+
+static struct clk uart3_16xx = {
+ .name = "uart3_ck",
+ /* Direct from ULPD, no real parent */
+ .parent = &armper_ck,
+ .rate = 48000000,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk usb_clk0 = { /* 6 MHz output on W4_USB_CLK0 */
+ .name = "usb_clk0",
+ .alias = "usb.clko",
+ /* Direct from ULPD, no parent */
+ .rate = 6000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk usb_hhc_ck1510 = {
+ .name = "usb_hhc_ck",
+ /* Direct from ULPD, no parent */
+ .rate = 48000000, /* Actually 2 clocks, 12MHz and 48MHz */
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP310,
+};
+
+static struct clk usb_hhc_ck16xx = {
+ .name = "usb_hhc_ck",
+ /* Direct from ULPD, no parent */
+ .rate = 48000000,
+ /* OTG_SYSCON_2.OTG_PADEN == 0 (not 1510-compatible) */
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk usb_w2fc_mclk = {
+ .name = "usb_w2fc_mclk",
+ .alias = "usb_w2fc_ck",
+ .parent = &ck_48m,
+ .rate = 48000000,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk mclk_1510 = {
+ .name = "mclk",
+ /* Direct from ULPD, no parent. May be enabled by ext hardware. */
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510,
+};
+
+static struct clk bclk_310 = {
+ .name = "bt_mclk_out", /* Alias midi_mclk_out? */
+ .parent = &armper_ck,
+ .flags = CLOCK_IN_OMAP310,
+};
+
+static struct clk mclk_310 = {
+ .name = "com_mclk_out",
+ .parent = &armper_ck,
+ .flags = CLOCK_IN_OMAP310,
+};
+
+static struct clk mclk_16xx = {
+ .name = "mclk",
+ /* Direct from ULPD, no parent. May be enabled by ext hardware. */
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk bclk_1510 = {
+ .name = "bclk",
+ /* Direct from ULPD, no parent. May be enabled by ext hardware. */
+ .rate = 12000000,
+ .flags = CLOCK_IN_OMAP1510,
+};
+
+static struct clk bclk_16xx = {
+ .name = "bclk",
+ /* Direct from ULPD, no parent. May be enabled by ext hardware. */
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk mmc1_ck = {
+ .name = "mmc_ck",
+ .id = 1,
+ /* Functional clock is direct from ULPD, interface clock is ARMPER */
+ .parent = &armper_ck, /* either armper_ck or dpll4 */
+ .rate = 48000000,
+ .flags = CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+};
+
+static struct clk mmc2_ck = {
+ .name = "mmc_ck",
+ .id = 2,
+ /* Functional clock is direct from ULPD, interface clock is ARMPER */
+ .parent = &armper_ck,
+ .rate = 48000000,
+ .flags = CLOCK_IN_OMAP16XX,
+};
+
+static struct clk cam_mclk = {
+ .name = "cam.mclk",
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+ .rate = 12000000,
+};
+
+static struct clk cam_exclk = {
+ .name = "cam.exclk",
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+ /* Either 12M from cam.mclk or 48M from dpll4 */
+ .parent = &cam_mclk,
+};
+
+static struct clk cam_lclk = {
+ .name = "cam.lclk",
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX,
+};
+
+static struct clk i2c_fck = {
+ .name = "i2c_fck",
+ .id = 1,
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX |
+ ALWAYS_ENABLED,
+ .parent = &armxor_ck,
+};
+
+static struct clk i2c_ick = {
+ .name = "i2c_ick",
+ .id = 1,
+ .flags = CLOCK_IN_OMAP16XX | ALWAYS_ENABLED,
+ .parent = &armper_ck,
+};
+
+static struct clk clk32k = {
+ .name = "clk32-kHz",
+ .flags = CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX |
+ CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .parent = &xtal_osc32k,
+};
+
+static struct clk ref_clk = {
+ .name = "ref_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 12000000, /* 12 MHz or 13 MHz or 19.2 MHz */
+ /*.parent = sys.xtalin */
+};
+
+static struct clk apll_96m = {
+ .name = "apll_96m",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 96000000,
+ /*.parent = ref_clk */
+};
+
+static struct clk apll_54m = {
+ .name = "apll_54m",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 54000000,
+ /*.parent = ref_clk */
+};
+
+static struct clk sys_clk = {
+ .name = "sys_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 32768,
+ /*.parent = sys.xtalin */
+};
+
+static struct clk sleep_clk = {
+ .name = "sleep_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 32768,
+ /*.parent = sys.xtalin */
+};
+
+static struct clk dpll_ck = {
+ .name = "dpll",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .parent = &ref_clk,
+};
+
+static struct clk dpll_x2_ck = {
+ .name = "dpll_x2",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .parent = &ref_clk,
+};
+
+static struct clk wdt1_sys_clk = {
+ .name = "wdt1_sys_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+ .rate = 32768,
+ /*.parent = sys.xtalin */
+};
+
+static struct clk func_96m_clk = {
+ .name = "func_96m_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .divisor = 1,
+ .parent = &apll_96m,
+};
+
+static struct clk func_48m_clk = {
+ .name = "func_48m_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .divisor = 2,
+ .parent = &apll_96m,
+};
+
+static struct clk func_12m_clk = {
+ .name = "func_12m_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .divisor = 8,
+ .parent = &apll_96m,
+};
+
+static struct clk func_54m_clk = {
+ .name = "func_54m_clk",
+ .flags = CLOCK_IN_OMAP242X,
+ .divisor = 1,
+ .parent = &apll_54m,
+};
+
+static struct clk sys_clkout = {
+ .name = "clkout",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk sys_clkout2 = {
+ .name = "clkout2",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_clk = {
+ .name = "core_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &dpll_x2_ck, /* Switchable between dpll_ck and clk32k */
+};
+
+static struct clk l3_clk = {
+ .name = "l3_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk core_l4_iclk = {
+ .name = "core_l4_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &l3_clk,
+};
+
+static struct clk wu_l4_iclk = {
+ .name = "wu_l4_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &l3_clk,
+};
+
+static struct clk core_l3_iclk = {
+ .name = "core_l3_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk core_l4_usb_clk = {
+ .name = "core_l4_usb_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &l3_clk,
+};
+
+static struct clk wu_gpt1_clk = {
+ .name = "wu_gpt1_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk wu_32k_clk = {
+ .name = "wu_32k_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk uart1_fclk = {
+ .name = "uart1_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+};
+
+static struct clk uart1_iclk = {
+ .name = "uart1_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk uart2_fclk = {
+ .name = "uart2_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+};
+
+static struct clk uart2_iclk = {
+ .name = "uart2_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk uart3_fclk = {
+ .name = "uart3_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+};
+
+static struct clk uart3_iclk = {
+ .name = "uart3_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk mpu_fclk = {
+ .name = "mpu_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk mpu_iclk = {
+ .name = "mpu_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk int_m_fclk = {
+ .name = "int_m_fclk",
+ .alias = "mpu_intc_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk int_m_iclk = {
+ .name = "int_m_iclk",
+ .alias = "mpu_intc_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+};
+
+static struct clk core_gpt2_clk = {
+ .name = "core_gpt2_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt3_clk = {
+ .name = "core_gpt3_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt4_clk = {
+ .name = "core_gpt4_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt5_clk = {
+ .name = "core_gpt5_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt6_clk = {
+ .name = "core_gpt6_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt7_clk = {
+ .name = "core_gpt7_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt8_clk = {
+ .name = "core_gpt8_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt9_clk = {
+ .name = "core_gpt9_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt10_clk = {
+ .name = "core_gpt10_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt11_clk = {
+ .name = "core_gpt11_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk core_gpt12_clk = {
+ .name = "core_gpt12_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+};
+
+static struct clk mcbsp1_clk = {
+ .name = "mcbsp1_cg",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .divisor = 2,
+ .parent = &func_96m_clk,
+};
+
+static struct clk mcbsp2_clk = {
+ .name = "mcbsp2_cg",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .divisor = 2,
+ .parent = &func_96m_clk,
+};
+
+static struct clk emul_clk = {
+ .name = "emul_ck",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_54m_clk,
+};
+
+static struct clk sdma_fclk = {
+ .name = "sdma_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &l3_clk,
+};
+
+static struct clk sdma_iclk = {
+ .name = "sdma_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l3_iclk, /* core_l4_iclk for the configuration port */
+};
+
+static struct clk i2c1_fclk = {
+ .name = "i2c1.fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_12m_clk,
+ .divisor = 1,
+};
+
+static struct clk i2c1_iclk = {
+ .name = "i2c1.iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk i2c2_fclk = {
+ .name = "i2c2.fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_12m_clk,
+ .divisor = 1,
+};
+
+static struct clk i2c2_iclk = {
+ .name = "i2c2.iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk gpio_dbclk[5] = {
+ {
+ .name = "gpio1_dbclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &wu_32k_clk,
+ }, {
+ .name = "gpio2_dbclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &wu_32k_clk,
+ }, {
+ .name = "gpio3_dbclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &wu_32k_clk,
+ }, {
+ .name = "gpio4_dbclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &wu_32k_clk,
+ }, {
+ .name = "gpio5_dbclk",
+ .flags = CLOCK_IN_OMAP243X,
+ .parent = &wu_32k_clk,
+ },
+};
+
+static struct clk gpio_iclk = {
+ .name = "gpio_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &wu_l4_iclk,
+};
+
+static struct clk mmc_fck = {
+ .name = "mmc_fclk",
+ .flags = CLOCK_IN_OMAP242X,
+ .parent = &func_96m_clk,
+};
+
+static struct clk mmc_ick = {
+ .name = "mmc_iclk",
+ .flags = CLOCK_IN_OMAP242X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk spi_fclk[3] = {
+ {
+ .name = "spi1_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+ }, {
+ .name = "spi2_fclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+ }, {
+ .name = "spi3_fclk",
+ .flags = CLOCK_IN_OMAP243X,
+ .parent = &func_48m_clk,
+ },
+};
+
+static struct clk dss_clk[2] = {
+ {
+ .name = "dss_clk1",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_clk,
+ }, {
+ .name = "dss_clk2",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &sys_clk,
+ },
+};
+
+static struct clk dss_54m_clk = {
+ .name = "dss_54m_clk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &func_54m_clk,
+};
+
+static struct clk dss_l3_iclk = {
+ .name = "dss_l3_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l3_iclk,
+};
+
+static struct clk dss_l4_iclk = {
+ .name = "dss_l4_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+};
+
+static struct clk spi_iclk[3] = {
+ {
+ .name = "spi1_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+ }, {
+ .name = "spi2_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+ }, {
+ .name = "spi3_iclk",
+ .flags = CLOCK_IN_OMAP243X,
+ .parent = &core_l4_iclk,
+ },
+};
+
+static struct clk omapctrl_clk = {
+ .name = "omapctrl_iclk",
+ .flags = CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+ /* XXX Should be in WKUP domain */
+ .parent = &core_l4_iclk,
+};
+
+static struct clk *onchip_clks[] = {
+ /* OMAP 1 */
+
+ /* non-ULPD clocks */
+ &xtal_osc12m,
+ &xtal_osc32k,
+ &ck_ref,
+ &dpll1,
+ &dpll2,
+ &dpll3,
+ &dpll4,
+ &apll,
+ &ck_48m,
+ /* CK_GEN1 clocks */
+ &clkm1,
+ &ck_dpll1out,
+ &sossi_ck,
+ &arm_ck,
+ &armper_ck,
+ &arm_gpio_ck,
+ &armxor_ck,
+ &armtim_ck,
+ &armwdt_ck,
+ &arminth_ck15xx, &arminth_ck16xx,
+ /* CK_GEN2 clocks */
+ &clkm2,
+ &dsp_ck,
+ &dspmmu_ck,
+ &dspper_ck,
+ &dspxor_ck,
+ &dsptim_ck,
+ /* CK_GEN3 clocks */
+ &clkm3,
+ &tc_ck,
+ &tipb_ck,
+ &l3_ocpi_ck,
+ &tc1_ck,
+ &tc2_ck,
+ &dma_ck,
+ &dma_lcdfree_ck,
+ &api_ck,
+ &lb_ck,
+ &lbfree_ck,
+ &hsab_ck,
+ &rhea1_ck,
+ &rhea2_ck,
+ &lcd_ck_16xx,
+ &lcd_ck_1510,
+ /* ULPD clocks */
+ &uart1_1510,
+ &uart1_16xx,
+ &uart2_ck,
+ &uart3_1510,
+ &uart3_16xx,
+ &usb_clk0,
+ &usb_hhc_ck1510, &usb_hhc_ck16xx,
+ &mclk_1510, &mclk_16xx, &mclk_310,
+ &bclk_1510, &bclk_16xx, &bclk_310,
+ &mmc1_ck,
+ &mmc2_ck,
+ &cam_mclk,
+ &cam_exclk,
+ &cam_lclk,
+ &clk32k,
+ &usb_w2fc_mclk,
+ /* Virtual clocks */
+ &i2c_fck,
+ &i2c_ick,
+
+ /* OMAP 2 */
+
+ &ref_clk,
+ &apll_96m,
+ &apll_54m,
+ &sys_clk,
+ &sleep_clk,
+ &dpll_ck,
+ &dpll_x2_ck,
+ &wdt1_sys_clk,
+ &func_96m_clk,
+ &func_48m_clk,
+ &func_12m_clk,
+ &func_54m_clk,
+ &sys_clkout,
+ &sys_clkout2,
+ &core_clk,
+ &l3_clk,
+ &core_l4_iclk,
+ &wu_l4_iclk,
+ &core_l3_iclk,
+ &core_l4_usb_clk,
+ &wu_gpt1_clk,
+ &wu_32k_clk,
+ &uart1_fclk,
+ &uart1_iclk,
+ &uart2_fclk,
+ &uart2_iclk,
+ &uart3_fclk,
+ &uart3_iclk,
+ &mpu_fclk,
+ &mpu_iclk,
+ &int_m_fclk,
+ &int_m_iclk,
+ &core_gpt2_clk,
+ &core_gpt3_clk,
+ &core_gpt4_clk,
+ &core_gpt5_clk,
+ &core_gpt6_clk,
+ &core_gpt7_clk,
+ &core_gpt8_clk,
+ &core_gpt9_clk,
+ &core_gpt10_clk,
+ &core_gpt11_clk,
+ &core_gpt12_clk,
+ &mcbsp1_clk,
+ &mcbsp2_clk,
+ &emul_clk,
+ &sdma_fclk,
+ &sdma_iclk,
+ &i2c1_fclk,
+ &i2c1_iclk,
+ &i2c2_fclk,
+ &i2c2_iclk,
+ &gpio_dbclk[0],
+ &gpio_dbclk[1],
+ &gpio_dbclk[2],
+ &gpio_dbclk[3],
+ &gpio_iclk,
+ &mmc_fck,
+ &mmc_ick,
+ &spi_fclk[0],
+ &spi_iclk[0],
+ &spi_fclk[1],
+ &spi_iclk[1],
+ &spi_fclk[2],
+ &spi_iclk[2],
+ &dss_clk[0],
+ &dss_clk[1],
+ &dss_54m_clk,
+ &dss_l3_iclk,
+ &dss_l4_iclk,
+ &omapctrl_clk,
+
+ NULL
+};
+
+void omap_clk_adduser(struct clk *clk, qemu_irq user)
+{
+ qemu_irq *i;
+
+ for (i = clk->users; *i; i ++);
+ *i = user;
+}
+
+struct clk *omap_findclk(struct omap_mpu_state_s *mpu, const char *name)
+{
+ struct clk *i;
+
+ for (i = mpu->clks; i->name; i ++)
+ if (!strcmp(i->name, name) || (i->alias && !strcmp(i->alias, name)))
+ return i;
+ hw_error("%s: %s not found\n", __func__, name);
+}
+
+void omap_clk_get(struct clk *clk)
+{
+ clk->usecount ++;
+}
+
+void omap_clk_put(struct clk *clk)
+{
+ if (!(clk->usecount --))
+ hw_error("%s: %s is not in use\n", __func__, clk->name);
+}
+
+static void omap_clk_update(struct clk *clk)
+{
+ int parent, running;
+ qemu_irq *user;
+ struct clk *i;
+
+ if (clk->parent)
+ parent = clk->parent->running;
+ else
+ parent = 1;
+
+ running = parent && (clk->enabled ||
+ ((clk->flags & ALWAYS_ENABLED) && clk->usecount));
+ if (clk->running != running) {
+ clk->running = running;
+ for (user = clk->users; *user; user ++)
+ qemu_set_irq(*user, running);
+ for (i = clk->child1; i; i = i->sibling)
+ omap_clk_update(i);
+ }
+}
+
+static void omap_clk_rate_update_full(struct clk *clk, unsigned long int rate,
+ unsigned long int div, unsigned long int mult)
+{
+ struct clk *i;
+ qemu_irq *user;
+
+ clk->rate = muldiv64(rate, mult, div);
+ if (clk->running)
+ for (user = clk->users; *user; user ++)
+ qemu_irq_raise(*user);
+ for (i = clk->child1; i; i = i->sibling)
+ omap_clk_rate_update_full(i, rate,
+ div * i->divisor, mult * i->multiplier);
+}
+
+static void omap_clk_rate_update(struct clk *clk)
+{
+ struct clk *i;
+ unsigned long int div, mult = div = 1;
+
+ for (i = clk; i->parent; i = i->parent) {
+ div *= i->divisor;
+ mult *= i->multiplier;
+ }
+
+ omap_clk_rate_update_full(clk, i->rate, div, mult);
+}
+
+void omap_clk_reparent(struct clk *clk, struct clk *parent)
+{
+ struct clk **p;
+
+ if (clk->parent) {
+ for (p = &clk->parent->child1; *p != clk; p = &(*p)->sibling);
+ *p = clk->sibling;
+ }
+
+ clk->parent = parent;
+ if (parent) {
+ clk->sibling = parent->child1;
+ parent->child1 = clk;
+ omap_clk_update(clk);
+ omap_clk_rate_update(clk);
+ } else
+ clk->sibling = NULL;
+}
+
+void omap_clk_onoff(struct clk *clk, int on)
+{
+ clk->enabled = on;
+ omap_clk_update(clk);
+}
+
+void omap_clk_canidle(struct clk *clk, int can)
+{
+ if (can)
+ omap_clk_put(clk);
+ else
+ omap_clk_get(clk);
+}
+
+void omap_clk_setrate(struct clk *clk, int divide, int multiply)
+{
+ clk->divisor = divide;
+ clk->multiplier = multiply;
+ omap_clk_rate_update(clk);
+}
+
+int64_t omap_clk_getrate(omap_clk clk)
+{
+ return clk->rate;
+}
+
+void omap_clk_init(struct omap_mpu_state_s *mpu)
+{
+ struct clk **i, *j, *k;
+ int count;
+ int flag;
+
+ if (cpu_is_omap310(mpu))
+ flag = CLOCK_IN_OMAP310;
+ else if (cpu_is_omap1510(mpu))
+ flag = CLOCK_IN_OMAP1510;
+ else if (cpu_is_omap2410(mpu) || cpu_is_omap2420(mpu))
+ flag = CLOCK_IN_OMAP242X;
+ else if (cpu_is_omap2430(mpu))
+ flag = CLOCK_IN_OMAP243X;
+ else if (cpu_is_omap3430(mpu))
+ flag = CLOCK_IN_OMAP243X;
+ else
+ return;
+
+ for (i = onchip_clks, count = 0; *i; i ++)
+ if ((*i)->flags & flag)
+ count ++;
+ mpu->clks = g_new0(struct clk, count + 1);
+ for (i = onchip_clks, j = mpu->clks; *i; i ++)
+ if ((*i)->flags & flag) {
+ memcpy(j, *i, sizeof(struct clk));
+ for (k = mpu->clks; k < j; k ++)
+ if (j->parent && !strcmp(j->parent->name, k->name)) {
+ j->parent = k;
+ j->sibling = k->child1;
+ k->child1 = j;
+ } else if (k->parent && !strcmp(k->parent->name, j->name)) {
+ k->parent = j;
+ k->sibling = j->child1;
+ j->child1 = k;
+ }
+ j->divisor = j->divisor ?: 1;
+ j->multiplier = j->multiplier ?: 1;
+ j ++;
+ }
+ for (j = mpu->clks; count --; j ++) {
+ omap_clk_update(j);
+ omap_clk_rate_update(j);
+ }
+}
diff --git a/hw/misc/omap_gpmc.c b/hw/misc/omap_gpmc.c
new file mode 100644
index 000000000..10de7a552
--- /dev/null
+++ b/hw/misc/omap_gpmc.c
@@ -0,0 +1,898 @@
+/*
+ * TI OMAP general purpose memory controller emulation.
+ *
+ * Copyright (C) 2007-2009 Nokia Corporation
+ * Original code written by Andrzej Zaborowski <andrew@openedhand.com>
+ * Enhancements for OMAP3 and NAND support written by Juha Riihimäki
+ *
+ * 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 or
+ * (at your option) any later version of the License.
+ *
+ * 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 "hw/irq.h"
+#include "hw/block/flash.h"
+#include "hw/arm/omap.h"
+#include "exec/memory.h"
+#include "exec/address-spaces.h"
+
+/* General-Purpose Memory Controller */
+struct omap_gpmc_s {
+ qemu_irq irq;
+ qemu_irq drq;
+ MemoryRegion iomem;
+ int accept_256;
+
+ uint8_t revision;
+ uint8_t sysconfig;
+ uint16_t irqst;
+ uint16_t irqen;
+ uint16_t lastirq;
+ uint16_t timeout;
+ uint16_t config;
+ struct omap_gpmc_cs_file_s {
+ uint32_t config[7];
+ MemoryRegion *iomem;
+ MemoryRegion container;
+ MemoryRegion nandiomem;
+ DeviceState *dev;
+ } cs_file[8];
+ int ecc_cs;
+ int ecc_ptr;
+ uint32_t ecc_cfg;
+ ECCState ecc[9];
+ struct prefetch {
+ uint32_t config1; /* GPMC_PREFETCH_CONFIG1 */
+ uint32_t transfercount; /* GPMC_PREFETCH_CONFIG2:TRANSFERCOUNT */
+ int startengine; /* GPMC_PREFETCH_CONTROL:STARTENGINE */
+ int fifopointer; /* GPMC_PREFETCH_STATUS:FIFOPOINTER */
+ int count; /* GPMC_PREFETCH_STATUS:COUNTVALUE */
+ MemoryRegion iomem;
+ uint8_t fifo[64];
+ } prefetch;
+};
+
+#define OMAP_GPMC_8BIT 0
+#define OMAP_GPMC_16BIT 1
+#define OMAP_GPMC_NOR 0
+#define OMAP_GPMC_NAND 2
+
+static int omap_gpmc_devtype(struct omap_gpmc_cs_file_s *f)
+{
+ return (f->config[0] >> 10) & 3;
+}
+
+static int omap_gpmc_devsize(struct omap_gpmc_cs_file_s *f)
+{
+ /* devsize field is really 2 bits but we ignore the high
+ * bit to ensure consistent behaviour if the guest sets
+ * it (values 2 and 3 are reserved in the TRM)
+ */
+ return (f->config[0] >> 12) & 1;
+}
+
+/* Extract the chip-select value from the prefetch config1 register */
+static int prefetch_cs(uint32_t config1)
+{
+ return (config1 >> 24) & 7;
+}
+
+static int prefetch_threshold(uint32_t config1)
+{
+ return (config1 >> 8) & 0x7f;
+}
+
+static void omap_gpmc_int_update(struct omap_gpmc_s *s)
+{
+ /* The TRM is a bit unclear, but it seems to say that
+ * the TERMINALCOUNTSTATUS bit is set only on the
+ * transition when the prefetch engine goes from
+ * active to inactive, whereas the FIFOEVENTSTATUS
+ * bit is held high as long as the fifo has at
+ * least THRESHOLD bytes available.
+ * So we do the latter here, but TERMINALCOUNTSTATUS
+ * is set elsewhere.
+ */
+ if (s->prefetch.fifopointer >= prefetch_threshold(s->prefetch.config1)) {
+ s->irqst |= 1;
+ }
+ if ((s->irqen & s->irqst) != s->lastirq) {
+ s->lastirq = s->irqen & s->irqst;
+ qemu_set_irq(s->irq, s->lastirq);
+ }
+}
+
+static void omap_gpmc_dma_update(struct omap_gpmc_s *s, int value)
+{
+ if (s->prefetch.config1 & 4) {
+ qemu_set_irq(s->drq, value);
+ }
+}
+
+/* Access functions for when a NAND-like device is mapped into memory:
+ * all addresses in the region behave like accesses to the relevant
+ * GPMC_NAND_DATA_i register (which is actually implemented to call these)
+ */
+static uint64_t omap_nand_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_gpmc_cs_file_s *f = (struct omap_gpmc_cs_file_s *)opaque;
+ uint64_t v;
+ nand_setpins(f->dev, 0, 0, 0, 1, 0);
+ switch (omap_gpmc_devsize(f)) {
+ case OMAP_GPMC_8BIT:
+ v = nand_getio(f->dev);
+ if (size == 1) {
+ return v;
+ }
+ v |= (nand_getio(f->dev) << 8);
+ if (size == 2) {
+ return v;
+ }
+ v |= (nand_getio(f->dev) << 16);
+ v |= (nand_getio(f->dev) << 24);
+ return v;
+ case OMAP_GPMC_16BIT:
+ v = nand_getio(f->dev);
+ if (size == 1) {
+ /* 8 bit read from 16 bit device : probably a guest bug */
+ return v & 0xff;
+ }
+ if (size == 2) {
+ return v;
+ }
+ v |= (nand_getio(f->dev) << 16);
+ return v;
+ default:
+ abort();
+ }
+}
+
+static void omap_nand_setio(DeviceState *dev, uint64_t value,
+ int nandsize, int size)
+{
+ /* Write the specified value to the NAND device, respecting
+ * both size of the NAND device and size of the write access.
+ */
+ switch (nandsize) {
+ case OMAP_GPMC_8BIT:
+ switch (size) {
+ case 1:
+ nand_setio(dev, value & 0xff);
+ break;
+ case 2:
+ nand_setio(dev, value & 0xff);
+ nand_setio(dev, (value >> 8) & 0xff);
+ break;
+ case 4:
+ default:
+ nand_setio(dev, value & 0xff);
+ nand_setio(dev, (value >> 8) & 0xff);
+ nand_setio(dev, (value >> 16) & 0xff);
+ nand_setio(dev, (value >> 24) & 0xff);
+ break;
+ }
+ break;
+ case OMAP_GPMC_16BIT:
+ switch (size) {
+ case 1:
+ /* writing to a 16bit device with 8bit access is probably a guest
+ * bug; pass the value through anyway.
+ */
+ case 2:
+ nand_setio(dev, value & 0xffff);
+ break;
+ case 4:
+ default:
+ nand_setio(dev, value & 0xffff);
+ nand_setio(dev, (value >> 16) & 0xffff);
+ break;
+ }
+ break;
+ }
+}
+
+static void omap_nand_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_gpmc_cs_file_s *f = (struct omap_gpmc_cs_file_s *)opaque;
+ nand_setpins(f->dev, 0, 0, 0, 1, 0);
+ omap_nand_setio(f->dev, value, omap_gpmc_devsize(f), size);
+}
+
+static const MemoryRegionOps omap_nand_ops = {
+ .read = omap_nand_read,
+ .write = omap_nand_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void fill_prefetch_fifo(struct omap_gpmc_s *s)
+{
+ /* Fill the prefetch FIFO by reading data from NAND.
+ * We do this synchronously, unlike the hardware which
+ * will do this asynchronously. We refill when the
+ * FIFO has THRESHOLD bytes free, and we always refill
+ * as much data as possible starting at the top end
+ * of the FIFO.
+ * (We have to refill at THRESHOLD rather than waiting
+ * for the FIFO to empty to allow for the case where
+ * the FIFO size isn't an exact multiple of THRESHOLD
+ * and we're doing DMA transfers.)
+ * This means we never need to handle wrap-around in
+ * the fifo-reading code, and the next byte of data
+ * to read is always fifo[63 - fifopointer].
+ */
+ int fptr;
+ int cs = prefetch_cs(s->prefetch.config1);
+ int is16bit = (((s->cs_file[cs].config[0] >> 12) & 3) != 0);
+ int bytes;
+ /* Don't believe the bit of the OMAP TRM that says that COUNTVALUE
+ * and TRANSFERCOUNT are in units of 16 bit words for 16 bit NAND.
+ * Instead believe the bit that says it is always a byte count.
+ */
+ bytes = 64 - s->prefetch.fifopointer;
+ if (bytes > s->prefetch.count) {
+ bytes = s->prefetch.count;
+ }
+ if (is16bit) {
+ bytes &= ~1;
+ }
+
+ s->prefetch.count -= bytes;
+ s->prefetch.fifopointer += bytes;
+ fptr = 64 - s->prefetch.fifopointer;
+ /* Move the existing data in the FIFO so it sits just
+ * before what we're about to read in
+ */
+ while (fptr < (64 - bytes)) {
+ s->prefetch.fifo[fptr] = s->prefetch.fifo[fptr + bytes];
+ fptr++;
+ }
+ while (fptr < 64) {
+ if (is16bit) {
+ uint32_t v = omap_nand_read(&s->cs_file[cs], 0, 2);
+ s->prefetch.fifo[fptr++] = v & 0xff;
+ s->prefetch.fifo[fptr++] = (v >> 8) & 0xff;
+ } else {
+ s->prefetch.fifo[fptr++] = omap_nand_read(&s->cs_file[cs], 0, 1);
+ }
+ }
+ if (s->prefetch.startengine && (s->prefetch.count == 0)) {
+ /* This was the final transfer: raise TERMINALCOUNTSTATUS */
+ s->irqst |= 2;
+ s->prefetch.startengine = 0;
+ }
+ /* If there are any bytes in the FIFO at this point then
+ * we must raise a DMA request (either this is a final part
+ * transfer, or we filled the FIFO in which case we certainly
+ * have THRESHOLD bytes available)
+ */
+ if (s->prefetch.fifopointer != 0) {
+ omap_gpmc_dma_update(s, 1);
+ }
+ omap_gpmc_int_update(s);
+}
+
+/* Access functions for a NAND-like device when the prefetch/postwrite
+ * engine is enabled -- all addresses in the region behave alike:
+ * data is read or written to the FIFO.
+ */
+static uint64_t omap_gpmc_prefetch_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque;
+ uint32_t data;
+ if (s->prefetch.config1 & 1) {
+ /* The TRM doesn't define the behaviour if you read from the
+ * FIFO when the prefetch engine is in write mode. We choose
+ * to always return zero.
+ */
+ return 0;
+ }
+ /* Note that trying to read an empty fifo repeats the last byte */
+ if (s->prefetch.fifopointer) {
+ s->prefetch.fifopointer--;
+ }
+ data = s->prefetch.fifo[63 - s->prefetch.fifopointer];
+ if (s->prefetch.fifopointer ==
+ (64 - prefetch_threshold(s->prefetch.config1))) {
+ /* We've drained THRESHOLD bytes now. So deassert the
+ * DMA request, then refill the FIFO (which will probably
+ * assert it again.)
+ */
+ omap_gpmc_dma_update(s, 0);
+ fill_prefetch_fifo(s);
+ }
+ omap_gpmc_int_update(s);
+ return data;
+}
+
+static void omap_gpmc_prefetch_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque;
+ int cs = prefetch_cs(s->prefetch.config1);
+ if ((s->prefetch.config1 & 1) == 0) {
+ /* The TRM doesn't define the behaviour of writing to the
+ * FIFO when the prefetch engine is in read mode. We
+ * choose to ignore the write.
+ */
+ return;
+ }
+ if (s->prefetch.count == 0) {
+ /* The TRM doesn't define the behaviour of writing to the
+ * FIFO if the transfer is complete. We choose to ignore.
+ */
+ return;
+ }
+ /* The only reason we do any data buffering in postwrite
+ * mode is if we are talking to a 16 bit NAND device, in
+ * which case we need to buffer the first byte of the
+ * 16 bit word until the other byte arrives.
+ */
+ int is16bit = (((s->cs_file[cs].config[0] >> 12) & 3) != 0);
+ if (is16bit) {
+ /* fifopointer alternates between 64 (waiting for first
+ * byte of word) and 63 (waiting for second byte)
+ */
+ if (s->prefetch.fifopointer == 64) {
+ s->prefetch.fifo[0] = value;
+ s->prefetch.fifopointer--;
+ } else {
+ value = (value << 8) | s->prefetch.fifo[0];
+ omap_nand_write(&s->cs_file[cs], 0, value, 2);
+ s->prefetch.count--;
+ s->prefetch.fifopointer = 64;
+ }
+ } else {
+ /* Just write the byte : fifopointer remains 64 at all times */
+ omap_nand_write(&s->cs_file[cs], 0, value, 1);
+ s->prefetch.count--;
+ }
+ if (s->prefetch.count == 0) {
+ /* Final transfer: raise TERMINALCOUNTSTATUS */
+ s->irqst |= 2;
+ s->prefetch.startengine = 0;
+ }
+ omap_gpmc_int_update(s);
+}
+
+static const MemoryRegionOps omap_prefetch_ops = {
+ .read = omap_gpmc_prefetch_read,
+ .write = omap_gpmc_prefetch_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+};
+
+static MemoryRegion *omap_gpmc_cs_memregion(struct omap_gpmc_s *s, int cs)
+{
+ /* Return the MemoryRegion* to map/unmap for this chipselect */
+ struct omap_gpmc_cs_file_s *f = &s->cs_file[cs];
+ if (omap_gpmc_devtype(f) == OMAP_GPMC_NOR) {
+ return f->iomem;
+ }
+ if ((s->prefetch.config1 & 0x80) &&
+ (prefetch_cs(s->prefetch.config1) == cs)) {
+ /* The prefetch engine is enabled for this CS: map the FIFO */
+ return &s->prefetch.iomem;
+ }
+ return &f->nandiomem;
+}
+
+static void omap_gpmc_cs_map(struct omap_gpmc_s *s, int cs)
+{
+ struct omap_gpmc_cs_file_s *f = &s->cs_file[cs];
+ uint32_t mask = (f->config[6] >> 8) & 0xf;
+ uint32_t base = f->config[6] & 0x3f;
+ uint32_t size;
+
+ if (!f->iomem && !f->dev) {
+ return;
+ }
+
+ if (!(f->config[6] & (1 << 6))) {
+ /* Do nothing unless CSVALID */
+ return;
+ }
+
+ /* TODO: check for overlapping regions and report access errors */
+ if (mask != 0x8 && mask != 0xc && mask != 0xe && mask != 0xf
+ && !(s->accept_256 && !mask)) {
+ fprintf(stderr, "%s: invalid chip-select mask address (0x%x)\n",
+ __func__, mask);
+ }
+
+ base <<= 24;
+ size = (0x0fffffff & ~(mask << 24)) + 1;
+ /* TODO: rather than setting the size of the mapping (which should be
+ * constant), the mask should cause wrapping of the address space, so
+ * that the same memory becomes accessible at every <i>size</i> bytes
+ * starting from <i>base</i>. */
+ memory_region_init(&f->container, NULL, "omap-gpmc-file", size);
+ memory_region_add_subregion(&f->container, 0,
+ omap_gpmc_cs_memregion(s, cs));
+ memory_region_add_subregion(get_system_memory(), base,
+ &f->container);
+}
+
+static void omap_gpmc_cs_unmap(struct omap_gpmc_s *s, int cs)
+{
+ struct omap_gpmc_cs_file_s *f = &s->cs_file[cs];
+ if (!(f->config[6] & (1 << 6))) {
+ /* Do nothing unless CSVALID */
+ return;
+ }
+ if (!f->iomem && !f->dev) {
+ return;
+ }
+ memory_region_del_subregion(get_system_memory(), &f->container);
+ memory_region_del_subregion(&f->container, omap_gpmc_cs_memregion(s, cs));
+ object_unparent(OBJECT(&f->container));
+}
+
+void omap_gpmc_reset(struct omap_gpmc_s *s)
+{
+ int i;
+
+ s->sysconfig = 0;
+ s->irqst = 0;
+ s->irqen = 0;
+ omap_gpmc_int_update(s);
+ for (i = 0; i < 8; i++) {
+ /* This has to happen before we change any of the config
+ * used to determine which memory regions are mapped or unmapped.
+ */
+ omap_gpmc_cs_unmap(s, i);
+ }
+ s->timeout = 0;
+ s->config = 0xa00;
+ s->prefetch.config1 = 0x00004000;
+ s->prefetch.transfercount = 0x00000000;
+ s->prefetch.startengine = 0;
+ s->prefetch.fifopointer = 0;
+ s->prefetch.count = 0;
+ for (i = 0; i < 8; i ++) {
+ s->cs_file[i].config[1] = 0x101001;
+ s->cs_file[i].config[2] = 0x020201;
+ s->cs_file[i].config[3] = 0x10031003;
+ s->cs_file[i].config[4] = 0x10f1111;
+ s->cs_file[i].config[5] = 0;
+ s->cs_file[i].config[6] = 0xf00;
+ /* In theory we could probe attached devices for some CFG1
+ * bits here, but we just retain them across resets as they
+ * were set initially by omap_gpmc_attach().
+ */
+ if (i == 0) {
+ s->cs_file[i].config[0] &= 0x00433e00;
+ s->cs_file[i].config[6] |= 1 << 6; /* CSVALID */
+ omap_gpmc_cs_map(s, i);
+ } else {
+ s->cs_file[i].config[0] &= 0x00403c00;
+ }
+ }
+ s->ecc_cs = 0;
+ s->ecc_ptr = 0;
+ s->ecc_cfg = 0x3fcff000;
+ for (i = 0; i < 9; i ++)
+ ecc_reset(&s->ecc[i]);
+}
+
+static int gpmc_wordaccess_only(hwaddr addr)
+{
+ /* Return true if the register offset is to a register that
+ * only permits word width accesses.
+ * Non-word accesses are only OK for GPMC_NAND_DATA/ADDRESS/COMMAND
+ * for any chipselect.
+ */
+ if (addr >= 0x60 && addr <= 0x1d4) {
+ int cs = (addr - 0x60) / 0x30;
+ addr -= cs * 0x30;
+ if (addr >= 0x7c && addr < 0x88) {
+ /* GPMC_NAND_COMMAND, GPMC_NAND_ADDRESS, GPMC_NAND_DATA */
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static uint64_t omap_gpmc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque;
+ int cs;
+ struct omap_gpmc_cs_file_s *f;
+
+ if (size != 4 && gpmc_wordaccess_only(addr)) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x000: /* GPMC_REVISION */
+ return s->revision;
+
+ case 0x010: /* GPMC_SYSCONFIG */
+ return s->sysconfig;
+
+ case 0x014: /* GPMC_SYSSTATUS */
+ return 1; /* RESETDONE */
+
+ case 0x018: /* GPMC_IRQSTATUS */
+ return s->irqst;
+
+ case 0x01c: /* GPMC_IRQENABLE */
+ return s->irqen;
+
+ case 0x040: /* GPMC_TIMEOUT_CONTROL */
+ return s->timeout;
+
+ case 0x044: /* GPMC_ERR_ADDRESS */
+ case 0x048: /* GPMC_ERR_TYPE */
+ return 0;
+
+ case 0x050: /* GPMC_CONFIG */
+ return s->config;
+
+ case 0x054: /* GPMC_STATUS */
+ return 0x001;
+
+ case 0x060 ... 0x1d4:
+ cs = (addr - 0x060) / 0x30;
+ addr -= cs * 0x30;
+ f = s->cs_file + cs;
+ switch (addr) {
+ case 0x60: /* GPMC_CONFIG1 */
+ return f->config[0];
+ case 0x64: /* GPMC_CONFIG2 */
+ return f->config[1];
+ case 0x68: /* GPMC_CONFIG3 */
+ return f->config[2];
+ case 0x6c: /* GPMC_CONFIG4 */
+ return f->config[3];
+ case 0x70: /* GPMC_CONFIG5 */
+ return f->config[4];
+ case 0x74: /* GPMC_CONFIG6 */
+ return f->config[5];
+ case 0x78: /* GPMC_CONFIG7 */
+ return f->config[6];
+ case 0x84 ... 0x87: /* GPMC_NAND_DATA */
+ if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) {
+ return omap_nand_read(f, 0, size);
+ }
+ return 0;
+ }
+ break;
+
+ case 0x1e0: /* GPMC_PREFETCH_CONFIG1 */
+ return s->prefetch.config1;
+ case 0x1e4: /* GPMC_PREFETCH_CONFIG2 */
+ return s->prefetch.transfercount;
+ case 0x1ec: /* GPMC_PREFETCH_CONTROL */
+ return s->prefetch.startengine;
+ case 0x1f0: /* GPMC_PREFETCH_STATUS */
+ /* NB: The OMAP3 TRM is inconsistent about whether the GPMC
+ * FIFOTHRESHOLDSTATUS bit should be set when
+ * FIFOPOINTER > FIFOTHRESHOLD or when it is >= FIFOTHRESHOLD.
+ * Apparently the underlying functional spec from which the TRM was
+ * created states that the behaviour is ">=", and this also
+ * makes more conceptual sense.
+ */
+ return (s->prefetch.fifopointer << 24) |
+ ((s->prefetch.fifopointer >=
+ ((s->prefetch.config1 >> 8) & 0x7f) ? 1 : 0) << 16) |
+ s->prefetch.count;
+
+ case 0x1f4: /* GPMC_ECC_CONFIG */
+ return s->ecc_cs;
+ case 0x1f8: /* GPMC_ECC_CONTROL */
+ return s->ecc_ptr;
+ case 0x1fc: /* GPMC_ECC_SIZE_CONFIG */
+ return s->ecc_cfg;
+ case 0x200 ... 0x220: /* GPMC_ECC_RESULT */
+ cs = (addr & 0x1f) >> 2;
+ /* TODO: check correctness */
+ return
+ ((s->ecc[cs].cp & 0x07) << 0) |
+ ((s->ecc[cs].cp & 0x38) << 13) |
+ ((s->ecc[cs].lp[0] & 0x1ff) << 3) |
+ ((s->ecc[cs].lp[1] & 0x1ff) << 19);
+
+ case 0x230: /* GPMC_TESTMODE_CTRL */
+ return 0;
+ case 0x234: /* GPMC_PSA_LSB */
+ case 0x238: /* GPMC_PSA_MSB */
+ return 0x00000000;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_gpmc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque;
+ int cs;
+ struct omap_gpmc_cs_file_s *f;
+
+ if (size != 4 && gpmc_wordaccess_only(addr)) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x000: /* GPMC_REVISION */
+ case 0x014: /* GPMC_SYSSTATUS */
+ case 0x054: /* GPMC_STATUS */
+ case 0x1f0: /* GPMC_PREFETCH_STATUS */
+ case 0x200 ... 0x220: /* GPMC_ECC_RESULT */
+ case 0x234: /* GPMC_PSA_LSB */
+ case 0x238: /* GPMC_PSA_MSB */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x010: /* GPMC_SYSCONFIG */
+ if ((value >> 3) == 0x3)
+ fprintf(stderr, "%s: bad SDRAM idle mode %"PRIi64"\n",
+ __func__, value >> 3);
+ if (value & 2)
+ omap_gpmc_reset(s);
+ s->sysconfig = value & 0x19;
+ break;
+
+ case 0x018: /* GPMC_IRQSTATUS */
+ s->irqst &= ~value;
+ omap_gpmc_int_update(s);
+ break;
+
+ case 0x01c: /* GPMC_IRQENABLE */
+ s->irqen = value & 0xf03;
+ omap_gpmc_int_update(s);
+ break;
+
+ case 0x040: /* GPMC_TIMEOUT_CONTROL */
+ s->timeout = value & 0x1ff1;
+ break;
+
+ case 0x044: /* GPMC_ERR_ADDRESS */
+ case 0x048: /* GPMC_ERR_TYPE */
+ break;
+
+ case 0x050: /* GPMC_CONFIG */
+ s->config = value & 0xf13;
+ break;
+
+ case 0x060 ... 0x1d4:
+ cs = (addr - 0x060) / 0x30;
+ addr -= cs * 0x30;
+ f = s->cs_file + cs;
+ switch (addr) {
+ case 0x60: /* GPMC_CONFIG1 */
+ f->config[0] = value & 0xffef3e13;
+ break;
+ case 0x64: /* GPMC_CONFIG2 */
+ f->config[1] = value & 0x001f1f8f;
+ break;
+ case 0x68: /* GPMC_CONFIG3 */
+ f->config[2] = value & 0x001f1f8f;
+ break;
+ case 0x6c: /* GPMC_CONFIG4 */
+ f->config[3] = value & 0x1f8f1f8f;
+ break;
+ case 0x70: /* GPMC_CONFIG5 */
+ f->config[4] = value & 0x0f1f1f1f;
+ break;
+ case 0x74: /* GPMC_CONFIG6 */
+ f->config[5] = value & 0x00000fcf;
+ break;
+ case 0x78: /* GPMC_CONFIG7 */
+ if ((f->config[6] ^ value) & 0xf7f) {
+ omap_gpmc_cs_unmap(s, cs);
+ f->config[6] = value & 0x00000f7f;
+ omap_gpmc_cs_map(s, cs);
+ }
+ break;
+ case 0x7c ... 0x7f: /* GPMC_NAND_COMMAND */
+ if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) {
+ nand_setpins(f->dev, 1, 0, 0, 1, 0); /* CLE */
+ omap_nand_setio(f->dev, value, omap_gpmc_devsize(f), size);
+ }
+ break;
+ case 0x80 ... 0x83: /* GPMC_NAND_ADDRESS */
+ if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) {
+ nand_setpins(f->dev, 0, 1, 0, 1, 0); /* ALE */
+ omap_nand_setio(f->dev, value, omap_gpmc_devsize(f), size);
+ }
+ break;
+ case 0x84 ... 0x87: /* GPMC_NAND_DATA */
+ if (omap_gpmc_devtype(f) == OMAP_GPMC_NAND) {
+ omap_nand_write(f, 0, value, size);
+ }
+ break;
+ default:
+ goto bad_reg;
+ }
+ break;
+
+ case 0x1e0: /* GPMC_PREFETCH_CONFIG1 */
+ if (!s->prefetch.startengine) {
+ uint32_t newconfig1 = value & 0x7f8f7fbf;
+ uint32_t changed;
+ changed = newconfig1 ^ s->prefetch.config1;
+ if (changed & (0x80 | 0x7000000)) {
+ /* Turning the engine on or off, or mapping it somewhere else.
+ * cs_map() and cs_unmap() check the prefetch config and
+ * overall CSVALID bits, so it is sufficient to unmap-and-map
+ * both the old cs and the new one. Note that we adhere to
+ * the "unmap/change config/map" order (and not unmap twice
+ * if newcs == oldcs), otherwise we'll try to delete the wrong
+ * memory region.
+ */
+ int oldcs = prefetch_cs(s->prefetch.config1);
+ int newcs = prefetch_cs(newconfig1);
+ omap_gpmc_cs_unmap(s, oldcs);
+ if (oldcs != newcs) {
+ omap_gpmc_cs_unmap(s, newcs);
+ }
+ s->prefetch.config1 = newconfig1;
+ omap_gpmc_cs_map(s, oldcs);
+ if (oldcs != newcs) {
+ omap_gpmc_cs_map(s, newcs);
+ }
+ } else {
+ s->prefetch.config1 = newconfig1;
+ }
+ }
+ break;
+
+ case 0x1e4: /* GPMC_PREFETCH_CONFIG2 */
+ if (!s->prefetch.startengine) {
+ s->prefetch.transfercount = value & 0x3fff;
+ }
+ break;
+
+ case 0x1ec: /* GPMC_PREFETCH_CONTROL */
+ if (s->prefetch.startengine != (value & 1)) {
+ s->prefetch.startengine = value & 1;
+ if (s->prefetch.startengine) {
+ /* Prefetch engine start */
+ s->prefetch.count = s->prefetch.transfercount;
+ if (s->prefetch.config1 & 1) {
+ /* Write */
+ s->prefetch.fifopointer = 64;
+ } else {
+ /* Read */
+ s->prefetch.fifopointer = 0;
+ fill_prefetch_fifo(s);
+ }
+ } else {
+ /* Prefetch engine forcibly stopped. The TRM
+ * doesn't define the behaviour if you do this.
+ * We clear the prefetch count, which means that
+ * we permit no more writes, and don't read any
+ * more data from NAND. The CPU can still drain
+ * the FIFO of unread data.
+ */
+ s->prefetch.count = 0;
+ }
+ omap_gpmc_int_update(s);
+ }
+ break;
+
+ case 0x1f4: /* GPMC_ECC_CONFIG */
+ s->ecc_cs = 0x8f;
+ break;
+ case 0x1f8: /* GPMC_ECC_CONTROL */
+ if (value & (1 << 8))
+ for (cs = 0; cs < 9; cs ++)
+ ecc_reset(&s->ecc[cs]);
+ s->ecc_ptr = value & 0xf;
+ if (s->ecc_ptr == 0 || s->ecc_ptr > 9) {
+ s->ecc_ptr = 0;
+ s->ecc_cs &= ~1;
+ }
+ break;
+ case 0x1fc: /* GPMC_ECC_SIZE_CONFIG */
+ s->ecc_cfg = value & 0x3fcff1ff;
+ break;
+ case 0x230: /* GPMC_TESTMODE_CTRL */
+ if (value & 7)
+ fprintf(stderr, "%s: test mode enable attempt\n", __func__);
+ break;
+
+ default:
+ bad_reg:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_gpmc_ops = {
+ .read = omap_gpmc_read,
+ .write = omap_gpmc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_gpmc_s *omap_gpmc_init(struct omap_mpu_state_s *mpu,
+ hwaddr base,
+ qemu_irq irq, qemu_irq drq)
+{
+ int cs;
+ struct omap_gpmc_s *s = g_new0(struct omap_gpmc_s, 1);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_gpmc_ops, s, "omap-gpmc", 0x1000);
+ memory_region_add_subregion(get_system_memory(), base, &s->iomem);
+
+ s->irq = irq;
+ s->drq = drq;
+ s->accept_256 = cpu_is_omap3630(mpu);
+ s->revision = cpu_class_omap3(mpu) ? 0x50 : 0x20;
+ s->lastirq = 0;
+ omap_gpmc_reset(s);
+
+ /* We have to register a different IO memory handler for each
+ * chip select region in case a NAND device is mapped there. We
+ * make the region the worst-case size of 256MB and rely on the
+ * container memory region in cs_map to chop it down to the actual
+ * guest-requested size.
+ */
+ for (cs = 0; cs < 8; cs++) {
+ memory_region_init_io(&s->cs_file[cs].nandiomem, NULL,
+ &omap_nand_ops,
+ &s->cs_file[cs],
+ "omap-nand",
+ 256 * 1024 * 1024);
+ }
+
+ memory_region_init_io(&s->prefetch.iomem, NULL, &omap_prefetch_ops, s,
+ "omap-gpmc-prefetch", 256 * 1024 * 1024);
+ return s;
+}
+
+void omap_gpmc_attach(struct omap_gpmc_s *s, int cs, MemoryRegion *iomem)
+{
+ struct omap_gpmc_cs_file_s *f;
+ assert(iomem);
+
+ if (cs < 0 || cs >= 8) {
+ fprintf(stderr, "%s: bad chip-select %i\n", __func__, cs);
+ exit(-1);
+ }
+ f = &s->cs_file[cs];
+
+ omap_gpmc_cs_unmap(s, cs);
+ f->config[0] &= ~(0xf << 10);
+ f->iomem = iomem;
+ omap_gpmc_cs_map(s, cs);
+}
+
+void omap_gpmc_attach_nand(struct omap_gpmc_s *s, int cs, DeviceState *nand)
+{
+ struct omap_gpmc_cs_file_s *f;
+ assert(nand);
+
+ if (cs < 0 || cs >= 8) {
+ fprintf(stderr, "%s: bad chip-select %i\n", __func__, cs);
+ exit(-1);
+ }
+ f = &s->cs_file[cs];
+
+ omap_gpmc_cs_unmap(s, cs);
+ f->config[0] &= ~(0xf << 10);
+ f->config[0] |= (OMAP_GPMC_NAND << 10);
+ f->dev = nand;
+ if (nand_getbuswidth(f->dev) == 16) {
+ f->config[0] |= OMAP_GPMC_16BIT << 12;
+ }
+ omap_gpmc_cs_map(s, cs);
+}
diff --git a/hw/misc/omap_l4.c b/hw/misc/omap_l4.c
new file mode 100644
index 000000000..54aeaecd6
--- /dev/null
+++ b/hw/misc/omap_l4.c
@@ -0,0 +1,163 @@
+/*
+ * TI OMAP L4 interconnect emulation.
+ *
+ * Copyright (C) 2007-2009 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.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 or
+ * (at your option) any later version of the License.
+ *
+ * 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 "hw/arm/omap.h"
+
+struct omap_l4_s {
+ MemoryRegion *address_space;
+ hwaddr base;
+ int ta_num;
+ struct omap_target_agent_s ta[];
+};
+
+struct omap_l4_s *omap_l4_init(MemoryRegion *address_space,
+ hwaddr base, int ta_num)
+{
+ struct omap_l4_s *bus = g_malloc0(
+ sizeof(*bus) + ta_num * sizeof(*bus->ta));
+
+ bus->address_space = address_space;
+ bus->ta_num = ta_num;
+ bus->base = base;
+
+ return bus;
+}
+
+hwaddr omap_l4_region_base(struct omap_target_agent_s *ta,
+ int region)
+{
+ return ta->bus->base + ta->start[region].offset;
+}
+
+hwaddr omap_l4_region_size(struct omap_target_agent_s *ta,
+ int region)
+{
+ return ta->start[region].size;
+}
+
+static uint64_t omap_l4ta_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_target_agent_s *s = (struct omap_target_agent_s *) opaque;
+
+ if (size != 2) {
+ return omap_badwidth_read16(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* COMPONENT */
+ return s->component;
+
+ case 0x20: /* AGENT_CONTROL */
+ return s->control;
+
+ case 0x28: /* AGENT_STATUS */
+ return s->status;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_l4ta_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_target_agent_s *s = (struct omap_target_agent_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* COMPONENT */
+ case 0x28: /* AGENT_STATUS */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x20: /* AGENT_CONTROL */
+ s->control = value & 0x01000700;
+ if (value & 1) /* OCP_RESET */
+ s->status &= ~1; /* REQ_TIMEOUT */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_l4ta_ops = {
+ .read = omap_l4ta_read,
+ .write = omap_l4ta_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_target_agent_s *omap_l4ta_get(struct omap_l4_s *bus,
+ const struct omap_l4_region_s *regions,
+ const struct omap_l4_agent_info_s *agents,
+ int cs)
+{
+ int i;
+ struct omap_target_agent_s *ta = NULL;
+ const struct omap_l4_agent_info_s *info = NULL;
+
+ for (i = 0; i < bus->ta_num; i ++)
+ if (agents[i].ta == cs) {
+ ta = &bus->ta[i];
+ info = &agents[i];
+ break;
+ }
+ if (!ta) {
+ fprintf(stderr, "%s: bad target agent (%i)\n", __func__, cs);
+ exit(-1);
+ }
+
+ ta->bus = bus;
+ ta->start = &regions[info->region];
+ ta->regions = info->regions;
+
+ ta->component = ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+ ta->status = 0x00000000;
+ ta->control = 0x00000200; /* XXX 01000200 for L4TAO */
+
+ memory_region_init_io(&ta->iomem, NULL, &omap_l4ta_ops, ta, "omap.l4ta",
+ omap_l4_region_size(ta, info->ta_region));
+ omap_l4_attach(ta, info->ta_region, &ta->iomem);
+
+ return ta;
+}
+
+hwaddr omap_l4_attach(struct omap_target_agent_s *ta,
+ int region, MemoryRegion *mr)
+{
+ hwaddr base;
+
+ if (region < 0 || region >= ta->regions) {
+ fprintf(stderr, "%s: bad io region (%i)\n", __func__, region);
+ exit(-1);
+ }
+
+ base = ta->bus->base + ta->start[region].offset;
+ if (mr) {
+ memory_region_add_subregion(ta->bus->address_space, base, mr);
+ }
+
+ return base;
+}
diff --git a/hw/misc/omap_sdrc.c b/hw/misc/omap_sdrc.c
new file mode 100644
index 000000000..f2f72f681
--- /dev/null
+++ b/hw/misc/omap_sdrc.c
@@ -0,0 +1,168 @@
+/*
+ * TI OMAP SDRAM controller emulation.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.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 or
+ * (at your option) any later version of the License.
+ *
+ * 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 "hw/arm/omap.h"
+
+/* SDRAM Controller Subsystem */
+struct omap_sdrc_s {
+ MemoryRegion iomem;
+ uint8_t config;
+};
+
+void omap_sdrc_reset(struct omap_sdrc_s *s)
+{
+ s->config = 0x10;
+}
+
+static uint64_t omap_sdrc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_sdrc_s *s = (struct omap_sdrc_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* SDRC_REVISION */
+ return 0x20;
+
+ case 0x10: /* SDRC_SYSCONFIG */
+ return s->config;
+
+ case 0x14: /* SDRC_SYSSTATUS */
+ return 1; /* RESETDONE */
+
+ case 0x40: /* SDRC_CS_CFG */
+ case 0x44: /* SDRC_SHARING */
+ case 0x48: /* SDRC_ERR_ADDR */
+ case 0x4c: /* SDRC_ERR_TYPE */
+ case 0x60: /* SDRC_DLLA_SCTRL */
+ case 0x64: /* SDRC_DLLA_STATUS */
+ case 0x68: /* SDRC_DLLB_CTRL */
+ case 0x6c: /* SDRC_DLLB_STATUS */
+ case 0x70: /* SDRC_POWER */
+ case 0x80: /* SDRC_MCFG_0 */
+ case 0x84: /* SDRC_MR_0 */
+ case 0x88: /* SDRC_EMR1_0 */
+ case 0x8c: /* SDRC_EMR2_0 */
+ case 0x90: /* SDRC_EMR3_0 */
+ case 0x94: /* SDRC_DCDL1_CTRL */
+ case 0x98: /* SDRC_DCDL2_CTRL */
+ case 0x9c: /* SDRC_ACTIM_CTRLA_0 */
+ case 0xa0: /* SDRC_ACTIM_CTRLB_0 */
+ case 0xa4: /* SDRC_RFR_CTRL_0 */
+ case 0xa8: /* SDRC_MANUAL_0 */
+ case 0xb0: /* SDRC_MCFG_1 */
+ case 0xb4: /* SDRC_MR_1 */
+ case 0xb8: /* SDRC_EMR1_1 */
+ case 0xbc: /* SDRC_EMR2_1 */
+ case 0xc0: /* SDRC_EMR3_1 */
+ case 0xc4: /* SDRC_ACTIM_CTRLA_1 */
+ case 0xc8: /* SDRC_ACTIM_CTRLB_1 */
+ case 0xd4: /* SDRC_RFR_CTRL_1 */
+ case 0xd8: /* SDRC_MANUAL_1 */
+ return 0x00;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_sdrc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_sdrc_s *s = (struct omap_sdrc_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* SDRC_REVISION */
+ case 0x14: /* SDRC_SYSSTATUS */
+ case 0x48: /* SDRC_ERR_ADDR */
+ case 0x64: /* SDRC_DLLA_STATUS */
+ case 0x6c: /* SDRC_DLLB_STATUS */
+ OMAP_RO_REG(addr);
+ return;
+
+ case 0x10: /* SDRC_SYSCONFIG */
+ if ((value >> 3) != 0x2)
+ fprintf(stderr, "%s: bad SDRAM idle mode %i\n",
+ __func__, (unsigned)value >> 3);
+ if (value & 2)
+ omap_sdrc_reset(s);
+ s->config = value & 0x18;
+ break;
+
+ case 0x40: /* SDRC_CS_CFG */
+ case 0x44: /* SDRC_SHARING */
+ case 0x4c: /* SDRC_ERR_TYPE */
+ case 0x60: /* SDRC_DLLA_SCTRL */
+ case 0x68: /* SDRC_DLLB_CTRL */
+ case 0x70: /* SDRC_POWER */
+ case 0x80: /* SDRC_MCFG_0 */
+ case 0x84: /* SDRC_MR_0 */
+ case 0x88: /* SDRC_EMR1_0 */
+ case 0x8c: /* SDRC_EMR2_0 */
+ case 0x90: /* SDRC_EMR3_0 */
+ case 0x94: /* SDRC_DCDL1_CTRL */
+ case 0x98: /* SDRC_DCDL2_CTRL */
+ case 0x9c: /* SDRC_ACTIM_CTRLA_0 */
+ case 0xa0: /* SDRC_ACTIM_CTRLB_0 */
+ case 0xa4: /* SDRC_RFR_CTRL_0 */
+ case 0xa8: /* SDRC_MANUAL_0 */
+ case 0xb0: /* SDRC_MCFG_1 */
+ case 0xb4: /* SDRC_MR_1 */
+ case 0xb8: /* SDRC_EMR1_1 */
+ case 0xbc: /* SDRC_EMR2_1 */
+ case 0xc0: /* SDRC_EMR3_1 */
+ case 0xc4: /* SDRC_ACTIM_CTRLA_1 */
+ case 0xc8: /* SDRC_ACTIM_CTRLB_1 */
+ case 0xd4: /* SDRC_RFR_CTRL_1 */
+ case 0xd8: /* SDRC_MANUAL_1 */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ return;
+ }
+}
+
+static const MemoryRegionOps omap_sdrc_ops = {
+ .read = omap_sdrc_read,
+ .write = omap_sdrc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_sdrc_s *omap_sdrc_init(MemoryRegion *sysmem,
+ hwaddr base)
+{
+ struct omap_sdrc_s *s = g_new0(struct omap_sdrc_s, 1);
+
+ omap_sdrc_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_sdrc_ops, s, "omap.sdrc", 0x1000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ return s;
+}
diff --git a/hw/misc/omap_tap.c b/hw/misc/omap_tap.c
new file mode 100644
index 000000000..3f595e8df
--- /dev/null
+++ b/hw/misc/omap_tap.c
@@ -0,0 +1,118 @@
+/*
+ * TI OMAP TEST-Chip-level TAP emulation.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.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 or
+ * (at your option) any later version of the License.
+ *
+ * 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 "hw/hw.h"
+#include "hw/arm/omap.h"
+
+/* TEST-Chip-level TAP */
+static uint64_t omap_tap_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x204: /* IDCODE_reg */
+ switch (s->mpu_model) {
+ case omap2420:
+ case omap2422:
+ case omap2423:
+ return 0x5b5d902f; /* ES 2.2 */
+ case omap2430:
+ return 0x5b68a02f; /* ES 2.2 */
+ case omap3430:
+ return 0x1b7ae02f; /* ES 2 */
+ default:
+ hw_error("%s: Bad mpu model\n", __func__);
+ }
+
+ case 0x208: /* PRODUCTION_ID_reg for OMAP2 */
+ case 0x210: /* PRODUCTION_ID_reg for OMAP3 */
+ switch (s->mpu_model) {
+ case omap2420:
+ return 0x000254f0; /* POP ESHS2.1.1 in N91/93/95, ES2 in N800 */
+ case omap2422:
+ return 0x000400f0;
+ case omap2423:
+ return 0x000800f0;
+ case omap2430:
+ return 0x000000f0;
+ case omap3430:
+ return 0x000000f0;
+ default:
+ hw_error("%s: Bad mpu model\n", __func__);
+ }
+
+ case 0x20c:
+ switch (s->mpu_model) {
+ case omap2420:
+ case omap2422:
+ case omap2423:
+ return 0xcafeb5d9; /* ES 2.2 */
+ case omap2430:
+ return 0xcafeb68a; /* ES 2.2 */
+ case omap3430:
+ return 0xcafeb7ae; /* ES 2 */
+ default:
+ hw_error("%s: Bad mpu model\n", __func__);
+ }
+
+ case 0x218: /* DIE_ID_reg */
+ return ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+ case 0x21c: /* DIE_ID_reg */
+ return 0x54 << 24;
+ case 0x220: /* DIE_ID_reg */
+ return ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+ case 0x224: /* DIE_ID_reg */
+ return ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_tap_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ OMAP_BAD_REG(addr);
+}
+
+static const MemoryRegionOps omap_tap_ops = {
+ .read = omap_tap_read,
+ .write = omap_tap_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void omap_tap_init(struct omap_target_agent_s *ta,
+ struct omap_mpu_state_s *mpu)
+{
+ memory_region_init_io(&mpu->tap_iomem, NULL, &omap_tap_ops, mpu, "omap.tap",
+ omap_l4_region_size(ta, 0));
+ omap_l4_attach(ta, 0, &mpu->tap_iomem);
+}
diff --git a/hw/misc/pc-testdev.c b/hw/misc/pc-testdev.c
new file mode 100644
index 000000000..e38965186
--- /dev/null
+++ b/hw/misc/pc-testdev.c
@@ -0,0 +1,216 @@
+/*
+ * QEMU x86 ISA testdev
+ *
+ * Copyright (c) 2012 Avi Kivity, Gerd Hoffmann, Marcelo Tosatti
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/*
+ * This device is used to test KVM features specific to the x86 port, such
+ * as emulation, power management, interrupt routing, among others. It's meant
+ * to be used like:
+ *
+ * qemu-system-x86_64 -device pc-testdev -serial stdio \
+ * -device isa-debug-exit,iobase=0xf4,iosize=0x4 \
+ * -kernel /home/lmr/Code/virt-test.git/kvm/unittests/msr.flat
+ *
+ * Where msr.flat is one of the KVM unittests, present on a separate repo,
+ * https://git.kernel.org/pub/scm/virt/kvm/kvm-unit-tests.git
+*/
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "hw/irq.h"
+#include "hw/isa/isa.h"
+#include "qom/object.h"
+
+#define IOMEM_LEN 0x10000
+
+struct PCTestdev {
+ ISADevice parent_obj;
+
+ MemoryRegion ioport;
+ MemoryRegion ioport_byte;
+ MemoryRegion flush;
+ MemoryRegion irq;
+ MemoryRegion iomem;
+ uint32_t ioport_data;
+ char iomem_buf[IOMEM_LEN];
+};
+
+#define TYPE_TESTDEV "pc-testdev"
+OBJECT_DECLARE_SIMPLE_TYPE(PCTestdev, TESTDEV)
+
+static uint64_t test_irq_line_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return 0;
+}
+
+static void test_irq_line_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned len)
+{
+ PCTestdev *dev = opaque;
+ ISADevice *isa = ISA_DEVICE(dev);
+
+ qemu_set_irq(isa_get_irq(isa, addr), !!data);
+}
+
+static const MemoryRegionOps test_irq_ops = {
+ .read = test_irq_line_read,
+ .write = test_irq_line_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void test_ioport_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned len)
+{
+ PCTestdev *dev = opaque;
+ int bits = len * 8;
+ int start_bit = (addr & 3) * 8;
+ uint32_t mask = ((uint32_t)-1 >> (32 - bits)) << start_bit;
+ dev->ioport_data &= ~mask;
+ dev->ioport_data |= data << start_bit;
+}
+
+static uint64_t test_ioport_read(void *opaque, hwaddr addr, unsigned len)
+{
+ PCTestdev *dev = opaque;
+ int bits = len * 8;
+ int start_bit = (addr & 3) * 8;
+ uint32_t mask = ((uint32_t)-1 >> (32 - bits)) << start_bit;
+ return (dev->ioport_data & mask) >> start_bit;
+}
+
+static const MemoryRegionOps test_ioport_ops = {
+ .read = test_ioport_read,
+ .write = test_ioport_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const MemoryRegionOps test_ioport_byte_ops = {
+ .read = test_ioport_read,
+ .write = test_ioport_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t test_flush_page_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return 0;
+}
+
+static void test_flush_page_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned len)
+{
+ hwaddr page = 4096;
+ void *a = cpu_physical_memory_map(data & ~0xffful, &page, false);
+
+ /* We might not be able to get the full page, only mprotect what we actually
+ have mapped */
+#if defined(CONFIG_POSIX)
+ mprotect(a, page, PROT_NONE);
+ mprotect(a, page, PROT_READ|PROT_WRITE);
+#endif
+ cpu_physical_memory_unmap(a, page, 0, 0);
+}
+
+static const MemoryRegionOps test_flush_ops = {
+ .read = test_flush_page_read,
+ .write = test_flush_page_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t test_iomem_read(void *opaque, hwaddr addr, unsigned len)
+{
+ PCTestdev *dev = opaque;
+ uint64_t ret = 0;
+ memcpy(&ret, &dev->iomem_buf[addr], len);
+
+ return ret;
+}
+
+static void test_iomem_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned len)
+{
+ PCTestdev *dev = opaque;
+ memcpy(&dev->iomem_buf[addr], &val, len);
+ dev->iomem_buf[addr] = val;
+}
+
+static const MemoryRegionOps test_iomem_ops = {
+ .read = test_iomem_read,
+ .write = test_iomem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void testdev_realizefn(DeviceState *d, Error **errp)
+{
+ ISADevice *isa = ISA_DEVICE(d);
+ PCTestdev *dev = TESTDEV(d);
+ MemoryRegion *mem = isa_address_space(isa);
+ MemoryRegion *io = isa_address_space_io(isa);
+
+ memory_region_init_io(&dev->ioport, OBJECT(dev), &test_ioport_ops, dev,
+ "pc-testdev-ioport", 4);
+ memory_region_init_io(&dev->ioport_byte, OBJECT(dev),
+ &test_ioport_byte_ops, dev,
+ "pc-testdev-ioport-byte", 4);
+ memory_region_init_io(&dev->flush, OBJECT(dev), &test_flush_ops, dev,
+ "pc-testdev-flush-page", 4);
+ memory_region_init_io(&dev->irq, OBJECT(dev), &test_irq_ops, dev,
+ "pc-testdev-irq-line", 24);
+ memory_region_init_io(&dev->iomem, OBJECT(dev), &test_iomem_ops, dev,
+ "pc-testdev-iomem", IOMEM_LEN);
+
+ memory_region_add_subregion(io, 0xe0, &dev->ioport);
+ memory_region_add_subregion(io, 0xe4, &dev->flush);
+ memory_region_add_subregion(io, 0xe8, &dev->ioport_byte);
+ memory_region_add_subregion(io, 0x2000, &dev->irq);
+ memory_region_add_subregion(mem, 0xff000000, &dev->iomem);
+}
+
+static void testdev_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->realize = testdev_realizefn;
+}
+
+static const TypeInfo testdev_info = {
+ .name = TYPE_TESTDEV,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(PCTestdev),
+ .class_init = testdev_class_init,
+};
+
+static void testdev_register_types(void)
+{
+ type_register_static(&testdev_info);
+}
+
+type_init(testdev_register_types)
diff --git a/hw/misc/pca9552.c b/hw/misc/pca9552.c
new file mode 100644
index 000000000..fff19e369
--- /dev/null
+++ b/hw/misc/pca9552.c
@@ -0,0 +1,437 @@
+/*
+ * PCA9552 I2C LED blinker
+ *
+ * https://www.nxp.com/docs/en/application-note/AN264.pdf
+ *
+ * Copyright (c) 2017-2018, IBM Corporation.
+ * Copyright (c) 2020 Philippe Mathieu-Daudé
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/bitops.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/pca9552.h"
+#include "hw/misc/pca9552_regs.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "trace.h"
+#include "qom/object.h"
+
+struct PCA955xClass {
+ /*< private >*/
+ I2CSlaveClass parent_class;
+ /*< public >*/
+
+ uint8_t pin_count;
+ uint8_t max_reg;
+};
+typedef struct PCA955xClass PCA955xClass;
+
+DECLARE_CLASS_CHECKERS(PCA955xClass, PCA955X,
+ TYPE_PCA955X)
+
+#define PCA9552_LED_ON 0x0
+#define PCA9552_LED_OFF 0x1
+#define PCA9552_LED_PWM0 0x2
+#define PCA9552_LED_PWM1 0x3
+
+static const char *led_state[] = {"on", "off", "pwm0", "pwm1"};
+
+static uint8_t pca955x_pin_get_config(PCA955xState *s, int pin)
+{
+ uint8_t reg = PCA9552_LS0 + (pin / 4);
+ uint8_t shift = (pin % 4) << 1;
+
+ return extract32(s->regs[reg], shift, 2);
+}
+
+/* Return INPUT status (bit #N belongs to GPIO #N) */
+static uint16_t pca955x_pins_get_status(PCA955xState *s)
+{
+ return (s->regs[PCA9552_INPUT1] << 8) | s->regs[PCA9552_INPUT0];
+}
+
+static void pca955x_display_pins_status(PCA955xState *s,
+ uint16_t previous_pins_status)
+{
+ PCA955xClass *k = PCA955X_GET_CLASS(s);
+ uint16_t pins_status, pins_changed;
+ int i;
+
+ pins_status = pca955x_pins_get_status(s);
+ pins_changed = previous_pins_status ^ pins_status;
+ if (!pins_changed) {
+ return;
+ }
+ if (trace_event_get_state_backends(TRACE_PCA955X_GPIO_STATUS)) {
+ char *buf = g_newa(char, k->pin_count + 1);
+
+ for (i = 0; i < k->pin_count; i++) {
+ if (extract32(pins_status, i, 1)) {
+ buf[i] = '*';
+ } else {
+ buf[i] = '.';
+ }
+ }
+ buf[i] = '\0';
+ trace_pca955x_gpio_status(s->description, buf);
+ }
+ if (trace_event_get_state_backends(TRACE_PCA955X_GPIO_CHANGE)) {
+ for (i = 0; i < k->pin_count; i++) {
+ if (extract32(pins_changed, i, 1)) {
+ unsigned new_state = extract32(pins_status, i, 1);
+
+ /*
+ * We display the state using the PCA logic ("active-high").
+ * This is not the state of the LED, which signal might be
+ * wired "active-low" on the board.
+ */
+ trace_pca955x_gpio_change(s->description, i,
+ !new_state, new_state);
+ }
+ }
+ }
+}
+
+static void pca955x_update_pin_input(PCA955xState *s)
+{
+ PCA955xClass *k = PCA955X_GET_CLASS(s);
+ int i;
+
+ for (i = 0; i < k->pin_count; i++) {
+ uint8_t input_reg = PCA9552_INPUT0 + (i / 8);
+ uint8_t input_shift = (i % 8);
+ uint8_t config = pca955x_pin_get_config(s, i);
+
+ switch (config) {
+ case PCA9552_LED_ON:
+ qemu_set_irq(s->gpio[i], 1);
+ s->regs[input_reg] |= 1 << input_shift;
+ break;
+ case PCA9552_LED_OFF:
+ qemu_set_irq(s->gpio[i], 0);
+ s->regs[input_reg] &= ~(1 << input_shift);
+ break;
+ case PCA9552_LED_PWM0:
+ case PCA9552_LED_PWM1:
+ /* TODO */
+ default:
+ break;
+ }
+ }
+}
+
+static uint8_t pca955x_read(PCA955xState *s, uint8_t reg)
+{
+ switch (reg) {
+ case PCA9552_INPUT0:
+ case PCA9552_INPUT1:
+ case PCA9552_PSC0:
+ case PCA9552_PWM0:
+ case PCA9552_PSC1:
+ case PCA9552_PWM1:
+ case PCA9552_LS0:
+ case PCA9552_LS1:
+ case PCA9552_LS2:
+ case PCA9552_LS3:
+ return s->regs[reg];
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected read to register %d\n",
+ __func__, reg);
+ return 0xFF;
+ }
+}
+
+static void pca955x_write(PCA955xState *s, uint8_t reg, uint8_t data)
+{
+ uint16_t pins_status;
+
+ switch (reg) {
+ case PCA9552_PSC0:
+ case PCA9552_PWM0:
+ case PCA9552_PSC1:
+ case PCA9552_PWM1:
+ s->regs[reg] = data;
+ break;
+
+ case PCA9552_LS0:
+ case PCA9552_LS1:
+ case PCA9552_LS2:
+ case PCA9552_LS3:
+ pins_status = pca955x_pins_get_status(s);
+ s->regs[reg] = data;
+ pca955x_update_pin_input(s);
+ pca955x_display_pins_status(s, pins_status);
+ break;
+
+ case PCA9552_INPUT0:
+ case PCA9552_INPUT1:
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected write to register %d\n",
+ __func__, reg);
+ }
+}
+
+/*
+ * When Auto-Increment is on, the register address is incremented
+ * after each byte is sent to or received by the device. The index
+ * rollovers to 0 when the maximum register address is reached.
+ */
+static void pca955x_autoinc(PCA955xState *s)
+{
+ PCA955xClass *k = PCA955X_GET_CLASS(s);
+
+ if (s->pointer != 0xFF && s->pointer & PCA9552_AUTOINC) {
+ uint8_t reg = s->pointer & 0xf;
+
+ reg = (reg + 1) % (k->max_reg + 1);
+ s->pointer = reg | PCA9552_AUTOINC;
+ }
+}
+
+static uint8_t pca955x_recv(I2CSlave *i2c)
+{
+ PCA955xState *s = PCA955X(i2c);
+ uint8_t ret;
+
+ ret = pca955x_read(s, s->pointer & 0xf);
+
+ /*
+ * From the Specs:
+ *
+ * Important Note: When a Read sequence is initiated and the
+ * AI bit is set to Logic Level 1, the Read Sequence MUST
+ * start by a register different from 0.
+ *
+ * I don't know what should be done in this case, so throw an
+ * error.
+ */
+ if (s->pointer == PCA9552_AUTOINC) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Autoincrement read starting with register 0\n",
+ __func__);
+ }
+
+ pca955x_autoinc(s);
+
+ return ret;
+}
+
+static int pca955x_send(I2CSlave *i2c, uint8_t data)
+{
+ PCA955xState *s = PCA955X(i2c);
+
+ /* First byte sent by is the register address */
+ if (s->len == 0) {
+ s->pointer = data;
+ s->len++;
+ } else {
+ pca955x_write(s, s->pointer & 0xf, data);
+
+ pca955x_autoinc(s);
+ }
+
+ return 0;
+}
+
+static int pca955x_event(I2CSlave *i2c, enum i2c_event event)
+{
+ PCA955xState *s = PCA955X(i2c);
+
+ s->len = 0;
+ return 0;
+}
+
+static void pca955x_get_led(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ PCA955xClass *k = PCA955X_GET_CLASS(obj);
+ PCA955xState *s = PCA955X(obj);
+ int led, rc, reg;
+ uint8_t state;
+
+ rc = sscanf(name, "led%2d", &led);
+ if (rc != 1) {
+ error_setg(errp, "%s: error reading %s", __func__, name);
+ return;
+ }
+ if (led < 0 || led > k->pin_count) {
+ error_setg(errp, "%s invalid led %s", __func__, name);
+ return;
+ }
+ /*
+ * Get the LSx register as the qom interface should expose the device
+ * state, not the modeled 'input line' behaviour which would come from
+ * reading the INPUTx reg
+ */
+ reg = PCA9552_LS0 + led / 4;
+ state = (pca955x_read(s, reg) >> ((led % 4) * 2)) & 0x3;
+ visit_type_str(v, name, (char **)&led_state[state], errp);
+}
+
+/*
+ * Return an LED selector register value based on an existing one, with
+ * the appropriate 2-bit state value set for the given LED number (0-3).
+ */
+static inline uint8_t pca955x_ledsel(uint8_t oldval, int led_num, int state)
+{
+ return (oldval & (~(0x3 << (led_num << 1)))) |
+ ((state & 0x3) << (led_num << 1));
+}
+
+static void pca955x_set_led(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ PCA955xClass *k = PCA955X_GET_CLASS(obj);
+ PCA955xState *s = PCA955X(obj);
+ int led, rc, reg, val;
+ uint8_t state;
+ char *state_str;
+
+ if (!visit_type_str(v, name, &state_str, errp)) {
+ return;
+ }
+ rc = sscanf(name, "led%2d", &led);
+ if (rc != 1) {
+ error_setg(errp, "%s: error reading %s", __func__, name);
+ return;
+ }
+ if (led < 0 || led > k->pin_count) {
+ error_setg(errp, "%s invalid led %s", __func__, name);
+ return;
+ }
+
+ for (state = 0; state < ARRAY_SIZE(led_state); state++) {
+ if (!strcmp(state_str, led_state[state])) {
+ break;
+ }
+ }
+ if (state >= ARRAY_SIZE(led_state)) {
+ error_setg(errp, "%s invalid led state %s", __func__, state_str);
+ return;
+ }
+
+ reg = PCA9552_LS0 + led / 4;
+ val = pca955x_read(s, reg);
+ val = pca955x_ledsel(val, led % 4, state);
+ pca955x_write(s, reg, val);
+}
+
+static const VMStateDescription pca9552_vmstate = {
+ .name = "PCA9552",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(len, PCA955xState),
+ VMSTATE_UINT8(pointer, PCA955xState),
+ VMSTATE_UINT8_ARRAY(regs, PCA955xState, PCA955X_NR_REGS),
+ VMSTATE_I2C_SLAVE(i2c, PCA955xState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pca9552_reset(DeviceState *dev)
+{
+ PCA955xState *s = PCA955X(dev);
+
+ s->regs[PCA9552_PSC0] = 0xFF;
+ s->regs[PCA9552_PWM0] = 0x80;
+ s->regs[PCA9552_PSC1] = 0xFF;
+ s->regs[PCA9552_PWM1] = 0x80;
+ s->regs[PCA9552_LS0] = 0x55; /* all OFF */
+ s->regs[PCA9552_LS1] = 0x55;
+ s->regs[PCA9552_LS2] = 0x55;
+ s->regs[PCA9552_LS3] = 0x55;
+
+ pca955x_update_pin_input(s);
+
+ s->pointer = 0xFF;
+ s->len = 0;
+}
+
+static void pca955x_initfn(Object *obj)
+{
+ PCA955xClass *k = PCA955X_GET_CLASS(obj);
+ int led;
+
+ assert(k->pin_count <= PCA955X_PIN_COUNT_MAX);
+ for (led = 0; led < k->pin_count; led++) {
+ char *name;
+
+ name = g_strdup_printf("led%d", led);
+ object_property_add(obj, name, "bool", pca955x_get_led, pca955x_set_led,
+ NULL, NULL);
+ g_free(name);
+ }
+}
+
+static void pca955x_realize(DeviceState *dev, Error **errp)
+{
+ PCA955xClass *k = PCA955X_GET_CLASS(dev);
+ PCA955xState *s = PCA955X(dev);
+
+ if (!s->description) {
+ s->description = g_strdup("pca-unspecified");
+ }
+
+ qdev_init_gpio_out(dev, s->gpio, k->pin_count);
+}
+
+static Property pca955x_properties[] = {
+ DEFINE_PROP_STRING("description", PCA955xState, description),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pca955x_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->event = pca955x_event;
+ k->recv = pca955x_recv;
+ k->send = pca955x_send;
+ dc->realize = pca955x_realize;
+ device_class_set_props(dc, pca955x_properties);
+}
+
+static const TypeInfo pca955x_info = {
+ .name = TYPE_PCA955X,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_init = pca955x_initfn,
+ .instance_size = sizeof(PCA955xState),
+ .class_init = pca955x_class_init,
+ .class_size = sizeof(PCA955xClass),
+ .abstract = true,
+};
+
+static void pca9552_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ PCA955xClass *pc = PCA955X_CLASS(oc);
+
+ dc->reset = pca9552_reset;
+ dc->vmsd = &pca9552_vmstate;
+ pc->max_reg = PCA9552_LS3;
+ pc->pin_count = 16;
+}
+
+static const TypeInfo pca9552_info = {
+ .name = TYPE_PCA9552,
+ .parent = TYPE_PCA955X,
+ .class_init = pca9552_class_init,
+};
+
+static void pca955x_register_types(void)
+{
+ type_register_static(&pca955x_info);
+ type_register_static(&pca9552_info);
+}
+
+type_init(pca955x_register_types)
diff --git a/hw/misc/pci-testdev.c b/hw/misc/pci-testdev.c
new file mode 100644
index 000000000..03845c8de
--- /dev/null
+++ b/hw/misc/pci-testdev.c
@@ -0,0 +1,361 @@
+/*
+ * QEMU PCI test device
+ *
+ * Copyright (c) 2012 Red Hat Inc.
+ * Author: Michael S. Tsirkin <mst@redhat.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 "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "qemu/event_notifier.h"
+#include "qemu/module.h"
+#include "sysemu/kvm.h"
+#include "qom/object.h"
+
+typedef struct PCITestDevHdr {
+ uint8_t test;
+ uint8_t width;
+ uint8_t pad0[2];
+ uint32_t offset;
+ uint8_t data;
+ uint8_t pad1[3];
+ uint32_t count;
+ uint8_t name[];
+} PCITestDevHdr;
+
+typedef struct IOTest {
+ MemoryRegion *mr;
+ EventNotifier notifier;
+ bool hasnotifier;
+ unsigned size;
+ bool match_data;
+ PCITestDevHdr *hdr;
+ unsigned bufsize;
+} IOTest;
+
+#define IOTEST_DATAMATCH 0xFA
+#define IOTEST_NOMATCH 0xCE
+
+#define IOTEST_IOSIZE 128
+#define IOTEST_MEMSIZE 2048
+
+static const char *iotest_test[] = {
+ "no-eventfd",
+ "wildcard-eventfd",
+ "datamatch-eventfd"
+};
+
+static const char *iotest_type[] = {
+ "mmio",
+ "portio"
+};
+
+#define IOTEST_TEST(i) (iotest_test[((i) % ARRAY_SIZE(iotest_test))])
+#define IOTEST_TYPE(i) (iotest_type[((i) / ARRAY_SIZE(iotest_test))])
+#define IOTEST_MAX_TEST (ARRAY_SIZE(iotest_test))
+#define IOTEST_MAX_TYPE (ARRAY_SIZE(iotest_type))
+#define IOTEST_MAX (IOTEST_MAX_TEST * IOTEST_MAX_TYPE)
+
+enum {
+ IOTEST_ACCESS_NAME,
+ IOTEST_ACCESS_DATA,
+ IOTEST_ACCESS_MAX,
+};
+
+#define IOTEST_ACCESS_TYPE uint8_t
+#define IOTEST_ACCESS_WIDTH (sizeof(uint8_t))
+
+struct PCITestDevState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion mmio;
+ MemoryRegion portio;
+ IOTest *tests;
+ int current;
+
+ uint64_t membar_size;
+ MemoryRegion membar;
+};
+
+#define TYPE_PCI_TEST_DEV "pci-testdev"
+
+OBJECT_DECLARE_SIMPLE_TYPE(PCITestDevState, PCI_TEST_DEV)
+
+#define IOTEST_IS_MEM(i) (strcmp(IOTEST_TYPE(i), "portio"))
+#define IOTEST_REGION(d, i) (IOTEST_IS_MEM(i) ? &(d)->mmio : &(d)->portio)
+#define IOTEST_SIZE(i) (IOTEST_IS_MEM(i) ? IOTEST_MEMSIZE : IOTEST_IOSIZE)
+#define IOTEST_PCI_BAR(i) (IOTEST_IS_MEM(i) ? PCI_BASE_ADDRESS_SPACE_MEMORY : \
+ PCI_BASE_ADDRESS_SPACE_IO)
+
+static int pci_testdev_start(IOTest *test)
+{
+ test->hdr->count = 0;
+ if (!test->hasnotifier) {
+ return 0;
+ }
+ event_notifier_test_and_clear(&test->notifier);
+ memory_region_add_eventfd(test->mr,
+ le32_to_cpu(test->hdr->offset),
+ test->size,
+ test->match_data,
+ test->hdr->data,
+ &test->notifier);
+ return 0;
+}
+
+static void pci_testdev_stop(IOTest *test)
+{
+ if (!test->hasnotifier) {
+ return;
+ }
+ memory_region_del_eventfd(test->mr,
+ le32_to_cpu(test->hdr->offset),
+ test->size,
+ test->match_data,
+ test->hdr->data,
+ &test->notifier);
+}
+
+static void
+pci_testdev_reset(PCITestDevState *d)
+{
+ if (d->current == -1) {
+ return;
+ }
+ pci_testdev_stop(&d->tests[d->current]);
+ d->current = -1;
+}
+
+static void pci_testdev_inc(IOTest *test, unsigned inc)
+{
+ uint32_t c = le32_to_cpu(test->hdr->count);
+ test->hdr->count = cpu_to_le32(c + inc);
+}
+
+static void
+pci_testdev_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size, int type)
+{
+ PCITestDevState *d = opaque;
+ IOTest *test;
+ int t, r;
+
+ if (addr == offsetof(PCITestDevHdr, test)) {
+ pci_testdev_reset(d);
+ if (val >= IOTEST_MAX_TEST) {
+ return;
+ }
+ t = type * IOTEST_MAX_TEST + val;
+ r = pci_testdev_start(&d->tests[t]);
+ if (r < 0) {
+ return;
+ }
+ d->current = t;
+ return;
+ }
+ if (d->current < 0) {
+ return;
+ }
+ test = &d->tests[d->current];
+ if (addr != le32_to_cpu(test->hdr->offset)) {
+ return;
+ }
+ if (test->match_data && test->size != size) {
+ return;
+ }
+ if (test->match_data && val != test->hdr->data) {
+ return;
+ }
+ pci_testdev_inc(test, 1);
+}
+
+static uint64_t
+pci_testdev_read(void *opaque, hwaddr addr, unsigned size)
+{
+ PCITestDevState *d = opaque;
+ const char *buf;
+ IOTest *test;
+ if (d->current < 0) {
+ return 0;
+ }
+ test = &d->tests[d->current];
+ buf = (const char *)test->hdr;
+ if (addr + size >= test->bufsize) {
+ return 0;
+ }
+ if (test->hasnotifier) {
+ event_notifier_test_and_clear(&test->notifier);
+ }
+ return buf[addr];
+}
+
+static void
+pci_testdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ pci_testdev_write(opaque, addr, val, size, 0);
+}
+
+static void
+pci_testdev_pio_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ pci_testdev_write(opaque, addr, val, size, 1);
+}
+
+static const MemoryRegionOps pci_testdev_mmio_ops = {
+ .read = pci_testdev_read,
+ .write = pci_testdev_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const MemoryRegionOps pci_testdev_pio_ops = {
+ .read = pci_testdev_read,
+ .write = pci_testdev_pio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void pci_testdev_realize(PCIDevice *pci_dev, Error **errp)
+{
+ PCITestDevState *d = PCI_TEST_DEV(pci_dev);
+ uint8_t *pci_conf;
+ char *name;
+ int r, i;
+ bool fastmmio = kvm_ioeventfd_any_length_enabled();
+
+ pci_conf = pci_dev->config;
+
+ pci_conf[PCI_INTERRUPT_PIN] = 0; /* no interrupt pin */
+
+ memory_region_init_io(&d->mmio, OBJECT(d), &pci_testdev_mmio_ops, d,
+ "pci-testdev-mmio", IOTEST_MEMSIZE * 2);
+ memory_region_init_io(&d->portio, OBJECT(d), &pci_testdev_pio_ops, d,
+ "pci-testdev-portio", IOTEST_IOSIZE * 2);
+ pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
+ pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &d->portio);
+
+ if (d->membar_size) {
+ memory_region_init(&d->membar, OBJECT(d), "pci-testdev-membar",
+ d->membar_size);
+ pci_register_bar(pci_dev, 2,
+ PCI_BASE_ADDRESS_SPACE_MEMORY |
+ PCI_BASE_ADDRESS_MEM_PREFETCH |
+ PCI_BASE_ADDRESS_MEM_TYPE_64,
+ &d->membar);
+ }
+
+ d->current = -1;
+ d->tests = g_malloc0(IOTEST_MAX * sizeof *d->tests);
+ for (i = 0; i < IOTEST_MAX; ++i) {
+ IOTest *test = &d->tests[i];
+ name = g_strdup_printf("%s-%s", IOTEST_TYPE(i), IOTEST_TEST(i));
+ test->bufsize = sizeof(PCITestDevHdr) + strlen(name) + 1;
+ test->hdr = g_malloc0(test->bufsize);
+ memcpy(test->hdr->name, name, strlen(name) + 1);
+ g_free(name);
+ test->hdr->offset = cpu_to_le32(IOTEST_SIZE(i) + i * IOTEST_ACCESS_WIDTH);
+ test->match_data = strcmp(IOTEST_TEST(i), "wildcard-eventfd");
+ if (fastmmio && IOTEST_IS_MEM(i) && !test->match_data) {
+ test->size = 0;
+ } else {
+ test->size = IOTEST_ACCESS_WIDTH;
+ }
+ test->hdr->test = i;
+ test->hdr->data = test->match_data ? IOTEST_DATAMATCH : IOTEST_NOMATCH;
+ test->hdr->width = IOTEST_ACCESS_WIDTH;
+ test->mr = IOTEST_REGION(d, i);
+ if (!strcmp(IOTEST_TEST(i), "no-eventfd")) {
+ test->hasnotifier = false;
+ continue;
+ }
+ r = event_notifier_init(&test->notifier, 0);
+ assert(r >= 0);
+ test->hasnotifier = true;
+ }
+}
+
+static void
+pci_testdev_uninit(PCIDevice *dev)
+{
+ PCITestDevState *d = PCI_TEST_DEV(dev);
+ int i;
+
+ pci_testdev_reset(d);
+ for (i = 0; i < IOTEST_MAX; ++i) {
+ if (d->tests[i].hasnotifier) {
+ event_notifier_cleanup(&d->tests[i].notifier);
+ }
+ g_free(d->tests[i].hdr);
+ }
+ g_free(d->tests);
+}
+
+static void qdev_pci_testdev_reset(DeviceState *dev)
+{
+ PCITestDevState *d = PCI_TEST_DEV(dev);
+ pci_testdev_reset(d);
+}
+
+static Property pci_testdev_properties[] = {
+ DEFINE_PROP_SIZE("membar", PCITestDevState, membar_size, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pci_testdev_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_testdev_realize;
+ k->exit = pci_testdev_uninit;
+ k->vendor_id = PCI_VENDOR_ID_REDHAT;
+ k->device_id = PCI_DEVICE_ID_REDHAT_TEST;
+ k->revision = 0x00;
+ k->class_id = PCI_CLASS_OTHERS;
+ dc->desc = "PCI Test Device";
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->reset = qdev_pci_testdev_reset;
+ device_class_set_props(dc, pci_testdev_properties);
+}
+
+static const TypeInfo pci_testdev_info = {
+ .name = TYPE_PCI_TEST_DEV,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCITestDevState),
+ .class_init = pci_testdev_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void pci_testdev_register_types(void)
+{
+ type_register_static(&pci_testdev_info);
+}
+
+type_init(pci_testdev_register_types)
diff --git a/hw/misc/pvpanic-isa.c b/hw/misc/pvpanic-isa.c
new file mode 100644
index 000000000..7b66d58ac
--- /dev/null
+++ b/hw/misc/pvpanic-isa.c
@@ -0,0 +1,93 @@
+/*
+ * QEMU simulated pvpanic device.
+ *
+ * Copyright Fujitsu, Corp. 2013
+ *
+ * Authors:
+ * Wen Congyang <wency@cn.fujitsu.com>
+ * Hu Tao <hutao@cn.fujitsu.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "sysemu/runstate.h"
+
+#include "hw/nvram/fw_cfg.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/pvpanic.h"
+#include "qom/object.h"
+#include "hw/isa/isa.h"
+
+OBJECT_DECLARE_SIMPLE_TYPE(PVPanicISAState, PVPANIC_ISA_DEVICE)
+
+/*
+ * PVPanicISAState for ISA device and
+ * use ioport.
+ */
+struct PVPanicISAState {
+ ISADevice parent_obj;
+
+ uint16_t ioport;
+ PVPanicState pvpanic;
+};
+
+static void pvpanic_isa_initfn(Object *obj)
+{
+ PVPanicISAState *s = PVPANIC_ISA_DEVICE(obj);
+
+ pvpanic_setup_io(&s->pvpanic, DEVICE(s), 1);
+}
+
+static void pvpanic_isa_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *d = ISA_DEVICE(dev);
+ PVPanicISAState *s = PVPANIC_ISA_DEVICE(dev);
+ PVPanicState *ps = &s->pvpanic;
+ FWCfgState *fw_cfg = fw_cfg_find();
+ uint16_t *pvpanic_port;
+
+ if (!fw_cfg) {
+ return;
+ }
+
+ pvpanic_port = g_malloc(sizeof(*pvpanic_port));
+ *pvpanic_port = cpu_to_le16(s->ioport);
+ fw_cfg_add_file(fw_cfg, "etc/pvpanic-port", pvpanic_port,
+ sizeof(*pvpanic_port));
+
+ isa_register_ioport(d, &ps->mr, s->ioport);
+}
+
+static Property pvpanic_isa_properties[] = {
+ DEFINE_PROP_UINT16(PVPANIC_IOPORT_PROP, PVPanicISAState, ioport, 0x505),
+ DEFINE_PROP_UINT8("events", PVPanicISAState, pvpanic.events, PVPANIC_PANICKED | PVPANIC_CRASHLOADED),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pvpanic_isa_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = pvpanic_isa_realizefn;
+ device_class_set_props(dc, pvpanic_isa_properties);
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static TypeInfo pvpanic_isa_info = {
+ .name = TYPE_PVPANIC_ISA_DEVICE,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(PVPanicISAState),
+ .instance_init = pvpanic_isa_initfn,
+ .class_init = pvpanic_isa_class_init,
+};
+
+static void pvpanic_register_types(void)
+{
+ type_register_static(&pvpanic_isa_info);
+}
+
+type_init(pvpanic_register_types)
diff --git a/hw/misc/pvpanic-pci.c b/hw/misc/pvpanic-pci.c
new file mode 100644
index 000000000..af8cbe283
--- /dev/null
+++ b/hw/misc/pvpanic-pci.c
@@ -0,0 +1,93 @@
+/*
+ * QEMU simulated PCI pvpanic device.
+ *
+ * Copyright (C) 2020 Oracle
+ *
+ * Authors:
+ * Mihai Carabas <mihai.carabas@oracle.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "sysemu/runstate.h"
+
+#include "hw/nvram/fw_cfg.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/misc/pvpanic.h"
+#include "qom/object.h"
+#include "hw/pci/pci.h"
+
+OBJECT_DECLARE_SIMPLE_TYPE(PVPanicPCIState, PVPANIC_PCI_DEVICE)
+
+/*
+ * PVPanicPCIState for PCI device
+ */
+typedef struct PVPanicPCIState {
+ PCIDevice dev;
+ PVPanicState pvpanic;
+} PVPanicPCIState;
+
+static const VMStateDescription vmstate_pvpanic_pci = {
+ .name = "pvpanic-pci",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PVPanicPCIState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pvpanic_pci_realizefn(PCIDevice *dev, Error **errp)
+{
+ PVPanicPCIState *s = PVPANIC_PCI_DEVICE(dev);
+ PVPanicState *ps = &s->pvpanic;
+
+ pvpanic_setup_io(&s->pvpanic, DEVICE(s), 2);
+
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &ps->mr);
+}
+
+static Property pvpanic_pci_properties[] = {
+ DEFINE_PROP_UINT8("events", PVPanicPCIState, pvpanic.events, PVPANIC_PANICKED | PVPANIC_CRASHLOADED),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pvpanic_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, pvpanic_pci_properties);
+
+ pc->realize = pvpanic_pci_realizefn;
+ pc->vendor_id = PCI_VENDOR_ID_REDHAT;
+ pc->device_id = PCI_DEVICE_ID_REDHAT_PVPANIC;
+ pc->revision = 1;
+ pc->class_id = PCI_CLASS_SYSTEM_OTHER;
+ dc->vmsd = &vmstate_pvpanic_pci;
+
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static TypeInfo pvpanic_pci_info = {
+ .name = TYPE_PVPANIC_PCI_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PVPanicPCIState),
+ .class_init = pvpanic_pci_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { }
+ }
+};
+
+static void pvpanic_register_types(void)
+{
+ type_register_static(&pvpanic_pci_info);
+}
+
+type_init(pvpanic_register_types);
diff --git a/hw/misc/pvpanic.c b/hw/misc/pvpanic.c
new file mode 100644
index 000000000..e2cb4a5d2
--- /dev/null
+++ b/hw/misc/pvpanic.c
@@ -0,0 +1,70 @@
+/*
+ * QEMU simulated pvpanic device.
+ *
+ * Copyright Fujitsu, Corp. 2013
+ *
+ * Authors:
+ * Wen Congyang <wency@cn.fujitsu.com>
+ * Hu Tao <hutao@cn.fujitsu.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "sysemu/runstate.h"
+
+#include "hw/nvram/fw_cfg.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/pvpanic.h"
+#include "qom/object.h"
+
+static void handle_event(int event)
+{
+ static bool logged;
+
+ if (event & ~(PVPANIC_PANICKED | PVPANIC_CRASHLOADED) && !logged) {
+ qemu_log_mask(LOG_GUEST_ERROR, "pvpanic: unknown event %#x.\n", event);
+ logged = true;
+ }
+
+ if (event & PVPANIC_PANICKED) {
+ qemu_system_guest_panicked(NULL);
+ return;
+ }
+
+ if (event & PVPANIC_CRASHLOADED) {
+ qemu_system_guest_crashloaded(NULL);
+ return;
+ }
+}
+
+/* return supported events on read */
+static uint64_t pvpanic_read(void *opaque, hwaddr addr, unsigned size)
+{
+ PVPanicState *pvp = opaque;
+ return pvp->events;
+}
+
+static void pvpanic_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ handle_event(val);
+}
+
+static const MemoryRegionOps pvpanic_ops = {
+ .read = pvpanic_read,
+ .write = pvpanic_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+void pvpanic_setup_io(PVPanicState *s, DeviceState *dev, unsigned size)
+{
+ memory_region_init_io(&s->mr, OBJECT(dev), &pvpanic_ops, s, "pvpanic", size);
+}
diff --git a/hw/misc/sbsa_ec.c b/hw/misc/sbsa_ec.c
new file mode 100644
index 000000000..83020fe9a
--- /dev/null
+++ b/hw/misc/sbsa_ec.c
@@ -0,0 +1,98 @@
+/*
+ * ARM SBSA Reference Platform Embedded Controller
+ *
+ * A device to allow PSCI running in the secure side of sbsa-ref machine
+ * to communicate platform power states to qemu.
+ *
+ * Copyright (c) 2020 Nuvia Inc
+ * Written by Graeme Gregory <graeme@nuviainc.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/log.h"
+#include "hw/sysbus.h"
+#include "sysemu/runstate.h"
+
+typedef struct {
+ SysBusDevice parent_obj;
+ MemoryRegion iomem;
+} SECUREECState;
+
+#define TYPE_SBSA_EC "sbsa-ec"
+#define SECURE_EC(obj) OBJECT_CHECK(SECUREECState, (obj), TYPE_SBSA_EC)
+
+enum sbsa_ec_powerstates {
+ SBSA_EC_CMD_POWEROFF = 0x01,
+ SBSA_EC_CMD_REBOOT = 0x02,
+};
+
+static uint64_t sbsa_ec_read(void *opaque, hwaddr offset, unsigned size)
+{
+ /* No use for this currently */
+ qemu_log_mask(LOG_GUEST_ERROR, "sbsa-ec: no readable registers");
+ return 0;
+}
+
+static void sbsa_ec_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ if (offset == 0) { /* PSCI machine power command register */
+ switch (value) {
+ case SBSA_EC_CMD_POWEROFF:
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ break;
+ case SBSA_EC_CMD_REBOOT:
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "sbsa-ec: unknown power command");
+ }
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "sbsa-ec: unknown EC register");
+ }
+}
+
+static const MemoryRegionOps sbsa_ec_ops = {
+ .read = sbsa_ec_read,
+ .write = sbsa_ec_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static void sbsa_ec_init(Object *obj)
+{
+ SECUREECState *s = SECURE_EC(obj);
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &sbsa_ec_ops, s, "sbsa-ec",
+ 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+}
+
+static void sbsa_ec_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ /* No vmstate or reset required: device has no internal state */
+ dc->user_creatable = false;
+}
+
+static const TypeInfo sbsa_ec_info = {
+ .name = TYPE_SBSA_EC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SECUREECState),
+ .instance_init = sbsa_ec_init,
+ .class_init = sbsa_ec_class_init,
+};
+
+static void sbsa_ec_register_type(void)
+{
+ type_register_static(&sbsa_ec_info);
+}
+
+type_init(sbsa_ec_register_type);
diff --git a/hw/misc/sga.c b/hw/misc/sga.c
new file mode 100644
index 000000000..1d04672b0
--- /dev/null
+++ b/hw/misc/sga.c
@@ -0,0 +1,71 @@
+/*
+ * QEMU dummy ISA device for loading sgabios option rom.
+ *
+ * Copyright (c) 2011 Glauber Costa, Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * sgabios code originally available at code.google.com/p/sgabios
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "hw/isa/isa.h"
+#include "hw/loader.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+#include "qemu/error-report.h"
+
+#define SGABIOS_FILENAME "sgabios.bin"
+
+#define TYPE_SGA "sga"
+OBJECT_DECLARE_SIMPLE_TYPE(ISASGAState, SGA)
+
+struct ISASGAState {
+ ISADevice parent_obj;
+};
+
+static void sga_realizefn(DeviceState *dev, Error **errp)
+{
+ warn_report("-device sga is deprecated, use -machine graphics=off");
+ rom_add_vga(SGABIOS_FILENAME);
+}
+
+static void sga_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->realize = sga_realizefn;
+ dc->desc = "Serial Graphics Adapter";
+}
+
+static const TypeInfo sga_info = {
+ .name = TYPE_SGA,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISASGAState),
+ .class_init = sga_class_initfn,
+};
+
+static void sga_register_types(void)
+{
+ type_register_static(&sga_info);
+}
+
+type_init(sga_register_types)
diff --git a/hw/misc/sifive_e_prci.c b/hw/misc/sifive_e_prci.c
new file mode 100644
index 000000000..a8702c6a5
--- /dev/null
+++ b/hw/misc/sifive_e_prci.c
@@ -0,0 +1,124 @@
+/*
+ * QEMU SiFive E PRCI (Power, Reset, Clock, Interrupt)
+ *
+ * Copyright (c) 2017 SiFive, Inc.
+ *
+ * Simple model of the PRCI to emulate register reads made by the SDK BSP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 "hw/sysbus.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/misc/sifive_e_prci.h"
+
+static uint64_t sifive_e_prci_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ SiFiveEPRCIState *s = opaque;
+ switch (addr) {
+ case SIFIVE_E_PRCI_HFROSCCFG:
+ return s->hfrosccfg;
+ case SIFIVE_E_PRCI_HFXOSCCFG:
+ return s->hfxosccfg;
+ case SIFIVE_E_PRCI_PLLCFG:
+ return s->pllcfg;
+ case SIFIVE_E_PRCI_PLLOUTDIV:
+ return s->plloutdiv;
+ }
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: read: addr=0x%x\n",
+ __func__, (int)addr);
+ return 0;
+}
+
+static void sifive_e_prci_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ SiFiveEPRCIState *s = opaque;
+ switch (addr) {
+ case SIFIVE_E_PRCI_HFROSCCFG:
+ s->hfrosccfg = (uint32_t) val64;
+ /* OSC stays ready */
+ s->hfrosccfg |= SIFIVE_E_PRCI_HFROSCCFG_RDY;
+ break;
+ case SIFIVE_E_PRCI_HFXOSCCFG:
+ s->hfxosccfg = (uint32_t) val64;
+ /* OSC stays ready */
+ s->hfxosccfg |= SIFIVE_E_PRCI_HFXOSCCFG_RDY;
+ break;
+ case SIFIVE_E_PRCI_PLLCFG:
+ s->pllcfg = (uint32_t) val64;
+ /* PLL stays locked */
+ s->pllcfg |= SIFIVE_E_PRCI_PLLCFG_LOCK;
+ break;
+ case SIFIVE_E_PRCI_PLLOUTDIV:
+ s->plloutdiv = (uint32_t) val64;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr=0x%x v=0x%x\n",
+ __func__, (int)addr, (int)val64);
+ }
+}
+
+static const MemoryRegionOps sifive_e_prci_ops = {
+ .read = sifive_e_prci_read,
+ .write = sifive_e_prci_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void sifive_e_prci_init(Object *obj)
+{
+ SiFiveEPRCIState *s = SIFIVE_E_PRCI(obj);
+
+ memory_region_init_io(&s->mmio, obj, &sifive_e_prci_ops, s,
+ TYPE_SIFIVE_E_PRCI, SIFIVE_E_PRCI_REG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+
+ s->hfrosccfg = (SIFIVE_E_PRCI_HFROSCCFG_RDY | SIFIVE_E_PRCI_HFROSCCFG_EN);
+ s->hfxosccfg = (SIFIVE_E_PRCI_HFXOSCCFG_RDY | SIFIVE_E_PRCI_HFXOSCCFG_EN);
+ s->pllcfg = (SIFIVE_E_PRCI_PLLCFG_REFSEL | SIFIVE_E_PRCI_PLLCFG_BYPASS |
+ SIFIVE_E_PRCI_PLLCFG_LOCK);
+ s->plloutdiv = SIFIVE_E_PRCI_PLLOUTDIV_DIV1;
+}
+
+static const TypeInfo sifive_e_prci_info = {
+ .name = TYPE_SIFIVE_E_PRCI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SiFiveEPRCIState),
+ .instance_init = sifive_e_prci_init,
+};
+
+static void sifive_e_prci_register_types(void)
+{
+ type_register_static(&sifive_e_prci_info);
+}
+
+type_init(sifive_e_prci_register_types)
+
+
+/*
+ * Create PRCI device.
+ */
+DeviceState *sifive_e_prci_create(hwaddr addr)
+{
+ DeviceState *dev = qdev_new(TYPE_SIFIVE_E_PRCI);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
+ return dev;
+}
diff --git a/hw/misc/sifive_test.c b/hw/misc/sifive_test.c
new file mode 100644
index 000000000..56df45bfe
--- /dev/null
+++ b/hw/misc/sifive_test.c
@@ -0,0 +1,99 @@
+/*
+ * QEMU SiFive Test Finisher
+ *
+ * Copyright (c) 2018 SiFive, Inc.
+ *
+ * Test finisher memory mapped device used to exit simulation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 "hw/sysbus.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "sysemu/runstate.h"
+#include "hw/misc/sifive_test.h"
+
+static uint64_t sifive_test_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ return 0;
+}
+
+static void sifive_test_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ if (addr == 0) {
+ int status = val64 & 0xffff;
+ int code = (val64 >> 16) & 0xffff;
+ switch (status) {
+ case FINISHER_FAIL:
+ exit(code);
+ case FINISHER_PASS:
+ exit(0);
+ case FINISHER_RESET:
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ return;
+ default:
+ break;
+ }
+ }
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: write: addr=0x%x val=0x%016" PRIx64 "\n",
+ __func__, (int)addr, val64);
+}
+
+static const MemoryRegionOps sifive_test_ops = {
+ .read = sifive_test_read,
+ .write = sifive_test_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 2,
+ .max_access_size = 4
+ }
+};
+
+static void sifive_test_init(Object *obj)
+{
+ SiFiveTestState *s = SIFIVE_TEST(obj);
+
+ memory_region_init_io(&s->mmio, obj, &sifive_test_ops, s,
+ TYPE_SIFIVE_TEST, 0x1000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+}
+
+static const TypeInfo sifive_test_info = {
+ .name = TYPE_SIFIVE_TEST,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SiFiveTestState),
+ .instance_init = sifive_test_init,
+};
+
+static void sifive_test_register_types(void)
+{
+ type_register_static(&sifive_test_info);
+}
+
+type_init(sifive_test_register_types)
+
+
+/*
+ * Create Test device.
+ */
+DeviceState *sifive_test_create(hwaddr addr)
+{
+ DeviceState *dev = qdev_new(TYPE_SIFIVE_TEST);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
+ return dev;
+}
diff --git a/hw/misc/sifive_u_otp.c b/hw/misc/sifive_u_otp.c
new file mode 100644
index 000000000..52fdb750c
--- /dev/null
+++ b/hw/misc/sifive_u_otp.c
@@ -0,0 +1,301 @@
+/*
+ * QEMU SiFive U OTP (One-Time Programmable) Memory interface
+ *
+ * Copyright (c) 2019 Bin Meng <bmeng.cn@gmail.com>
+ *
+ * Simple model of the OTP to emulate register reads made by the SDK BSP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 "qapi/error.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "hw/sysbus.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/misc/sifive_u_otp.h"
+#include "sysemu/blockdev.h"
+#include "sysemu/block-backend.h"
+
+#define WRITTEN_BIT_ON 0x1
+
+#define SET_FUSEARRAY_BIT(map, i, off, bit) \
+ map[i] = bit ? (map[i] | bit << off) : (map[i] & ~(0x1 << off))
+
+#define GET_FUSEARRAY_BIT(map, i, off) \
+ ((map[i] >> off) & 0x1)
+
+static uint64_t sifive_u_otp_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ SiFiveUOTPState *s = opaque;
+
+ switch (addr) {
+ case SIFIVE_U_OTP_PA:
+ return s->pa;
+ case SIFIVE_U_OTP_PAIO:
+ return s->paio;
+ case SIFIVE_U_OTP_PAS:
+ return s->pas;
+ case SIFIVE_U_OTP_PCE:
+ return s->pce;
+ case SIFIVE_U_OTP_PCLK:
+ return s->pclk;
+ case SIFIVE_U_OTP_PDIN:
+ return s->pdin;
+ case SIFIVE_U_OTP_PDOUT:
+ if ((s->pce & SIFIVE_U_OTP_PCE_EN) &&
+ (s->pdstb & SIFIVE_U_OTP_PDSTB_EN) &&
+ (s->ptrim & SIFIVE_U_OTP_PTRIM_EN)) {
+
+ /* read from backend */
+ if (s->blk) {
+ int32_t buf;
+
+ if (blk_pread(s->blk, s->pa * SIFIVE_U_OTP_FUSE_WORD, &buf,
+ SIFIVE_U_OTP_FUSE_WORD) < 0) {
+ error_report("read error index<%d>", s->pa);
+ return 0xff;
+ }
+
+ return buf;
+ }
+
+ return s->fuse[s->pa & SIFIVE_U_OTP_PA_MASK];
+ } else {
+ return 0xff;
+ }
+ case SIFIVE_U_OTP_PDSTB:
+ return s->pdstb;
+ case SIFIVE_U_OTP_PPROG:
+ return s->pprog;
+ case SIFIVE_U_OTP_PTC:
+ return s->ptc;
+ case SIFIVE_U_OTP_PTM:
+ return s->ptm;
+ case SIFIVE_U_OTP_PTM_REP:
+ return s->ptm_rep;
+ case SIFIVE_U_OTP_PTR:
+ return s->ptr;
+ case SIFIVE_U_OTP_PTRIM:
+ return s->ptrim;
+ case SIFIVE_U_OTP_PWE:
+ return s->pwe;
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: read: addr=0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+ return 0;
+}
+
+static void sifive_u_otp_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ SiFiveUOTPState *s = opaque;
+ uint32_t val32 = (uint32_t)val64;
+
+ switch (addr) {
+ case SIFIVE_U_OTP_PA:
+ s->pa = val32 & SIFIVE_U_OTP_PA_MASK;
+ break;
+ case SIFIVE_U_OTP_PAIO:
+ s->paio = val32;
+ break;
+ case SIFIVE_U_OTP_PAS:
+ s->pas = val32;
+ break;
+ case SIFIVE_U_OTP_PCE:
+ s->pce = val32;
+ break;
+ case SIFIVE_U_OTP_PCLK:
+ s->pclk = val32;
+ break;
+ case SIFIVE_U_OTP_PDIN:
+ s->pdin = val32;
+ break;
+ case SIFIVE_U_OTP_PDOUT:
+ /* read-only */
+ break;
+ case SIFIVE_U_OTP_PDSTB:
+ s->pdstb = val32;
+ break;
+ case SIFIVE_U_OTP_PPROG:
+ s->pprog = val32;
+ break;
+ case SIFIVE_U_OTP_PTC:
+ s->ptc = val32;
+ break;
+ case SIFIVE_U_OTP_PTM:
+ s->ptm = val32;
+ break;
+ case SIFIVE_U_OTP_PTM_REP:
+ s->ptm_rep = val32;
+ break;
+ case SIFIVE_U_OTP_PTR:
+ s->ptr = val32;
+ break;
+ case SIFIVE_U_OTP_PTRIM:
+ s->ptrim = val32;
+ break;
+ case SIFIVE_U_OTP_PWE:
+ s->pwe = val32 & SIFIVE_U_OTP_PWE_EN;
+
+ /* PWE is enabled. Ignore PAS=1 (no redundancy cell) */
+ if (s->pwe && !s->pas) {
+ if (GET_FUSEARRAY_BIT(s->fuse_wo, s->pa, s->paio)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "write once error: idx<%u>, bit<%u>\n",
+ s->pa, s->paio);
+ break;
+ }
+
+ /* write bit data */
+ SET_FUSEARRAY_BIT(s->fuse, s->pa, s->paio, s->pdin);
+
+ /* write to backend */
+ if (s->blk) {
+ if (blk_pwrite(s->blk, s->pa * SIFIVE_U_OTP_FUSE_WORD,
+ &s->fuse[s->pa], SIFIVE_U_OTP_FUSE_WORD,
+ 0) < 0) {
+ error_report("write error index<%d>", s->pa);
+ }
+ }
+
+ /* update written bit */
+ SET_FUSEARRAY_BIT(s->fuse_wo, s->pa, s->paio, WRITTEN_BIT_ON);
+ }
+
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr=0x%" HWADDR_PRIx
+ " v=0x%x\n", __func__, addr, val32);
+ }
+}
+
+static const MemoryRegionOps sifive_u_otp_ops = {
+ .read = sifive_u_otp_read,
+ .write = sifive_u_otp_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static Property sifive_u_otp_properties[] = {
+ DEFINE_PROP_UINT32("serial", SiFiveUOTPState, serial, 0),
+ DEFINE_PROP_DRIVE("drive", SiFiveUOTPState, blk),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sifive_u_otp_realize(DeviceState *dev, Error **errp)
+{
+ SiFiveUOTPState *s = SIFIVE_U_OTP(dev);
+ DriveInfo *dinfo;
+
+ memory_region_init_io(&s->mmio, OBJECT(dev), &sifive_u_otp_ops, s,
+ TYPE_SIFIVE_U_OTP, SIFIVE_U_OTP_REG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
+
+ dinfo = drive_get_next(IF_PFLASH);
+ if (!dinfo) {
+ dinfo = drive_get_next(IF_NONE);
+ if (dinfo) {
+ warn_report("using \"-drive if=none\" for the OTP is deprecated, "
+ "use \"-drive if=pflash\" instead.");
+ }
+ }
+ if (dinfo) {
+ int ret;
+ uint64_t perm;
+ int filesize;
+ BlockBackend *blk;
+
+ blk = blk_by_legacy_dinfo(dinfo);
+ filesize = SIFIVE_U_OTP_NUM_FUSES * SIFIVE_U_OTP_FUSE_WORD;
+ if (blk_getlength(blk) < filesize) {
+ error_setg(errp, "OTP drive size < 16K");
+ return;
+ }
+
+ qdev_prop_set_drive_err(dev, "drive", blk, errp);
+
+ if (s->blk) {
+ perm = BLK_PERM_CONSISTENT_READ |
+ (blk_supports_write_perm(s->blk) ? BLK_PERM_WRITE : 0);
+ ret = blk_set_perm(s->blk, perm, BLK_PERM_ALL, errp);
+ if (ret < 0) {
+ return;
+ }
+
+ if (blk_pread(s->blk, 0, s->fuse, filesize) != filesize) {
+ error_setg(errp, "failed to read the initial flash content");
+ return;
+ }
+ }
+ }
+
+ /* Initialize all fuses' initial value to 0xFFs */
+ memset(s->fuse, 0xff, sizeof(s->fuse));
+
+ /* Make a valid content of serial number */
+ s->fuse[SIFIVE_U_OTP_SERIAL_ADDR] = s->serial;
+ s->fuse[SIFIVE_U_OTP_SERIAL_ADDR + 1] = ~(s->serial);
+
+ if (s->blk) {
+ /* Put serial number to backend as well*/
+ uint32_t serial_data;
+ int index = SIFIVE_U_OTP_SERIAL_ADDR;
+
+ serial_data = s->serial;
+ if (blk_pwrite(s->blk, index * SIFIVE_U_OTP_FUSE_WORD,
+ &serial_data, SIFIVE_U_OTP_FUSE_WORD, 0) < 0) {
+ error_setg(errp, "failed to write index<%d>", index);
+ return;
+ }
+
+ serial_data = ~(s->serial);
+ if (blk_pwrite(s->blk, (index + 1) * SIFIVE_U_OTP_FUSE_WORD,
+ &serial_data, SIFIVE_U_OTP_FUSE_WORD, 0) < 0) {
+ error_setg(errp, "failed to write index<%d>", index + 1);
+ return;
+ }
+ }
+
+ /* Initialize write-once map */
+ memset(s->fuse_wo, 0x00, sizeof(s->fuse_wo));
+}
+
+static void sifive_u_otp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, sifive_u_otp_properties);
+ dc->realize = sifive_u_otp_realize;
+}
+
+static const TypeInfo sifive_u_otp_info = {
+ .name = TYPE_SIFIVE_U_OTP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SiFiveUOTPState),
+ .class_init = sifive_u_otp_class_init,
+};
+
+static void sifive_u_otp_register_types(void)
+{
+ type_register_static(&sifive_u_otp_info);
+}
+
+type_init(sifive_u_otp_register_types)
diff --git a/hw/misc/sifive_u_prci.c b/hw/misc/sifive_u_prci.c
new file mode 100644
index 000000000..5d9d446ee
--- /dev/null
+++ b/hw/misc/sifive_u_prci.c
@@ -0,0 +1,169 @@
+/*
+ * QEMU SiFive U PRCI (Power, Reset, Clock, Interrupt)
+ *
+ * Copyright (c) 2019 Bin Meng <bmeng.cn@gmail.com>
+ *
+ * Simple model of the PRCI to emulate register reads made by the SDK BSP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 "hw/sysbus.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/misc/sifive_u_prci.h"
+
+static uint64_t sifive_u_prci_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ SiFiveUPRCIState *s = opaque;
+
+ switch (addr) {
+ case SIFIVE_U_PRCI_HFXOSCCFG:
+ return s->hfxosccfg;
+ case SIFIVE_U_PRCI_COREPLLCFG0:
+ return s->corepllcfg0;
+ case SIFIVE_U_PRCI_DDRPLLCFG0:
+ return s->ddrpllcfg0;
+ case SIFIVE_U_PRCI_DDRPLLCFG1:
+ return s->ddrpllcfg1;
+ case SIFIVE_U_PRCI_GEMGXLPLLCFG0:
+ return s->gemgxlpllcfg0;
+ case SIFIVE_U_PRCI_GEMGXLPLLCFG1:
+ return s->gemgxlpllcfg1;
+ case SIFIVE_U_PRCI_CORECLKSEL:
+ return s->coreclksel;
+ case SIFIVE_U_PRCI_DEVICESRESET:
+ return s->devicesreset;
+ case SIFIVE_U_PRCI_CLKMUXSTATUS:
+ return s->clkmuxstatus;
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: read: addr=0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+
+ return 0;
+}
+
+static void sifive_u_prci_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ SiFiveUPRCIState *s = opaque;
+ uint32_t val32 = (uint32_t)val64;
+
+ switch (addr) {
+ case SIFIVE_U_PRCI_HFXOSCCFG:
+ s->hfxosccfg = val32;
+ /* OSC stays ready */
+ s->hfxosccfg |= SIFIVE_U_PRCI_HFXOSCCFG_RDY;
+ break;
+ case SIFIVE_U_PRCI_COREPLLCFG0:
+ s->corepllcfg0 = val32;
+ /* internal feedback */
+ s->corepllcfg0 |= SIFIVE_U_PRCI_PLLCFG0_FSE;
+ /* PLL stays locked */
+ s->corepllcfg0 |= SIFIVE_U_PRCI_PLLCFG0_LOCK;
+ break;
+ case SIFIVE_U_PRCI_DDRPLLCFG0:
+ s->ddrpllcfg0 = val32;
+ /* internal feedback */
+ s->ddrpllcfg0 |= SIFIVE_U_PRCI_PLLCFG0_FSE;
+ /* PLL stays locked */
+ s->ddrpllcfg0 |= SIFIVE_U_PRCI_PLLCFG0_LOCK;
+ break;
+ case SIFIVE_U_PRCI_DDRPLLCFG1:
+ s->ddrpllcfg1 = val32;
+ break;
+ case SIFIVE_U_PRCI_GEMGXLPLLCFG0:
+ s->gemgxlpllcfg0 = val32;
+ /* internal feedback */
+ s->gemgxlpllcfg0 |= SIFIVE_U_PRCI_PLLCFG0_FSE;
+ /* PLL stays locked */
+ s->gemgxlpllcfg0 |= SIFIVE_U_PRCI_PLLCFG0_LOCK;
+ break;
+ case SIFIVE_U_PRCI_GEMGXLPLLCFG1:
+ s->gemgxlpllcfg1 = val32;
+ break;
+ case SIFIVE_U_PRCI_CORECLKSEL:
+ s->coreclksel = val32;
+ break;
+ case SIFIVE_U_PRCI_DEVICESRESET:
+ s->devicesreset = val32;
+ break;
+ case SIFIVE_U_PRCI_CLKMUXSTATUS:
+ s->clkmuxstatus = val32;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr=0x%" HWADDR_PRIx
+ " v=0x%x\n", __func__, addr, val32);
+ }
+}
+
+static const MemoryRegionOps sifive_u_prci_ops = {
+ .read = sifive_u_prci_read,
+ .write = sifive_u_prci_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void sifive_u_prci_realize(DeviceState *dev, Error **errp)
+{
+ SiFiveUPRCIState *s = SIFIVE_U_PRCI(dev);
+
+ memory_region_init_io(&s->mmio, OBJECT(dev), &sifive_u_prci_ops, s,
+ TYPE_SIFIVE_U_PRCI, SIFIVE_U_PRCI_REG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
+}
+
+static void sifive_u_prci_reset(DeviceState *dev)
+{
+ SiFiveUPRCIState *s = SIFIVE_U_PRCI(dev);
+
+ /* Initialize register to power-on-reset values */
+ s->hfxosccfg = SIFIVE_U_PRCI_HFXOSCCFG_RDY | SIFIVE_U_PRCI_HFXOSCCFG_EN;
+ s->corepllcfg0 = SIFIVE_U_PRCI_PLLCFG0_DIVR | SIFIVE_U_PRCI_PLLCFG0_DIVF |
+ SIFIVE_U_PRCI_PLLCFG0_DIVQ | SIFIVE_U_PRCI_PLLCFG0_FSE |
+ SIFIVE_U_PRCI_PLLCFG0_LOCK;
+ s->ddrpllcfg0 = SIFIVE_U_PRCI_PLLCFG0_DIVR | SIFIVE_U_PRCI_PLLCFG0_DIVF |
+ SIFIVE_U_PRCI_PLLCFG0_DIVQ | SIFIVE_U_PRCI_PLLCFG0_FSE |
+ SIFIVE_U_PRCI_PLLCFG0_LOCK;
+ s->gemgxlpllcfg0 = SIFIVE_U_PRCI_PLLCFG0_DIVR | SIFIVE_U_PRCI_PLLCFG0_DIVF |
+ SIFIVE_U_PRCI_PLLCFG0_DIVQ | SIFIVE_U_PRCI_PLLCFG0_FSE |
+ SIFIVE_U_PRCI_PLLCFG0_LOCK;
+ s->coreclksel = SIFIVE_U_PRCI_CORECLKSEL_HFCLK;
+}
+
+static void sifive_u_prci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = sifive_u_prci_realize;
+ dc->reset = sifive_u_prci_reset;
+}
+
+static const TypeInfo sifive_u_prci_info = {
+ .name = TYPE_SIFIVE_U_PRCI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SiFiveUPRCIState),
+ .class_init = sifive_u_prci_class_init,
+};
+
+static void sifive_u_prci_register_types(void)
+{
+ type_register_static(&sifive_u_prci_info);
+}
+
+type_init(sifive_u_prci_register_types)
diff --git a/hw/misc/slavio_misc.c b/hw/misc/slavio_misc.c
new file mode 100644
index 000000000..e8eb71570
--- /dev/null
+++ b/hw/misc/slavio_misc.c
@@ -0,0 +1,515 @@
+/*
+ * QEMU Sparc SLAVIO aux io port emulation
+ *
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "sysemu/runstate.h"
+#include "trace.h"
+#include "qom/object.h"
+
+/*
+ * This is the auxio port, chip control and system control part of
+ * chip STP2001 (Slave I/O), also produced as NCR89C105. See
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C105.txt
+ *
+ * This also includes the PMC CPU idle controller.
+ */
+
+#define TYPE_SLAVIO_MISC "slavio_misc"
+OBJECT_DECLARE_SIMPLE_TYPE(MiscState, SLAVIO_MISC)
+
+struct MiscState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion cfg_iomem;
+ MemoryRegion diag_iomem;
+ MemoryRegion mdm_iomem;
+ MemoryRegion led_iomem;
+ MemoryRegion sysctrl_iomem;
+ MemoryRegion aux1_iomem;
+ MemoryRegion aux2_iomem;
+ qemu_irq irq;
+ qemu_irq fdc_tc;
+ uint32_t dummy;
+ uint8_t config;
+ uint8_t aux1, aux2;
+ uint8_t diag, mctrl;
+ uint8_t sysctrl;
+ uint16_t leds;
+};
+
+#define TYPE_APC "apc"
+typedef struct APCState APCState;
+DECLARE_INSTANCE_CHECKER(APCState, APC,
+ TYPE_APC)
+
+struct APCState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq cpu_halt;
+};
+
+#define MISC_SIZE 1
+#define LED_SIZE 2
+#define SYSCTRL_SIZE 4
+
+#define AUX1_TC 0x02
+
+#define AUX2_PWROFF 0x01
+#define AUX2_PWRINTCLR 0x02
+#define AUX2_PWRFAIL 0x20
+
+#define CFG_PWRINTEN 0x08
+
+#define SYS_RESET 0x01
+#define SYS_RESETSTAT 0x02
+
+static void slavio_misc_update_irq(void *opaque)
+{
+ MiscState *s = opaque;
+
+ if ((s->aux2 & AUX2_PWRFAIL) && (s->config & CFG_PWRINTEN)) {
+ trace_slavio_misc_update_irq_raise();
+ qemu_irq_raise(s->irq);
+ } else {
+ trace_slavio_misc_update_irq_lower();
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void slavio_misc_reset(DeviceState *d)
+{
+ MiscState *s = SLAVIO_MISC(d);
+
+ // Diagnostic and system control registers not cleared in reset
+ s->config = s->aux1 = s->aux2 = s->mctrl = 0;
+}
+
+static void slavio_set_power_fail(void *opaque, int irq, int power_failing)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_set_power_fail(power_failing, s->config);
+ if (power_failing && (s->config & CFG_PWRINTEN)) {
+ s->aux2 |= AUX2_PWRFAIL;
+ } else {
+ s->aux2 &= ~AUX2_PWRFAIL;
+ }
+ slavio_misc_update_irq(s);
+}
+
+static void slavio_cfg_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_cfg_mem_writeb(val & 0xff);
+ s->config = val & 0xff;
+ slavio_misc_update_irq(s);
+}
+
+static uint64_t slavio_cfg_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ ret = s->config;
+ trace_slavio_cfg_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps slavio_cfg_mem_ops = {
+ .read = slavio_cfg_mem_readb,
+ .write = slavio_cfg_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void slavio_diag_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_diag_mem_writeb(val & 0xff);
+ s->diag = val & 0xff;
+}
+
+static uint64_t slavio_diag_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ ret = s->diag;
+ trace_slavio_diag_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps slavio_diag_mem_ops = {
+ .read = slavio_diag_mem_readb,
+ .write = slavio_diag_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void slavio_mdm_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_mdm_mem_writeb(val & 0xff);
+ s->mctrl = val & 0xff;
+}
+
+static uint64_t slavio_mdm_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ ret = s->mctrl;
+ trace_slavio_mdm_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps slavio_mdm_mem_ops = {
+ .read = slavio_mdm_mem_readb,
+ .write = slavio_mdm_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void slavio_aux1_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_aux1_mem_writeb(val & 0xff);
+ if (val & AUX1_TC) {
+ // Send a pulse to floppy terminal count line
+ if (s->fdc_tc) {
+ qemu_irq_raise(s->fdc_tc);
+ qemu_irq_lower(s->fdc_tc);
+ }
+ val &= ~AUX1_TC;
+ }
+ s->aux1 = val & 0xff;
+}
+
+static uint64_t slavio_aux1_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ ret = s->aux1;
+ trace_slavio_aux1_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps slavio_aux1_mem_ops = {
+ .read = slavio_aux1_mem_readb,
+ .write = slavio_aux1_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void slavio_aux2_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ val &= AUX2_PWRINTCLR | AUX2_PWROFF;
+ trace_slavio_aux2_mem_writeb(val & 0xff);
+ val |= s->aux2 & AUX2_PWRFAIL;
+ if (val & AUX2_PWRINTCLR) // Clear Power Fail int
+ val &= AUX2_PWROFF;
+ s->aux2 = val;
+ if (val & AUX2_PWROFF)
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ slavio_misc_update_irq(s);
+}
+
+static uint64_t slavio_aux2_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ ret = s->aux2;
+ trace_slavio_aux2_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps slavio_aux2_mem_ops = {
+ .read = slavio_aux2_mem_readb,
+ .write = slavio_aux2_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void apc_mem_writeb(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ APCState *s = opaque;
+
+ trace_apc_mem_writeb(val & 0xff);
+ qemu_irq_raise(s->cpu_halt);
+}
+
+static uint64_t apc_mem_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint32_t ret = 0;
+
+ trace_apc_mem_readb(ret);
+ return ret;
+}
+
+static const MemoryRegionOps apc_mem_ops = {
+ .read = apc_mem_readb,
+ .write = apc_mem_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ }
+};
+
+static uint64_t slavio_sysctrl_mem_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ switch (addr) {
+ case 0:
+ ret = s->sysctrl;
+ break;
+ default:
+ break;
+ }
+ trace_slavio_sysctrl_mem_readl(ret);
+ return ret;
+}
+
+static void slavio_sysctrl_mem_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_sysctrl_mem_writel(val);
+ switch (addr) {
+ case 0:
+ if (val & SYS_RESET) {
+ s->sysctrl = SYS_RESETSTAT;
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps slavio_sysctrl_mem_ops = {
+ .read = slavio_sysctrl_mem_readl,
+ .write = slavio_sysctrl_mem_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t slavio_led_mem_readw(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MiscState *s = opaque;
+ uint32_t ret = 0;
+
+ switch (addr) {
+ case 0:
+ ret = s->leds;
+ break;
+ default:
+ break;
+ }
+ trace_slavio_led_mem_readw(ret);
+ return ret;
+}
+
+static void slavio_led_mem_writew(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MiscState *s = opaque;
+
+ trace_slavio_led_mem_writew(val & 0xffff);
+ switch (addr) {
+ case 0:
+ s->leds = val;
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps slavio_led_mem_ops = {
+ .read = slavio_led_mem_readw,
+ .write = slavio_led_mem_writew,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 2,
+ .max_access_size = 2,
+ },
+};
+
+static const VMStateDescription vmstate_misc = {
+ .name ="slavio_misc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(dummy, MiscState),
+ VMSTATE_UINT8(config, MiscState),
+ VMSTATE_UINT8(aux1, MiscState),
+ VMSTATE_UINT8(aux2, MiscState),
+ VMSTATE_UINT8(diag, MiscState),
+ VMSTATE_UINT8(mctrl, MiscState),
+ VMSTATE_UINT8(sysctrl, MiscState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void apc_init(Object *obj)
+{
+ APCState *s = APC(obj);
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+
+ sysbus_init_irq(dev, &s->cpu_halt);
+
+ /* Power management (APC) XXX: not a Slavio device */
+ memory_region_init_io(&s->iomem, obj, &apc_mem_ops, s,
+ "apc", MISC_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+}
+
+static void slavio_misc_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ MiscState *s = SLAVIO_MISC(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->fdc_tc);
+
+ /* 8 bit registers */
+ /* Slavio control */
+ memory_region_init_io(&s->cfg_iomem, obj, &slavio_cfg_mem_ops, s,
+ "configuration", MISC_SIZE);
+ sysbus_init_mmio(sbd, &s->cfg_iomem);
+
+ /* Diagnostics */
+ memory_region_init_io(&s->diag_iomem, obj, &slavio_diag_mem_ops, s,
+ "diagnostic", MISC_SIZE);
+ sysbus_init_mmio(sbd, &s->diag_iomem);
+
+ /* Modem control */
+ memory_region_init_io(&s->mdm_iomem, obj, &slavio_mdm_mem_ops, s,
+ "modem", MISC_SIZE);
+ sysbus_init_mmio(sbd, &s->mdm_iomem);
+
+ /* 16 bit registers */
+ /* ss600mp diag LEDs */
+ memory_region_init_io(&s->led_iomem, obj, &slavio_led_mem_ops, s,
+ "leds", LED_SIZE);
+ sysbus_init_mmio(sbd, &s->led_iomem);
+
+ /* 32 bit registers */
+ /* System control */
+ memory_region_init_io(&s->sysctrl_iomem, obj, &slavio_sysctrl_mem_ops, s,
+ "system-control", SYSCTRL_SIZE);
+ sysbus_init_mmio(sbd, &s->sysctrl_iomem);
+
+ /* AUX 1 (Misc System Functions) */
+ memory_region_init_io(&s->aux1_iomem, obj, &slavio_aux1_mem_ops, s,
+ "misc-system-functions", MISC_SIZE);
+ sysbus_init_mmio(sbd, &s->aux1_iomem);
+
+ /* AUX 2 (Software Powerdown Control) */
+ memory_region_init_io(&s->aux2_iomem, obj, &slavio_aux2_mem_ops, s,
+ "software-powerdown-control", MISC_SIZE);
+ sysbus_init_mmio(sbd, &s->aux2_iomem);
+
+ qdev_init_gpio_in(dev, slavio_set_power_fail, 1);
+}
+
+static void slavio_misc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = slavio_misc_reset;
+ dc->vmsd = &vmstate_misc;
+}
+
+static const TypeInfo slavio_misc_info = {
+ .name = TYPE_SLAVIO_MISC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MiscState),
+ .instance_init = slavio_misc_init,
+ .class_init = slavio_misc_class_init,
+};
+
+static const TypeInfo apc_info = {
+ .name = TYPE_APC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MiscState),
+ .instance_init = apc_init,
+};
+
+static void slavio_misc_register_types(void)
+{
+ type_register_static(&slavio_misc_info);
+ type_register_static(&apc_info);
+}
+
+type_init(slavio_misc_register_types)
diff --git a/hw/misc/stm32f2xx_syscfg.c b/hw/misc/stm32f2xx_syscfg.c
new file mode 100644
index 000000000..04c22c285
--- /dev/null
+++ b/hw/misc/stm32f2xx_syscfg.c
@@ -0,0 +1,161 @@
+/*
+ * STM32F2XX SYSCFG
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/stm32f2xx_syscfg.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#ifndef STM_SYSCFG_ERR_DEBUG
+#define STM_SYSCFG_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(lvl, fmt, args...) do { \
+ if (STM_SYSCFG_ERR_DEBUG >= lvl) { \
+ qemu_log("%s: " fmt, __func__, ## args); \
+ } \
+} while (0)
+
+#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args)
+
+static void stm32f2xx_syscfg_reset(DeviceState *dev)
+{
+ STM32F2XXSyscfgState *s = STM32F2XX_SYSCFG(dev);
+
+ s->syscfg_memrmp = 0x00000000;
+ s->syscfg_pmc = 0x00000000;
+ s->syscfg_exticr1 = 0x00000000;
+ s->syscfg_exticr2 = 0x00000000;
+ s->syscfg_exticr3 = 0x00000000;
+ s->syscfg_exticr4 = 0x00000000;
+ s->syscfg_cmpcr = 0x00000000;
+}
+
+static uint64_t stm32f2xx_syscfg_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ STM32F2XXSyscfgState *s = opaque;
+
+ DB_PRINT("0x%"HWADDR_PRIx"\n", addr);
+
+ switch (addr) {
+ case SYSCFG_MEMRMP:
+ return s->syscfg_memrmp;
+ case SYSCFG_PMC:
+ return s->syscfg_pmc;
+ case SYSCFG_EXTICR1:
+ return s->syscfg_exticr1;
+ case SYSCFG_EXTICR2:
+ return s->syscfg_exticr2;
+ case SYSCFG_EXTICR3:
+ return s->syscfg_exticr3;
+ case SYSCFG_EXTICR4:
+ return s->syscfg_exticr4;
+ case SYSCFG_CMPCR:
+ return s->syscfg_cmpcr;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
+ return 0;
+ }
+
+ return 0;
+}
+
+static void stm32f2xx_syscfg_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ STM32F2XXSyscfgState *s = opaque;
+ uint32_t value = val64;
+
+ DB_PRINT("0x%x, 0x%"HWADDR_PRIx"\n", value, addr);
+
+ switch (addr) {
+ case SYSCFG_MEMRMP:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Changeing the memory mapping isn't supported " \
+ "in QEMU\n", __func__);
+ return;
+ case SYSCFG_PMC:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Changeing the memory mapping isn't supported " \
+ "in QEMU\n", __func__);
+ return;
+ case SYSCFG_EXTICR1:
+ s->syscfg_exticr1 = (value & 0xFFFF);
+ return;
+ case SYSCFG_EXTICR2:
+ s->syscfg_exticr2 = (value & 0xFFFF);
+ return;
+ case SYSCFG_EXTICR3:
+ s->syscfg_exticr3 = (value & 0xFFFF);
+ return;
+ case SYSCFG_EXTICR4:
+ s->syscfg_exticr4 = (value & 0xFFFF);
+ return;
+ case SYSCFG_CMPCR:
+ s->syscfg_cmpcr = value;
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
+ }
+}
+
+static const MemoryRegionOps stm32f2xx_syscfg_ops = {
+ .read = stm32f2xx_syscfg_read,
+ .write = stm32f2xx_syscfg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void stm32f2xx_syscfg_init(Object *obj)
+{
+ STM32F2XXSyscfgState *s = STM32F2XX_SYSCFG(obj);
+
+ memory_region_init_io(&s->mmio, obj, &stm32f2xx_syscfg_ops, s,
+ TYPE_STM32F2XX_SYSCFG, 0x400);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+}
+
+static void stm32f2xx_syscfg_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = stm32f2xx_syscfg_reset;
+}
+
+static const TypeInfo stm32f2xx_syscfg_info = {
+ .name = TYPE_STM32F2XX_SYSCFG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(STM32F2XXSyscfgState),
+ .instance_init = stm32f2xx_syscfg_init,
+ .class_init = stm32f2xx_syscfg_class_init,
+};
+
+static void stm32f2xx_syscfg_register_types(void)
+{
+ type_register_static(&stm32f2xx_syscfg_info);
+}
+
+type_init(stm32f2xx_syscfg_register_types)
diff --git a/hw/misc/stm32f4xx_exti.c b/hw/misc/stm32f4xx_exti.c
new file mode 100644
index 000000000..02e781004
--- /dev/null
+++ b/hw/misc/stm32f4xx_exti.c
@@ -0,0 +1,188 @@
+/*
+ * STM32F4XX EXTI
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "hw/misc/stm32f4xx_exti.h"
+
+static void stm32f4xx_exti_reset(DeviceState *dev)
+{
+ STM32F4xxExtiState *s = STM32F4XX_EXTI(dev);
+
+ s->exti_imr = 0x00000000;
+ s->exti_emr = 0x00000000;
+ s->exti_rtsr = 0x00000000;
+ s->exti_ftsr = 0x00000000;
+ s->exti_swier = 0x00000000;
+ s->exti_pr = 0x00000000;
+}
+
+static void stm32f4xx_exti_set_irq(void *opaque, int irq, int level)
+{
+ STM32F4xxExtiState *s = opaque;
+
+ trace_stm32f4xx_exti_set_irq(irq, level);
+
+ if (((1 << irq) & s->exti_rtsr) && level) {
+ /* Rising Edge */
+ s->exti_pr |= 1 << irq;
+ }
+
+ if (((1 << irq) & s->exti_ftsr) && !level) {
+ /* Falling Edge */
+ s->exti_pr |= 1 << irq;
+ }
+
+ if (!((1 << irq) & s->exti_imr)) {
+ /* Interrupt is masked */
+ return;
+ }
+ qemu_irq_pulse(s->irq[irq]);
+}
+
+static uint64_t stm32f4xx_exti_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ STM32F4xxExtiState *s = opaque;
+
+ trace_stm32f4xx_exti_read(addr);
+
+ switch (addr) {
+ case EXTI_IMR:
+ return s->exti_imr;
+ case EXTI_EMR:
+ return s->exti_emr;
+ case EXTI_RTSR:
+ return s->exti_rtsr;
+ case EXTI_FTSR:
+ return s->exti_ftsr;
+ case EXTI_SWIER:
+ return s->exti_swier;
+ case EXTI_PR:
+ return s->exti_pr;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "STM32F4XX_exti_read: Bad offset %x\n", (int)addr);
+ return 0;
+ }
+ return 0;
+}
+
+static void stm32f4xx_exti_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ STM32F4xxExtiState *s = opaque;
+ uint32_t value = (uint32_t) val64;
+
+ trace_stm32f4xx_exti_write(addr, value);
+
+ switch (addr) {
+ case EXTI_IMR:
+ s->exti_imr = value;
+ return;
+ case EXTI_EMR:
+ s->exti_emr = value;
+ return;
+ case EXTI_RTSR:
+ s->exti_rtsr = value;
+ return;
+ case EXTI_FTSR:
+ s->exti_ftsr = value;
+ return;
+ case EXTI_SWIER:
+ s->exti_swier = value;
+ return;
+ case EXTI_PR:
+ /* This bit is cleared by writing a 1 to it */
+ s->exti_pr &= ~value;
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "STM32F4XX_exti_write: Bad offset %x\n", (int)addr);
+ }
+}
+
+static const MemoryRegionOps stm32f4xx_exti_ops = {
+ .read = stm32f4xx_exti_read,
+ .write = stm32f4xx_exti_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void stm32f4xx_exti_init(Object *obj)
+{
+ STM32F4xxExtiState *s = STM32F4XX_EXTI(obj);
+ int i;
+
+ for (i = 0; i < NUM_INTERRUPT_OUT_LINES; i++) {
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[i]);
+ }
+
+ memory_region_init_io(&s->mmio, obj, &stm32f4xx_exti_ops, s,
+ TYPE_STM32F4XX_EXTI, 0x400);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+
+ qdev_init_gpio_in(DEVICE(obj), stm32f4xx_exti_set_irq,
+ NUM_GPIO_EVENT_IN_LINES);
+}
+
+static const VMStateDescription vmstate_stm32f4xx_exti = {
+ .name = TYPE_STM32F4XX_EXTI,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(exti_imr, STM32F4xxExtiState),
+ VMSTATE_UINT32(exti_emr, STM32F4xxExtiState),
+ VMSTATE_UINT32(exti_rtsr, STM32F4xxExtiState),
+ VMSTATE_UINT32(exti_ftsr, STM32F4xxExtiState),
+ VMSTATE_UINT32(exti_swier, STM32F4xxExtiState),
+ VMSTATE_UINT32(exti_pr, STM32F4xxExtiState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void stm32f4xx_exti_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = stm32f4xx_exti_reset;
+ dc->vmsd = &vmstate_stm32f4xx_exti;
+}
+
+static const TypeInfo stm32f4xx_exti_info = {
+ .name = TYPE_STM32F4XX_EXTI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(STM32F4xxExtiState),
+ .instance_init = stm32f4xx_exti_init,
+ .class_init = stm32f4xx_exti_class_init,
+};
+
+static void stm32f4xx_exti_register_types(void)
+{
+ type_register_static(&stm32f4xx_exti_info);
+}
+
+type_init(stm32f4xx_exti_register_types)
diff --git a/hw/misc/stm32f4xx_syscfg.c b/hw/misc/stm32f4xx_syscfg.c
new file mode 100644
index 000000000..f960e4ea1
--- /dev/null
+++ b/hw/misc/stm32f4xx_syscfg.c
@@ -0,0 +1,171 @@
+/*
+ * STM32F4xx SYSCFG
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "hw/misc/stm32f4xx_syscfg.h"
+
+static void stm32f4xx_syscfg_reset(DeviceState *dev)
+{
+ STM32F4xxSyscfgState *s = STM32F4XX_SYSCFG(dev);
+
+ s->syscfg_memrmp = 0x00000000;
+ s->syscfg_pmc = 0x00000000;
+ s->syscfg_exticr[0] = 0x00000000;
+ s->syscfg_exticr[1] = 0x00000000;
+ s->syscfg_exticr[2] = 0x00000000;
+ s->syscfg_exticr[3] = 0x00000000;
+ s->syscfg_cmpcr = 0x00000000;
+}
+
+static void stm32f4xx_syscfg_set_irq(void *opaque, int irq, int level)
+{
+ STM32F4xxSyscfgState *s = opaque;
+ int icrreg = irq / 4;
+ int startbit = (irq & 3) * 4;
+ uint8_t config = irq / 16;
+
+ trace_stm32f4xx_syscfg_set_irq(irq / 16, irq % 16, level);
+
+ g_assert(icrreg < SYSCFG_NUM_EXTICR);
+
+ if (extract32(s->syscfg_exticr[icrreg], startbit, 4) == config) {
+ qemu_set_irq(s->gpio_out[irq], level);
+ trace_stm32f4xx_pulse_exti(irq);
+ }
+}
+
+static uint64_t stm32f4xx_syscfg_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ STM32F4xxSyscfgState *s = opaque;
+
+ trace_stm32f4xx_syscfg_read(addr);
+
+ switch (addr) {
+ case SYSCFG_MEMRMP:
+ return s->syscfg_memrmp;
+ case SYSCFG_PMC:
+ return s->syscfg_pmc;
+ case SYSCFG_EXTICR1...SYSCFG_EXTICR4:
+ return s->syscfg_exticr[addr / 4 - SYSCFG_EXTICR1 / 4];
+ case SYSCFG_CMPCR:
+ return s->syscfg_cmpcr;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
+ return 0;
+ }
+}
+
+static void stm32f4xx_syscfg_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ STM32F4xxSyscfgState *s = opaque;
+ uint32_t value = val64;
+
+ trace_stm32f4xx_syscfg_write(value, addr);
+
+ switch (addr) {
+ case SYSCFG_MEMRMP:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Changing the memory mapping isn't supported " \
+ "in QEMU\n", __func__);
+ return;
+ case SYSCFG_PMC:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Changing the memory mapping isn't supported " \
+ "in QEMU\n", __func__);
+ return;
+ case SYSCFG_EXTICR1...SYSCFG_EXTICR4:
+ s->syscfg_exticr[addr / 4 - SYSCFG_EXTICR1 / 4] = (value & 0xFFFF);
+ return;
+ case SYSCFG_CMPCR:
+ s->syscfg_cmpcr = value;
+ return;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
+ }
+}
+
+static const MemoryRegionOps stm32f4xx_syscfg_ops = {
+ .read = stm32f4xx_syscfg_read,
+ .write = stm32f4xx_syscfg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void stm32f4xx_syscfg_init(Object *obj)
+{
+ STM32F4xxSyscfgState *s = STM32F4XX_SYSCFG(obj);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+
+ memory_region_init_io(&s->mmio, obj, &stm32f4xx_syscfg_ops, s,
+ TYPE_STM32F4XX_SYSCFG, 0x400);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+
+ qdev_init_gpio_in(DEVICE(obj), stm32f4xx_syscfg_set_irq, 16 * 9);
+ qdev_init_gpio_out(DEVICE(obj), s->gpio_out, 16);
+}
+
+static const VMStateDescription vmstate_stm32f4xx_syscfg = {
+ .name = TYPE_STM32F4XX_SYSCFG,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(syscfg_memrmp, STM32F4xxSyscfgState),
+ VMSTATE_UINT32(syscfg_pmc, STM32F4xxSyscfgState),
+ VMSTATE_UINT32_ARRAY(syscfg_exticr, STM32F4xxSyscfgState,
+ SYSCFG_NUM_EXTICR),
+ VMSTATE_UINT32(syscfg_cmpcr, STM32F4xxSyscfgState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void stm32f4xx_syscfg_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = stm32f4xx_syscfg_reset;
+ dc->vmsd = &vmstate_stm32f4xx_syscfg;
+}
+
+static const TypeInfo stm32f4xx_syscfg_info = {
+ .name = TYPE_STM32F4XX_SYSCFG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(STM32F4xxSyscfgState),
+ .instance_init = stm32f4xx_syscfg_init,
+ .class_init = stm32f4xx_syscfg_class_init,
+};
+
+static void stm32f4xx_syscfg_register_types(void)
+{
+ type_register_static(&stm32f4xx_syscfg_info);
+}
+
+type_init(stm32f4xx_syscfg_register_types)
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
new file mode 100644
index 000000000..2da96d167
--- /dev/null
+++ b/hw/misc/trace-events
@@ -0,0 +1,255 @@
+# See docs/devel/tracing.rst 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
+
+# avr_power.c
+avr_power_read(uint8_t value) "power_reduc read value:%u"
+avr_power_write(uint8_t value) "power_reduc write value:%u"
+
+# 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"
+ecc_mem_writel_mfsr(uint32_t val) "Write memory fault status 0x%08x"
+ecc_mem_writel_vcr(uint32_t val) "Write slot configuration 0x%08x"
+ecc_mem_writel_dr(uint32_t val) "Write diagnostic 0x%08x"
+ecc_mem_writel_ecr0(uint32_t val) "Write event count 1 0x%08x"
+ecc_mem_writel_ecr1(uint32_t val) "Write event count 2 0x%08x"
+ecc_mem_readl_mer(uint32_t ret) "Read memory enable 0x%08x"
+ecc_mem_readl_mdr(uint32_t ret) "Read memory delay 0x%08x"
+ecc_mem_readl_mfsr(uint32_t ret) "Read memory fault status 0x%08x"
+ecc_mem_readl_vcr(uint32_t ret) "Read slot configuration 0x%08x"
+ecc_mem_readl_mfar0(uint32_t ret) "Read memory fault address 0 0x%08x"
+ecc_mem_readl_mfar1(uint32_t ret) "Read memory fault address 1 0x%08x"
+ecc_mem_readl_dr(uint32_t ret) "Read diagnostic 0x%08x"
+ecc_mem_readl_ecr0(uint32_t ret) "Read event count 1 0x%08x"
+ecc_mem_readl_ecr1(uint32_t ret) "Read event count 2 0x%08x"
+ecc_diag_mem_writeb(uint64_t addr, uint32_t val) "Write diagnostic %"PRId64" = 0x%02x"
+ecc_diag_mem_readb(uint64_t addr, uint32_t ret) "Read diagnostic %"PRId64"= 0x%02x"
+
+# empty_slot.c
+empty_slot_write(uint64_t addr, unsigned width, uint64_t value, unsigned size, const char *name) "wr addr:0x%04"PRIx64" data:0x%0*"PRIx64" size %u [%s]"
+
+# slavio_misc.c
+slavio_misc_update_irq_raise(void) "Raise IRQ"
+slavio_misc_update_irq_lower(void) "Lower IRQ"
+slavio_set_power_fail(int power_failing, uint8_t config) "Power fail: %d, config: %d"
+slavio_cfg_mem_writeb(uint32_t val) "Write config 0x%02x"
+slavio_cfg_mem_readb(uint32_t ret) "Read config 0x%02x"
+slavio_diag_mem_writeb(uint32_t val) "Write diag 0x%02x"
+slavio_diag_mem_readb(uint32_t ret) "Read diag 0x%02x"
+slavio_mdm_mem_writeb(uint32_t val) "Write modem control 0x%02x"
+slavio_mdm_mem_readb(uint32_t ret) "Read modem control 0x%02x"
+slavio_aux1_mem_writeb(uint32_t val) "Write aux1 0x%02x"
+slavio_aux1_mem_readb(uint32_t ret) "Read aux1 0x%02x"
+slavio_aux2_mem_writeb(uint32_t val) "Write aux2 0x%02x"
+slavio_aux2_mem_readb(uint32_t ret) "Read aux2 0x%02x"
+apc_mem_writeb(uint32_t val) "Write power management 0x%02x"
+apc_mem_readb(uint32_t ret) "Read power management 0x%02x"
+slavio_sysctrl_mem_writel(uint32_t val) "Write system control 0x%08x"
+slavio_sysctrl_mem_readl(uint32_t ret) "Read system control 0x%08x"
+slavio_led_mem_writew(uint32_t val) "Write diagnostic LED 0x%04x"
+slavio_led_mem_readw(uint32_t ret) "Read diagnostic LED 0x%04x"
+
+# aspeed_scu.c
+aspeed_scu_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32
+
+# mps2-scc.c
+mps2_scc_read(uint64_t offset, uint64_t data, unsigned size) "MPS2 SCC read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+mps2_scc_write(uint64_t offset, uint64_t data, unsigned size) "MPS2 SCC write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+mps2_scc_reset(void) "MPS2 SCC: reset"
+mps2_scc_cfg_write(unsigned function, unsigned device, uint32_t value) "MPS2 SCC config write: function %d device %d data 0x%" PRIx32
+mps2_scc_cfg_read(unsigned function, unsigned device, uint32_t value) "MPS2 SCC config read: function %d device %d data 0x%" PRIx32
+
+# mps2-fpgaio.c
+mps2_fpgaio_read(uint64_t offset, uint64_t data, unsigned size) "MPS2 FPGAIO read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+mps2_fpgaio_write(uint64_t offset, uint64_t data, unsigned size) "MPS2 FPGAIO write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+mps2_fpgaio_reset(void) "MPS2 FPGAIO: reset"
+
+# msf2-sysreg.c
+msf2_sysreg_write(uint64_t offset, uint32_t val, uint32_t prev) "msf2-sysreg write: addr 0x%08" PRIx64 " data 0x%" PRIx32 " prev 0x%" PRIx32
+msf2_sysreg_read(uint64_t offset, uint32_t val) "msf2-sysreg read: addr 0x%08" PRIx64 " data 0x%08" PRIx32
+msf2_sysreg_write_pll_status(void) "Invalid write to read only PLL status register"
+
+# imx7_gpr.c
+imx7_gpr_read(uint64_t offset) "addr 0x%08" PRIx64
+imx7_gpr_write(uint64_t offset, uint64_t value) "addr 0x%08" PRIx64 "value 0x%08" PRIx64
+
+# mos6522.c
+mos6522_set_counter(int index, unsigned int val) "T%d.counter=%d"
+mos6522_get_next_irq_time(uint16_t latch, int64_t d, int64_t delta) "latch=%d counter=0x%"PRId64 " delta_next=0x%"PRId64
+mos6522_set_sr_int(void) "set sr_int"
+mos6522_write(uint64_t addr, uint64_t val) "reg=0x%"PRIx64 " val=0x%"PRIx64
+mos6522_read(uint64_t addr, unsigned val) "reg=0x%"PRIx64 " val=0x%x"
+
+# npcm7xx_clk.c
+npcm7xx_clk_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
+npcm7xx_clk_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
+
+# npcm7xx_gcr.c
+npcm7xx_gcr_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
+npcm7xx_gcr_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
+
+# npcm7xx_mft.c
+npcm7xx_mft_read(const char *name, uint64_t offset, uint16_t value) "%s: offset: 0x%04" PRIx64 " value: 0x%04" PRIx16
+npcm7xx_mft_write(const char *name, uint64_t offset, uint16_t value) "%s: offset: 0x%04" PRIx64 " value: 0x%04" PRIx16
+npcm7xx_mft_rpm(const char *clock, uint32_t clock_hz, int state, int32_t cnt, uint32_t rpm, uint32_t duty) " fan clk: %s clock_hz: %" PRIu32 ", state: %d, cnt: %" PRIi32 ", rpm: %" PRIu32 ", duty: %" PRIu32
+npcm7xx_mft_capture(const char *name, int irq_level) "%s: level: %d"
+npcm7xx_mft_update_clock(const char *name, uint16_t sel, uint64_t clock_period, uint64_t prescaled_clock_period) "%s: sel: 0x%02" PRIx16 ", period: %" PRIu64 ", prescaled: %" PRIu64
+npcm7xx_mft_set_duty(const char *name, int n, int value) "%s[%d]: %d"
+
+# npcm7xx_rng.c
+npcm7xx_rng_read(uint64_t offset, uint64_t value, unsigned size) "offset: 0x%04" PRIx64 " value: 0x%02" PRIx64 " size: %u"
+npcm7xx_rng_write(uint64_t offset, uint64_t value, unsigned size) "offset: 0x%04" PRIx64 " value: 0x%02" PRIx64 " size: %u"
+
+# npcm7xx_pwm.c
+npcm7xx_pwm_read(const char *id, uint64_t offset, uint32_t value) "%s offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
+npcm7xx_pwm_write(const char *id, uint64_t offset, uint32_t value) "%s offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
+npcm7xx_pwm_update_freq(const char *id, uint8_t index, uint32_t old_value, uint32_t new_value) "%s pwm[%u] Update Freq: old_freq: %u, new_freq: %u"
+npcm7xx_pwm_update_duty(const char *id, uint8_t index, uint32_t old_value, uint32_t new_value) "%s pwm[%u] Update Duty: old_duty: %u, new_duty: %u"
+
+# stm32f4xx_syscfg.c
+stm32f4xx_syscfg_set_irq(int gpio, int line, int level) "Interrupt: GPIO: %d, Line: %d; Level: %d"
+stm32f4xx_pulse_exti(int irq) "Pulse EXTI: %d"
+stm32f4xx_syscfg_read(uint64_t addr) "reg read: addr: 0x%" PRIx64 " "
+stm32f4xx_syscfg_write(uint64_t addr, uint64_t data) "reg write: addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
+
+# stm32f4xx_exti.c
+stm32f4xx_exti_set_irq(int irq, int leve) "Set EXTI: %d to %d"
+stm32f4xx_exti_read(uint64_t addr) "reg read: addr: 0x%" PRIx64 " "
+stm32f4xx_exti_write(uint64_t addr, uint64_t data) "reg write: addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
+
+# tz-mpc.c
+tz_mpc_reg_read(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs read: offset 0x%x data 0x%" PRIx64 " size %u"
+tz_mpc_reg_write(uint32_t offset, uint64_t data, unsigned size) "TZ MPC regs write: offset 0x%x data 0x%" PRIx64 " size %u"
+tz_mpc_mem_blocked_read(uint64_t addr, unsigned size, bool secure) "TZ MPC blocked read: offset 0x%" PRIx64 " size %u secure %d"
+tz_mpc_mem_blocked_write(uint64_t addr, uint64_t data, unsigned size, bool secure) "TZ MPC blocked write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u secure %d"
+tz_mpc_translate(uint64_t addr, int flags, const char *idx, const char *res) "TZ MPC translate: addr 0x%" PRIx64 " flags 0x%x iommu_idx %s: %s"
+tz_mpc_iommu_notify(uint64_t addr) "TZ MPC iommu: notifying UNMAP/MAP for 0x%" PRIx64
+
+# tz-msc.c
+tz_msc_reset(void) "TZ MSC: reset"
+tz_msc_cfg_nonsec(int level) "TZ MSC: cfg_nonsec = %d"
+tz_msc_cfg_sec_resp(int level) "TZ MSC: cfg_sec_resp = %d"
+tz_msc_irq_clear(int level) "TZ MSC: int_clear = %d"
+tz_msc_update_irq(int level) "TZ MSC: setting irq line to %d"
+tz_msc_access_blocked(uint64_t offset) "TZ MSC: offset 0x%" PRIx64 " access blocked"
+
+# tz-ppc.c
+tz_ppc_reset(void) "TZ PPC: reset"
+tz_ppc_cfg_nonsec(int n, int level) "TZ PPC: cfg_nonsec[%d] = %d"
+tz_ppc_cfg_ap(int n, int level) "TZ PPC: cfg_ap[%d] = %d"
+tz_ppc_cfg_sec_resp(int level) "TZ PPC: cfg_sec_resp = %d"
+tz_ppc_irq_enable(int level) "TZ PPC: int_enable = %d"
+tz_ppc_irq_clear(int level) "TZ PPC: int_clear = %d"
+tz_ppc_update_irq(int level) "TZ PPC: setting irq line to %d"
+tz_ppc_read_blocked(int n, uint64_t offset, bool secure, bool user) "TZ PPC: port %d offset 0x%" PRIx64 " read (secure %d user %d) blocked"
+tz_ppc_write_blocked(int n, uint64_t offset, bool secure, bool user) "TZ PPC: port %d offset 0x%" PRIx64 " write (secure %d user %d) blocked"
+
+# iotkit-secctl.c
+iotkit_secctl_s_read(uint32_t offset, uint64_t data, unsigned size) "IoTKit SecCtl S regs read: offset 0x%x data 0x%" PRIx64 " size %u"
+iotkit_secctl_s_write(uint32_t offset, uint64_t data, unsigned size) "IoTKit SecCtl S regs write: offset 0x%x data 0x%" PRIx64 " size %u"
+iotkit_secctl_ns_read(uint32_t offset, uint64_t data, unsigned size) "IoTKit SecCtl NS regs read: offset 0x%x data 0x%" PRIx64 " size %u"
+iotkit_secctl_ns_write(uint32_t offset, uint64_t data, unsigned size) "IoTKit SecCtl NS regs write: offset 0x%x data 0x%" PRIx64 " size %u"
+
+# imx6ul_ccm.c
+ccm_entry(void) ""
+ccm_freq(uint32_t freq) "freq = %d"
+ccm_clock_freq(uint32_t clock, uint32_t freq) "(Clock = %d) = %d"
+ccm_read_reg(const char *reg_name, uint32_t value) "reg[%s] <= 0x%" PRIx32
+ccm_write_reg(const char *reg_name, uint32_t value) "reg[%s] => 0x%" PRIx32
+
+# iotkit-sysinfo.c
+iotkit_sysinfo_read(uint64_t offset, uint64_t data, unsigned size) "IoTKit SysInfo read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+iotkit_sysinfo_write(uint64_t offset, uint64_t data, unsigned size) "IoTKit SysInfo write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+
+# iotkit-sysctl.c
+iotkit_sysctl_read(uint64_t offset, uint64_t data, unsigned size) "IoTKit SysCtl read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+iotkit_sysctl_write(uint64_t offset, uint64_t data, unsigned size) "IoTKit SysCtl write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+iotkit_sysctl_reset(void) "IoTKit SysCtl: reset"
+
+# armsse-cpu-pwrctrl.c
+armsse_cpu_pwrctrl_read(uint64_t offset, uint64_t data, unsigned size) "SSE-300 CPU_PWRCTRL read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+armsse_cpu_pwrctrl_write(uint64_t offset, uint64_t data, unsigned size) "SSE-300 CPU_PWRCTRL write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+
+# armsse-cpuid.c
+armsse_cpuid_read(uint64_t offset, uint64_t data, unsigned size) "SSE-200 CPU_IDENTITY read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+armsse_cpuid_write(uint64_t offset, uint64_t data, unsigned size) "SSE-200 CPU_IDENTITY write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+
+# armsse-mhu.c
+armsse_mhu_read(uint64_t offset, uint64_t data, unsigned size) "SSE-200 MHU read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+armsse_mhu_write(uint64_t offset, uint64_t data, unsigned size) "SSE-200 MHU write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
+
+# aspeed_xdma.c
+aspeed_xdma_write(uint64_t offset, uint64_t data) "XDMA write: offset 0x%" PRIx64 " data 0x%" PRIx64
+
+# bcm2835_property.c
+bcm2835_mbox_property(uint32_t tag, uint32_t bufsize, size_t resplen) "mbox property tag:0x%08x in_sz:%u out_sz:%zu"
+
+# bcm2835_mbox.c
+bcm2835_mbox_write(unsigned int size, uint64_t addr, uint64_t value) "mbox write sz:%u addr:0x%"PRIx64" data:0x%"PRIx64
+bcm2835_mbox_read(unsigned int size, uint64_t addr, uint64_t value) "mbox read sz:%u addr:0x%"PRIx64" data:0x%"PRIx64
+bcm2835_mbox_irq(unsigned level) "mbox irq:ARM level:%u"
+
+# mac_via.c
+via1_rtc_update_data_out(int count, int value) "count=%d value=0x%02x"
+via1_rtc_update_data_in(int count, int value) "count=%d value=0x%02x"
+via1_rtc_internal_status(int cmd, int alt, int value) "cmd=0x%02x alt=0x%02x value=0x%02x"
+via1_rtc_internal_cmd(int cmd) "cmd=0x%02x"
+via1_rtc_cmd_invalid(int value) "value=0x%02x"
+via1_rtc_internal_time(uint32_t time) "time=0x%08x"
+via1_rtc_internal_set_cmd(int cmd) "cmd=0x%02x"
+via1_rtc_internal_ignore_cmd(int cmd) "cmd=0x%02x"
+via1_rtc_internal_set_alt(int alt, int sector, int offset) "alt=0x%02x sector=%u offset=%u"
+via1_rtc_cmd_seconds_read(int reg, int value) "reg=%d value=0x%02x"
+via1_rtc_cmd_seconds_write(int reg, int value) "reg=%d value=0x%02x"
+via1_rtc_cmd_test_write(int value) "value=0x%02x"
+via1_rtc_cmd_wprotect_write(int value) "value=0x%02x"
+via1_rtc_cmd_pram_read(int addr, int value) "addr=%u value=0x%02x"
+via1_rtc_cmd_pram_write(int addr, int value) "addr=%u value=0x%02x"
+via1_rtc_cmd_pram_sect_read(int sector, int offset, int addr, int value) "sector=%u offset=%u addr=0x%x value=0x%02x"
+via1_rtc_cmd_pram_sect_write(int sector, int offset, int addr, int value) "sector=%u offset=%u addr=0x%x value=0x%02x"
+via1_adb_send(const char *state, uint8_t data, const char *vadbint) "state %s data=0x%02x vADBInt=%s"
+via1_adb_receive(const char *state, uint8_t data, const char *vadbint, int status, int index, int size) "state %s data=0x%02x vADBInt=%s status=0x%x index=%d size=%d"
+via1_adb_poll(uint8_t data, const char *vadbint, int status, int index, int size) "data=0x%02x vADBInt=%s status=0x%x index=%d size=%d"
+via1_auxmode(int mode) "setting auxmode to %d"
+
+# grlib_ahb_apb_pnp.c
+grlib_ahb_pnp_read(uint64_t addr, uint32_t value) "AHB PnP read addr:0x%03"PRIx64" data:0x%08x"
+grlib_apb_pnp_read(uint64_t addr, uint32_t value) "APB PnP read addr:0x%03"PRIx64" data:0x%08x"
+
+# led.c
+led_set_intensity(const char *color, const char *desc, uint8_t intensity_percent) "LED desc:'%s' color:%s intensity: %u%%"
+led_change_intensity(const char *color, const char *desc, uint8_t old_intensity_percent, uint8_t new_intensity_percent) "LED desc:'%s' color:%s intensity %u%% -> %u%%"
+
+# pca9552.c
+pca955x_gpio_status(const char *description, const char *buf) "%s GPIOs 0-15 [%s]"
+pca955x_gpio_change(const char *description, unsigned id, unsigned prev_state, unsigned current_state) "%s GPIO id:%u status: %u -> %u"
+
+# bcm2835_cprman.c
+bcm2835_cprman_read(uint64_t offset, uint64_t value) "offset:0x%" PRIx64 " value:0x%" PRIx64
+bcm2835_cprman_write(uint64_t offset, uint64_t value) "offset:0x%" PRIx64 " value:0x%" PRIx64
+bcm2835_cprman_write_invalid_magic(uint64_t offset, uint64_t value) "offset:0x%" PRIx64 " value:0x%" PRIx64
+
+# virt_ctrl.c
+virt_ctrl_read(void *dev, unsigned int addr, unsigned int size, uint64_t value) "ctrl: %p reg: 0x%02x size: %d value: 0x%"PRIx64
+virt_ctrl_write(void *dev, unsigned int addr, unsigned int size, uint64_t value) "ctrl: %p reg: 0x%02x size: %d value: 0x%"PRIx64
+virt_ctrl_reset(void *dev) "ctrl: %p"
+virt_ctrl_realize(void *dev) "ctrl: %p"
+virt_ctrl_instance_init(void *dev) "ctrl: %p"
diff --git a/hw/misc/trace.h b/hw/misc/trace.h
new file mode 100644
index 000000000..1ab6923d1
--- /dev/null
+++ b/hw/misc/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_misc.h"
diff --git a/hw/misc/tz-mpc.c b/hw/misc/tz-mpc.c
new file mode 100644
index 000000000..30481e1c9
--- /dev/null
+++ b/hw/misc/tz-mpc.c
@@ -0,0 +1,636 @@
+/*
+ * ARM AHB5 TrustZone Memory Protection Controller emulation
+ *
+ * Copyright (c) 2018 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/registerfields.h"
+#include "hw/irq.h"
+#include "hw/misc/tz-mpc.h"
+#include "hw/qdev-properties.h"
+
+/* Our IOMMU has two IOMMU indexes, one for secure transactions and one for
+ * non-secure transactions.
+ */
+enum {
+ IOMMU_IDX_S,
+ IOMMU_IDX_NS,
+ IOMMU_NUM_INDEXES,
+};
+
+/* Config registers */
+REG32(CTRL, 0x00)
+ FIELD(CTRL, SEC_RESP, 4, 1)
+ FIELD(CTRL, AUTOINC, 8, 1)
+ FIELD(CTRL, LOCKDOWN, 31, 1)
+REG32(BLK_MAX, 0x10)
+REG32(BLK_CFG, 0x14)
+REG32(BLK_IDX, 0x18)
+REG32(BLK_LUT, 0x1c)
+REG32(INT_STAT, 0x20)
+ FIELD(INT_STAT, IRQ, 0, 1)
+REG32(INT_CLEAR, 0x24)
+ FIELD(INT_CLEAR, IRQ, 0, 1)
+REG32(INT_EN, 0x28)
+ FIELD(INT_EN, IRQ, 0, 1)
+REG32(INT_INFO1, 0x2c)
+REG32(INT_INFO2, 0x30)
+ FIELD(INT_INFO2, HMASTER, 0, 16)
+ FIELD(INT_INFO2, HNONSEC, 16, 1)
+ FIELD(INT_INFO2, CFG_NS, 17, 1)
+REG32(INT_SET, 0x34)
+ FIELD(INT_SET, IRQ, 0, 1)
+REG32(PIDR4, 0xfd0)
+REG32(PIDR5, 0xfd4)
+REG32(PIDR6, 0xfd8)
+REG32(PIDR7, 0xfdc)
+REG32(PIDR0, 0xfe0)
+REG32(PIDR1, 0xfe4)
+REG32(PIDR2, 0xfe8)
+REG32(PIDR3, 0xfec)
+REG32(CIDR0, 0xff0)
+REG32(CIDR1, 0xff4)
+REG32(CIDR2, 0xff8)
+REG32(CIDR3, 0xffc)
+
+static const uint8_t tz_mpc_idregs[] = {
+ 0x04, 0x00, 0x00, 0x00,
+ 0x60, 0xb8, 0x1b, 0x00,
+ 0x0d, 0xf0, 0x05, 0xb1,
+};
+
+static void tz_mpc_irq_update(TZMPC *s)
+{
+ qemu_set_irq(s->irq, s->int_stat && s->int_en);
+}
+
+static void tz_mpc_iommu_notify(TZMPC *s, uint32_t lutidx,
+ uint32_t oldlut, uint32_t newlut)
+{
+ /* Called when the LUT word at lutidx has changed from oldlut to newlut;
+ * must call the IOMMU notifiers for the changed blocks.
+ */
+ IOMMUTLBEvent event = {
+ .entry = {
+ .addr_mask = s->blocksize - 1,
+ }
+ };
+ hwaddr addr = lutidx * s->blocksize * 32;
+ int i;
+
+ for (i = 0; i < 32; i++, addr += s->blocksize) {
+ bool block_is_ns;
+
+ if (!((oldlut ^ newlut) & (1 << i))) {
+ continue;
+ }
+ /* This changes the mappings for both the S and the NS space,
+ * so we need to do four notifies: an UNMAP then a MAP for each.
+ */
+ block_is_ns = newlut & (1 << i);
+
+ trace_tz_mpc_iommu_notify(addr);
+ event.entry.iova = addr;
+ event.entry.translated_addr = addr;
+
+ event.type = IOMMU_NOTIFIER_UNMAP;
+ event.entry.perm = IOMMU_NONE;
+ memory_region_notify_iommu(&s->upstream, IOMMU_IDX_S, event);
+ memory_region_notify_iommu(&s->upstream, IOMMU_IDX_NS, event);
+
+ event.type = IOMMU_NOTIFIER_MAP;
+ event.entry.perm = IOMMU_RW;
+ if (block_is_ns) {
+ event.entry.target_as = &s->blocked_io_as;
+ } else {
+ event.entry.target_as = &s->downstream_as;
+ }
+ memory_region_notify_iommu(&s->upstream, IOMMU_IDX_S, event);
+ if (block_is_ns) {
+ event.entry.target_as = &s->downstream_as;
+ } else {
+ event.entry.target_as = &s->blocked_io_as;
+ }
+ memory_region_notify_iommu(&s->upstream, IOMMU_IDX_NS, event);
+ }
+}
+
+static void tz_mpc_autoinc_idx(TZMPC *s, unsigned access_size)
+{
+ /* Auto-increment BLK_IDX if necessary */
+ if (access_size == 4 && (s->ctrl & R_CTRL_AUTOINC_MASK)) {
+ s->blk_idx++;
+ s->blk_idx %= s->blk_max;
+ }
+}
+
+static MemTxResult tz_mpc_reg_read(void *opaque, hwaddr addr,
+ uint64_t *pdata,
+ unsigned size, MemTxAttrs attrs)
+{
+ TZMPC *s = TZ_MPC(opaque);
+ uint64_t r;
+ uint32_t offset = addr & ~0x3;
+
+ if (!attrs.secure && offset < A_PIDR4) {
+ /* NS accesses can only see the ID registers */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "TZ MPC register read: NS access to offset 0x%x\n",
+ offset);
+ r = 0;
+ goto read_out;
+ }
+
+ switch (offset) {
+ case A_CTRL:
+ r = s->ctrl;
+ break;
+ case A_BLK_MAX:
+ r = s->blk_max - 1;
+ break;
+ case A_BLK_CFG:
+ /* We are never in "init in progress state", so this just indicates
+ * the block size. s->blocksize == (1 << BLK_CFG + 5), so
+ * BLK_CFG == ctz32(s->blocksize) - 5
+ */
+ r = ctz32(s->blocksize) - 5;
+ break;
+ case A_BLK_IDX:
+ r = s->blk_idx;
+ break;
+ case A_BLK_LUT:
+ r = s->blk_lut[s->blk_idx];
+ tz_mpc_autoinc_idx(s, size);
+ break;
+ case A_INT_STAT:
+ r = s->int_stat;
+ break;
+ case A_INT_EN:
+ r = s->int_en;
+ break;
+ case A_INT_INFO1:
+ r = s->int_info1;
+ break;
+ case A_INT_INFO2:
+ r = s->int_info2;
+ break;
+ case A_PIDR4:
+ case A_PIDR5:
+ case A_PIDR6:
+ case A_PIDR7:
+ case A_PIDR0:
+ case A_PIDR1:
+ case A_PIDR2:
+ case A_PIDR3:
+ case A_CIDR0:
+ case A_CIDR1:
+ case A_CIDR2:
+ case A_CIDR3:
+ r = tz_mpc_idregs[(offset - A_PIDR4) / 4];
+ break;
+ case A_INT_CLEAR:
+ case A_INT_SET:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "TZ MPC register read: write-only offset 0x%x\n",
+ offset);
+ r = 0;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "TZ MPC register read: bad offset 0x%x\n", offset);
+ r = 0;
+ break;
+ }
+
+ if (size != 4) {
+ /* None of our registers are read-sensitive (except BLK_LUT,
+ * which can special case the "size not 4" case), so just
+ * pull the right bytes out of the word read result.
+ */
+ r = extract32(r, (addr & 3) * 8, size * 8);
+ }
+
+read_out:
+ trace_tz_mpc_reg_read(addr, r, size);
+ *pdata = r;
+ return MEMTX_OK;
+}
+
+static MemTxResult tz_mpc_reg_write(void *opaque, hwaddr addr,
+ uint64_t value,
+ unsigned size, MemTxAttrs attrs)
+{
+ TZMPC *s = TZ_MPC(opaque);
+ uint32_t offset = addr & ~0x3;
+
+ trace_tz_mpc_reg_write(addr, value, size);
+
+ if (!attrs.secure && offset < A_PIDR4) {
+ /* NS accesses can only see the ID registers */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "TZ MPC register write: NS access to offset 0x%x\n",
+ offset);
+ return MEMTX_OK;
+ }
+
+ if (size != 4) {
+ /* Expand the byte or halfword write to a full word size.
+ * In most cases we can do this with zeroes; the exceptions
+ * are CTRL, BLK_IDX and BLK_LUT.
+ */
+ uint32_t oldval;
+
+ switch (offset) {
+ case A_CTRL:
+ oldval = s->ctrl;
+ break;
+ case A_BLK_IDX:
+ oldval = s->blk_idx;
+ break;
+ case A_BLK_LUT:
+ oldval = s->blk_lut[s->blk_idx];
+ break;
+ default:
+ oldval = 0;
+ break;
+ }
+ value = deposit32(oldval, (addr & 3) * 8, size * 8, value);
+ }
+
+ if ((s->ctrl & R_CTRL_LOCKDOWN_MASK) &&
+ (offset == A_CTRL || offset == A_BLK_LUT || offset == A_INT_EN)) {
+ /* Lockdown mode makes these three registers read-only, and
+ * the only way out of it is to reset the device.
+ */
+ qemu_log_mask(LOG_GUEST_ERROR, "TZ MPC register write to offset 0x%x "
+ "while MPC is in lockdown mode\n", offset);
+ return MEMTX_OK;
+ }
+
+ switch (offset) {
+ case A_CTRL:
+ /* We don't implement the 'data gating' feature so all other bits
+ * are reserved and we make them RAZ/WI.
+ */
+ s->ctrl = value & (R_CTRL_SEC_RESP_MASK |
+ R_CTRL_AUTOINC_MASK |
+ R_CTRL_LOCKDOWN_MASK);
+ break;
+ case A_BLK_IDX:
+ s->blk_idx = value % s->blk_max;
+ break;
+ case A_BLK_LUT:
+ tz_mpc_iommu_notify(s, s->blk_idx, s->blk_lut[s->blk_idx], value);
+ s->blk_lut[s->blk_idx] = value;
+ tz_mpc_autoinc_idx(s, size);
+ break;
+ case A_INT_CLEAR:
+ if (value & R_INT_CLEAR_IRQ_MASK) {
+ s->int_stat = 0;
+ tz_mpc_irq_update(s);
+ }
+ break;
+ case A_INT_EN:
+ s->int_en = value & R_INT_EN_IRQ_MASK;
+ tz_mpc_irq_update(s);
+ break;
+ case A_INT_SET:
+ if (value & R_INT_SET_IRQ_MASK) {
+ s->int_stat = R_INT_STAT_IRQ_MASK;
+ tz_mpc_irq_update(s);
+ }
+ break;
+ case A_PIDR4:
+ case A_PIDR5:
+ case A_PIDR6:
+ case A_PIDR7:
+ case A_PIDR0:
+ case A_PIDR1:
+ case A_PIDR2:
+ case A_PIDR3:
+ case A_CIDR0:
+ case A_CIDR1:
+ case A_CIDR2:
+ case A_CIDR3:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "TZ MPC register write: read-only offset 0x%x\n", offset);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "TZ MPC register write: bad offset 0x%x\n", offset);
+ break;
+ }
+
+ return MEMTX_OK;
+}
+
+static const MemoryRegionOps tz_mpc_reg_ops = {
+ .read_with_attrs = tz_mpc_reg_read,
+ .write_with_attrs = tz_mpc_reg_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+};
+
+static inline bool tz_mpc_cfg_ns(TZMPC *s, hwaddr addr)
+{
+ /* Return the cfg_ns bit from the LUT for the specified address */
+ hwaddr blknum = addr / s->blocksize;
+ hwaddr blkword = blknum / 32;
+ uint32_t blkbit = 1U << (blknum % 32);
+
+ /* This would imply the address was larger than the size we
+ * defined this memory region to be, so it can't happen.
+ */
+ assert(blkword < s->blk_max);
+ return s->blk_lut[blkword] & blkbit;
+}
+
+static MemTxResult tz_mpc_handle_block(TZMPC *s, hwaddr addr, MemTxAttrs attrs)
+{
+ /* Handle a blocked transaction: raise IRQ, capture info, etc */
+ if (!s->int_stat) {
+ /* First blocked transfer: capture information into INT_INFO1 and
+ * INT_INFO2. Subsequent transfers are still blocked but don't
+ * capture information until the guest clears the interrupt.
+ */
+
+ s->int_info1 = addr;
+ s->int_info2 = 0;
+ s->int_info2 = FIELD_DP32(s->int_info2, INT_INFO2, HMASTER,
+ attrs.requester_id & 0xffff);
+ s->int_info2 = FIELD_DP32(s->int_info2, INT_INFO2, HNONSEC,
+ ~attrs.secure);
+ s->int_info2 = FIELD_DP32(s->int_info2, INT_INFO2, CFG_NS,
+ tz_mpc_cfg_ns(s, addr));
+ s->int_stat |= R_INT_STAT_IRQ_MASK;
+ tz_mpc_irq_update(s);
+ }
+
+ /* Generate bus error if desired; otherwise RAZ/WI */
+ return (s->ctrl & R_CTRL_SEC_RESP_MASK) ? MEMTX_ERROR : MEMTX_OK;
+}
+
+/* Accesses only reach these read and write functions if the MPC is
+ * blocking them; non-blocked accesses go directly to the downstream
+ * memory region without passing through this code.
+ */
+static MemTxResult tz_mpc_mem_blocked_read(void *opaque, hwaddr addr,
+ uint64_t *pdata,
+ unsigned size, MemTxAttrs attrs)
+{
+ TZMPC *s = TZ_MPC(opaque);
+
+ trace_tz_mpc_mem_blocked_read(addr, size, attrs.secure);
+
+ *pdata = 0;
+ return tz_mpc_handle_block(s, addr, attrs);
+}
+
+static MemTxResult tz_mpc_mem_blocked_write(void *opaque, hwaddr addr,
+ uint64_t value,
+ unsigned size, MemTxAttrs attrs)
+{
+ TZMPC *s = TZ_MPC(opaque);
+
+ trace_tz_mpc_mem_blocked_write(addr, value, size, attrs.secure);
+
+ return tz_mpc_handle_block(s, addr, attrs);
+}
+
+static const MemoryRegionOps tz_mpc_mem_blocked_ops = {
+ .read_with_attrs = tz_mpc_mem_blocked_read,
+ .write_with_attrs = tz_mpc_mem_blocked_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 8,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 8,
+};
+
+static IOMMUTLBEntry tz_mpc_translate(IOMMUMemoryRegion *iommu,
+ hwaddr addr, IOMMUAccessFlags flags,
+ int iommu_idx)
+{
+ TZMPC *s = TZ_MPC(container_of(iommu, TZMPC, upstream));
+ bool ok;
+
+ IOMMUTLBEntry ret = {
+ .iova = addr & ~(s->blocksize - 1),
+ .translated_addr = addr & ~(s->blocksize - 1),
+ .addr_mask = s->blocksize - 1,
+ .perm = IOMMU_RW,
+ };
+
+ /* Look at the per-block configuration for this address, and
+ * return a TLB entry directing the transaction at either
+ * downstream_as or blocked_io_as, as appropriate.
+ * If the LUT cfg_ns bit is 1, only non-secure transactions
+ * may pass. If the bit is 0, only secure transactions may pass.
+ */
+ ok = tz_mpc_cfg_ns(s, addr) == (iommu_idx == IOMMU_IDX_NS);
+
+ trace_tz_mpc_translate(addr, flags,
+ iommu_idx == IOMMU_IDX_S ? "S" : "NS",
+ ok ? "pass" : "block");
+
+ ret.target_as = ok ? &s->downstream_as : &s->blocked_io_as;
+ return ret;
+}
+
+static int tz_mpc_attrs_to_index(IOMMUMemoryRegion *iommu, MemTxAttrs attrs)
+{
+ /* We treat unspecified attributes like secure. Transactions with
+ * unspecified attributes come from places like
+ * rom_reset() for initial image load, and we want
+ * those to pass through the from-reset "everything is secure" config.
+ * All the real during-emulation transactions from the CPU will
+ * specify attributes.
+ */
+ return (attrs.unspecified || attrs.secure) ? IOMMU_IDX_S : IOMMU_IDX_NS;
+}
+
+static int tz_mpc_num_indexes(IOMMUMemoryRegion *iommu)
+{
+ return IOMMU_NUM_INDEXES;
+}
+
+static void tz_mpc_reset(DeviceState *dev)
+{
+ TZMPC *s = TZ_MPC(dev);
+
+ s->ctrl = 0x00000100;
+ s->blk_idx = 0;
+ s->int_stat = 0;
+ s->int_en = 1;
+ s->int_info1 = 0;
+ s->int_info2 = 0;
+
+ memset(s->blk_lut, 0, s->blk_max * sizeof(uint32_t));
+}
+
+static void tz_mpc_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ TZMPC *s = TZ_MPC(obj);
+
+ qdev_init_gpio_out_named(dev, &s->irq, "irq", 1);
+}
+
+static void tz_mpc_realize(DeviceState *dev, Error **errp)
+{
+ Object *obj = OBJECT(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ TZMPC *s = TZ_MPC(dev);
+ uint64_t size;
+
+ /* We can't create the upstream end of the port until realize,
+ * as we don't know the size of the MR used as the downstream until then.
+ * We insist on having a downstream, to avoid complicating the code
+ * with handling the "don't know how big this is" case. It's easy
+ * enough for the user to create an unimplemented_device as downstream
+ * if they have nothing else to plug into this.
+ */
+ if (!s->downstream) {
+ error_setg(errp, "MPC 'downstream' link not set");
+ return;
+ }
+
+ size = memory_region_size(s->downstream);
+
+ memory_region_init_iommu(&s->upstream, sizeof(s->upstream),
+ TYPE_TZ_MPC_IOMMU_MEMORY_REGION,
+ obj, "tz-mpc-upstream", size);
+
+ /* In real hardware the block size is configurable. In QEMU we could
+ * make it configurable but will need it to be at least as big as the
+ * target page size so we can execute out of the resulting MRs. Guest
+ * software is supposed to check the block size using the BLK_CFG
+ * register, so make it fixed at the page size.
+ */
+ s->blocksize = memory_region_iommu_get_min_page_size(&s->upstream);
+ if (size % s->blocksize != 0) {
+ error_setg(errp,
+ "MPC 'downstream' size %" PRId64
+ " is not a multiple of %" HWADDR_PRIx " bytes",
+ size, s->blocksize);
+ object_unref(OBJECT(&s->upstream));
+ return;
+ }
+
+ /* BLK_MAX is the max value of BLK_IDX, which indexes an array of 32-bit
+ * words, each bit of which indicates one block.
+ */
+ s->blk_max = DIV_ROUND_UP(size / s->blocksize, 32);
+
+ memory_region_init_io(&s->regmr, obj, &tz_mpc_reg_ops,
+ s, "tz-mpc-regs", 0x1000);
+ sysbus_init_mmio(sbd, &s->regmr);
+
+ sysbus_init_mmio(sbd, MEMORY_REGION(&s->upstream));
+
+ /* This memory region is not exposed to users of this device as a
+ * sysbus MMIO region, but is instead used internally as something
+ * that our IOMMU translate function might direct accesses to.
+ */
+ memory_region_init_io(&s->blocked_io, obj, &tz_mpc_mem_blocked_ops,
+ s, "tz-mpc-blocked-io", size);
+
+ address_space_init(&s->downstream_as, s->downstream,
+ "tz-mpc-downstream");
+ address_space_init(&s->blocked_io_as, &s->blocked_io,
+ "tz-mpc-blocked-io");
+
+ s->blk_lut = g_new0(uint32_t, s->blk_max);
+}
+
+static int tz_mpc_post_load(void *opaque, int version_id)
+{
+ TZMPC *s = TZ_MPC(opaque);
+
+ /* Check the incoming data doesn't point blk_idx off the end of blk_lut. */
+ if (s->blk_idx >= s->blk_max) {
+ return -1;
+ }
+ return 0;
+}
+
+static const VMStateDescription tz_mpc_vmstate = {
+ .name = "tz-mpc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = tz_mpc_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ctrl, TZMPC),
+ VMSTATE_UINT32(blk_idx, TZMPC),
+ VMSTATE_UINT32(int_stat, TZMPC),
+ VMSTATE_UINT32(int_en, TZMPC),
+ VMSTATE_UINT32(int_info1, TZMPC),
+ VMSTATE_UINT32(int_info2, TZMPC),
+ VMSTATE_VARRAY_UINT32(blk_lut, TZMPC, blk_max,
+ 0, vmstate_info_uint32, uint32_t),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property tz_mpc_properties[] = {
+ DEFINE_PROP_LINK("downstream", TZMPC, downstream,
+ TYPE_MEMORY_REGION, MemoryRegion *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tz_mpc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = tz_mpc_realize;
+ dc->vmsd = &tz_mpc_vmstate;
+ dc->reset = tz_mpc_reset;
+ device_class_set_props(dc, tz_mpc_properties);
+}
+
+static const TypeInfo tz_mpc_info = {
+ .name = TYPE_TZ_MPC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TZMPC),
+ .instance_init = tz_mpc_init,
+ .class_init = tz_mpc_class_init,
+};
+
+static void tz_mpc_iommu_memory_region_class_init(ObjectClass *klass,
+ void *data)
+{
+ IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass);
+
+ imrc->translate = tz_mpc_translate;
+ imrc->attrs_to_index = tz_mpc_attrs_to_index;
+ imrc->num_indexes = tz_mpc_num_indexes;
+}
+
+static const TypeInfo tz_mpc_iommu_memory_region_info = {
+ .name = TYPE_TZ_MPC_IOMMU_MEMORY_REGION,
+ .parent = TYPE_IOMMU_MEMORY_REGION,
+ .class_init = tz_mpc_iommu_memory_region_class_init,
+};
+
+static void tz_mpc_register_types(void)
+{
+ type_register_static(&tz_mpc_info);
+ type_register_static(&tz_mpc_iommu_memory_region_info);
+}
+
+type_init(tz_mpc_register_types);
diff --git a/hw/misc/tz-msc.c b/hw/misc/tz-msc.c
new file mode 100644
index 000000000..acbe94400
--- /dev/null
+++ b/hw/misc/tz-msc.c
@@ -0,0 +1,312 @@
+/*
+ * ARM TrustZone master security controller emulation
+ *
+ * Copyright (c) 2018 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/registerfields.h"
+#include "hw/irq.h"
+#include "hw/misc/tz-msc.h"
+#include "hw/qdev-properties.h"
+
+static void tz_msc_update_irq(TZMSC *s)
+{
+ bool level = s->irq_status;
+
+ trace_tz_msc_update_irq(level);
+ qemu_set_irq(s->irq, level);
+}
+
+static void tz_msc_cfg_nonsec(void *opaque, int n, int level)
+{
+ TZMSC *s = TZ_MSC(opaque);
+
+ trace_tz_msc_cfg_nonsec(level);
+ s->cfg_nonsec = level;
+}
+
+static void tz_msc_cfg_sec_resp(void *opaque, int n, int level)
+{
+ TZMSC *s = TZ_MSC(opaque);
+
+ trace_tz_msc_cfg_sec_resp(level);
+ s->cfg_sec_resp = level;
+}
+
+static void tz_msc_irq_clear(void *opaque, int n, int level)
+{
+ TZMSC *s = TZ_MSC(opaque);
+
+ trace_tz_msc_irq_clear(level);
+
+ s->irq_clear = level;
+ if (level) {
+ s->irq_status = false;
+ tz_msc_update_irq(s);
+ }
+}
+
+/* The MSC may either block a transaction by aborting it, block a
+ * transaction by making it RAZ/WI, allow it through with
+ * MemTxAttrs indicating a secure transaction, or allow it with
+ * MemTxAttrs indicating a non-secure transaction.
+ */
+typedef enum MSCAction {
+ MSCBlockAbort,
+ MSCBlockRAZWI,
+ MSCAllowSecure,
+ MSCAllowNonSecure,
+} MSCAction;
+
+static MSCAction tz_msc_check(TZMSC *s, hwaddr addr)
+{
+ /*
+ * Check whether to allow an access from the bus master, returning
+ * an MSCAction indicating the required behaviour. If the transaction
+ * is blocked, the caller must check cfg_sec_resp to determine
+ * whether to abort or RAZ/WI the transaction.
+ */
+ IDAUInterfaceClass *iic = IDAU_INTERFACE_GET_CLASS(s->idau);
+ IDAUInterface *ii = IDAU_INTERFACE(s->idau);
+ bool idau_exempt = false, idau_ns = true, idau_nsc = true;
+ int idau_region = IREGION_NOTVALID;
+
+ iic->check(ii, addr, &idau_region, &idau_exempt, &idau_ns, &idau_nsc);
+
+ if (idau_exempt) {
+ /*
+ * Uncheck region -- OK, transaction type depends on
+ * whether bus master is configured as Secure or NonSecure
+ */
+ return s->cfg_nonsec ? MSCAllowNonSecure : MSCAllowSecure;
+ }
+
+ if (idau_ns) {
+ /* NonSecure region -- always forward as NS transaction */
+ return MSCAllowNonSecure;
+ }
+
+ if (!s->cfg_nonsec) {
+ /* Access to Secure region by Secure bus master: OK */
+ return MSCAllowSecure;
+ }
+
+ /* Attempted access to Secure region by NS bus master: block */
+ trace_tz_msc_access_blocked(addr);
+ if (!s->cfg_sec_resp) {
+ return MSCBlockRAZWI;
+ }
+
+ /*
+ * The TRM isn't clear on behaviour if irq_clear is high when a
+ * transaction is blocked. We assume that the MSC behaves like the
+ * PPC, where holding irq_clear high suppresses the interrupt.
+ */
+ if (!s->irq_clear) {
+ s->irq_status = true;
+ tz_msc_update_irq(s);
+ }
+ return MSCBlockAbort;
+}
+
+static MemTxResult tz_msc_read(void *opaque, hwaddr addr, uint64_t *pdata,
+ unsigned size, MemTxAttrs attrs)
+{
+ TZMSC *s = opaque;
+ AddressSpace *as = &s->downstream_as;
+ uint64_t data;
+ MemTxResult res;
+
+ switch (tz_msc_check(s, addr)) {
+ case MSCBlockAbort:
+ return MEMTX_ERROR;
+ case MSCBlockRAZWI:
+ *pdata = 0;
+ return MEMTX_OK;
+ case MSCAllowSecure:
+ attrs.secure = 1;
+ attrs.unspecified = 0;
+ break;
+ case MSCAllowNonSecure:
+ attrs.secure = 0;
+ attrs.unspecified = 0;
+ break;
+ }
+
+ switch (size) {
+ case 1:
+ data = address_space_ldub(as, addr, attrs, &res);
+ break;
+ case 2:
+ data = address_space_lduw_le(as, addr, attrs, &res);
+ break;
+ case 4:
+ data = address_space_ldl_le(as, addr, attrs, &res);
+ break;
+ case 8:
+ data = address_space_ldq_le(as, addr, attrs, &res);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ *pdata = data;
+ return res;
+}
+
+static MemTxResult tz_msc_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size, MemTxAttrs attrs)
+{
+ TZMSC *s = opaque;
+ AddressSpace *as = &s->downstream_as;
+ MemTxResult res;
+
+ switch (tz_msc_check(s, addr)) {
+ case MSCBlockAbort:
+ return MEMTX_ERROR;
+ case MSCBlockRAZWI:
+ return MEMTX_OK;
+ case MSCAllowSecure:
+ attrs.secure = 1;
+ attrs.unspecified = 0;
+ break;
+ case MSCAllowNonSecure:
+ attrs.secure = 0;
+ attrs.unspecified = 0;
+ break;
+ }
+
+ switch (size) {
+ case 1:
+ address_space_stb(as, addr, val, attrs, &res);
+ break;
+ case 2:
+ address_space_stw_le(as, addr, val, attrs, &res);
+ break;
+ case 4:
+ address_space_stl_le(as, addr, val, attrs, &res);
+ break;
+ case 8:
+ address_space_stq_le(as, addr, val, attrs, &res);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ return res;
+}
+
+static const MemoryRegionOps tz_msc_ops = {
+ .read_with_attrs = tz_msc_read,
+ .write_with_attrs = tz_msc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void tz_msc_reset(DeviceState *dev)
+{
+ TZMSC *s = TZ_MSC(dev);
+
+ trace_tz_msc_reset();
+ s->cfg_sec_resp = false;
+ s->cfg_nonsec = false;
+ s->irq_clear = 0;
+ s->irq_status = 0;
+}
+
+static void tz_msc_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ TZMSC *s = TZ_MSC(obj);
+
+ qdev_init_gpio_in_named(dev, tz_msc_cfg_nonsec, "cfg_nonsec", 1);
+ qdev_init_gpio_in_named(dev, tz_msc_cfg_sec_resp, "cfg_sec_resp", 1);
+ qdev_init_gpio_in_named(dev, tz_msc_irq_clear, "irq_clear", 1);
+ qdev_init_gpio_out_named(dev, &s->irq, "irq", 1);
+}
+
+static void tz_msc_realize(DeviceState *dev, Error **errp)
+{
+ Object *obj = OBJECT(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ TZMSC *s = TZ_MSC(dev);
+ const char *name = "tz-msc-downstream";
+ uint64_t size;
+
+ /*
+ * We can't create the upstream end of the port until realize,
+ * as we don't know the size of the MR used as the downstream until then.
+ * We insist on having a downstream, to avoid complicating the
+ * code with handling the "don't know how big this is" case. It's easy
+ * enough for the user to create an unimplemented_device as downstream
+ * if they have nothing else to plug into this.
+ */
+ if (!s->downstream) {
+ error_setg(errp, "MSC 'downstream' link not set");
+ return;
+ }
+ if (!s->idau) {
+ error_setg(errp, "MSC 'idau' link not set");
+ return;
+ }
+
+ size = memory_region_size(s->downstream);
+ address_space_init(&s->downstream_as, s->downstream, name);
+ memory_region_init_io(&s->upstream, obj, &tz_msc_ops, s, name, size);
+ sysbus_init_mmio(sbd, &s->upstream);
+}
+
+static const VMStateDescription tz_msc_vmstate = {
+ .name = "tz-msc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(cfg_nonsec, TZMSC),
+ VMSTATE_BOOL(cfg_sec_resp, TZMSC),
+ VMSTATE_BOOL(irq_clear, TZMSC),
+ VMSTATE_BOOL(irq_status, TZMSC),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property tz_msc_properties[] = {
+ DEFINE_PROP_LINK("downstream", TZMSC, downstream,
+ TYPE_MEMORY_REGION, MemoryRegion *),
+ DEFINE_PROP_LINK("idau", TZMSC, idau,
+ TYPE_IDAU_INTERFACE, IDAUInterface *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tz_msc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = tz_msc_realize;
+ dc->vmsd = &tz_msc_vmstate;
+ dc->reset = tz_msc_reset;
+ device_class_set_props(dc, tz_msc_properties);
+}
+
+static const TypeInfo tz_msc_info = {
+ .name = TYPE_TZ_MSC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TZMSC),
+ .instance_init = tz_msc_init,
+ .class_init = tz_msc_class_init,
+};
+
+static void tz_msc_register_types(void)
+{
+ type_register_static(&tz_msc_info);
+}
+
+type_init(tz_msc_register_types);
diff --git a/hw/misc/tz-ppc.c b/hw/misc/tz-ppc.c
new file mode 100644
index 000000000..36495c68e
--- /dev/null
+++ b/hw/misc/tz-ppc.c
@@ -0,0 +1,352 @@
+/*
+ * ARM TrustZone peripheral protection controller emulation
+ *
+ * Copyright (c) 2018 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/registerfields.h"
+#include "hw/irq.h"
+#include "hw/misc/tz-ppc.h"
+#include "hw/qdev-properties.h"
+
+static void tz_ppc_update_irq(TZPPC *s)
+{
+ bool level = s->irq_status && s->irq_enable;
+
+ trace_tz_ppc_update_irq(level);
+ qemu_set_irq(s->irq, level);
+}
+
+static void tz_ppc_cfg_nonsec(void *opaque, int n, int level)
+{
+ TZPPC *s = TZ_PPC(opaque);
+
+ assert(n < TZ_NUM_PORTS);
+ trace_tz_ppc_cfg_nonsec(n, level);
+ s->cfg_nonsec[n] = level;
+}
+
+static void tz_ppc_cfg_ap(void *opaque, int n, int level)
+{
+ TZPPC *s = TZ_PPC(opaque);
+
+ assert(n < TZ_NUM_PORTS);
+ trace_tz_ppc_cfg_ap(n, level);
+ s->cfg_ap[n] = level;
+}
+
+static void tz_ppc_cfg_sec_resp(void *opaque, int n, int level)
+{
+ TZPPC *s = TZ_PPC(opaque);
+
+ trace_tz_ppc_cfg_sec_resp(level);
+ s->cfg_sec_resp = level;
+}
+
+static void tz_ppc_irq_enable(void *opaque, int n, int level)
+{
+ TZPPC *s = TZ_PPC(opaque);
+
+ trace_tz_ppc_irq_enable(level);
+ s->irq_enable = level;
+ tz_ppc_update_irq(s);
+}
+
+static void tz_ppc_irq_clear(void *opaque, int n, int level)
+{
+ TZPPC *s = TZ_PPC(opaque);
+
+ trace_tz_ppc_irq_clear(level);
+
+ s->irq_clear = level;
+ if (level) {
+ s->irq_status = false;
+ tz_ppc_update_irq(s);
+ }
+}
+
+static bool tz_ppc_check(TZPPC *s, int n, MemTxAttrs attrs)
+{
+ /* Check whether to allow an access to port n; return true if
+ * the check passes, and false if the transaction must be blocked.
+ * If the latter, the caller must check cfg_sec_resp to determine
+ * whether to abort or RAZ/WI the transaction.
+ * The checks are:
+ * + nonsec_mask suppresses any check of the secure attribute
+ * + otherwise, block if cfg_nonsec is 1 and transaction is secure,
+ * or if cfg_nonsec is 0 and transaction is non-secure
+ * + block if transaction is usermode and cfg_ap is 0
+ */
+ if ((attrs.secure == s->cfg_nonsec[n] && !(s->nonsec_mask & (1 << n))) ||
+ (attrs.user && !s->cfg_ap[n])) {
+ /* Block the transaction. */
+ if (!s->irq_clear) {
+ /* Note that holding irq_clear high suppresses interrupts */
+ s->irq_status = true;
+ tz_ppc_update_irq(s);
+ }
+ return false;
+ }
+ return true;
+}
+
+static MemTxResult tz_ppc_read(void *opaque, hwaddr addr, uint64_t *pdata,
+ unsigned size, MemTxAttrs attrs)
+{
+ TZPPCPort *p = opaque;
+ TZPPC *s = p->ppc;
+ int n = p - s->port;
+ AddressSpace *as = &p->downstream_as;
+ uint64_t data;
+ MemTxResult res;
+
+ if (!tz_ppc_check(s, n, attrs)) {
+ trace_tz_ppc_read_blocked(n, addr, attrs.secure, attrs.user);
+ if (s->cfg_sec_resp) {
+ return MEMTX_ERROR;
+ } else {
+ *pdata = 0;
+ return MEMTX_OK;
+ }
+ }
+
+ switch (size) {
+ case 1:
+ data = address_space_ldub(as, addr, attrs, &res);
+ break;
+ case 2:
+ data = address_space_lduw_le(as, addr, attrs, &res);
+ break;
+ case 4:
+ data = address_space_ldl_le(as, addr, attrs, &res);
+ break;
+ case 8:
+ data = address_space_ldq_le(as, addr, attrs, &res);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ *pdata = data;
+ return res;
+}
+
+static MemTxResult tz_ppc_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size, MemTxAttrs attrs)
+{
+ TZPPCPort *p = opaque;
+ TZPPC *s = p->ppc;
+ AddressSpace *as = &p->downstream_as;
+ int n = p - s->port;
+ MemTxResult res;
+
+ if (!tz_ppc_check(s, n, attrs)) {
+ trace_tz_ppc_write_blocked(n, addr, attrs.secure, attrs.user);
+ if (s->cfg_sec_resp) {
+ return MEMTX_ERROR;
+ } else {
+ return MEMTX_OK;
+ }
+ }
+
+ switch (size) {
+ case 1:
+ address_space_stb(as, addr, val, attrs, &res);
+ break;
+ case 2:
+ address_space_stw_le(as, addr, val, attrs, &res);
+ break;
+ case 4:
+ address_space_stl_le(as, addr, val, attrs, &res);
+ break;
+ case 8:
+ address_space_stq_le(as, addr, val, attrs, &res);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ return res;
+}
+
+static const MemoryRegionOps tz_ppc_ops = {
+ .read_with_attrs = tz_ppc_read,
+ .write_with_attrs = tz_ppc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static bool tz_ppc_dummy_accepts(void *opaque, hwaddr addr,
+ unsigned size, bool is_write,
+ MemTxAttrs attrs)
+{
+ /*
+ * Board code should never map the upstream end of an unused port,
+ * so we should never try to make a memory access to it.
+ */
+ g_assert_not_reached();
+}
+
+static uint64_t tz_ppc_dummy_read(void *opaque, hwaddr addr, unsigned size)
+{
+ g_assert_not_reached();
+}
+
+static void tz_ppc_dummy_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ g_assert_not_reached();
+}
+
+static const MemoryRegionOps tz_ppc_dummy_ops = {
+ /* define r/w methods to avoid assert failure in memory_region_init_io */
+ .read = tz_ppc_dummy_read,
+ .write = tz_ppc_dummy_write,
+ .valid.accepts = tz_ppc_dummy_accepts,
+};
+
+static void tz_ppc_reset(DeviceState *dev)
+{
+ TZPPC *s = TZ_PPC(dev);
+
+ trace_tz_ppc_reset();
+ s->cfg_sec_resp = false;
+ memset(s->cfg_nonsec, 0, sizeof(s->cfg_nonsec));
+ memset(s->cfg_ap, 0, sizeof(s->cfg_ap));
+}
+
+static void tz_ppc_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ TZPPC *s = TZ_PPC(obj);
+
+ qdev_init_gpio_in_named(dev, tz_ppc_cfg_nonsec, "cfg_nonsec", TZ_NUM_PORTS);
+ qdev_init_gpio_in_named(dev, tz_ppc_cfg_ap, "cfg_ap", TZ_NUM_PORTS);
+ qdev_init_gpio_in_named(dev, tz_ppc_cfg_sec_resp, "cfg_sec_resp", 1);
+ qdev_init_gpio_in_named(dev, tz_ppc_irq_enable, "irq_enable", 1);
+ qdev_init_gpio_in_named(dev, tz_ppc_irq_clear, "irq_clear", 1);
+ qdev_init_gpio_out_named(dev, &s->irq, "irq", 1);
+}
+
+static void tz_ppc_realize(DeviceState *dev, Error **errp)
+{
+ Object *obj = OBJECT(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ TZPPC *s = TZ_PPC(dev);
+ int i;
+ int max_port = 0;
+
+ /* We can't create the upstream end of the port until realize,
+ * as we don't know the size of the MR used as the downstream until then.
+ */
+ for (i = 0; i < TZ_NUM_PORTS; i++) {
+ if (s->port[i].downstream) {
+ max_port = i;
+ }
+ }
+
+ for (i = 0; i <= max_port; i++) {
+ TZPPCPort *port = &s->port[i];
+ char *name;
+ uint64_t size;
+
+ if (!port->downstream) {
+ /*
+ * Create dummy sysbus MMIO region so the sysbus region
+ * numbering doesn't get out of sync with the port numbers.
+ * The size is entirely arbitrary.
+ */
+ name = g_strdup_printf("tz-ppc-dummy-port[%d]", i);
+ memory_region_init_io(&port->upstream, obj, &tz_ppc_dummy_ops,
+ port, name, 0x10000);
+ sysbus_init_mmio(sbd, &port->upstream);
+ g_free(name);
+ continue;
+ }
+
+ name = g_strdup_printf("tz-ppc-port[%d]", i);
+
+ port->ppc = s;
+ address_space_init(&port->downstream_as, port->downstream, name);
+
+ size = memory_region_size(port->downstream);
+ memory_region_init_io(&port->upstream, obj, &tz_ppc_ops,
+ port, name, size);
+ sysbus_init_mmio(sbd, &port->upstream);
+ g_free(name);
+ }
+}
+
+static const VMStateDescription tz_ppc_vmstate = {
+ .name = "tz-ppc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL_ARRAY(cfg_nonsec, TZPPC, 16),
+ VMSTATE_BOOL_ARRAY(cfg_ap, TZPPC, 16),
+ VMSTATE_BOOL(cfg_sec_resp, TZPPC),
+ VMSTATE_BOOL(irq_enable, TZPPC),
+ VMSTATE_BOOL(irq_clear, TZPPC),
+ VMSTATE_BOOL(irq_status, TZPPC),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define DEFINE_PORT(N) \
+ DEFINE_PROP_LINK("port[" #N "]", TZPPC, port[N].downstream, \
+ TYPE_MEMORY_REGION, MemoryRegion *)
+
+static Property tz_ppc_properties[] = {
+ DEFINE_PROP_UINT32("NONSEC_MASK", TZPPC, nonsec_mask, 0),
+ DEFINE_PORT(0),
+ DEFINE_PORT(1),
+ DEFINE_PORT(2),
+ DEFINE_PORT(3),
+ DEFINE_PORT(4),
+ DEFINE_PORT(5),
+ DEFINE_PORT(6),
+ DEFINE_PORT(7),
+ DEFINE_PORT(8),
+ DEFINE_PORT(9),
+ DEFINE_PORT(10),
+ DEFINE_PORT(11),
+ DEFINE_PORT(12),
+ DEFINE_PORT(13),
+ DEFINE_PORT(14),
+ DEFINE_PORT(15),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tz_ppc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = tz_ppc_realize;
+ dc->vmsd = &tz_ppc_vmstate;
+ dc->reset = tz_ppc_reset;
+ device_class_set_props(dc, tz_ppc_properties);
+}
+
+static const TypeInfo tz_ppc_info = {
+ .name = TYPE_TZ_PPC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TZPPC),
+ .instance_init = tz_ppc_init,
+ .class_init = tz_ppc_class_init,
+};
+
+static void tz_ppc_register_types(void)
+{
+ type_register_static(&tz_ppc_info);
+}
+
+type_init(tz_ppc_register_types);
diff --git a/hw/misc/unimp.c b/hw/misc/unimp.c
new file mode 100644
index 000000000..6cfc5727f
--- /dev/null
+++ b/hw/misc/unimp.c
@@ -0,0 +1,99 @@
+/* "Unimplemented" device
+ *
+ * This is a dummy device which accepts and logs all accesses.
+ * It's useful for stubbing out regions of an SoC or board
+ * map which correspond to devices that have not yet been
+ * implemented. This is often sufficient to placate initial
+ * guest device driver probing such that the system will
+ * come up.
+ *
+ * Copyright Linaro Limited, 2017
+ * Written by Peter Maydell
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/misc/unimp.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+
+static uint64_t unimp_read(void *opaque, hwaddr offset, unsigned size)
+{
+ UnimplementedDeviceState *s = UNIMPLEMENTED_DEVICE(opaque);
+
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
+ "(size %d, offset 0x%0*" HWADDR_PRIx ")\n",
+ s->name, size, s->offset_fmt_width, offset);
+ return 0;
+}
+
+static void unimp_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ UnimplementedDeviceState *s = UNIMPLEMENTED_DEVICE(opaque);
+
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
+ "(size %d, offset 0x%0*" HWADDR_PRIx
+ ", value 0x%0*" PRIx64 ")\n",
+ s->name, size, s->offset_fmt_width, offset, size << 1, value);
+}
+
+static const MemoryRegionOps unimp_ops = {
+ .read = unimp_read,
+ .write = unimp_write,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 8,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 8,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void unimp_realize(DeviceState *dev, Error **errp)
+{
+ UnimplementedDeviceState *s = UNIMPLEMENTED_DEVICE(dev);
+
+ if (s->size == 0) {
+ error_setg(errp, "property 'size' not specified or zero");
+ return;
+ }
+
+ if (s->name == NULL) {
+ error_setg(errp, "property 'name' not specified");
+ return;
+ }
+
+ s->offset_fmt_width = DIV_ROUND_UP(64 - clz64(s->size - 1), 4);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &unimp_ops, s,
+ s->name, s->size);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+}
+
+static Property unimp_properties[] = {
+ DEFINE_PROP_UINT64("size", UnimplementedDeviceState, size, 0),
+ DEFINE_PROP_STRING("name", UnimplementedDeviceState, name),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void unimp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = unimp_realize;
+ device_class_set_props(dc, unimp_properties);
+}
+
+static const TypeInfo unimp_info = {
+ .name = TYPE_UNIMPLEMENTED_DEVICE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(UnimplementedDeviceState),
+ .class_init = unimp_class_init,
+};
+
+static void unimp_register_types(void)
+{
+ type_register_static(&unimp_info);
+}
+
+type_init(unimp_register_types)
diff --git a/hw/misc/virt_ctrl.c b/hw/misc/virt_ctrl.c
new file mode 100644
index 000000000..e75d1e7e1
--- /dev/null
+++ b/hw/misc/virt_ctrl.c
@@ -0,0 +1,150 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Virt system Controller
+ */
+
+#include "qemu/osdep.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "sysemu/runstate.h"
+#include "hw/misc/virt_ctrl.h"
+
+enum {
+ REG_FEATURES = 0x00,
+ REG_CMD = 0x04,
+};
+
+#define FEAT_POWER_CTRL 0x00000001
+
+enum {
+ CMD_NOOP,
+ CMD_RESET,
+ CMD_HALT,
+ CMD_PANIC,
+};
+
+static uint64_t virt_ctrl_read(void *opaque, hwaddr addr, unsigned size)
+{
+ VirtCtrlState *s = opaque;
+ uint64_t value = 0;
+
+ switch (addr) {
+ case REG_FEATURES:
+ value = FEAT_POWER_CTRL;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unimplemented register read 0x%02"HWADDR_PRIx"\n",
+ __func__, addr);
+ break;
+ }
+
+ trace_virt_ctrl_write(s, addr, size, value);
+
+ return value;
+}
+
+static void virt_ctrl_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ VirtCtrlState *s = opaque;
+
+ trace_virt_ctrl_write(s, addr, size, value);
+
+ switch (addr) {
+ case REG_CMD:
+ switch (value) {
+ case CMD_NOOP:
+ break;
+ case CMD_RESET:
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ break;
+ case CMD_HALT:
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ break;
+ case CMD_PANIC:
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_PANIC);
+ break;
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unimplemented register write 0x%02"HWADDR_PRIx"\n",
+ __func__, addr);
+ break;
+ }
+}
+
+static const MemoryRegionOps virt_ctrl_ops = {
+ .read = virt_ctrl_read,
+ .write = virt_ctrl_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.max_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
+static void virt_ctrl_reset(DeviceState *dev)
+{
+ VirtCtrlState *s = VIRT_CTRL(dev);
+
+ trace_virt_ctrl_reset(s);
+}
+
+static void virt_ctrl_realize(DeviceState *dev, Error **errp)
+{
+ VirtCtrlState *s = VIRT_CTRL(dev);
+
+ trace_virt_ctrl_instance_init(s);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &virt_ctrl_ops, s,
+ "virt-ctrl", 0x100);
+}
+
+static const VMStateDescription vmstate_virt_ctrl = {
+ .name = "virt-ctrl",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(irq_enabled, VirtCtrlState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void virt_ctrl_instance_init(Object *obj)
+{
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+ VirtCtrlState *s = VIRT_CTRL(obj);
+
+ trace_virt_ctrl_instance_init(s);
+
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_irq(dev, &s->irq);
+}
+
+static void virt_ctrl_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->reset = virt_ctrl_reset;
+ dc->realize = virt_ctrl_realize;
+ dc->vmsd = &vmstate_virt_ctrl;
+}
+
+static const TypeInfo virt_ctrl_info = {
+ .name = TYPE_VIRT_CTRL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .class_init = virt_ctrl_class_init,
+ .instance_init = virt_ctrl_instance_init,
+ .instance_size = sizeof(VirtCtrlState),
+};
+
+static void virt_ctrl_register_types(void)
+{
+ type_register_static(&virt_ctrl_info);
+}
+
+type_init(virt_ctrl_register_types)
diff --git a/hw/misc/vmcoreinfo.c b/hw/misc/vmcoreinfo.c
new file mode 100644
index 000000000..a9d718fc2
--- /dev/null
+++ b/hw/misc/vmcoreinfo.c
@@ -0,0 +1,108 @@
+/*
+ * Virtual Machine coreinfo device
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * Authors: Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "sysemu/reset.h"
+#include "hw/nvram/fw_cfg.h"
+#include "migration/vmstate.h"
+#include "hw/misc/vmcoreinfo.h"
+
+static void fw_cfg_vmci_write(void *dev, off_t offset, size_t len)
+{
+ VMCoreInfoState *s = VMCOREINFO(dev);
+
+ s->has_vmcoreinfo = offset == 0 && len == sizeof(s->vmcoreinfo)
+ && s->vmcoreinfo.guest_format != FW_CFG_VMCOREINFO_FORMAT_NONE;
+}
+
+static void vmcoreinfo_reset(void *dev)
+{
+ VMCoreInfoState *s = VMCOREINFO(dev);
+
+ s->has_vmcoreinfo = false;
+ memset(&s->vmcoreinfo, 0, sizeof(s->vmcoreinfo));
+ s->vmcoreinfo.host_format = cpu_to_le16(FW_CFG_VMCOREINFO_FORMAT_ELF);
+}
+
+static void vmcoreinfo_realize(DeviceState *dev, Error **errp)
+{
+ VMCoreInfoState *s = VMCOREINFO(dev);
+ FWCfgState *fw_cfg = fw_cfg_find();
+ /* for gdb script dump-guest-memory.py */
+ static VMCoreInfoState * volatile vmcoreinfo_state G_GNUC_UNUSED;
+
+ /* Given that this function is executing, there is at least one VMCOREINFO
+ * device. Check if there are several.
+ */
+ if (!vmcoreinfo_find()) {
+ error_setg(errp, "at most one %s device is permitted",
+ VMCOREINFO_DEVICE);
+ return;
+ }
+
+ if (!fw_cfg || !fw_cfg->dma_enabled) {
+ error_setg(errp, "%s device requires fw_cfg with DMA",
+ VMCOREINFO_DEVICE);
+ return;
+ }
+
+ fw_cfg_add_file_callback(fw_cfg, FW_CFG_VMCOREINFO_FILENAME,
+ NULL, fw_cfg_vmci_write, s,
+ &s->vmcoreinfo, sizeof(s->vmcoreinfo), false);
+
+ /*
+ * This device requires to register a global reset because it is
+ * not plugged to a bus (which, as its QOM parent, would reset it).
+ */
+ qemu_register_reset(vmcoreinfo_reset, dev);
+ vmcoreinfo_state = s;
+}
+
+static const VMStateDescription vmstate_vmcoreinfo = {
+ .name = "vmcoreinfo",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(has_vmcoreinfo, VMCoreInfoState),
+ VMSTATE_UINT16(vmcoreinfo.host_format, VMCoreInfoState),
+ VMSTATE_UINT16(vmcoreinfo.guest_format, VMCoreInfoState),
+ VMSTATE_UINT32(vmcoreinfo.size, VMCoreInfoState),
+ VMSTATE_UINT64(vmcoreinfo.paddr, VMCoreInfoState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void vmcoreinfo_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_vmcoreinfo;
+ dc->realize = vmcoreinfo_realize;
+ dc->hotpluggable = false;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo vmcoreinfo_device_info = {
+ .name = VMCOREINFO_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(VMCoreInfoState),
+ .class_init = vmcoreinfo_device_class_init,
+};
+
+static void vmcoreinfo_register_types(void)
+{
+ type_register_static(&vmcoreinfo_device_info);
+}
+
+type_init(vmcoreinfo_register_types)
diff --git a/hw/misc/xlnx-versal-xramc.c b/hw/misc/xlnx-versal-xramc.c
new file mode 100644
index 000000000..e5b719a0e
--- /dev/null
+++ b/hw/misc/xlnx-versal-xramc.c
@@ -0,0 +1,253 @@
+/*
+ * QEMU model of the Xilinx XRAM Controller.
+ *
+ * Copyright (c) 2021 Xilinx Inc.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * Written by Edgar E. Iglesias <edgar.iglesias@xilinx.com>
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "migration/vmstate.h"
+#include "hw/sysbus.h"
+#include "hw/register.h"
+#include "hw/qdev-properties.h"
+#include "hw/irq.h"
+#include "hw/misc/xlnx-versal-xramc.h"
+
+#ifndef XLNX_XRAM_CTRL_ERR_DEBUG
+#define XLNX_XRAM_CTRL_ERR_DEBUG 0
+#endif
+
+static void xram_update_irq(XlnxXramCtrl *s)
+{
+ bool pending = s->regs[R_XRAM_ISR] & ~s->regs[R_XRAM_IMR];
+ qemu_set_irq(s->irq, pending);
+}
+
+static void xram_isr_postw(RegisterInfo *reg, uint64_t val64)
+{
+ XlnxXramCtrl *s = XLNX_XRAM_CTRL(reg->opaque);
+ xram_update_irq(s);
+}
+
+static uint64_t xram_ien_prew(RegisterInfo *reg, uint64_t val64)
+{
+ XlnxXramCtrl *s = XLNX_XRAM_CTRL(reg->opaque);
+ uint32_t val = val64;
+
+ s->regs[R_XRAM_IMR] &= ~val;
+ xram_update_irq(s);
+ return 0;
+}
+
+static uint64_t xram_ids_prew(RegisterInfo *reg, uint64_t val64)
+{
+ XlnxXramCtrl *s = XLNX_XRAM_CTRL(reg->opaque);
+ uint32_t val = val64;
+
+ s->regs[R_XRAM_IMR] |= val;
+ xram_update_irq(s);
+ return 0;
+}
+
+static const RegisterAccessInfo xram_ctrl_regs_info[] = {
+ { .name = "XRAM_ERR_CTRL", .addr = A_XRAM_ERR_CTRL,
+ .reset = 0xf,
+ .rsvd = 0xfffffff0,
+ },{ .name = "XRAM_ISR", .addr = A_XRAM_ISR,
+ .rsvd = 0xfffff800,
+ .w1c = 0x7ff,
+ .post_write = xram_isr_postw,
+ },{ .name = "XRAM_IMR", .addr = A_XRAM_IMR,
+ .reset = 0x7ff,
+ .rsvd = 0xfffff800,
+ .ro = 0x7ff,
+ },{ .name = "XRAM_IEN", .addr = A_XRAM_IEN,
+ .rsvd = 0xfffff800,
+ .pre_write = xram_ien_prew,
+ },{ .name = "XRAM_IDS", .addr = A_XRAM_IDS,
+ .rsvd = 0xfffff800,
+ .pre_write = xram_ids_prew,
+ },{ .name = "XRAM_ECC_CNTL", .addr = A_XRAM_ECC_CNTL,
+ .rsvd = 0xfffffff8,
+ },{ .name = "XRAM_CLR_EXE", .addr = A_XRAM_CLR_EXE,
+ .rsvd = 0xffffff00,
+ },{ .name = "XRAM_CE_FFA", .addr = A_XRAM_CE_FFA,
+ .rsvd = 0xfff00000,
+ .ro = 0xfffff,
+ },{ .name = "XRAM_CE_FFD0", .addr = A_XRAM_CE_FFD0,
+ .ro = 0xffffffff,
+ },{ .name = "XRAM_CE_FFD1", .addr = A_XRAM_CE_FFD1,
+ .ro = 0xffffffff,
+ },{ .name = "XRAM_CE_FFD2", .addr = A_XRAM_CE_FFD2,
+ .ro = 0xffffffff,
+ },{ .name = "XRAM_CE_FFD3", .addr = A_XRAM_CE_FFD3,
+ .ro = 0xffffffff,
+ },{ .name = "XRAM_CE_FFE", .addr = A_XRAM_CE_FFE,
+ .rsvd = 0xffff0000,
+ .ro = 0xffff,
+ },{ .name = "XRAM_UE_FFA", .addr = A_XRAM_UE_FFA,
+ .rsvd = 0xfff00000,
+ .ro = 0xfffff,
+ },{ .name = "XRAM_UE_FFD0", .addr = A_XRAM_UE_FFD0,
+ .ro = 0xffffffff,
+ },{ .name = "XRAM_UE_FFD1", .addr = A_XRAM_UE_FFD1,
+ .ro = 0xffffffff,
+ },{ .name = "XRAM_UE_FFD2", .addr = A_XRAM_UE_FFD2,
+ .ro = 0xffffffff,
+ },{ .name = "XRAM_UE_FFD3", .addr = A_XRAM_UE_FFD3,
+ .ro = 0xffffffff,
+ },{ .name = "XRAM_UE_FFE", .addr = A_XRAM_UE_FFE,
+ .rsvd = 0xffff0000,
+ .ro = 0xffff,
+ },{ .name = "XRAM_FI_D0", .addr = A_XRAM_FI_D0,
+ },{ .name = "XRAM_FI_D1", .addr = A_XRAM_FI_D1,
+ },{ .name = "XRAM_FI_D2", .addr = A_XRAM_FI_D2,
+ },{ .name = "XRAM_FI_D3", .addr = A_XRAM_FI_D3,
+ },{ .name = "XRAM_FI_SY", .addr = A_XRAM_FI_SY,
+ .rsvd = 0xffff0000,
+ },{ .name = "XRAM_RMW_UE_FFA", .addr = A_XRAM_RMW_UE_FFA,
+ .rsvd = 0xfff00000,
+ .ro = 0xfffff,
+ },{ .name = "XRAM_FI_CNTR", .addr = A_XRAM_FI_CNTR,
+ .rsvd = 0xff000000,
+ },{ .name = "XRAM_IMP", .addr = A_XRAM_IMP,
+ .reset = 0x4,
+ .rsvd = 0xfffffff0,
+ .ro = 0xf,
+ },{ .name = "XRAM_PRDY_DBG", .addr = A_XRAM_PRDY_DBG,
+ .reset = 0xffff,
+ .rsvd = 0xffff0000,
+ .ro = 0xffff,
+ },{ .name = "XRAM_SAFETY_CHK", .addr = A_XRAM_SAFETY_CHK,
+ }
+};
+
+static void xram_ctrl_reset_enter(Object *obj, ResetType type)
+{
+ XlnxXramCtrl *s = XLNX_XRAM_CTRL(obj);
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) {
+ register_reset(&s->regs_info[i]);
+ }
+
+ ARRAY_FIELD_DP32(s->regs, XRAM_IMP, SIZE, s->cfg.encoded_size);
+}
+
+static void xram_ctrl_reset_hold(Object *obj)
+{
+ XlnxXramCtrl *s = XLNX_XRAM_CTRL(obj);
+
+ xram_update_irq(s);
+}
+
+static const MemoryRegionOps xram_ctrl_ops = {
+ .read = register_read_memory,
+ .write = register_write_memory,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void xram_ctrl_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ XlnxXramCtrl *s = XLNX_XRAM_CTRL(dev);
+
+ switch (s->cfg.size) {
+ case 64 * KiB:
+ s->cfg.encoded_size = 0;
+ break;
+ case 128 * KiB:
+ s->cfg.encoded_size = 1;
+ break;
+ case 256 * KiB:
+ s->cfg.encoded_size = 2;
+ break;
+ case 512 * KiB:
+ s->cfg.encoded_size = 3;
+ break;
+ case 1 * MiB:
+ s->cfg.encoded_size = 4;
+ break;
+ default:
+ error_setg(errp, "Unsupported XRAM size %" PRId64, s->cfg.size);
+ return;
+ }
+
+ memory_region_init_ram(&s->ram, OBJECT(s),
+ object_get_canonical_path_component(OBJECT(s)),
+ s->cfg.size, &error_fatal);
+ sysbus_init_mmio(sbd, &s->ram);
+}
+
+static void xram_ctrl_init(Object *obj)
+{
+ XlnxXramCtrl *s = XLNX_XRAM_CTRL(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ s->reg_array =
+ register_init_block32(DEVICE(obj), xram_ctrl_regs_info,
+ ARRAY_SIZE(xram_ctrl_regs_info),
+ s->regs_info, s->regs,
+ &xram_ctrl_ops,
+ XLNX_XRAM_CTRL_ERR_DEBUG,
+ XRAM_CTRL_R_MAX * 4);
+ sysbus_init_mmio(sbd, &s->reg_array->mem);
+ sysbus_init_irq(sbd, &s->irq);
+}
+
+static void xram_ctrl_finalize(Object *obj)
+{
+ XlnxXramCtrl *s = XLNX_XRAM_CTRL(obj);
+ register_finalize_block(s->reg_array);
+}
+
+static const VMStateDescription vmstate_xram_ctrl = {
+ .name = TYPE_XLNX_XRAM_CTRL,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, XlnxXramCtrl, XRAM_CTRL_R_MAX),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static Property xram_ctrl_properties[] = {
+ DEFINE_PROP_UINT64("size", XlnxXramCtrl, cfg.size, 1 * MiB),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xram_ctrl_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = xram_ctrl_realize;
+ dc->vmsd = &vmstate_xram_ctrl;
+ device_class_set_props(dc, xram_ctrl_properties);
+
+ rc->phases.enter = xram_ctrl_reset_enter;
+ rc->phases.hold = xram_ctrl_reset_hold;
+}
+
+static const TypeInfo xram_ctrl_info = {
+ .name = TYPE_XLNX_XRAM_CTRL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XlnxXramCtrl),
+ .class_init = xram_ctrl_class_init,
+ .instance_init = xram_ctrl_init,
+ .instance_finalize = xram_ctrl_finalize,
+};
+
+static void xram_ctrl_register_types(void)
+{
+ type_register_static(&xram_ctrl_info);
+}
+
+type_init(xram_ctrl_register_types)
diff --git a/hw/misc/zynq_slcr.c b/hw/misc/zynq_slcr.c
new file mode 100644
index 000000000..8b7028596
--- /dev/null
+++ b/hw/misc/zynq_slcr.c
@@ -0,0 +1,637 @@
+/*
+ * Status and system control registers for Xilinx Zynq Platform
+ *
+ * Copyright (c) 2011 Michal Simek <monstr@monstr.eu>
+ * Copyright (c) 2012 PetaLogix Pty Ltd.
+ * Based on hw/arm_sysctl.c, written by Paul Brook
+ *
+ * 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.
+ *
+ * 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/timer.h"
+#include "sysemu/runstate.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/registerfields.h"
+#include "hw/qdev-clock.h"
+#include "qom/object.h"
+
+#ifndef ZYNQ_SLCR_ERR_DEBUG
+#define ZYNQ_SLCR_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT(...) do { \
+ if (ZYNQ_SLCR_ERR_DEBUG) { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define XILINX_LOCK_KEY 0x767b
+#define XILINX_UNLOCK_KEY 0xdf0d
+
+REG32(SCL, 0x000)
+REG32(LOCK, 0x004)
+REG32(UNLOCK, 0x008)
+REG32(LOCKSTA, 0x00c)
+
+REG32(ARM_PLL_CTRL, 0x100)
+REG32(DDR_PLL_CTRL, 0x104)
+REG32(IO_PLL_CTRL, 0x108)
+/* fields for [ARM|DDR|IO]_PLL_CTRL registers */
+ FIELD(xxx_PLL_CTRL, PLL_RESET, 0, 1)
+ FIELD(xxx_PLL_CTRL, PLL_PWRDWN, 1, 1)
+ FIELD(xxx_PLL_CTRL, PLL_BYPASS_QUAL, 3, 1)
+ FIELD(xxx_PLL_CTRL, PLL_BYPASS_FORCE, 4, 1)
+ FIELD(xxx_PLL_CTRL, PLL_FPDIV, 12, 7)
+REG32(PLL_STATUS, 0x10c)
+REG32(ARM_PLL_CFG, 0x110)
+REG32(DDR_PLL_CFG, 0x114)
+REG32(IO_PLL_CFG, 0x118)
+
+REG32(ARM_CLK_CTRL, 0x120)
+REG32(DDR_CLK_CTRL, 0x124)
+REG32(DCI_CLK_CTRL, 0x128)
+REG32(APER_CLK_CTRL, 0x12c)
+REG32(USB0_CLK_CTRL, 0x130)
+REG32(USB1_CLK_CTRL, 0x134)
+REG32(GEM0_RCLK_CTRL, 0x138)
+REG32(GEM1_RCLK_CTRL, 0x13c)
+REG32(GEM0_CLK_CTRL, 0x140)
+REG32(GEM1_CLK_CTRL, 0x144)
+REG32(SMC_CLK_CTRL, 0x148)
+REG32(LQSPI_CLK_CTRL, 0x14c)
+REG32(SDIO_CLK_CTRL, 0x150)
+REG32(UART_CLK_CTRL, 0x154)
+ FIELD(UART_CLK_CTRL, CLKACT0, 0, 1)
+ FIELD(UART_CLK_CTRL, CLKACT1, 1, 1)
+ FIELD(UART_CLK_CTRL, SRCSEL, 4, 2)
+ FIELD(UART_CLK_CTRL, DIVISOR, 8, 6)
+REG32(SPI_CLK_CTRL, 0x158)
+REG32(CAN_CLK_CTRL, 0x15c)
+REG32(CAN_MIOCLK_CTRL, 0x160)
+REG32(DBG_CLK_CTRL, 0x164)
+REG32(PCAP_CLK_CTRL, 0x168)
+REG32(TOPSW_CLK_CTRL, 0x16c)
+
+#define FPGA_CTRL_REGS(n, start) \
+ REG32(FPGA ## n ## _CLK_CTRL, (start)) \
+ REG32(FPGA ## n ## _THR_CTRL, (start) + 0x4)\
+ REG32(FPGA ## n ## _THR_CNT, (start) + 0x8)\
+ REG32(FPGA ## n ## _THR_STA, (start) + 0xc)
+FPGA_CTRL_REGS(0, 0x170)
+FPGA_CTRL_REGS(1, 0x180)
+FPGA_CTRL_REGS(2, 0x190)
+FPGA_CTRL_REGS(3, 0x1a0)
+
+REG32(BANDGAP_TRIP, 0x1b8)
+REG32(PLL_PREDIVISOR, 0x1c0)
+REG32(CLK_621_TRUE, 0x1c4)
+
+REG32(PSS_RST_CTRL, 0x200)
+ FIELD(PSS_RST_CTRL, SOFT_RST, 0, 1)
+REG32(DDR_RST_CTRL, 0x204)
+REG32(TOPSW_RESET_CTRL, 0x208)
+REG32(DMAC_RST_CTRL, 0x20c)
+REG32(USB_RST_CTRL, 0x210)
+REG32(GEM_RST_CTRL, 0x214)
+REG32(SDIO_RST_CTRL, 0x218)
+REG32(SPI_RST_CTRL, 0x21c)
+REG32(CAN_RST_CTRL, 0x220)
+REG32(I2C_RST_CTRL, 0x224)
+REG32(UART_RST_CTRL, 0x228)
+REG32(GPIO_RST_CTRL, 0x22c)
+REG32(LQSPI_RST_CTRL, 0x230)
+REG32(SMC_RST_CTRL, 0x234)
+REG32(OCM_RST_CTRL, 0x238)
+REG32(FPGA_RST_CTRL, 0x240)
+REG32(A9_CPU_RST_CTRL, 0x244)
+
+REG32(RS_AWDT_CTRL, 0x24c)
+REG32(RST_REASON, 0x250)
+
+REG32(REBOOT_STATUS, 0x258)
+REG32(BOOT_MODE, 0x25c)
+
+REG32(APU_CTRL, 0x300)
+REG32(WDT_CLK_SEL, 0x304)
+
+REG32(TZ_DMA_NS, 0x440)
+REG32(TZ_DMA_IRQ_NS, 0x444)
+REG32(TZ_DMA_PERIPH_NS, 0x448)
+
+REG32(PSS_IDCODE, 0x530)
+
+REG32(DDR_URGENT, 0x600)
+REG32(DDR_CAL_START, 0x60c)
+REG32(DDR_REF_START, 0x614)
+REG32(DDR_CMD_STA, 0x618)
+REG32(DDR_URGENT_SEL, 0x61c)
+REG32(DDR_DFI_STATUS, 0x620)
+
+REG32(MIO, 0x700)
+#define MIO_LENGTH 54
+
+REG32(MIO_LOOPBACK, 0x804)
+REG32(MIO_MST_TRI0, 0x808)
+REG32(MIO_MST_TRI1, 0x80c)
+
+REG32(SD0_WP_CD_SEL, 0x830)
+REG32(SD1_WP_CD_SEL, 0x834)
+
+REG32(LVL_SHFTR_EN, 0x900)
+REG32(OCM_CFG, 0x910)
+
+REG32(CPU_RAM, 0xa00)
+
+REG32(IOU, 0xa30)
+
+REG32(DMAC_RAM, 0xa50)
+
+REG32(AFI0, 0xa60)
+REG32(AFI1, 0xa6c)
+REG32(AFI2, 0xa78)
+REG32(AFI3, 0xa84)
+#define AFI_LENGTH 3
+
+REG32(OCM, 0xa90)
+
+REG32(DEVCI_RAM, 0xaa0)
+
+REG32(CSG_RAM, 0xab0)
+
+REG32(GPIOB_CTRL, 0xb00)
+REG32(GPIOB_CFG_CMOS18, 0xb04)
+REG32(GPIOB_CFG_CMOS25, 0xb08)
+REG32(GPIOB_CFG_CMOS33, 0xb0c)
+REG32(GPIOB_CFG_HSTL, 0xb14)
+REG32(GPIOB_DRVR_BIAS_CTRL, 0xb18)
+
+REG32(DDRIOB, 0xb40)
+#define DDRIOB_LENGTH 14
+
+#define ZYNQ_SLCR_MMIO_SIZE 0x1000
+#define ZYNQ_SLCR_NUM_REGS (ZYNQ_SLCR_MMIO_SIZE / 4)
+
+#define TYPE_ZYNQ_SLCR "xilinx-zynq_slcr"
+OBJECT_DECLARE_SIMPLE_TYPE(ZynqSLCRState, ZYNQ_SLCR)
+
+struct ZynqSLCRState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t regs[ZYNQ_SLCR_NUM_REGS];
+
+ Clock *ps_clk;
+ Clock *uart0_ref_clk;
+ Clock *uart1_ref_clk;
+};
+
+/*
+ * return the output frequency of ARM/DDR/IO pll
+ * using input frequency and PLL_CTRL register
+ */
+static uint64_t zynq_slcr_compute_pll(uint64_t input, uint32_t ctrl_reg)
+{
+ uint32_t mult = ((ctrl_reg & R_xxx_PLL_CTRL_PLL_FPDIV_MASK) >>
+ R_xxx_PLL_CTRL_PLL_FPDIV_SHIFT);
+
+ /* first, check if pll is bypassed */
+ if (ctrl_reg & R_xxx_PLL_CTRL_PLL_BYPASS_FORCE_MASK) {
+ return input;
+ }
+
+ /* is pll disabled ? */
+ if (ctrl_reg & (R_xxx_PLL_CTRL_PLL_RESET_MASK |
+ R_xxx_PLL_CTRL_PLL_PWRDWN_MASK)) {
+ return 0;
+ }
+
+ /* Consider zero feedback as maximum divide ratio possible */
+ if (!mult) {
+ mult = 1 << R_xxx_PLL_CTRL_PLL_FPDIV_LENGTH;
+ }
+
+ /* frequency multiplier -> period division */
+ return input / mult;
+}
+
+/*
+ * return the output period of a clock given:
+ * + the periods in an array corresponding to input mux selector
+ * + the register xxx_CLK_CTRL value
+ * + enable bit index in ctrl register
+ *
+ * This function makes the assumption that the ctrl_reg value is organized as
+ * follows:
+ * + bits[13:8] clock frequency divisor
+ * + bits[5:4] clock mux selector (index in array)
+ * + bits[index] clock enable
+ */
+static uint64_t zynq_slcr_compute_clock(const uint64_t periods[],
+ uint32_t ctrl_reg,
+ unsigned index)
+{
+ uint32_t srcsel = extract32(ctrl_reg, 4, 2); /* bits [5:4] */
+ uint32_t divisor = extract32(ctrl_reg, 8, 6); /* bits [13:8] */
+
+ /* first, check if clock is disabled */
+ if (((ctrl_reg >> index) & 1u) == 0) {
+ return 0;
+ }
+
+ /*
+ * according to the Zynq technical ref. manual UG585 v1.12.2 in
+ * Clocks chapter, section 25.10.1 page 705:
+ * "The 6-bit divider provides a divide range of 1 to 63"
+ * We follow here what is implemented in linux kernel and consider
+ * the 0 value as a bypass (no division).
+ */
+ /* frequency divisor -> period multiplication */
+ return periods[srcsel] * (divisor ? divisor : 1u);
+}
+
+/*
+ * macro helper around zynq_slcr_compute_clock to avoid repeating
+ * the register name.
+ */
+#define ZYNQ_COMPUTE_CLK(state, plls, reg, enable_field) \
+ zynq_slcr_compute_clock((plls), (state)->regs[reg], \
+ reg ## _ ## enable_field ## _SHIFT)
+
+static void zynq_slcr_compute_clocks_internal(ZynqSLCRState *s, uint64_t ps_clk)
+{
+ uint64_t io_pll = zynq_slcr_compute_pll(ps_clk, s->regs[R_IO_PLL_CTRL]);
+ uint64_t arm_pll = zynq_slcr_compute_pll(ps_clk, s->regs[R_ARM_PLL_CTRL]);
+ uint64_t ddr_pll = zynq_slcr_compute_pll(ps_clk, s->regs[R_DDR_PLL_CTRL]);
+
+ uint64_t uart_mux[4] = {io_pll, io_pll, arm_pll, ddr_pll};
+
+ /* compute uartX reference clocks */
+ clock_set(s->uart0_ref_clk,
+ ZYNQ_COMPUTE_CLK(s, uart_mux, R_UART_CLK_CTRL, CLKACT0));
+ clock_set(s->uart1_ref_clk,
+ ZYNQ_COMPUTE_CLK(s, uart_mux, R_UART_CLK_CTRL, CLKACT1));
+}
+
+/**
+ * Compute and set the ouputs clocks periods.
+ * But do not propagate them further. Connected clocks
+ * will not receive any updates (See zynq_slcr_compute_clocks())
+ */
+static void zynq_slcr_compute_clocks(ZynqSLCRState *s)
+{
+ uint64_t ps_clk = clock_get(s->ps_clk);
+
+ /* consider outputs clocks are disabled while in reset */
+ if (device_is_in_reset(DEVICE(s))) {
+ ps_clk = 0;
+ }
+
+ zynq_slcr_compute_clocks_internal(s, ps_clk);
+}
+
+/**
+ * Propagate the outputs clocks.
+ * zynq_slcr_compute_clocks() should have been called before
+ * to configure them.
+ */
+static void zynq_slcr_propagate_clocks(ZynqSLCRState *s)
+{
+ clock_propagate(s->uart0_ref_clk);
+ clock_propagate(s->uart1_ref_clk);
+}
+
+static void zynq_slcr_ps_clk_callback(void *opaque, ClockEvent event)
+{
+ ZynqSLCRState *s = (ZynqSLCRState *) opaque;
+
+ zynq_slcr_compute_clocks(s);
+ zynq_slcr_propagate_clocks(s);
+}
+
+static void zynq_slcr_reset_init(Object *obj, ResetType type)
+{
+ ZynqSLCRState *s = ZYNQ_SLCR(obj);
+ int i;
+
+ DB_PRINT("RESET\n");
+
+ s->regs[R_LOCKSTA] = 1;
+ /* 0x100 - 0x11C */
+ s->regs[R_ARM_PLL_CTRL] = 0x0001A008;
+ s->regs[R_DDR_PLL_CTRL] = 0x0001A008;
+ s->regs[R_IO_PLL_CTRL] = 0x0001A008;
+ s->regs[R_PLL_STATUS] = 0x0000003F;
+ s->regs[R_ARM_PLL_CFG] = 0x00014000;
+ s->regs[R_DDR_PLL_CFG] = 0x00014000;
+ s->regs[R_IO_PLL_CFG] = 0x00014000;
+
+ /* 0x120 - 0x16C */
+ s->regs[R_ARM_CLK_CTRL] = 0x1F000400;
+ s->regs[R_DDR_CLK_CTRL] = 0x18400003;
+ s->regs[R_DCI_CLK_CTRL] = 0x01E03201;
+ s->regs[R_APER_CLK_CTRL] = 0x01FFCCCD;
+ s->regs[R_USB0_CLK_CTRL] = s->regs[R_USB1_CLK_CTRL] = 0x00101941;
+ s->regs[R_GEM0_RCLK_CTRL] = s->regs[R_GEM1_RCLK_CTRL] = 0x00000001;
+ s->regs[R_GEM0_CLK_CTRL] = s->regs[R_GEM1_CLK_CTRL] = 0x00003C01;
+ s->regs[R_SMC_CLK_CTRL] = 0x00003C01;
+ s->regs[R_LQSPI_CLK_CTRL] = 0x00002821;
+ s->regs[R_SDIO_CLK_CTRL] = 0x00001E03;
+ s->regs[R_UART_CLK_CTRL] = 0x00003F03;
+ s->regs[R_SPI_CLK_CTRL] = 0x00003F03;
+ s->regs[R_CAN_CLK_CTRL] = 0x00501903;
+ s->regs[R_DBG_CLK_CTRL] = 0x00000F03;
+ s->regs[R_PCAP_CLK_CTRL] = 0x00000F01;
+
+ /* 0x170 - 0x1AC */
+ s->regs[R_FPGA0_CLK_CTRL] = s->regs[R_FPGA1_CLK_CTRL]
+ = s->regs[R_FPGA2_CLK_CTRL]
+ = s->regs[R_FPGA3_CLK_CTRL] = 0x00101800;
+ s->regs[R_FPGA0_THR_STA] = s->regs[R_FPGA1_THR_STA]
+ = s->regs[R_FPGA2_THR_STA]
+ = s->regs[R_FPGA3_THR_STA] = 0x00010000;
+
+ /* 0x1B0 - 0x1D8 */
+ s->regs[R_BANDGAP_TRIP] = 0x0000001F;
+ s->regs[R_PLL_PREDIVISOR] = 0x00000001;
+ s->regs[R_CLK_621_TRUE] = 0x00000001;
+
+ /* 0x200 - 0x25C */
+ s->regs[R_FPGA_RST_CTRL] = 0x01F33F0F;
+ s->regs[R_RST_REASON] = 0x00000040;
+
+ s->regs[R_BOOT_MODE] = 0x00000001;
+
+ /* 0x700 - 0x7D4 */
+ for (i = 0; i < 54; i++) {
+ s->regs[R_MIO + i] = 0x00001601;
+ }
+ for (i = 2; i <= 8; i++) {
+ s->regs[R_MIO + i] = 0x00000601;
+ }
+
+ s->regs[R_MIO_MST_TRI0] = s->regs[R_MIO_MST_TRI1] = 0xFFFFFFFF;
+
+ s->regs[R_CPU_RAM + 0] = s->regs[R_CPU_RAM + 1] = s->regs[R_CPU_RAM + 3]
+ = s->regs[R_CPU_RAM + 4] = s->regs[R_CPU_RAM + 7]
+ = 0x00010101;
+ s->regs[R_CPU_RAM + 2] = s->regs[R_CPU_RAM + 5] = 0x01010101;
+ s->regs[R_CPU_RAM + 6] = 0x00000001;
+
+ s->regs[R_IOU + 0] = s->regs[R_IOU + 1] = s->regs[R_IOU + 2]
+ = s->regs[R_IOU + 3] = 0x09090909;
+ s->regs[R_IOU + 4] = s->regs[R_IOU + 5] = 0x00090909;
+ s->regs[R_IOU + 6] = 0x00000909;
+
+ s->regs[R_DMAC_RAM] = 0x00000009;
+
+ s->regs[R_AFI0 + 0] = s->regs[R_AFI0 + 1] = 0x09090909;
+ s->regs[R_AFI1 + 0] = s->regs[R_AFI1 + 1] = 0x09090909;
+ s->regs[R_AFI2 + 0] = s->regs[R_AFI2 + 1] = 0x09090909;
+ s->regs[R_AFI3 + 0] = s->regs[R_AFI3 + 1] = 0x09090909;
+ s->regs[R_AFI0 + 2] = s->regs[R_AFI1 + 2] = s->regs[R_AFI2 + 2]
+ = s->regs[R_AFI3 + 2] = 0x00000909;
+
+ s->regs[R_OCM + 0] = 0x01010101;
+ s->regs[R_OCM + 1] = s->regs[R_OCM + 2] = 0x09090909;
+
+ s->regs[R_DEVCI_RAM] = 0x00000909;
+ s->regs[R_CSG_RAM] = 0x00000001;
+
+ s->regs[R_DDRIOB + 0] = s->regs[R_DDRIOB + 1] = s->regs[R_DDRIOB + 2]
+ = s->regs[R_DDRIOB + 3] = 0x00000e00;
+ s->regs[R_DDRIOB + 4] = s->regs[R_DDRIOB + 5] = s->regs[R_DDRIOB + 6]
+ = 0x00000e00;
+ s->regs[R_DDRIOB + 12] = 0x00000021;
+}
+
+static void zynq_slcr_reset_hold(Object *obj)
+{
+ ZynqSLCRState *s = ZYNQ_SLCR(obj);
+
+ /* will disable all output clocks */
+ zynq_slcr_compute_clocks_internal(s, 0);
+ zynq_slcr_propagate_clocks(s);
+}
+
+static void zynq_slcr_reset_exit(Object *obj)
+{
+ ZynqSLCRState *s = ZYNQ_SLCR(obj);
+
+ /* will compute output clocks according to ps_clk and registers */
+ zynq_slcr_compute_clocks_internal(s, clock_get(s->ps_clk));
+ zynq_slcr_propagate_clocks(s);
+}
+
+static bool zynq_slcr_check_offset(hwaddr offset, bool rnw)
+{
+ switch (offset) {
+ case R_LOCK:
+ case R_UNLOCK:
+ case R_DDR_CAL_START:
+ case R_DDR_REF_START:
+ return !rnw; /* Write only */
+ case R_LOCKSTA:
+ case R_FPGA0_THR_STA:
+ case R_FPGA1_THR_STA:
+ case R_FPGA2_THR_STA:
+ case R_FPGA3_THR_STA:
+ case R_BOOT_MODE:
+ case R_PSS_IDCODE:
+ case R_DDR_CMD_STA:
+ case R_DDR_DFI_STATUS:
+ case R_PLL_STATUS:
+ return rnw;/* read only */
+ case R_SCL:
+ case R_ARM_PLL_CTRL ... R_IO_PLL_CTRL:
+ case R_ARM_PLL_CFG ... R_IO_PLL_CFG:
+ case R_ARM_CLK_CTRL ... R_TOPSW_CLK_CTRL:
+ case R_FPGA0_CLK_CTRL ... R_FPGA0_THR_CNT:
+ case R_FPGA1_CLK_CTRL ... R_FPGA1_THR_CNT:
+ case R_FPGA2_CLK_CTRL ... R_FPGA2_THR_CNT:
+ case R_FPGA3_CLK_CTRL ... R_FPGA3_THR_CNT:
+ case R_BANDGAP_TRIP:
+ case R_PLL_PREDIVISOR:
+ case R_CLK_621_TRUE:
+ case R_PSS_RST_CTRL ... R_A9_CPU_RST_CTRL:
+ case R_RS_AWDT_CTRL:
+ case R_RST_REASON:
+ case R_REBOOT_STATUS:
+ case R_APU_CTRL:
+ case R_WDT_CLK_SEL:
+ case R_TZ_DMA_NS ... R_TZ_DMA_PERIPH_NS:
+ case R_DDR_URGENT:
+ case R_DDR_URGENT_SEL:
+ case R_MIO ... R_MIO + MIO_LENGTH - 1:
+ case R_MIO_LOOPBACK ... R_MIO_MST_TRI1:
+ case R_SD0_WP_CD_SEL:
+ case R_SD1_WP_CD_SEL:
+ case R_LVL_SHFTR_EN:
+ case R_OCM_CFG:
+ case R_CPU_RAM:
+ case R_IOU:
+ case R_DMAC_RAM:
+ case R_AFI0 ... R_AFI3 + AFI_LENGTH - 1:
+ case R_OCM:
+ case R_DEVCI_RAM:
+ case R_CSG_RAM:
+ case R_GPIOB_CTRL ... R_GPIOB_CFG_CMOS33:
+ case R_GPIOB_CFG_HSTL:
+ case R_GPIOB_DRVR_BIAS_CTRL:
+ case R_DDRIOB ... R_DDRIOB + DDRIOB_LENGTH - 1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static uint64_t zynq_slcr_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ ZynqSLCRState *s = opaque;
+ offset /= 4;
+ uint32_t ret = s->regs[offset];
+
+ if (!zynq_slcr_check_offset(offset, true)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "zynq_slcr: Invalid read access to "
+ " addr %" HWADDR_PRIx "\n", offset * 4);
+ }
+
+ DB_PRINT("addr: %08" HWADDR_PRIx " data: %08" PRIx32 "\n", offset * 4, ret);
+ return ret;
+}
+
+static void zynq_slcr_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ ZynqSLCRState *s = (ZynqSLCRState *)opaque;
+ offset /= 4;
+
+ DB_PRINT("addr: %08" HWADDR_PRIx " data: %08" PRIx64 "\n", offset * 4, val);
+
+ if (!zynq_slcr_check_offset(offset, false)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "zynq_slcr: Invalid write access to "
+ "addr %" HWADDR_PRIx "\n", offset * 4);
+ return;
+ }
+
+ switch (offset) {
+ case R_SCL:
+ s->regs[R_SCL] = val & 0x1;
+ return;
+ case R_LOCK:
+ if ((val & 0xFFFF) == XILINX_LOCK_KEY) {
+ DB_PRINT("XILINX LOCK 0xF8000000 + 0x%x <= 0x%x\n", (int)offset,
+ (unsigned)val & 0xFFFF);
+ s->regs[R_LOCKSTA] = 1;
+ } else {
+ DB_PRINT("WRONG XILINX LOCK KEY 0xF8000000 + 0x%x <= 0x%x\n",
+ (int)offset, (unsigned)val & 0xFFFF);
+ }
+ return;
+ case R_UNLOCK:
+ if ((val & 0xFFFF) == XILINX_UNLOCK_KEY) {
+ DB_PRINT("XILINX UNLOCK 0xF8000000 + 0x%x <= 0x%x\n", (int)offset,
+ (unsigned)val & 0xFFFF);
+ s->regs[R_LOCKSTA] = 0;
+ } else {
+ DB_PRINT("WRONG XILINX UNLOCK KEY 0xF8000000 + 0x%x <= 0x%x\n",
+ (int)offset, (unsigned)val & 0xFFFF);
+ }
+ return;
+ }
+
+ if (s->regs[R_LOCKSTA]) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SCLR registers are locked. Unlock them first\n");
+ return;
+ }
+ s->regs[offset] = val;
+
+ switch (offset) {
+ case R_PSS_RST_CTRL:
+ if (FIELD_EX32(val, PSS_RST_CTRL, SOFT_RST)) {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ }
+ break;
+ case R_IO_PLL_CTRL:
+ case R_ARM_PLL_CTRL:
+ case R_DDR_PLL_CTRL:
+ case R_UART_CLK_CTRL:
+ zynq_slcr_compute_clocks(s);
+ zynq_slcr_propagate_clocks(s);
+ break;
+ }
+}
+
+static const MemoryRegionOps slcr_ops = {
+ .read = zynq_slcr_read,
+ .write = zynq_slcr_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const ClockPortInitArray zynq_slcr_clocks = {
+ QDEV_CLOCK_IN(ZynqSLCRState, ps_clk, zynq_slcr_ps_clk_callback, ClockUpdate),
+ QDEV_CLOCK_OUT(ZynqSLCRState, uart0_ref_clk),
+ QDEV_CLOCK_OUT(ZynqSLCRState, uart1_ref_clk),
+ QDEV_CLOCK_END
+};
+
+static void zynq_slcr_init(Object *obj)
+{
+ ZynqSLCRState *s = ZYNQ_SLCR(obj);
+
+ memory_region_init_io(&s->iomem, obj, &slcr_ops, s, "slcr",
+ ZYNQ_SLCR_MMIO_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+
+ qdev_init_clocks(DEVICE(obj), zynq_slcr_clocks);
+}
+
+static const VMStateDescription vmstate_zynq_slcr = {
+ .name = "zynq_slcr",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, ZynqSLCRState, ZYNQ_SLCR_NUM_REGS),
+ VMSTATE_CLOCK_V(ps_clk, ZynqSLCRState, 3),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void zynq_slcr_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->vmsd = &vmstate_zynq_slcr;
+ rc->phases.enter = zynq_slcr_reset_init;
+ rc->phases.hold = zynq_slcr_reset_hold;
+ rc->phases.exit = zynq_slcr_reset_exit;
+}
+
+static const TypeInfo zynq_slcr_info = {
+ .class_init = zynq_slcr_class_init,
+ .name = TYPE_ZYNQ_SLCR,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ZynqSLCRState),
+ .instance_init = zynq_slcr_init,
+};
+
+static void zynq_slcr_register_types(void)
+{
+ type_register_static(&zynq_slcr_info);
+}
+
+type_init(zynq_slcr_register_types)