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:
Peter Maydell 2018-02-09 13:27:40 +00:00
commit f31cd9e4e2
38 changed files with 2928 additions and 187 deletions

View file

@ -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);
}

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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

View file

@ -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
View 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)

View file

@ -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"

View file

@ -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
View 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
View 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
View 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
View 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)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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
View 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)