mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-10 11:04:58 -06:00
target-arm queue:
* Support M profile derived exceptions on exception entry and exit * Implement AArch64 v8.2 crypto insns (SHA-512, SHA-3, SM3, SM4) * Implement working i.MX6 SD controller * Various devices preparatory to i.MX7 support * Preparatory patches for SVE emulation * v8M: Fix bug in implementation of 'TT' insn * Give useful error if user tries to use userspace GICv3 with KVM -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABCAAGBQJafX+DAAoJEDwlJe0UNgzegGgP/2OepsVnpg++NkBpJdCq0wjC DFaGLpDqaXabv0E5b56flkqBsK6mO2vXoIiWOLF3f25G5pBf5zu7XOEYXWuOIBEP Yr/vl4/yWRZfiNKl6aPC5HwHKN8SWMV+Mp/GtKz0PtHLY+oGl3fBRBu8fbeoOyj9 3x+HB0c5fpAtWrRoS+XrUJfQPdkkZ4c5lDvpxec57cqOzJ8qYZEcFCi7WP3Rglox sQJEls5IXkOVGx35o7k0rIw0qzcV8iCGBsuA+4QAonxE5B0ip6aolPAaq+gf/mLP StX2UJG9JO5tOhaNBexHE/QwLNu2pver8mghIyluirLvisB6kx+dEoa0Zj/7J7or LD9O6pqpSPCCKLSw0xPOl2FqjXzbIYcjgddxrOGvUQgAhdagB2EeRHiCdZ7hQXvm Pg4gAsIAZURPrbj7LaXgDCzM0IP6bZjNR2U+C0ekJDBRJb/NTLcvlmx3MQo3s4ot s/t6FP728tnmMk7ib8w95oo2oSMiPUZBfj2IMYogXgY+pup0OunKwOQL93Lmj4By LJwawcsa/9ctyzF/XvBUHO/n4l0zZi3wLmtVojbxkxtHiFmvv5gQMlyHYM08aTJ1 Vu/V0tiX6+oXoNrJg5e8TAchsS5PmO54Sj/ywMm5Q8FWfBE19KTRl5BatxbMYahU gQQt64esxQQFxxjdSrZY =bokK -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20180209' into staging target-arm queue: * Support M profile derived exceptions on exception entry and exit * Implement AArch64 v8.2 crypto insns (SHA-512, SHA-3, SM3, SM4) * Implement working i.MX6 SD controller * Various devices preparatory to i.MX7 support * Preparatory patches for SVE emulation * v8M: Fix bug in implementation of 'TT' insn * Give useful error if user tries to use userspace GICv3 with KVM # gpg: Signature made Fri 09 Feb 2018 11:01:23 GMT # gpg: using RSA key 3C2525ED14360CDE # gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" # gpg: aka "Peter Maydell <pmaydell@gmail.com>" # gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" # Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE * remotes/pmaydell/tags/pull-target-arm-20180209: (30 commits) hw/core/generic-loader: Allow PC to be set on command line target/arm/translate.c: Fix missing 'break' for TT insns target/arm/kvm: gic: Prevent creating userspace GICv3 with KVM target/arm: Add SVE state to TB->FLAGS target/arm: Add ZCR_ELx target/arm: Add SVE to migration state target/arm: Add predicate registers for SVE target/arm: Expand vector registers for SVE hw/arm: Move virt's PSCI DT fixup code to arm/boot.c usb: Add basic code to emulate Chipidea USB IP i.MX: Add implementation of i.MX7 GPR IP block i.MX: Add i.MX7 GPT variant i.MX: Add code to emulate GPCv2 IP block i.MX: Add code to emulate i.MX7 SNVS IP-block i.MX: Add code to emulate i.MX2 watchdog IP block i.MX: Add code to emulate i.MX7 CCM, PMU and ANALOG IP blocks hw: i.MX: Convert i.MX6 to use TYPE_IMX_USDHC sdhci: Add i.MX specific subtype of SDHCI target/arm: enable user-mode SHA-3, SM3, SM4 and SHA-512 instruction support target/arm: implement SM4 instructions ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
f31cd9e4e2
38 changed files with 2928 additions and 187 deletions
|
@ -385,6 +385,69 @@ static void set_kernel_args_old(const struct arm_boot_info *info)
|
|||
}
|
||||
}
|
||||
|
||||
static void fdt_add_psci_node(void *fdt)
|
||||
{
|
||||
uint32_t cpu_suspend_fn;
|
||||
uint32_t cpu_off_fn;
|
||||
uint32_t cpu_on_fn;
|
||||
uint32_t migrate_fn;
|
||||
ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(0));
|
||||
const char *psci_method;
|
||||
int64_t psci_conduit;
|
||||
|
||||
psci_conduit = object_property_get_int(OBJECT(armcpu),
|
||||
"psci-conduit",
|
||||
&error_abort);
|
||||
switch (psci_conduit) {
|
||||
case QEMU_PSCI_CONDUIT_DISABLED:
|
||||
return;
|
||||
case QEMU_PSCI_CONDUIT_HVC:
|
||||
psci_method = "hvc";
|
||||
break;
|
||||
case QEMU_PSCI_CONDUIT_SMC:
|
||||
psci_method = "smc";
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
qemu_fdt_add_subnode(fdt, "/psci");
|
||||
if (armcpu->psci_version == 2) {
|
||||
const char comp[] = "arm,psci-0.2\0arm,psci";
|
||||
qemu_fdt_setprop(fdt, "/psci", "compatible", comp, sizeof(comp));
|
||||
|
||||
cpu_off_fn = QEMU_PSCI_0_2_FN_CPU_OFF;
|
||||
if (arm_feature(&armcpu->env, ARM_FEATURE_AARCH64)) {
|
||||
cpu_suspend_fn = QEMU_PSCI_0_2_FN64_CPU_SUSPEND;
|
||||
cpu_on_fn = QEMU_PSCI_0_2_FN64_CPU_ON;
|
||||
migrate_fn = QEMU_PSCI_0_2_FN64_MIGRATE;
|
||||
} else {
|
||||
cpu_suspend_fn = QEMU_PSCI_0_2_FN_CPU_SUSPEND;
|
||||
cpu_on_fn = QEMU_PSCI_0_2_FN_CPU_ON;
|
||||
migrate_fn = QEMU_PSCI_0_2_FN_MIGRATE;
|
||||
}
|
||||
} else {
|
||||
qemu_fdt_setprop_string(fdt, "/psci", "compatible", "arm,psci");
|
||||
|
||||
cpu_suspend_fn = QEMU_PSCI_0_1_FN_CPU_SUSPEND;
|
||||
cpu_off_fn = QEMU_PSCI_0_1_FN_CPU_OFF;
|
||||
cpu_on_fn = QEMU_PSCI_0_1_FN_CPU_ON;
|
||||
migrate_fn = QEMU_PSCI_0_1_FN_MIGRATE;
|
||||
}
|
||||
|
||||
/* We adopt the PSCI spec's nomenclature, and use 'conduit' to refer
|
||||
* to the instruction that should be used to invoke PSCI functions.
|
||||
* However, the device tree binding uses 'method' instead, so that is
|
||||
* what we should use here.
|
||||
*/
|
||||
qemu_fdt_setprop_string(fdt, "/psci", "method", psci_method);
|
||||
|
||||
qemu_fdt_setprop_cell(fdt, "/psci", "cpu_suspend", cpu_suspend_fn);
|
||||
qemu_fdt_setprop_cell(fdt, "/psci", "cpu_off", cpu_off_fn);
|
||||
qemu_fdt_setprop_cell(fdt, "/psci", "cpu_on", cpu_on_fn);
|
||||
qemu_fdt_setprop_cell(fdt, "/psci", "migrate", migrate_fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* load_dtb() - load a device tree binary image into memory
|
||||
* @addr: the address to load the image at
|
||||
|
@ -541,6 +604,8 @@ static int load_dtb(hwaddr addr, const struct arm_boot_info *binfo,
|
|||
}
|
||||
}
|
||||
|
||||
fdt_add_psci_node(fdt);
|
||||
|
||||
if (binfo->modify_dtb) {
|
||||
binfo->modify_dtb(binfo, fdt);
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ static void fsl_imx6_init(Object *obj)
|
|||
}
|
||||
|
||||
for (i = 0; i < FSL_IMX6_NUM_ESDHCS; i++) {
|
||||
object_initialize(&s->esdhc[i], sizeof(s->esdhc[i]), TYPE_SYSBUS_SDHCI);
|
||||
object_initialize(&s->esdhc[i], sizeof(s->esdhc[i]), TYPE_IMX_USDHC);
|
||||
qdev_set_parent_bus(DEVICE(&s->esdhc[i]), sysbus_get_default());
|
||||
snprintf(name, NAME_SIZE, "sdhc%d", i + 1);
|
||||
object_property_add_child(obj, name, OBJECT(&s->esdhc[i]), NULL);
|
||||
|
|
|
@ -244,66 +244,6 @@ static void create_fdt(VirtMachineState *vms)
|
|||
}
|
||||
}
|
||||
|
||||
static void fdt_add_psci_node(const VirtMachineState *vms)
|
||||
{
|
||||
uint32_t cpu_suspend_fn;
|
||||
uint32_t cpu_off_fn;
|
||||
uint32_t cpu_on_fn;
|
||||
uint32_t migrate_fn;
|
||||
void *fdt = vms->fdt;
|
||||
ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(0));
|
||||
const char *psci_method;
|
||||
|
||||
switch (vms->psci_conduit) {
|
||||
case QEMU_PSCI_CONDUIT_DISABLED:
|
||||
return;
|
||||
case QEMU_PSCI_CONDUIT_HVC:
|
||||
psci_method = "hvc";
|
||||
break;
|
||||
case QEMU_PSCI_CONDUIT_SMC:
|
||||
psci_method = "smc";
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
qemu_fdt_add_subnode(fdt, "/psci");
|
||||
if (armcpu->psci_version == 2) {
|
||||
const char comp[] = "arm,psci-0.2\0arm,psci";
|
||||
qemu_fdt_setprop(fdt, "/psci", "compatible", comp, sizeof(comp));
|
||||
|
||||
cpu_off_fn = QEMU_PSCI_0_2_FN_CPU_OFF;
|
||||
if (arm_feature(&armcpu->env, ARM_FEATURE_AARCH64)) {
|
||||
cpu_suspend_fn = QEMU_PSCI_0_2_FN64_CPU_SUSPEND;
|
||||
cpu_on_fn = QEMU_PSCI_0_2_FN64_CPU_ON;
|
||||
migrate_fn = QEMU_PSCI_0_2_FN64_MIGRATE;
|
||||
} else {
|
||||
cpu_suspend_fn = QEMU_PSCI_0_2_FN_CPU_SUSPEND;
|
||||
cpu_on_fn = QEMU_PSCI_0_2_FN_CPU_ON;
|
||||
migrate_fn = QEMU_PSCI_0_2_FN_MIGRATE;
|
||||
}
|
||||
} else {
|
||||
qemu_fdt_setprop_string(fdt, "/psci", "compatible", "arm,psci");
|
||||
|
||||
cpu_suspend_fn = QEMU_PSCI_0_1_FN_CPU_SUSPEND;
|
||||
cpu_off_fn = QEMU_PSCI_0_1_FN_CPU_OFF;
|
||||
cpu_on_fn = QEMU_PSCI_0_1_FN_CPU_ON;
|
||||
migrate_fn = QEMU_PSCI_0_1_FN_MIGRATE;
|
||||
}
|
||||
|
||||
/* We adopt the PSCI spec's nomenclature, and use 'conduit' to refer
|
||||
* to the instruction that should be used to invoke PSCI functions.
|
||||
* However, the device tree binding uses 'method' instead, so that is
|
||||
* what we should use here.
|
||||
*/
|
||||
qemu_fdt_setprop_string(fdt, "/psci", "method", psci_method);
|
||||
|
||||
qemu_fdt_setprop_cell(fdt, "/psci", "cpu_suspend", cpu_suspend_fn);
|
||||
qemu_fdt_setprop_cell(fdt, "/psci", "cpu_off", cpu_off_fn);
|
||||
qemu_fdt_setprop_cell(fdt, "/psci", "cpu_on", cpu_on_fn);
|
||||
qemu_fdt_setprop_cell(fdt, "/psci", "migrate", migrate_fn);
|
||||
}
|
||||
|
||||
static void fdt_add_timer_nodes(const VirtMachineState *vms)
|
||||
{
|
||||
/* On real hardware these interrupts are level-triggered.
|
||||
|
@ -1409,7 +1349,6 @@ static void machvirt_init(MachineState *machine)
|
|||
}
|
||||
fdt_add_timer_nodes(vms);
|
||||
fdt_add_cpu_nodes(vms);
|
||||
fdt_add_psci_node(vms);
|
||||
|
||||
memory_region_allocate_system_memory(ram, NULL, "mach-virt.ram",
|
||||
machine->ram_size);
|
||||
|
|
|
@ -105,7 +105,7 @@ static void generic_loader_realize(DeviceState *dev, Error **errp)
|
|||
error_setg(errp, "data can not be specified when setting a "
|
||||
"program counter");
|
||||
return;
|
||||
} else if (!s->cpu_num) {
|
||||
} else if (s->cpu_num == CPU_NONE) {
|
||||
error_setg(errp, "cpu_num must be specified when setting a "
|
||||
"program counter");
|
||||
return;
|
||||
|
|
|
@ -6,7 +6,7 @@ common-obj-$(CONFIG_XILINX) += xilinx_intc.o
|
|||
common-obj-$(CONFIG_XLNX_ZYNQMP) += xlnx-pmu-iomod-intc.o
|
||||
common-obj-$(CONFIG_XLNX_ZYNQMP) += xlnx-zynqmp-ipi.o
|
||||
common-obj-$(CONFIG_ETRAXFS) += etraxfs_pic.o
|
||||
common-obj-$(CONFIG_IMX) += imx_avic.o
|
||||
common-obj-$(CONFIG_IMX) += imx_avic.o imx_gpcv2.o
|
||||
common-obj-$(CONFIG_LM32) += lm32_pic.o
|
||||
common-obj-$(CONFIG_REALVIEW) += realview_gic.o
|
||||
common-obj-$(CONFIG_SLAVIO) += slavio_intctl.o
|
||||
|
|
|
@ -503,8 +503,25 @@ static void armv7m_nvic_clear_pending(void *opaque, int irq, bool secure)
|
|||
}
|
||||
}
|
||||
|
||||
void armv7m_nvic_set_pending(void *opaque, int irq, bool secure)
|
||||
static void do_armv7m_nvic_set_pending(void *opaque, int irq, bool secure,
|
||||
bool derived)
|
||||
{
|
||||
/* Pend an exception, including possibly escalating it to HardFault.
|
||||
*
|
||||
* This function handles both "normal" pending of interrupts and
|
||||
* exceptions, and also derived exceptions (ones which occur as
|
||||
* a result of trying to take some other exception).
|
||||
*
|
||||
* If derived == true, the caller guarantees that we are part way through
|
||||
* trying to take an exception (but have not yet called
|
||||
* armv7m_nvic_acknowledge_irq() to make it active), and so:
|
||||
* - s->vectpending is the "original exception" we were trying to take
|
||||
* - irq is the "derived exception"
|
||||
* - nvic_exec_prio(s) gives the priority before exception entry
|
||||
* Here we handle the prioritization logic which the pseudocode puts
|
||||
* in the DerivedLateArrival() function.
|
||||
*/
|
||||
|
||||
NVICState *s = (NVICState *)opaque;
|
||||
bool banked = exc_is_banked(irq);
|
||||
VecInfo *vec;
|
||||
|
@ -514,7 +531,44 @@ void armv7m_nvic_set_pending(void *opaque, int irq, bool secure)
|
|||
|
||||
vec = (banked && secure) ? &s->sec_vectors[irq] : &s->vectors[irq];
|
||||
|
||||
trace_nvic_set_pending(irq, secure, vec->enabled, vec->prio);
|
||||
trace_nvic_set_pending(irq, secure, derived, vec->enabled, vec->prio);
|
||||
|
||||
if (derived) {
|
||||
/* Derived exceptions are always synchronous. */
|
||||
assert(irq >= ARMV7M_EXCP_HARD && irq < ARMV7M_EXCP_PENDSV);
|
||||
|
||||
if (irq == ARMV7M_EXCP_DEBUG &&
|
||||
exc_group_prio(s, vec->prio, secure) >= nvic_exec_prio(s)) {
|
||||
/* DebugMonitorFault, but its priority is lower than the
|
||||
* preempted exception priority: just ignore it.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
if (irq == ARMV7M_EXCP_HARD && vec->prio >= s->vectpending_prio) {
|
||||
/* If this is a terminal exception (one which means we cannot
|
||||
* take the original exception, like a failure to read its
|
||||
* vector table entry), then we must take the derived exception.
|
||||
* If the derived exception can't take priority over the
|
||||
* original exception, then we go into Lockup.
|
||||
*
|
||||
* For QEMU, we rely on the fact that a derived exception is
|
||||
* terminal if and only if it's reported to us as HardFault,
|
||||
* which saves having to have an extra argument is_terminal
|
||||
* that we'd only use in one place.
|
||||
*/
|
||||
cpu_abort(&s->cpu->parent_obj,
|
||||
"Lockup: can't take terminal derived exception "
|
||||
"(original exception priority %d)\n",
|
||||
s->vectpending_prio);
|
||||
}
|
||||
/* We now continue with the same code as for a normal pending
|
||||
* exception, which will cause us to pend the derived exception.
|
||||
* We'll then take either the original or the derived exception
|
||||
* based on which is higher priority by the usual mechanism
|
||||
* for selecting the highest priority pending interrupt.
|
||||
*/
|
||||
}
|
||||
|
||||
if (irq >= ARMV7M_EXCP_HARD && irq < ARMV7M_EXCP_PENDSV) {
|
||||
/* If a synchronous exception is pending then it may be
|
||||
|
@ -585,25 +639,31 @@ void armv7m_nvic_set_pending(void *opaque, int irq, bool secure)
|
|||
}
|
||||
}
|
||||
|
||||
void armv7m_nvic_set_pending(void *opaque, int irq, bool secure)
|
||||
{
|
||||
do_armv7m_nvic_set_pending(opaque, irq, secure, false);
|
||||
}
|
||||
|
||||
void armv7m_nvic_set_pending_derived(void *opaque, int irq, bool secure)
|
||||
{
|
||||
do_armv7m_nvic_set_pending(opaque, irq, secure, true);
|
||||
}
|
||||
|
||||
/* Make pending IRQ active. */
|
||||
bool armv7m_nvic_acknowledge_irq(void *opaque)
|
||||
void armv7m_nvic_acknowledge_irq(void *opaque)
|
||||
{
|
||||
NVICState *s = (NVICState *)opaque;
|
||||
CPUARMState *env = &s->cpu->env;
|
||||
const int pending = s->vectpending;
|
||||
const int running = nvic_exec_prio(s);
|
||||
VecInfo *vec;
|
||||
bool targets_secure;
|
||||
|
||||
assert(pending > ARMV7M_EXCP_RESET && pending < s->num_irq);
|
||||
|
||||
if (s->vectpending_is_s_banked) {
|
||||
vec = &s->sec_vectors[pending];
|
||||
targets_secure = true;
|
||||
} else {
|
||||
vec = &s->vectors[pending];
|
||||
targets_secure = !exc_is_banked(s->vectpending) &&
|
||||
exc_targets_secure(s, s->vectpending);
|
||||
}
|
||||
|
||||
assert(vec->enabled);
|
||||
|
@ -611,7 +671,7 @@ bool armv7m_nvic_acknowledge_irq(void *opaque)
|
|||
|
||||
assert(s->vectpending_prio < running);
|
||||
|
||||
trace_nvic_acknowledge_irq(pending, s->vectpending_prio, targets_secure);
|
||||
trace_nvic_acknowledge_irq(pending, s->vectpending_prio);
|
||||
|
||||
vec->active = 1;
|
||||
vec->pending = 0;
|
||||
|
@ -619,8 +679,28 @@ bool armv7m_nvic_acknowledge_irq(void *opaque)
|
|||
write_v7m_exception(env, s->vectpending);
|
||||
|
||||
nvic_irq_update(s);
|
||||
}
|
||||
|
||||
return targets_secure;
|
||||
void armv7m_nvic_get_pending_irq_info(void *opaque,
|
||||
int *pirq, bool *ptargets_secure)
|
||||
{
|
||||
NVICState *s = (NVICState *)opaque;
|
||||
const int pending = s->vectpending;
|
||||
bool targets_secure;
|
||||
|
||||
assert(pending > ARMV7M_EXCP_RESET && pending < s->num_irq);
|
||||
|
||||
if (s->vectpending_is_s_banked) {
|
||||
targets_secure = true;
|
||||
} else {
|
||||
targets_secure = !exc_is_banked(pending) &&
|
||||
exc_targets_secure(s, pending);
|
||||
}
|
||||
|
||||
trace_nvic_get_pending_irq_info(pending, targets_secure);
|
||||
|
||||
*ptargets_secure = targets_secure;
|
||||
*pirq = pending;
|
||||
}
|
||||
|
||||
int armv7m_nvic_complete_irq(void *opaque, int irq, bool secure)
|
||||
|
|
125
hw/intc/imx_gpcv2.c
Normal file
125
hw/intc/imx_gpcv2.c
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2018, Impinj, Inc.
|
||||
*
|
||||
* i.MX7 GPCv2 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.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/intc/imx_gpcv2.h"
|
||||
#include "qemu/log.h"
|
||||
|
||||
#define GPC_PU_PGC_SW_PUP_REQ 0x0f8
|
||||
#define GPC_PU_PGC_SW_PDN_REQ 0x104
|
||||
|
||||
#define USB_HSIC_PHY_SW_Pxx_REQ BIT(4)
|
||||
#define USB_OTG2_PHY_SW_Pxx_REQ BIT(3)
|
||||
#define USB_OTG1_PHY_SW_Pxx_REQ BIT(2)
|
||||
#define PCIE_PHY_SW_Pxx_REQ BIT(1)
|
||||
#define MIPI_PHY_SW_Pxx_REQ BIT(0)
|
||||
|
||||
|
||||
static void imx_gpcv2_reset(DeviceState *dev)
|
||||
{
|
||||
IMXGPCv2State *s = IMX_GPCV2(dev);
|
||||
|
||||
memset(s->regs, 0, sizeof(s->regs));
|
||||
}
|
||||
|
||||
static uint64_t imx_gpcv2_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
IMXGPCv2State *s = opaque;
|
||||
|
||||
return s->regs[offset / sizeof(uint32_t)];
|
||||
}
|
||||
|
||||
static void imx_gpcv2_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
IMXGPCv2State *s = opaque;
|
||||
const size_t idx = offset / sizeof(uint32_t);
|
||||
|
||||
s->regs[idx] = value;
|
||||
|
||||
/*
|
||||
* Real HW will clear those bits once as a way to indicate that
|
||||
* power up request is complete
|
||||
*/
|
||||
if (offset == GPC_PU_PGC_SW_PUP_REQ ||
|
||||
offset == GPC_PU_PGC_SW_PDN_REQ) {
|
||||
s->regs[idx] &= ~(USB_HSIC_PHY_SW_Pxx_REQ |
|
||||
USB_OTG2_PHY_SW_Pxx_REQ |
|
||||
USB_OTG1_PHY_SW_Pxx_REQ |
|
||||
PCIE_PHY_SW_Pxx_REQ |
|
||||
MIPI_PHY_SW_Pxx_REQ);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct MemoryRegionOps imx_gpcv2_ops = {
|
||||
.read = imx_gpcv2_read,
|
||||
.write = imx_gpcv2_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 imx_gpcv2_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sd = SYS_BUS_DEVICE(obj);
|
||||
IMXGPCv2State *s = IMX_GPCV2(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem,
|
||||
obj,
|
||||
&imx_gpcv2_ops,
|
||||
s,
|
||||
TYPE_IMX_GPCV2 ".iomem",
|
||||
sizeof(s->regs));
|
||||
sysbus_init_mmio(sd, &s->iomem);
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_imx_gpcv2 = {
|
||||
.name = TYPE_IMX_GPCV2,
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32_ARRAY(regs, IMXGPCv2State, GPC_NUM),
|
||||
VMSTATE_END_OF_LIST()
|
||||
},
|
||||
};
|
||||
|
||||
static void imx_gpcv2_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->reset = imx_gpcv2_reset;
|
||||
dc->vmsd = &vmstate_imx_gpcv2;
|
||||
dc->desc = "i.MX GPCv2 Module";
|
||||
}
|
||||
|
||||
static const TypeInfo imx_gpcv2_info = {
|
||||
.name = TYPE_IMX_GPCV2,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(IMXGPCv2State),
|
||||
.instance_init = imx_gpcv2_init,
|
||||
.class_init = imx_gpcv2_class_init,
|
||||
};
|
||||
|
||||
static void imx_gpcv2_register_type(void)
|
||||
{
|
||||
type_register_static(&imx_gpcv2_info);
|
||||
}
|
||||
type_init(imx_gpcv2_register_type)
|
|
@ -177,10 +177,11 @@ nvic_set_prio(int irq, bool secure, uint8_t prio) "NVIC set irq %d secure-bank %
|
|||
nvic_irq_update(int vectpending, int pendprio, int exception_prio, int level) "NVIC vectpending %d pending prio %d exception_prio %d: setting irq line to %d"
|
||||
nvic_escalate_prio(int irq, int irqprio, int runprio) "NVIC escalating irq %d to HardFault: insufficient priority %d >= %d"
|
||||
nvic_escalate_disabled(int irq) "NVIC escalating irq %d to HardFault: disabled"
|
||||
nvic_set_pending(int irq, bool secure, int en, int prio) "NVIC set pending irq %d secure-bank %d (enabled: %d priority %d)"
|
||||
nvic_set_pending(int irq, bool secure, bool derived, int en, int prio) "NVIC set pending irq %d secure-bank %d derived %d (enabled: %d priority %d)"
|
||||
nvic_clear_pending(int irq, bool secure, int en, int prio) "NVIC clear pending irq %d secure-bank %d (enabled: %d priority %d)"
|
||||
nvic_set_pending_level(int irq) "NVIC set pending: irq %d higher prio than vectpending: setting irq line to 1"
|
||||
nvic_acknowledge_irq(int irq, int prio, bool targets_secure) "NVIC acknowledge IRQ: %d now active (prio %d targets_secure %d)"
|
||||
nvic_acknowledge_irq(int irq, int prio) "NVIC acknowledge IRQ: %d now active (prio %d)"
|
||||
nvic_get_pending_irq_info(int irq, bool secure) "NVIC next IRQ %d: targets_secure: %d"
|
||||
nvic_complete_irq(int irq, bool secure) "NVIC complete IRQ %d (secure %d)"
|
||||
nvic_set_irq_level(int irq, int level) "NVIC external irq %d level set to %d"
|
||||
nvic_sysreg_read(uint64_t addr, uint32_t value, unsigned size) "NVIC sysreg read addr 0x%" PRIx64 " data 0x%" PRIx32 " size %u"
|
||||
|
|
|
@ -33,6 +33,10 @@ obj-$(CONFIG_IMX) += imx31_ccm.o
|
|||
obj-$(CONFIG_IMX) += imx25_ccm.o
|
||||
obj-$(CONFIG_IMX) += imx6_ccm.o
|
||||
obj-$(CONFIG_IMX) += imx6_src.o
|
||||
obj-$(CONFIG_IMX) += imx7_ccm.o
|
||||
obj-$(CONFIG_IMX) += imx2_wdt.o
|
||||
obj-$(CONFIG_IMX) += imx7_snvs.o
|
||||
obj-$(CONFIG_IMX) += imx7_gpr.o
|
||||
obj-$(CONFIG_MILKYMIST) += milkymist-hpdmc.o
|
||||
obj-$(CONFIG_MILKYMIST) += milkymist-pfpu.o
|
||||
obj-$(CONFIG_MAINSTONE) += mst_fpga.o
|
||||
|
|
89
hw/misc/imx2_wdt.c
Normal file
89
hw/misc/imx2_wdt.c
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2018, Impinj, Inc.
|
||||
*
|
||||
* i.MX2 Watchdog IP block
|
||||
*
|
||||
* 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/bitops.h"
|
||||
#include "sysemu/watchdog.h"
|
||||
|
||||
#include "hw/misc/imx2_wdt.h"
|
||||
|
||||
#define IMX2_WDT_WCR_WDA BIT(5) /* -> External Reset WDOG_B */
|
||||
#define IMX2_WDT_WCR_SRS BIT(4) /* -> Software Reset Signal */
|
||||
|
||||
static uint64_t imx2_wdt_read(void *opaque, hwaddr addr,
|
||||
unsigned int size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void imx2_wdt_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned int size)
|
||||
{
|
||||
if (addr == IMX2_WDT_WCR &&
|
||||
(value & (IMX2_WDT_WCR_WDA | IMX2_WDT_WCR_SRS))) {
|
||||
watchdog_perform_action();
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps imx2_wdt_ops = {
|
||||
.read = imx2_wdt_read,
|
||||
.write = imx2_wdt_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 imx2_wdt_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
IMX2WdtState *s = IMX2_WDT(dev);
|
||||
|
||||
memory_region_init_io(&s->mmio, OBJECT(dev),
|
||||
&imx2_wdt_ops, s,
|
||||
TYPE_IMX2_WDT".mmio",
|
||||
IMX2_WDT_REG_NUM * sizeof(uint16_t));
|
||||
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
|
||||
}
|
||||
|
||||
static void imx2_wdt_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->realize = imx2_wdt_realize;
|
||||
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
|
||||
}
|
||||
|
||||
static const TypeInfo imx2_wdt_info = {
|
||||
.name = TYPE_IMX2_WDT,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(IMX2WdtState),
|
||||
.class_init = imx2_wdt_class_init,
|
||||
};
|
||||
|
||||
static WatchdogTimerModel model = {
|
||||
.wdt_name = "imx2-watchdog",
|
||||
.wdt_description = "i.MX2 Watchdog",
|
||||
};
|
||||
|
||||
static void imx2_wdt_register_type(void)
|
||||
{
|
||||
watchdog_add_model(&model);
|
||||
type_register_static(&imx2_wdt_info);
|
||||
}
|
||||
type_init(imx2_wdt_register_type)
|
277
hw/misc/imx7_ccm.c
Normal file
277
hw/misc/imx7_ccm.c
Normal file
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* 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 "hw/misc/imx7_ccm.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 const struct MemoryRegionOps imx7_digprog_ops = {
|
||||
.read = imx7_set_clr_tog_read,
|
||||
.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)
|
124
hw/misc/imx7_gpr.c
Normal file
124
hw/misc/imx7_gpr.c
Normal file
|
@ -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 "sysemu/sysemu.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)
|
83
hw/misc/imx7_snvs.c
Normal file
83
hw/misc/imx7_snvs.c
Normal file
|
@ -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/log.h"
|
||||
#include "sysemu/sysemu.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)
|
|
@ -66,3 +66,7 @@ mps2_scc_cfg_read(unsigned function, unsigned device, uint32_t value) "MPS2 SCC
|
|||
msf2_sysreg_write(uint64_t offset, uint32_t val, uint32_t prev) "msf2-sysreg write: addr 0x%08" HWADDR_PRIx " data 0x%" PRIx32 " prev 0x%" PRIx32
|
||||
msf2_sysreg_read(uint64_t offset, uint32_t val) "msf2-sysreg read: addr 0x%08" HWADDR_PRIx " data 0x%08" PRIx32
|
||||
msf2_sysreg_write_pll_status(void) "Invalid write to read only PLL status register"
|
||||
|
||||
#hw/misc/imx7_gpr.c
|
||||
imx7_gpr_read(uint64_t offset) "addr 0x%08" HWADDR_PRIx
|
||||
imx7_gpr_write(uint64_t offset, uint64_t value) "addr 0x%08" HWADDR_PRIx "value 0x%08" HWADDR_PRIx
|
||||
|
|
|
@ -84,12 +84,18 @@
|
|||
|
||||
/* R/W Host control Register 0x0 */
|
||||
#define SDHC_HOSTCTL 0x28
|
||||
#define SDHC_CTRL_LED 0x01
|
||||
#define SDHC_CTRL_DMA_CHECK_MASK 0x18
|
||||
#define SDHC_CTRL_SDMA 0x00
|
||||
#define SDHC_CTRL_ADMA1_32 0x08
|
||||
#define SDHC_CTRL_ADMA2_32 0x10
|
||||
#define SDHC_CTRL_ADMA2_64 0x18
|
||||
#define SDHC_DMA_TYPE(x) ((x) & SDHC_CTRL_DMA_CHECK_MASK)
|
||||
#define SDHC_CTRL_4BITBUS 0x02
|
||||
#define SDHC_CTRL_8BITBUS 0x20
|
||||
#define SDHC_CTRL_CDTEST_INS 0x40
|
||||
#define SDHC_CTRL_CDTEST_EN 0x80
|
||||
|
||||
|
||||
/* R/W Power Control Register 0x0 */
|
||||
#define SDHC_PWRCON 0x29
|
||||
|
@ -226,4 +232,21 @@ enum {
|
|||
sdhc_gap_write = 2 /* SDHC stopped at block gap during write operation */
|
||||
};
|
||||
|
||||
extern const VMStateDescription sdhci_vmstate;
|
||||
|
||||
|
||||
#define ESDHC_MIX_CTRL 0x48
|
||||
#define ESDHC_VENDOR_SPEC 0xc0
|
||||
#define ESDHC_DLL_CTRL 0x60
|
||||
|
||||
#define ESDHC_TUNING_CTRL 0xcc
|
||||
#define ESDHC_TUNE_CTRL_STATUS 0x68
|
||||
#define ESDHC_WTMK_LVL 0x44
|
||||
|
||||
/* Undocumented register used by guests working around erratum ERR004536 */
|
||||
#define ESDHC_UNDOCUMENTED_REG27 0x6c
|
||||
|
||||
#define ESDHC_CTRL_4BITBUS (0x1 << 1)
|
||||
#define ESDHC_CTRL_8BITBUS (0x2 << 1)
|
||||
|
||||
#endif
|
||||
|
|
230
hw/sd/sdhci.c
230
hw/sd/sdhci.c
|
@ -244,7 +244,8 @@ static void sdhci_send_command(SDHCIState *s)
|
|||
}
|
||||
}
|
||||
|
||||
if ((s->norintstsen & SDHC_NISEN_TRSCMP) &&
|
||||
if (!(s->quirks & SDHCI_QUIRK_NO_BUSY_IRQ) &&
|
||||
(s->norintstsen & SDHC_NISEN_TRSCMP) &&
|
||||
(s->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY) {
|
||||
s->norintsts |= SDHC_NIS_TRSCMP;
|
||||
}
|
||||
|
@ -1189,6 +1190,8 @@ static void sdhci_initfn(SDHCIState *s)
|
|||
|
||||
s->insert_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sdhci_raise_insertion_irq, s);
|
||||
s->transfer_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sdhci_data_transfer, s);
|
||||
|
||||
s->io_ops = &sdhci_mmio_ops;
|
||||
}
|
||||
|
||||
static void sdhci_uninitfn(SDHCIState *s)
|
||||
|
@ -1396,6 +1399,10 @@ static void sdhci_sysbus_realize(DeviceState *dev, Error ** errp)
|
|||
}
|
||||
|
||||
sysbus_init_irq(sbd, &s->irq);
|
||||
|
||||
memory_region_init_io(&s->iomem, OBJECT(s), s->io_ops, s, "sdhci",
|
||||
SDHC_REGISTERS_MAP_SIZE);
|
||||
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
|
@ -1447,11 +1454,232 @@ static const TypeInfo sdhci_bus_info = {
|
|||
.class_init = sdhci_bus_class_init,
|
||||
};
|
||||
|
||||
static uint64_t usdhc_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
SDHCIState *s = SYSBUS_SDHCI(opaque);
|
||||
uint32_t ret;
|
||||
uint16_t hostctl;
|
||||
|
||||
switch (offset) {
|
||||
default:
|
||||
return sdhci_read(opaque, offset, size);
|
||||
|
||||
case SDHC_HOSTCTL:
|
||||
/*
|
||||
* For a detailed explanation on the following bit
|
||||
* manipulation code see comments in a similar part of
|
||||
* usdhc_write()
|
||||
*/
|
||||
hostctl = SDHC_DMA_TYPE(s->hostctl) << (8 - 3);
|
||||
|
||||
if (s->hostctl & SDHC_CTRL_8BITBUS) {
|
||||
hostctl |= ESDHC_CTRL_8BITBUS;
|
||||
}
|
||||
|
||||
if (s->hostctl & SDHC_CTRL_4BITBUS) {
|
||||
hostctl |= ESDHC_CTRL_4BITBUS;
|
||||
}
|
||||
|
||||
ret = hostctl;
|
||||
ret |= (uint32_t)s->blkgap << 16;
|
||||
ret |= (uint32_t)s->wakcon << 24;
|
||||
|
||||
break;
|
||||
|
||||
case ESDHC_DLL_CTRL:
|
||||
case ESDHC_TUNE_CTRL_STATUS:
|
||||
case ESDHC_UNDOCUMENTED_REG27:
|
||||
case ESDHC_TUNING_CTRL:
|
||||
case ESDHC_VENDOR_SPEC:
|
||||
case ESDHC_MIX_CTRL:
|
||||
case ESDHC_WTMK_LVL:
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
usdhc_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
|
||||
{
|
||||
SDHCIState *s = SYSBUS_SDHCI(opaque);
|
||||
uint8_t hostctl;
|
||||
uint32_t value = (uint32_t)val;
|
||||
|
||||
switch (offset) {
|
||||
case ESDHC_DLL_CTRL:
|
||||
case ESDHC_TUNE_CTRL_STATUS:
|
||||
case ESDHC_UNDOCUMENTED_REG27:
|
||||
case ESDHC_TUNING_CTRL:
|
||||
case ESDHC_WTMK_LVL:
|
||||
case ESDHC_VENDOR_SPEC:
|
||||
break;
|
||||
|
||||
case SDHC_HOSTCTL:
|
||||
/*
|
||||
* Here's What ESDHCI has at offset 0x28 (SDHC_HOSTCTL)
|
||||
*
|
||||
* 7 6 5 4 3 2 1 0
|
||||
* |-----------+--------+--------+-----------+----------+---------|
|
||||
* | Card | Card | Endian | DATA3 | Data | Led |
|
||||
* | Detect | Detect | Mode | as Card | Transfer | Control |
|
||||
* | Signal | Test | | Detection | Width | |
|
||||
* | Selection | Level | | Pin | | |
|
||||
* |-----------+--------+--------+-----------+----------+---------|
|
||||
*
|
||||
* and 0x29
|
||||
*
|
||||
* 15 10 9 8
|
||||
* |----------+------|
|
||||
* | Reserved | DMA |
|
||||
* | | Sel. |
|
||||
* | | |
|
||||
* |----------+------|
|
||||
*
|
||||
* and here's what SDCHI spec expects those offsets to be:
|
||||
*
|
||||
* 0x28 (Host Control Register)
|
||||
*
|
||||
* 7 6 5 4 3 2 1 0
|
||||
* |--------+--------+----------+------+--------+----------+---------|
|
||||
* | Card | Card | Extended | DMA | High | Data | LED |
|
||||
* | Detect | Detect | Data | Sel. | Speed | Transfer | Control |
|
||||
* | Signal | Test | Transfer | | Enable | Width | |
|
||||
* | Sel. | Level | Width | | | | |
|
||||
* |--------+--------+----------+------+--------+----------+---------|
|
||||
*
|
||||
* and 0x29 (Power Control Register)
|
||||
*
|
||||
* |----------------------------------|
|
||||
* | Power Control Register |
|
||||
* | |
|
||||
* | Description omitted, |
|
||||
* | since it has no analog in ESDHCI |
|
||||
* | |
|
||||
* |----------------------------------|
|
||||
*
|
||||
* Since offsets 0x2A and 0x2B should be compatible between
|
||||
* both IP specs we only need to reconcile least 16-bit of the
|
||||
* word we've been given.
|
||||
*/
|
||||
|
||||
/*
|
||||
* First, save bits 7 6 and 0 since they are identical
|
||||
*/
|
||||
hostctl = value & (SDHC_CTRL_LED |
|
||||
SDHC_CTRL_CDTEST_INS |
|
||||
SDHC_CTRL_CDTEST_EN);
|
||||
/*
|
||||
* Second, split "Data Transfer Width" from bits 2 and 1 in to
|
||||
* bits 5 and 1
|
||||
*/
|
||||
if (value & ESDHC_CTRL_8BITBUS) {
|
||||
hostctl |= SDHC_CTRL_8BITBUS;
|
||||
}
|
||||
|
||||
if (value & ESDHC_CTRL_4BITBUS) {
|
||||
hostctl |= ESDHC_CTRL_4BITBUS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Third, move DMA select from bits 9 and 8 to bits 4 and 3
|
||||
*/
|
||||
hostctl |= SDHC_DMA_TYPE(value >> (8 - 3));
|
||||
|
||||
/*
|
||||
* Now place the corrected value into low 16-bit of the value
|
||||
* we are going to give standard SDHCI write function
|
||||
*
|
||||
* NOTE: This transformation should be the inverse of what can
|
||||
* be found in drivers/mmc/host/sdhci-esdhc-imx.c in Linux
|
||||
* kernel
|
||||
*/
|
||||
value &= ~UINT16_MAX;
|
||||
value |= hostctl;
|
||||
value |= (uint16_t)s->pwrcon << 8;
|
||||
|
||||
sdhci_write(opaque, offset, value, size);
|
||||
break;
|
||||
|
||||
case ESDHC_MIX_CTRL:
|
||||
/*
|
||||
* So, when SD/MMC stack in Linux tries to write to "Transfer
|
||||
* Mode Register", ESDHC i.MX quirk code will translate it
|
||||
* into a write to ESDHC_MIX_CTRL, so we do the opposite in
|
||||
* order to get where we started
|
||||
*
|
||||
* Note that Auto CMD23 Enable bit is located in a wrong place
|
||||
* on i.MX, but since it is not used by QEMU we do not care.
|
||||
*
|
||||
* We don't want to call sdhci_write(.., SDHC_TRNMOD, ...)
|
||||
* here becuase it will result in a call to
|
||||
* sdhci_send_command(s) which we don't want.
|
||||
*
|
||||
*/
|
||||
s->trnmod = value & UINT16_MAX;
|
||||
break;
|
||||
case SDHC_TRNMOD:
|
||||
/*
|
||||
* Similar to above, but this time a write to "Command
|
||||
* Register" will be translated into a 4-byte write to
|
||||
* "Transfer Mode register" where lower 16-bit of value would
|
||||
* be set to zero. So what we do is fill those bits with
|
||||
* cached value from s->trnmod and let the SDHCI
|
||||
* infrastructure handle the rest
|
||||
*/
|
||||
sdhci_write(opaque, offset, val | s->trnmod, size);
|
||||
break;
|
||||
case SDHC_BLKSIZE:
|
||||
/*
|
||||
* ESDHCI does not implement "Host SDMA Buffer Boundary", and
|
||||
* Linux driver will try to zero this field out which will
|
||||
* break the rest of SDHCI emulation.
|
||||
*
|
||||
* Linux defaults to maximum possible setting (512K boundary)
|
||||
* and it seems to be the only option that i.MX IP implements,
|
||||
* so we artificially set it to that value.
|
||||
*/
|
||||
val |= 0x7 << 12;
|
||||
/* FALLTHROUGH */
|
||||
default:
|
||||
sdhci_write(opaque, offset, val, size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const MemoryRegionOps usdhc_mmio_ops = {
|
||||
.read = usdhc_read,
|
||||
.write = usdhc_write,
|
||||
.valid = {
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 4,
|
||||
.unaligned = false
|
||||
},
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
};
|
||||
|
||||
static void imx_usdhc_init(Object *obj)
|
||||
{
|
||||
SDHCIState *s = SYSBUS_SDHCI(obj);
|
||||
|
||||
s->io_ops = &usdhc_mmio_ops;
|
||||
s->quirks = SDHCI_QUIRK_NO_BUSY_IRQ;
|
||||
}
|
||||
|
||||
static const TypeInfo imx_usdhc_info = {
|
||||
.name = TYPE_IMX_USDHC,
|
||||
.parent = TYPE_SYSBUS_SDHCI,
|
||||
.instance_init = imx_usdhc_init,
|
||||
};
|
||||
|
||||
static void sdhci_register_types(void)
|
||||
{
|
||||
type_register_static(&sdhci_pci_info);
|
||||
type_register_static(&sdhci_sysbus_info);
|
||||
type_register_static(&sdhci_bus_info);
|
||||
type_register_static(&imx_usdhc_info);
|
||||
}
|
||||
|
||||
type_init(sdhci_register_types)
|
||||
|
|
|
@ -113,6 +113,17 @@ static const IMXClk imx6_gpt_clocks[] = {
|
|||
CLK_HIGH, /* 111 reference clock */
|
||||
};
|
||||
|
||||
static const IMXClk imx7_gpt_clocks[] = {
|
||||
CLK_NONE, /* 000 No clock source */
|
||||
CLK_IPG, /* 001 ipg_clk, 532MHz*/
|
||||
CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */
|
||||
CLK_EXT, /* 011 External clock */
|
||||
CLK_32k, /* 100 ipg_clk_32k */
|
||||
CLK_HIGH, /* 101 reference clock */
|
||||
CLK_NONE, /* 110 not defined */
|
||||
CLK_NONE, /* 111 not defined */
|
||||
};
|
||||
|
||||
static void imx_gpt_set_freq(IMXGPTState *s)
|
||||
{
|
||||
uint32_t clksrc = extract32(s->cr, GPT_CR_CLKSRC_SHIFT, 3);
|
||||
|
@ -512,6 +523,13 @@ static void imx6_gpt_init(Object *obj)
|
|||
s->clocks = imx6_gpt_clocks;
|
||||
}
|
||||
|
||||
static void imx7_gpt_init(Object *obj)
|
||||
{
|
||||
IMXGPTState *s = IMX_GPT(obj);
|
||||
|
||||
s->clocks = imx7_gpt_clocks;
|
||||
}
|
||||
|
||||
static const TypeInfo imx25_gpt_info = {
|
||||
.name = TYPE_IMX25_GPT,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
|
@ -532,11 +550,18 @@ static const TypeInfo imx6_gpt_info = {
|
|||
.instance_init = imx6_gpt_init,
|
||||
};
|
||||
|
||||
static const TypeInfo imx7_gpt_info = {
|
||||
.name = TYPE_IMX7_GPT,
|
||||
.parent = TYPE_IMX25_GPT,
|
||||
.instance_init = imx7_gpt_init,
|
||||
};
|
||||
|
||||
static void imx_gpt_register_types(void)
|
||||
{
|
||||
type_register_static(&imx25_gpt_info);
|
||||
type_register_static(&imx31_gpt_info);
|
||||
type_register_static(&imx6_gpt_info);
|
||||
type_register_static(&imx7_gpt_info);
|
||||
}
|
||||
|
||||
type_init(imx_gpt_register_types)
|
||||
|
|
|
@ -12,6 +12,7 @@ common-obj-$(CONFIG_USB_XHCI_NEC) += hcd-xhci-nec.o
|
|||
common-obj-$(CONFIG_USB_MUSB) += hcd-musb.o
|
||||
|
||||
obj-$(CONFIG_TUSB6010) += tusb6010.o
|
||||
obj-$(CONFIG_IMX) += chipidea.o
|
||||
|
||||
# emulated usb devices
|
||||
common-obj-$(CONFIG_USB) += dev-hub.o
|
||||
|
|
176
hw/usb/chipidea.c
Normal file
176
hw/usb/chipidea.c
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright (c) 2018, Impinj, Inc.
|
||||
*
|
||||
* Chipidea USB 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.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/usb/hcd-ehci.h"
|
||||
#include "hw/usb/chipidea.h"
|
||||
#include "qemu/log.h"
|
||||
|
||||
enum {
|
||||
CHIPIDEA_USBx_DCIVERSION = 0x000,
|
||||
CHIPIDEA_USBx_DCCPARAMS = 0x004,
|
||||
CHIPIDEA_USBx_DCCPARAMS_HC = BIT(8),
|
||||
};
|
||||
|
||||
static uint64_t chipidea_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void chipidea_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct MemoryRegionOps chipidea_ops = {
|
||||
.read = chipidea_read,
|
||||
.write = chipidea_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 uint64_t chipidea_dc_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
switch (offset) {
|
||||
case CHIPIDEA_USBx_DCIVERSION:
|
||||
return 0x1;
|
||||
case CHIPIDEA_USBx_DCCPARAMS:
|
||||
/*
|
||||
* Real hardware (at least i.MX7) will also report the
|
||||
* controller as "Device Capable" (and 8 supported endpoints),
|
||||
* but there doesn't seem to be much point in doing so, since
|
||||
* we don't emulate that part.
|
||||
*/
|
||||
return CHIPIDEA_USBx_DCCPARAMS_HC;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void chipidea_dc_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct MemoryRegionOps chipidea_dc_ops = {
|
||||
.read = chipidea_dc_read,
|
||||
.write = chipidea_dc_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 chipidea_init(Object *obj)
|
||||
{
|
||||
EHCIState *ehci = &SYS_BUS_EHCI(obj)->ehci;
|
||||
ChipideaState *ci = CHIPIDEA(obj);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ci->iomem); i++) {
|
||||
const struct {
|
||||
const char *name;
|
||||
hwaddr offset;
|
||||
uint64_t size;
|
||||
const struct MemoryRegionOps *ops;
|
||||
} regions[ARRAY_SIZE(ci->iomem)] = {
|
||||
/*
|
||||
* Registers located between offsets 0x000 and 0xFC
|
||||
*/
|
||||
{
|
||||
.name = TYPE_CHIPIDEA ".misc",
|
||||
.offset = 0x000,
|
||||
.size = 0x100,
|
||||
.ops = &chipidea_ops,
|
||||
},
|
||||
/*
|
||||
* Registers located between offsets 0x1A4 and 0x1DC
|
||||
*/
|
||||
{
|
||||
.name = TYPE_CHIPIDEA ".endpoints",
|
||||
.offset = 0x1A4,
|
||||
.size = 0x1DC - 0x1A4 + 4,
|
||||
.ops = &chipidea_ops,
|
||||
},
|
||||
/*
|
||||
* USB_x_DCIVERSION and USB_x_DCCPARAMS
|
||||
*/
|
||||
{
|
||||
.name = TYPE_CHIPIDEA ".dc",
|
||||
.offset = 0x120,
|
||||
.size = 8,
|
||||
.ops = &chipidea_dc_ops,
|
||||
},
|
||||
};
|
||||
|
||||
memory_region_init_io(&ci->iomem[i],
|
||||
obj,
|
||||
regions[i].ops,
|
||||
ci,
|
||||
regions[i].name,
|
||||
regions[i].size);
|
||||
|
||||
memory_region_add_subregion(&ehci->mem,
|
||||
regions[i].offset,
|
||||
&ci->iomem[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void chipidea_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(klass);
|
||||
|
||||
/*
|
||||
* Offsets used were taken from i.MX7Dual Applications Processor
|
||||
* Reference Manual, Rev 0.1, p. 3177, Table 11-59
|
||||
*/
|
||||
sec->capsbase = 0x100;
|
||||
sec->opregbase = 0x140;
|
||||
sec->portnr = 1;
|
||||
|
||||
set_bit(DEVICE_CATEGORY_USB, dc->categories);
|
||||
dc->desc = "Chipidea USB Module";
|
||||
}
|
||||
|
||||
static const TypeInfo chipidea_info = {
|
||||
.name = TYPE_CHIPIDEA,
|
||||
.parent = TYPE_SYS_BUS_EHCI,
|
||||
.instance_size = sizeof(ChipideaState),
|
||||
.instance_init = chipidea_init,
|
||||
.class_init = chipidea_class_init,
|
||||
};
|
||||
|
||||
static void chipidea_register_type(void)
|
||||
{
|
||||
type_register_static(&chipidea_info);
|
||||
}
|
||||
type_init(chipidea_register_type)
|
Loading…
Add table
Add a link
Reference in a new issue