aboutsummaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2020-12-10 11:48:25 +0000
committerPeter Maydell <peter.maydell@linaro.org>2020-12-10 11:48:25 +0000
commit180834dcb8277a687b62f035b477abfd5a1ff978 (patch)
tree435634a8ba46ac2177b86a5d1c12b23a467a7974 /hw
parent5e7b204dbfae9a562fc73684986f936b97f63877 (diff)
parent71f916be1c7e9ede0e37d9cabc781b5a9e8638ff (diff)
downloadqemu-180834dcb8277a687b62f035b477abfd5a1ff978.zip
qemu-180834dcb8277a687b62f035b477abfd5a1ff978.tar.gz
qemu-180834dcb8277a687b62f035b477abfd5a1ff978.tar.bz2
Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20201210' into staging
target-arm queue: * hw/arm/smmuv3: Fix up L1STD_SPAN decoding * xlnx-zynqmp: Support Xilinx ZynqMP CAN controllers * sbsa-ref: allow to use Cortex-A53/57/72 cpus * Various minor code cleanups * hw/intc/armv7m_nvic: Make all of system PPB range be RAZWI/BusFault * Implement more pieces of ARMv8.1M support # gpg: Signature made Thu 10 Dec 2020 11:46:43 GMT # gpg: using RSA key E1A5C593CD419DE28E8315CF3C2525ED14360CDE # gpg: issuer "peter.maydell@linaro.org" # gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@gmail.com>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" [ultimate] # Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE * remotes/pmaydell/tags/pull-target-arm-20201210: (36 commits) hw/arm/armv7m: Correct typo in QOM object name hw/intc/armv7m_nvic: Implement read/write for RAS register block target/arm: Implement M-profile "minimal RAS implementation" hw/intc/armv7m_nvic: Fix "return from inactive handler" check target/arm: Implement CCR_S.TRD behaviour for SG insns hw/intc/armv7m_nvic: Support v8.1M CCR.TRD bit target/arm: Implement new v8.1M VLLDM and VLSTM encodings target/arm: Implement new v8.1M NOCP check for exception return target/arm: Implement v8.1M REVIDR register target/arm: In v8.1M, don't set HFSR.FORCED on vector table fetch failures target/arm: For v8.1M, always clear R0-R3, R12, APSR, EPSR on exception entry hw/intc/armv7m_nvic: Update FPDSCR masking for v8.1M target/arm: Implement FPCXT_S fp system register target/arm: Factor out preserve-fp-state from full_vfp_access_check() target/arm: Use new FPCR_NZCV_MASK constant target/arm: Implement M-profile FPSCR_nzcvqc target/arm: Implement VLDR/VSTR system register target/arm: Move general-use constant expanders up in translate.c target/arm: Refactor M-profile VMSR/VMRS handling target/arm: Enforce M-profile VMRS/VMSR register restrictions ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw')
-rw-r--r--hw/Kconfig1
-rw-r--r--hw/arm/armv7m.c4
-rw-r--r--hw/arm/sbsa-ref.c23
-rw-r--r--hw/arm/smmuv3-internal.h2
-rw-r--r--hw/arm/xlnx-zcu102.c20
-rw-r--r--hw/arm/xlnx-zynqmp.c34
-rw-r--r--hw/intc/armv7m_nvic.c246
-rw-r--r--hw/misc/imx25_ccm.c12
-rw-r--r--hw/misc/imx31_ccm.c14
-rw-r--r--hw/misc/imx6_ccm.c20
-rw-r--r--hw/misc/imx6_src.c2
-rw-r--r--hw/misc/imx6ul_ccm.c4
-rw-r--r--hw/misc/imx_ccm.c4
-rw-r--r--hw/net/can/meson.build1
-rw-r--r--hw/net/can/trace-events9
-rw-r--r--hw/net/can/trace.h1
-rw-r--r--hw/net/can/xlnx-zynqmp-can.c1161
17 files changed, 1477 insertions, 81 deletions
diff --git a/hw/Kconfig b/hw/Kconfig
index 4de1797ffd..5ad3c6b 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -80,3 +80,4 @@ config XILINX_AXI
config XLNX_ZYNQMP
bool
select REGISTER
+ select CAN_BUS
diff --git a/hw/arm/armv7m.c b/hw/arm/armv7m.c
index 8113b29..8224d4a 100644
--- a/hw/arm/armv7m.c
+++ b/hw/arm/armv7m.c
@@ -136,7 +136,7 @@ static void armv7m_instance_init(Object *obj)
memory_region_init(&s->container, obj, "armv7m-container", UINT64_MAX);
- object_initialize_child(obj, "nvnic", &s->nvic, TYPE_NVIC);
+ object_initialize_child(obj, "nvic", &s->nvic, TYPE_NVIC);
object_property_add_alias(obj, "num-irq",
OBJECT(&s->nvic), "num-irq");
@@ -225,7 +225,7 @@ static void armv7m_realize(DeviceState *dev, Error **errp)
sysbus_connect_irq(sbd, 0,
qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ));
- memory_region_add_subregion(&s->container, 0xe000e000,
+ memory_region_add_subregion(&s->container, 0xe0000000,
sysbus_mmio_get_region(sbd, 0));
for (i = 0; i < ARRAY_SIZE(s->bitband); i++) {
diff --git a/hw/arm/sbsa-ref.c b/hw/arm/sbsa-ref.c
index 7d9e180..4a5ea42 100644
--- a/hw/arm/sbsa-ref.c
+++ b/hw/arm/sbsa-ref.c
@@ -143,6 +143,24 @@ static const int sbsa_ref_irqmap[] = {
[SBSA_GWDT] = 16,
};
+static const char * const valid_cpus[] = {
+ ARM_CPU_TYPE_NAME("cortex-a53"),
+ ARM_CPU_TYPE_NAME("cortex-a57"),
+ ARM_CPU_TYPE_NAME("cortex-a72"),
+};
+
+static bool cpu_type_valid(const char *cpu)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(valid_cpus); i++) {
+ if (strcmp(cpu, valid_cpus[i]) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
static uint64_t sbsa_ref_cpu_mp_affinity(SBSAMachineState *sms, int idx)
{
uint8_t clustersz = ARM_DEFAULT_CPUS_PER_CLUSTER;
@@ -649,9 +667,8 @@ static void sbsa_ref_init(MachineState *machine)
const CPUArchIdList *possible_cpus;
int n, sbsa_max_cpus;
- if (strcmp(machine->cpu_type, ARM_CPU_TYPE_NAME("cortex-a57"))) {
- error_report("sbsa-ref: CPU type other than the built-in "
- "cortex-a57 not supported");
+ if (!cpu_type_valid(machine->cpu_type)) {
+ error_report("mach-virt: CPU type %s not supported", machine->cpu_type);
exit(1);
}
diff --git a/hw/arm/smmuv3-internal.h b/hw/arm/smmuv3-internal.h
index fa3c088..b6f7e53 100644
--- a/hw/arm/smmuv3-internal.h
+++ b/hw/arm/smmuv3-internal.h
@@ -633,6 +633,6 @@ static inline uint64_t l1std_l2ptr(STEDesc *desc)
return hi << 32 | lo;
}
-#define L1STD_SPAN(stm) (extract32((stm)->word[0], 0, 4))
+#define L1STD_SPAN(stm) (extract32((stm)->word[0], 0, 5))
#endif
diff --git a/hw/arm/xlnx-zcu102.c b/hw/arm/xlnx-zcu102.c
index ad7fff9..4ef0c51 100644
--- a/hw/arm/xlnx-zcu102.c
+++ b/hw/arm/xlnx-zcu102.c
@@ -25,6 +25,7 @@
#include "sysemu/qtest.h"
#include "sysemu/device_tree.h"
#include "qom/object.h"
+#include "net/can_emu.h"
struct XlnxZCU102 {
MachineState parent_obj;
@@ -34,6 +35,8 @@ struct XlnxZCU102 {
bool secure;
bool virt;
+ CanBusState *canbus[XLNX_ZYNQMP_NUM_CAN];
+
struct arm_boot_info binfo;
};
@@ -125,6 +128,14 @@ static void xlnx_zcu102_init(MachineState *machine)
object_property_set_bool(OBJECT(&s->soc), "virtualization", s->virt,
&error_fatal);
+ for (i = 0; i < XLNX_ZYNQMP_NUM_CAN; i++) {
+ gchar *bus_name = g_strdup_printf("canbus%d", i);
+
+ object_property_set_link(OBJECT(&s->soc), bus_name,
+ OBJECT(s->canbus[i]), &error_fatal);
+ g_free(bus_name);
+ }
+
qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
/* Create and plug in the SD cards */
@@ -208,6 +219,15 @@ static void xlnx_zcu102_machine_instance_init(Object *obj)
s->secure = false;
/* Default to virt (EL2) being disabled */
s->virt = false;
+ object_property_add_link(obj, "xlnx-zcu102.canbus0", TYPE_CAN_BUS,
+ (Object **)&s->canbus[0],
+ object_property_allow_set_link,
+ 0);
+
+ object_property_add_link(obj, "xlnx-zcu102.canbus1", TYPE_CAN_BUS,
+ (Object **)&s->canbus[1],
+ object_property_allow_set_link,
+ 0);
}
static void xlnx_zcu102_machine_class_init(ObjectClass *oc, void *data)
diff --git a/hw/arm/xlnx-zynqmp.c b/hw/arm/xlnx-zynqmp.c
index 7885bb1..8818472 100644
--- a/hw/arm/xlnx-zynqmp.c
+++ b/hw/arm/xlnx-zynqmp.c
@@ -81,6 +81,14 @@ static const int uart_intr[XLNX_ZYNQMP_NUM_UARTS] = {
21, 22,
};
+static const uint64_t can_addr[XLNX_ZYNQMP_NUM_CAN] = {
+ 0xFF060000, 0xFF070000,
+};
+
+static const int can_intr[XLNX_ZYNQMP_NUM_CAN] = {
+ 23, 24,
+};
+
static const uint64_t sdhci_addr[XLNX_ZYNQMP_NUM_SDHCI] = {
0xFF160000, 0xFF170000,
};
@@ -243,6 +251,11 @@ static void xlnx_zynqmp_init(Object *obj)
TYPE_CADENCE_UART);
}
+ for (i = 0; i < XLNX_ZYNQMP_NUM_CAN; i++) {
+ object_initialize_child(obj, "can[*]", &s->can[i],
+ TYPE_XLNX_ZYNQMP_CAN);
+ }
+
object_initialize_child(obj, "sata", &s->sata, TYPE_SYSBUS_AHCI);
for (i = 0; i < XLNX_ZYNQMP_NUM_SDHCI; i++) {
@@ -482,6 +495,23 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp)
gic_spi[uart_intr[i]]);
}
+ for (i = 0; i < XLNX_ZYNQMP_NUM_CAN; i++) {
+ object_property_set_int(OBJECT(&s->can[i]), "ext_clk_freq",
+ XLNX_ZYNQMP_CAN_REF_CLK, &error_abort);
+
+ object_property_set_link(OBJECT(&s->can[i]), "canbus",
+ OBJECT(s->canbus[i]), &error_fatal);
+
+ sysbus_realize(SYS_BUS_DEVICE(&s->can[i]), &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->can[i]), 0, can_addr[i]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->can[i]), 0,
+ gic_spi[can_intr[i]]);
+ }
+
object_property_set_int(OBJECT(&s->sata), "num-ports", SATA_NUM_PORTS,
&error_abort);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->sata), errp)) {
@@ -619,6 +649,10 @@ static Property xlnx_zynqmp_props[] = {
DEFINE_PROP_BOOL("has_rpu", XlnxZynqMPState, has_rpu, false),
DEFINE_PROP_LINK("ddr-ram", XlnxZynqMPState, ddr_ram, TYPE_MEMORY_REGION,
MemoryRegion *),
+ DEFINE_PROP_LINK("canbus0", XlnxZynqMPState, canbus[0], TYPE_CAN_BUS,
+ CanBusState *),
+ DEFINE_PROP_LINK("canbus1", XlnxZynqMPState, canbus[1], TYPE_CAN_BUS,
+ CanBusState *),
DEFINE_PROP_END_OF_LIST()
};
diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c
index 42b1ad5..f63aa2d 100644
--- a/hw/intc/armv7m_nvic.c
+++ b/hw/intc/armv7m_nvic.c
@@ -832,10 +832,40 @@ int armv7m_nvic_complete_irq(void *opaque, int irq, bool secure)
{
NVICState *s = (NVICState *)opaque;
VecInfo *vec = NULL;
- int ret;
+ int ret = 0;
assert(irq > ARMV7M_EXCP_RESET && irq < s->num_irq);
+ trace_nvic_complete_irq(irq, secure);
+
+ if (secure && exc_is_banked(irq)) {
+ vec = &s->sec_vectors[irq];
+ } else {
+ vec = &s->vectors[irq];
+ }
+
+ /*
+ * Identify illegal exception return cases. We can't immediately
+ * return at this point because we still need to deactivate
+ * (either this exception or NMI/HardFault) first.
+ */
+ if (!exc_is_banked(irq) && exc_targets_secure(s, irq) != secure) {
+ /*
+ * Return from a configurable exception targeting the opposite
+ * security state from the one we're trying to complete it for.
+ * Clear vec because it's not really the VecInfo for this
+ * (irq, secstate) so we mustn't deactivate it.
+ */
+ ret = -1;
+ vec = NULL;
+ } else if (!vec->active) {
+ /* Return from an inactive interrupt */
+ ret = -1;
+ } else {
+ /* Legal return, we will return the RETTOBASE bit value to the caller */
+ ret = nvic_rettobase(s);
+ }
+
/*
* For negative priorities, v8M will forcibly deactivate the appropriate
* NMI or HardFault regardless of what interrupt we're being asked to
@@ -865,32 +895,7 @@ int armv7m_nvic_complete_irq(void *opaque, int irq, bool secure)
}
if (!vec) {
- if (secure && exc_is_banked(irq)) {
- vec = &s->sec_vectors[irq];
- } else {
- vec = &s->vectors[irq];
- }
- }
-
- trace_nvic_complete_irq(irq, secure);
-
- if (!vec->active) {
- /* Tell the caller this was an illegal exception return */
- return -1;
- }
-
- /*
- * If this is a configurable exception and it is currently
- * targeting the opposite security state from the one we're trying
- * to complete it for, this counts as an illegal exception return.
- * We still need to deactivate whatever vector the logic above has
- * selected, though, as it might not be the same as the one for the
- * requested exception number.
- */
- if (!exc_is_banked(irq) && exc_targets_secure(s, irq) != secure) {
- ret = -1;
- } else {
- ret = nvic_rettobase(s);
+ return ret;
}
vec->active = 0;
@@ -1025,6 +1030,11 @@ static uint32_t nvic_readl(NVICState *s, uint32_t offset, MemTxAttrs attrs)
}
return val;
}
+ case 0xcfc:
+ if (!arm_feature(&cpu->env, ARM_FEATURE_V8_1M)) {
+ goto bad_offset;
+ }
+ return cpu->revidr;
case 0xd00: /* CPUID Base. */
return cpu->midr;
case 0xd04: /* Interrupt Control State (ICSR) */
@@ -1090,8 +1100,9 @@ static uint32_t nvic_readl(NVICState *s, uint32_t offset, MemTxAttrs attrs)
}
return cpu->env.v7m.scr[attrs.secure];
case 0xd14: /* Configuration Control. */
- /* The BFHFNMIGN bit is the only non-banked bit; we
- * keep it in the non-secure copy of the register.
+ /*
+ * Non-banked bits: BFHFNMIGN (stored in the NS copy of the register)
+ * and TRD (stored in the S copy of the register)
*/
val = cpu->env.v7m.ccr[attrs.secure];
val |= cpu->env.v7m.ccr[M_REG_NS] & R_V7M_CCR_BFHFNMIGN_MASK;
@@ -1472,6 +1483,12 @@ static uint32_t nvic_readl(NVICState *s, uint32_t offset, MemTxAttrs attrs)
return 0;
}
return cpu->env.v7m.sfar;
+ case 0xf04: /* RFSR */
+ if (!cpu_isar_feature(aa32_ras, cpu)) {
+ goto bad_offset;
+ }
+ /* We provide minimal-RAS only: RFSR is RAZ/WI */
+ return 0;
case 0xf34: /* FPCCR */
if (!cpu_isar_feature(aa32_vfp_simd, cpu)) {
return 0;
@@ -1600,6 +1617,7 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
R_V7M_AIRCR_PRIGROUP_SHIFT,
R_V7M_AIRCR_PRIGROUP_LENGTH);
}
+ /* AIRCR.IESB is RAZ/WI because we implement only minimal RAS */
if (attrs.secure) {
/* These bits are only writable by secure */
cpu->env.v7m.aircr = value &
@@ -1634,17 +1652,25 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
cpu->env.v7m.scr[attrs.secure] = value;
break;
case 0xd14: /* Configuration Control. */
+ {
+ uint32_t mask;
+
if (!arm_feature(&cpu->env, ARM_FEATURE_M_MAIN)) {
goto bad_offset;
}
/* Enforce RAZ/WI on reserved and must-RAZ/WI bits */
- value &= (R_V7M_CCR_STKALIGN_MASK |
- R_V7M_CCR_BFHFNMIGN_MASK |
- R_V7M_CCR_DIV_0_TRP_MASK |
- R_V7M_CCR_UNALIGN_TRP_MASK |
- R_V7M_CCR_USERSETMPEND_MASK |
- R_V7M_CCR_NONBASETHRDENA_MASK);
+ mask = R_V7M_CCR_STKALIGN_MASK |
+ R_V7M_CCR_BFHFNMIGN_MASK |
+ R_V7M_CCR_DIV_0_TRP_MASK |
+ R_V7M_CCR_UNALIGN_TRP_MASK |
+ R_V7M_CCR_USERSETMPEND_MASK |
+ R_V7M_CCR_NONBASETHRDENA_MASK;
+ if (arm_feature(&cpu->env, ARM_FEATURE_V8_1M) && attrs.secure) {
+ /* TRD is always RAZ/WI from NS */
+ mask |= R_V7M_CCR_TRD_MASK;
+ }
+ value &= mask;
if (arm_feature(&cpu->env, ARM_FEATURE_V8)) {
/* v8M makes NONBASETHRDENA and STKALIGN be RES1 */
@@ -1661,6 +1687,7 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
cpu->env.v7m.ccr[attrs.secure] = value;
break;
+ }
case 0xd24: /* System Handler Control and State (SHCSR) */
if (!arm_feature(&cpu->env, ARM_FEATURE_V7)) {
goto bad_offset;
@@ -2006,6 +2033,12 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
}
break;
}
+ case 0xf04: /* RFSR */
+ if (!cpu_isar_feature(aa32_ras, cpu)) {
+ goto bad_offset;
+ }
+ /* We provide minimal-RAS only: RFSR is RAZ/WI */
+ break;
case 0xf34: /* FPCCR */
if (cpu_isar_feature(aa32_vfp_simd, cpu)) {
/* Not all bits here are banked. */
@@ -2068,7 +2101,14 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
break;
case 0xf3c: /* FPDSCR */
if (cpu_isar_feature(aa32_vfp_simd, cpu)) {
- value &= 0x07c00000;
+ uint32_t mask = FPCR_AHP | FPCR_DN | FPCR_FZ | FPCR_RMODE_MASK;
+ if (cpu_isar_feature(any_fp16, cpu)) {
+ mask |= FPCR_FZ16;
+ }
+ value &= mask;
+ if (cpu_isar_feature(aa32_lob, cpu)) {
+ value |= 4 << FPCR_LTPSIZE_SHIFT;
+ }
cpu->env.v7m.fpdscr[attrs.secure] = value;
}
break;
@@ -2479,6 +2519,93 @@ static const MemoryRegionOps nvic_systick_ops = {
.endianness = DEVICE_NATIVE_ENDIAN,
};
+
+static MemTxResult ras_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ if (attrs.user) {
+ return MEMTX_ERROR;
+ }
+
+ switch (addr) {
+ case 0xe10: /* ERRIIDR */
+ /* architect field = Arm; product/variant/revision 0 */
+ *data = 0x43b;
+ break;
+ case 0xfc8: /* ERRDEVID */
+ /* Minimal RAS: we implement 0 error record indexes */
+ *data = 0;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "Read RAS register offset 0x%x\n",
+ (uint32_t)addr);
+ *data = 0;
+ break;
+ }
+ return MEMTX_OK;
+}
+
+static MemTxResult ras_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ if (attrs.user) {
+ return MEMTX_ERROR;
+ }
+
+ switch (addr) {
+ default:
+ qemu_log_mask(LOG_UNIMP, "Write to RAS register offset 0x%x\n",
+ (uint32_t)addr);
+ break;
+ }
+ return MEMTX_OK;
+}
+
+static const MemoryRegionOps ras_ops = {
+ .read_with_attrs = ras_read,
+ .write_with_attrs = ras_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * Unassigned portions of the PPB space are RAZ/WI for privileged
+ * accesses, and fault for non-privileged accesses.
+ */
+static MemTxResult ppb_default_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ qemu_log_mask(LOG_UNIMP, "Read of unassigned area of PPB: offset 0x%x\n",
+ (uint32_t)addr);
+ if (attrs.user) {
+ return MEMTX_ERROR;
+ }
+ *data = 0;
+ return MEMTX_OK;
+}
+
+static MemTxResult ppb_default_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size,
+ MemTxAttrs attrs)
+{
+ qemu_log_mask(LOG_UNIMP, "Write of unassigned area of PPB: offset 0x%x\n",
+ (uint32_t)addr);
+ if (attrs.user) {
+ return MEMTX_ERROR;
+ }
+ return MEMTX_OK;
+}
+
+static const MemoryRegionOps ppb_default_ops = {
+ .read_with_attrs = ppb_default_read,
+ .write_with_attrs = ppb_default_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 8,
+};
+
static int nvic_post_load(void *opaque, int version_id)
{
NVICState *s = opaque;
@@ -2675,7 +2802,6 @@ static void nvic_systick_trigger(void *opaque, int n, int level)
static void armv7m_nvic_realize(DeviceState *dev, Error **errp)
{
NVICState *s = NVIC(dev);
- int regionlen;
/* The armv7m container object will have set our CPU pointer */
if (!s->cpu || !arm_feature(&s->cpu->env, ARM_FEATURE_M)) {
@@ -2718,7 +2844,20 @@ static void armv7m_nvic_realize(DeviceState *dev, Error **errp)
M_REG_S));
}
- /* The NVIC and System Control Space (SCS) starts at 0xe000e000
+ /*
+ * This device provides a single sysbus memory region which
+ * represents the whole of the "System PPB" space. This is the
+ * range from 0xe0000000 to 0xe00fffff and includes the NVIC,
+ * the System Control Space (system registers), the systick timer,
+ * and for CPUs with the Security extension an NS banked version
+ * of all of these.
+ *
+ * The default behaviour for unimplemented registers/ranges
+ * (for instance the Data Watchpoint and Trace unit at 0xe0001000)
+ * is to RAZ/WI for privileged access and BusFault for non-privileged
+ * access.
+ *
+ * The NVIC and System Control Space (SCS) starts at 0xe000e000
* and looks like this:
* 0x004 - ICTR
* 0x010 - 0xff - systick
@@ -2741,35 +2880,48 @@ static void armv7m_nvic_realize(DeviceState *dev, Error **errp)
* generally code determining which banked register to use should
* use attrs.secure; code determining actual behaviour of the system
* should use env->v7m.secure.
+ *
+ * The container covers the whole PPB space. Within it the priority
+ * of overlapping regions is:
+ * - default region (for RAZ/WI and BusFault) : -1
+ * - system register regions : 0
+ * - systick : 1
+ * This is because the systick device is a small block of registers
+ * in the middle of the other system control registers.
*/
- regionlen = arm_feature(&s->cpu->env, ARM_FEATURE_V8) ? 0x21000 : 0x1000;
- memory_region_init(&s->container, OBJECT(s), "nvic", regionlen);
- /* The system register region goes at the bottom of the priority
- * stack as it covers the whole page.
- */
+ memory_region_init(&s->container, OBJECT(s), "nvic", 0x100000);
+ memory_region_init_io(&s->defaultmem, OBJECT(s), &ppb_default_ops, s,
+ "nvic-default", 0x100000);
+ memory_region_add_subregion_overlap(&s->container, 0, &s->defaultmem, -1);
memory_region_init_io(&s->sysregmem, OBJECT(s), &nvic_sysreg_ops, s,
"nvic_sysregs", 0x1000);
- memory_region_add_subregion(&s->container, 0, &s->sysregmem);
+ memory_region_add_subregion(&s->container, 0xe000, &s->sysregmem);
memory_region_init_io(&s->systickmem, OBJECT(s),
&nvic_systick_ops, s,
"nvic_systick", 0xe0);
- memory_region_add_subregion_overlap(&s->container, 0x10,
+ memory_region_add_subregion_overlap(&s->container, 0xe010,
&s->systickmem, 1);
if (arm_feature(&s->cpu->env, ARM_FEATURE_V8)) {
memory_region_init_io(&s->sysreg_ns_mem, OBJECT(s),
&nvic_sysreg_ns_ops, &s->sysregmem,
"nvic_sysregs_ns", 0x1000);
- memory_region_add_subregion(&s->container, 0x20000, &s->sysreg_ns_mem);
+ memory_region_add_subregion(&s->container, 0x2e000, &s->sysreg_ns_mem);
memory_region_init_io(&s->systick_ns_mem, OBJECT(s),
&nvic_sysreg_ns_ops, &s->systickmem,
"nvic_systick_ns", 0xe0);
- memory_region_add_subregion_overlap(&s->container, 0x20010,
+ memory_region_add_subregion_overlap(&s->container, 0x2e010,
&s->systick_ns_mem, 1);
}
+ if (cpu_isar_feature(aa32_ras, s->cpu)) {
+ memory_region_init_io(&s->ras_mem, OBJECT(s),
+ &ras_ops, s, "nvic_ras", 0x1000);
+ memory_region_add_subregion(&s->container, 0x5000, &s->ras_mem);
+ }
+
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->container);
}
diff --git a/hw/misc/imx25_ccm.c b/hw/misc/imx25_ccm.c
index d3107e5..ff996e2 100644
--- a/hw/misc/imx25_ccm.c
+++ b/hw/misc/imx25_ccm.c
@@ -91,7 +91,7 @@ static const char *imx25_ccm_reg_name(uint32_t reg)
case IMX25_CCM_LPIMR1_REG:
return "lpimr1";
default:
- sprintf(unknown, "[%d ?]", reg);
+ sprintf(unknown, "[%u ?]", reg);
return unknown;
}
}
@@ -118,7 +118,7 @@ static uint32_t imx25_ccm_get_mpll_clk(IMXCCMState *dev)
freq = imx_ccm_calc_pll(s->reg[IMX25_CCM_MPCTL_REG], CKIH_FREQ);
}
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -136,7 +136,7 @@ static uint32_t imx25_ccm_get_mcu_clk(IMXCCMState *dev)
freq = freq / (1 + EXTRACT(s->reg[IMX25_CCM_CCTL_REG], ARM_CLK_DIV));
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -149,7 +149,7 @@ static uint32_t imx25_ccm_get_ahb_clk(IMXCCMState *dev)
freq = imx25_ccm_get_mcu_clk(dev)
/ (1 + EXTRACT(s->reg[IMX25_CCM_CCTL_REG], AHB_CLK_DIV));
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -160,7 +160,7 @@ static uint32_t imx25_ccm_get_ipg_clk(IMXCCMState *dev)
freq = imx25_ccm_get_ahb_clk(dev) / 2;
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -186,7 +186,7 @@ static uint32_t imx25_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
break;
}
- DPRINTF("Clock = %d) = %d\n", clock, freq);
+ DPRINTF("Clock = %d) = %u\n", clock, freq);
return freq;
}
diff --git a/hw/misc/imx31_ccm.c b/hw/misc/imx31_ccm.c
index 6e24682..ad30a4b 100644
--- a/hw/misc/imx31_ccm.c
+++ b/hw/misc/imx31_ccm.c
@@ -89,7 +89,7 @@ static const char *imx31_ccm_reg_name(uint32_t reg)
case IMX31_CCM_PDR2_REG:
return "PDR2";
default:
- sprintf(unknown, "[%d ?]", reg);
+ sprintf(unknown, "[%u ?]", reg);
return unknown;
}
}
@@ -120,7 +120,7 @@ static uint32_t imx31_ccm_get_pll_ref_clk(IMXCCMState *dev)
freq = CKIH_FREQ;
}
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -133,7 +133,7 @@ static uint32_t imx31_ccm_get_mpll_clk(IMXCCMState *dev)
freq = imx_ccm_calc_pll(s->reg[IMX31_CCM_MPCTL_REG],
imx31_ccm_get_pll_ref_clk(dev));
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -150,7 +150,7 @@ static uint32_t imx31_ccm_get_mcu_main_clk(IMXCCMState *dev)
freq = imx31_ccm_get_mpll_clk(dev);
}
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -163,7 +163,7 @@ static uint32_t imx31_ccm_get_hclk_clk(IMXCCMState *dev)
freq = imx31_ccm_get_mcu_main_clk(dev)
/ (1 + EXTRACT(s->reg[IMX31_CCM_PDR0_REG], MAX));
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -176,7 +176,7 @@ static uint32_t imx31_ccm_get_ipg_clk(IMXCCMState *dev)
freq = imx31_ccm_get_hclk_clk(dev)
/ (1 + EXTRACT(s->reg[IMX31_CCM_PDR0_REG], IPG));
- DPRINTF("freq = %d\n", freq);
+ DPRINTF("freq = %u\n", freq);
return freq;
}
@@ -201,7 +201,7 @@ static uint32_t imx31_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
break;
}
- DPRINTF("Clock = %d) = %d\n", clock, freq);
+ DPRINTF("Clock = %d) = %u\n", clock, freq);
return freq;
}
diff --git a/hw/misc/imx6_ccm.c b/hw/misc/imx6_ccm.c
index 7fec8f0..cb74042 100644
--- a/hw/misc/imx6_ccm.c
+++ b/hw/misc/imx6_ccm.c
@@ -96,7 +96,7 @@ static const char *imx6_ccm_reg_name(uint32_t reg)
case CCM_CMEOR:
return "CMEOR";
default:
- sprintf(unknown, "%d ?", reg);
+ sprintf(unknown, "%u ?", reg);
return unknown;
}
}
@@ -235,7 +235,7 @@ static const char *imx6_analog_reg_name(uint32_t reg)
case USB_ANALOG_DIGPROG:
return "USB_ANALOG_DIGPROG";
default:
- sprintf(unknown, "%d ?", reg);
+ sprintf(unknown, "%u ?", reg);
return unknown;
}
}
@@ -263,7 +263,7 @@ static uint64_t imx6_analog_get_pll2_clk(IMX6CCMState *dev)
freq *= 20;
}
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -275,7 +275,7 @@ static uint64_t imx6_analog_get_pll2_pfd0_clk(IMX6CCMState *dev)
freq = imx6_analog_get_pll2_clk(dev) * 18
/ EXTRACT(dev->analog[CCM_ANALOG_PFD_528], PFD0_FRAC);
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -287,7 +287,7 @@ static uint64_t imx6_analog_get_pll2_pfd2_clk(IMX6CCMState *dev)
freq = imx6_analog_get_pll2_clk(dev) * 18
/ EXTRACT(dev->analog[CCM_ANALOG_PFD_528], PFD2_FRAC);
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -315,7 +315,7 @@ static uint64_t imx6_analog_get_periph_clk(IMX6CCMState *dev)
break;
}
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -327,7 +327,7 @@ static uint64_t imx6_ccm_get_ahb_clk(IMX6CCMState *dev)
freq = imx6_analog_get_periph_clk(dev)
/ (1 + EXTRACT(dev->ccm[CCM_CBCDR], AHB_PODF));
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -339,7 +339,7 @@ static uint64_t imx6_ccm_get_ipg_clk(IMX6CCMState *dev)
freq = imx6_ccm_get_ahb_clk(dev)
/ (1 + EXTRACT(dev->ccm[CCM_CBCDR], IPG_PODF));
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -351,7 +351,7 @@ static uint64_t imx6_ccm_get_per_clk(IMX6CCMState *dev)
freq = imx6_ccm_get_ipg_clk(dev)
/ (1 + EXTRACT(dev->ccm[CCM_CSCMR1], PERCLK_PODF));
- DPRINTF("freq = %d\n", (uint32_t)freq);
+ DPRINTF("freq = %u\n", (uint32_t)freq);
return freq;
}
@@ -385,7 +385,7 @@ static uint32_t imx6_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
break;
}
- DPRINTF("Clock = %d) = %d\n", clock, freq);
+ DPRINTF("Clock = %d) = %u\n", clock, freq);
return freq;
}
diff --git a/hw/misc/imx6_src.c b/hw/misc/imx6_src.c
index dd99cc7..79f4375 100644
--- a/hw/misc/imx6_src.c
+++ b/hw/misc/imx6_src.c
@@ -68,7 +68,7 @@ static const char *imx6_src_reg_name(uint32_t reg)
case SRC_GPR10:
return "SRC_GPR10";
default:
- sprintf(unknown, "%d ?", reg);
+ sprintf(unknown, "%u ?", reg);
return unknown;
}
}
diff --git a/hw/misc/imx6ul_ccm.c b/hw/misc/imx6ul_ccm.c
index 5e0661d..a65d031 100644
--- a/hw/misc/imx6ul_ccm.c
+++ b/hw/misc/imx6ul_ccm.c
@@ -143,7 +143,7 @@ static const char *imx6ul_ccm_reg_name(uint32_t reg)
case CCM_CMEOR:
return "CMEOR";
default:
- sprintf(unknown, "%d ?", reg);
+ sprintf(unknown, "%u ?", reg);
return unknown;
}
}
@@ -274,7 +274,7 @@ static const char *imx6ul_analog_reg_name(uint32_t reg)
case USB_ANALOG_DIGPROG:
return "USB_ANALOG_DIGPROG";
default:
- sprintf(unknown, "%d ?", reg);
+ sprintf(unknown, "%u ?", reg);
return unknown;
}
}
diff --git a/hw/misc/imx_ccm.c b/hw/misc/imx_ccm.c
index 5288207..08a50ee 100644
--- a/hw/misc/imx_ccm.c
+++ b/hw/misc/imx_ccm.c
@@ -38,7 +38,7 @@ uint32_t imx_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
freq = klass->get_clock_frequency(dev, clock);
}
- DPRINTF("(clock = %d) = %d\n", clock, freq);
+ DPRINTF("(clock = %d) = %u\n", clock, freq);
return freq;
}
@@ -65,7 +65,7 @@ uint32_t imx_ccm_calc_pll(uint32_t pllreg, uint32_t base_freq)
freq = ((2 * (base_freq >> 10) * (mfi * mfd + mfn)) /
(mfd * pd)) << 10;
- DPRINTF("(pllreg = 0x%08x, base_freq = %d) = %d\n", pllreg, base_freq,
+ DPRINTF("(pllreg = 0x%08x, base_freq = %u) = %d\n", pllreg, base_freq,
freq);
return freq;
diff --git a/hw/net/can/meson.build b/hw/net/can/meson.build
index 714951f..8fabbd9 100644
--- a/hw/net/can/meson.build
+++ b/hw/net/can/meson.build
@@ -4,3 +4,4 @@ softmmu_ss.add(when: 'CONFIG_CAN_PCI', if_true: files('can_pcm3680_pci.c'))
softmmu_ss.add(when: 'CONFIG_CAN_PCI', if_true: files('can_mioe3680_pci.c'))
softmmu_ss.add(when: 'CONFIG_CAN_CTUCANFD', if_true: files('ctucan_core.c'))
softmmu_ss.add(when: 'CONFIG_CAN_CTUCANFD_PCI', if_true: files('ctucan_pci.c'))
+softmmu_ss.add(when: 'CONFIG_XLNX_ZYNQMP', if_true: files('xlnx-zynqmp-can.c'))
diff --git a/hw/net/can/trace-events b/hw/net/can/trace-events
new file mode 100644
index 0000000..8346a98
--- /dev/null
+++ b/hw/net/can/trace-events
@@ -0,0 +1,9 @@
+# xlnx-zynqmp-can.c
+xlnx_can_update_irq(uint32_t isr, uint32_t ier, uint32_t irq) "ISR: 0x%08x IER: 0x%08x IRQ: 0x%08x"
+xlnx_can_reset(uint32_t val) "Resetting controller with value = 0x%08x"
+xlnx_can_rx_fifo_filter_reject(uint32_t id, uint8_t dlc) "Frame: ID: 0x%08x DLC: 0x%02x"
+xlnx_can_filter_id_pre_write(uint8_t filter_num, uint32_t value) "Filter%d ID: 0x%08x"
+xlnx_can_filter_mask_pre_write(uint8_t filter_num, uint32_t value) "Filter%d MASK: 0x%08x"
+xlnx_can_tx_data(uint32_t id, uint8_t dlc, uint8_t db0, uint8_t db1, uint8_t db2, uint8_t db3, uint8_t db4, uint8_t db5, uint8_t db6, uint8_t db7) "Frame: ID: 0x%08x DLC: 0x%02x DATA: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x"
+xlnx_can_rx_data(uint32_t id, uint32_t dlc, uint8_t db0, uint8_t db1, uint8_t db2, uint8_t db3, uint8_t db4, uint8_t db5, uint8_t db6, uint8_t db7) "Frame: ID: 0x%08x DLC: 0x%02x DATA: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x"
+xlnx_can_rx_discard(uint32_t status) "Controller is not enabled for bus communication. Status Register: 0x%08x"
diff --git a/hw/net/can/trace.h b/hw/net/can/trace.h
new file mode 100644
index 0000000..d391c64
--- /dev/null
+++ b/hw/net/can/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_net_can.h"
diff --git a/hw/net/can/xlnx-zynqmp-can.c b/hw/net/can/xlnx-zynqmp-can.c
new file mode 100644
index 0000000..affa21a
--- /dev/null
+++ b/hw/net/can/xlnx-zynqmp-can.c
@@ -0,0 +1,1161 @@
+/*
+ * QEMU model of the Xilinx ZynqMP CAN controller.
+ * This implementation is based on the following datasheet:
+ * https://www.xilinx.com/support/documentation/user_guides/ug1085-zynq-ultrascale-trm.pdf
+ *
+ * Copyright (c) 2020 Xilinx Inc.
+ *
+ * Written-by: Vikram Garhwal<fnu.vikram@xilinx.com>
+ *
+ * Based on QEMU CAN Device emulation implemented by Jin Yang, Deniz Eren and
+ * Pavel Pisa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/register.h"
+#include "hw/irq.h"
+#include "qapi/error.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/cutils.h"
+#include "sysemu/sysemu.h"
+#include "migration/vmstate.h"
+#include "hw/qdev-properties.h"
+#include "net/can_emu.h"
+#include "net/can_host.h"
+#include "qemu/event_notifier.h"
+#include "qom/object_interfaces.h"
+#include "hw/net/xlnx-zynqmp-can.h"
+#include "trace.h"
+
+#ifndef XLNX_ZYNQMP_CAN_ERR_DEBUG
+#define XLNX_ZYNQMP_CAN_ERR_DEBUG 0
+#endif
+
+#define MAX_DLC 8
+#undef ERROR
+
+REG32(SOFTWARE_RESET_REGISTER, 0x0)
+ FIELD(SOFTWARE_RESET_REGISTER, CEN, 1, 1)
+ FIELD(SOFTWARE_RESET_REGISTER, SRST, 0, 1)
+REG32(MODE_SELECT_REGISTER, 0x4)
+ FIELD(MODE_SELECT_REGISTER, SNOOP, 2, 1)
+ FIELD(MODE_SELECT_REGISTER, LBACK, 1, 1)
+ FIELD(MODE_SELECT_REGISTER, SLEEP, 0, 1)
+REG32(ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER, 0x8)
+ FIELD(ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER, BRP, 0, 8)
+REG32(ARBITRATION_PHASE_BIT_TIMING_REGISTER, 0xc)
+ FIELD(ARBITRATION_PHASE_BIT_TIMING_REGISTER, SJW, 7, 2)
+ FIELD(ARBITRATION_PHASE_BIT_TIMING_REGISTER, TS2, 4, 3)
+ FIELD(ARBITRATION_PHASE_BIT_TIMING_REGISTER, TS1, 0, 4)
+REG32(ERROR_COUNTER_REGISTER, 0x10)
+ FIELD(ERROR_COUNTER_REGISTER, REC, 8, 8)
+ FIELD(ERROR_COUNTER_REGISTER, TEC, 0, 8)
+REG32(ERROR_STATUS_REGISTER, 0x14)
+ FIELD(ERROR_STATUS_REGISTER, ACKER, 4, 1)
+ FIELD(ERROR_STATUS_REGISTER, BERR, 3, 1)
+ FIELD(ERROR_STATUS_REGISTER, STER, 2, 1)
+ FIELD(ERROR_STATUS_REGISTER, FMER, 1, 1)
+ FIELD(ERROR_STATUS_REGISTER, CRCER, 0, 1)
+REG32(STATUS_REGISTER, 0x18)
+ FIELD(STATUS_REGISTER, SNOOP, 12, 1)
+ FIELD(STATUS_REGISTER, ACFBSY, 11, 1)
+ FIELD(STATUS_REGISTER, TXFLL, 10, 1)
+ FIELD(STATUS_REGISTER, TXBFLL, 9, 1)
+ FIELD(STATUS_REGISTER, ESTAT, 7, 2)
+ FIELD(STATUS_REGISTER, ERRWRN, 6, 1)
+ FIELD(STATUS_REGISTER, BBSY, 5, 1)
+ FIELD(STATUS_REGISTER, BIDLE, 4, 1)
+ FIELD(STATUS_REGISTER, NORMAL, 3, 1)
+ FIELD(STATUS_REGISTER, SLEEP, 2, 1)
+ FIELD(STATUS_REGISTER, LBACK, 1, 1)
+ FIELD(STATUS_REGISTER, CONFIG, 0, 1)
+REG32(INTERRUPT_STATUS_REGISTER, 0x1c)
+ FIELD(INTERRUPT_STATUS_REGISTER, TXFEMP, 14, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, TXFWMEMP, 13, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, RXFWMFLL, 12, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, WKUP, 11, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, SLP, 10, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, BSOFF, 9, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, ERROR, 8, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, RXNEMP, 7, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, RXOFLW, 6, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, RXUFLW, 5, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, RXOK, 4, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, TXBFLL, 3, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, TXFLL, 2, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, TXOK, 1, 1)
+ FIELD(INTERRUPT_STATUS_REGISTER, ARBLST, 0, 1)
+REG32(INTERRUPT_ENABLE_REGISTER, 0x20)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ETXFEMP, 14, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ETXFWMEMP, 13, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ERXFWMFLL, 12, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, EWKUP, 11, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ESLP, 10, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, EBSOFF, 9, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, EERROR, 8, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ERXNEMP, 7, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ERXOFLW, 6, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ERXUFLW, 5, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ERXOK, 4, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ETXBFLL, 3, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ETXFLL, 2, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, ETXOK, 1, 1)
+ FIELD(INTERRUPT_ENABLE_REGISTER, EARBLST, 0, 1)
+REG32(INTERRUPT_CLEAR_REGISTER, 0x24)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CTXFEMP, 14, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CTXFWMEMP, 13, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CRXFWMFLL, 12, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CWKUP, 11, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CSLP, 10, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CBSOFF, 9, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CERROR, 8, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CRXNEMP, 7, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CRXOFLW, 6, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CRXUFLW, 5, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CRXOK, 4, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CTXBFLL, 3, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CTXFLL, 2, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CTXOK, 1, 1)
+ FIELD(INTERRUPT_CLEAR_REGISTER, CARBLST, 0, 1)
+REG32(TIMESTAMP_REGISTER, 0x28)
+ FIELD(TIMESTAMP_REGISTER, CTS, 0, 1)
+REG32(WIR, 0x2c)
+ FIELD(WIR, EW, 8, 8)
+ FIELD(WIR, FW, 0, 8)
+REG32(TXFIFO_ID, 0x30)
+ FIELD(TXFIFO_ID, IDH, 21, 11)
+ FIELD(TXFIFO_ID, SRRRTR, 20, 1)
+ FIELD(TXFIFO_ID, IDE, 19, 1)
+ FIELD(TXFIFO_ID, IDL, 1, 18)
+ FIELD(TXFIFO_ID, RTR, 0, 1)
+REG32(TXFIFO_DLC, 0x34)
+ FIELD(TXFIFO_DLC, DLC, 28, 4)
+REG32(TXFIFO_DATA1, 0x38)
+ FIELD(TXFIFO_DATA1, DB0, 24, 8)
+ FIELD(TXFIFO_DATA1, DB1, 16, 8)
+ FIELD(TXFIFO_DATA1, DB2, 8, 8)
+ FIELD(TXFIFO_DATA1, DB3, 0, 8)
+REG32(TXFIFO_DATA2, 0x3c)
+ FIELD(TXFIFO_DATA2, DB4, 24, 8)
+ FIELD(TXFIFO_DATA2, DB5, 16, 8)
+ FIELD(TXFIFO_DATA2, DB6, 8, 8)
+ FIELD(TXFIFO_DATA2, DB7, 0, 8)
+REG32(TXHPB_ID, 0x40)
+ FIELD(TXHPB_ID, IDH, 21, 11)
+ FIELD(TXHPB_ID, SRRRTR, 20, 1)
+ FIELD(TXHPB_ID, IDE, 19, 1)
+ FIELD(TXHPB_ID, IDL, 1, 18)
+ FIELD(TXHPB_ID, RTR, 0, 1)
+REG32(TXHPB_DLC, 0x44)
+ FIELD(TXHPB_DLC, DLC, 28, 4)
+REG32(TXHPB_DATA1, 0x48)
+ FIELD(TXHPB_DATA1, DB0, 24, 8)
+ FIELD(TXHPB_DATA1, DB1, 16, 8)
+ FIELD(TXHPB_DATA1, DB2, 8, 8)
+ FIELD(TXHPB_DATA1, DB3, 0, 8)
+REG32(TXHPB_DATA2, 0x4c)
+ FIELD(TXHPB_DATA2, DB4, 24, 8)
+ FIELD(TXHPB_DATA2, DB5, 16, 8)
+ FIELD(TXHPB_DATA2, DB6, 8, 8)
+ FIELD(TXHPB_DATA2, DB7, 0, 8)
+REG32(RXFIFO_ID, 0x50)
+ FIELD(RXFIFO_ID, IDH, 21, 11)
+ FIELD(RXFIFO_ID, SRRRTR, 20, 1)
+ FIELD(RXFIFO_ID, IDE, 19, 1)
+ FIELD(RXFIFO_ID, IDL, 1, 18)
+ FIELD(RXFIFO_ID, RTR, 0, 1)
+REG32(RXFIFO_DLC, 0x54)
+ FIELD(RXFIFO_DLC, DLC, 28, 4)
+ FIELD(RXFIFO_DLC, RXT, 0, 16)
+REG32(RXFIFO_DATA1, 0x58)
+ FIELD(RXFIFO_DATA1, DB0, 24, 8)
+ FIELD(RXFIFO_DATA1, DB1, 16, 8)
+ FIELD(RXFIFO_DATA1, DB2, 8, 8)
+ FIELD(RXFIFO_DATA1, DB3, 0, 8)
+REG32(RXFIFO_DATA2, 0x5c)
+ FIELD(RXFIFO_DATA2, DB4, 24, 8)
+ FIELD(RXFIFO_DATA2, DB5, 16, 8)
+ FIELD(RXFIFO_DATA2, DB6, 8, 8)
+ FIELD(RXFIFO_DATA2, DB7, 0, 8)
+REG32(AFR, 0x60)
+ FIELD(AFR, UAF4, 3, 1)
+ FIELD(AFR, UAF3, 2, 1)
+ FIELD(AFR, UAF2, 1, 1)
+ FIELD(AFR, UAF1, 0, 1)
+REG32(AFMR1, 0x64)
+ FIELD(AFMR1, AMIDH, 21, 11)
+ FIELD(AFMR1, AMSRR, 20, 1)
+ FIELD(AFMR1, AMIDE, 19, 1)
+ FIELD(AFMR1, AMIDL, 1, 18)
+ FIELD(AFMR1, AMRTR, 0, 1)
+REG32(AFIR1, 0x68)
+ FIELD(AFIR1, AIIDH, 21, 11)
+ FIELD(AFIR1, AISRR, 20, 1)
+ FIELD(AFIR1, AIIDE, 19, 1)
+ FIELD(AFIR1, AIIDL, 1, 18)
+ FIELD(AFIR1, AIRTR, 0, 1)
+REG32(AFMR2, 0x6c)
+ FIELD(AFMR2, AMIDH, 21, 11)
+ FIELD(AFMR2, AMSRR, 20, 1)
+ FIELD(AFMR2, AMIDE, 19, 1)
+ FIELD(AFMR2, AMIDL, 1, 18)
+ FIELD(AFMR2, AMRTR, 0, 1)
+REG32(AFIR2, 0x70)
+ FIELD(AFIR2, AIIDH, 21, 11)
+ FIELD(AFIR2, AISRR, 20, 1)
+ FIELD(AFIR2, AIIDE, 19, 1)
+ FIELD(AFIR2, AIIDL, 1, 18)
+ FIELD(AFIR2, AIRTR, 0, 1)
+REG32(AFMR3, 0x74)
+ FIELD(AFMR3, AMIDH, 21, 11)
+ FIELD(AFMR3, AMSRR, 20, 1)
+ FIELD(AFMR3, AMIDE, 19, 1)
+ FIELD(AFMR3, AMIDL, 1, 18)
+ FIELD(AFMR3, AMRTR, 0, 1)
+REG32(AFIR3, 0x78)
+ FIELD(AFIR3, AIIDH, 21, 11)
+ FIELD(AFIR3, AISRR, 20, 1)
+ FIELD(AFIR3, AIIDE, 19, 1)
+ FIELD(AFIR3, AIIDL, 1, 18)
+ FIELD(AFIR3, AIRTR, 0, 1)
+REG32(AFMR4, 0x7c)
+ FIELD(AFMR4, AMIDH, 21, 11)
+ FIELD(AFMR4, AMSRR, 20, 1)
+ FIELD(AFMR4, AMIDE, 19, 1)
+ FIELD(AFMR4, AMIDL, 1, 18)
+ FIELD(AFMR4, AMRTR, 0, 1)
+REG32(AFIR4, 0x80)
+ FIELD(AFIR4, AIIDH, 21, 11)
+ FIELD(AFIR4, AISRR, 20, 1)
+ FIELD(AFIR4, AIIDE, 19, 1)
+ FIELD(AFIR4, AIIDL, 1, 18)
+ FIELD(AFIR4, AIRTR, 0, 1)
+
+static void can_update_irq(XlnxZynqMPCANState *s)
+{
+ uint32_t irq;
+
+ /* Watermark register interrupts. */
+ if ((fifo32_num_free(&s->tx_fifo) / CAN_FRAME_SIZE) >
+ ARRAY_FIELD_EX32(s->regs, WIR, EW)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXFWMEMP, 1);
+ }
+
+ if ((fifo32_num_used(&s->rx_fifo) / CAN_FRAME_SIZE) >
+ ARRAY_FIELD_EX32(s->regs, WIR, FW)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXFWMFLL, 1);
+ }
+
+ /* RX Interrupts. */
+ if (fifo32_num_used(&s->rx_fifo) >= CAN_FRAME_SIZE) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXNEMP, 1);
+ }
+
+ /* TX interrupts. */
+ if (fifo32_is_empty(&s->tx_fifo)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXFEMP, 1);
+ }
+
+ if (fifo32_is_full(&s->tx_fifo)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXFLL, 1);
+ }
+
+ if (fifo32_is_full(&s->txhpb_fifo)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXBFLL, 1);
+ }
+
+ irq = s->regs[R_INTERRUPT_STATUS_REGISTER];
+ irq &= s->regs[R_INTERRUPT_ENABLE_REGISTER];
+
+ trace_xlnx_can_update_irq(s->regs[R_INTERRUPT_STATUS_REGISTER],
+ s->regs[R_INTERRUPT_ENABLE_REGISTER], irq);
+ qemu_set_irq(s->irq, irq);
+}
+
+static void can_ier_post_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ can_update_irq(s);
+}
+
+static uint64_t can_icr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ s->regs[R_INTERRUPT_STATUS_REGISTER] &= ~val;
+ can_update_irq(s);
+
+ return 0;
+}
+
+static void can_config_reset(XlnxZynqMPCANState *s)
+{
+ /* Reset all the configuration registers. */
+ register_reset(&s->reg_info[R_SOFTWARE_RESET_REGISTER]);
+ register_reset(&s->reg_info[R_MODE_SELECT_REGISTER]);
+ register_reset(
+ &s->reg_info[R_ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER]);
+ register_reset(&s->reg_info[R_ARBITRATION_PHASE_BIT_TIMING_REGISTER]);
+ register_reset(&s->reg_info[R_STATUS_REGISTER]);
+ register_reset(&s->reg_info[R_INTERRUPT_STATUS_REGISTER]);
+ register_reset(&s->reg_info[R_INTERRUPT_ENABLE_REGISTER]);
+ register_reset(&s->reg_info[R_INTERRUPT_CLEAR_REGISTER]);
+ register_reset(&s->reg_info[R_WIR]);
+}
+
+static void can_config_mode(XlnxZynqMPCANState *s)
+{
+ register_reset(&s->reg_info[R_ERROR_COUNTER_REGISTER]);
+ register_reset(&s->reg_info[R_ERROR_STATUS_REGISTER]);
+
+ /* Put XlnxZynqMPCAN in configuration mode. */
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, CONFIG, 1);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, WKUP, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, SLP, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, BSOFF, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, ERROR, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOFLW, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOK, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXOK, 0);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, ARBLST, 0);
+
+ can_update_irq(s);
+}
+
+static void update_status_register_mode_bits(XlnxZynqMPCANState *s)
+{
+ bool sleep_status = ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SLEEP);
+ bool sleep_mode = ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, SLEEP);
+ /* Wake up interrupt bit. */
+ bool wakeup_irq_val = sleep_status && (sleep_mode == 0);
+ /* Sleep interrupt bit. */
+ bool sleep_irq_val = sleep_mode && (sleep_status == 0);
+
+ /* Clear previous core mode status bits. */
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, LBACK, 0);
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SLEEP, 0);
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SNOOP, 0);
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, NORMAL, 0);
+
+ /* set current mode bit and generate irqs accordingly. */
+ if (ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, LBACK)) {
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, LBACK, 1);
+ } else if (ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, SLEEP)) {
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SLEEP, 1);
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, SLP,
+ sleep_irq_val);
+ } else if (ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, SNOOP)) {
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SNOOP, 1);
+ } else {
+ /*
+ * If all bits are zero then XlnxZynqMPCAN is set in normal mode.
+ */
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, NORMAL, 1);
+ /* Set wakeup interrupt bit. */
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, WKUP,
+ wakeup_irq_val);
+ }
+
+ can_update_irq(s);
+}
+
+static void can_exit_sleep_mode(XlnxZynqMPCANState *s)
+{
+ ARRAY_FIELD_DP32(s->regs, MODE_SELECT_REGISTER, SLEEP, 0);
+ update_status_register_mode_bits(s);
+}
+
+static void generate_frame(qemu_can_frame *frame, uint32_t *data)
+{
+ frame->can_id = data[0];
+ frame->can_dlc = FIELD_EX32(data[1], TXFIFO_DLC, DLC);
+
+ frame->data[0] = FIELD_EX32(data[2], TXFIFO_DATA1, DB3);
+ frame->data[1] = FIELD_EX32(data[2], TXFIFO_DATA1, DB2);
+ frame->data[2] = FIELD_EX32(data[2], TXFIFO_DATA1, DB1);
+ frame->data[3] = FIELD_EX32(data[2], TXFIFO_DATA1, DB0);
+
+ frame->data[4] = FIELD_EX32(data[3], TXFIFO_DATA2, DB7);
+ frame->data[5] = FIELD_EX32(data[3], TXFIFO_DATA2, DB6);
+ frame->data[6] = FIELD_EX32(data[3], TXFIFO_DATA2, DB5);
+ frame->data[7] = FIELD_EX32(data[3], TXFIFO_DATA2, DB4);
+}
+
+static bool tx_ready_check(XlnxZynqMPCANState *s)
+{
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, SRST)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to transfer data while"
+ " data while controller is in reset mode.\n",
+ path);
+ return false;
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN) == 0) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to transfer"
+ " data while controller is in configuration mode. Reset"
+ " the core so operations can start fresh.\n",
+ path);
+ return false;
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SNOOP)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to transfer"
+ " data while controller is in SNOOP MODE.\n",
+ path);
+ return false;
+ }
+
+ return true;
+}
+
+static void transfer_fifo(XlnxZynqMPCANState *s, Fifo32 *fifo)
+{
+ qemu_can_frame frame;
+ uint32_t data[CAN_FRAME_SIZE];
+ int i;
+ bool can_tx = tx_ready_check(s);
+
+ if (!can_tx) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Controller is not enabled for data"
+ " transfer.\n", path);
+ can_update_irq(s);
+ return;
+ }
+
+ while (!fifo32_is_empty(fifo)) {
+ for (i = 0; i < CAN_FRAME_SIZE; i++) {
+ data[i] = fifo32_pop(fifo);
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, LBACK)) {
+ /*
+ * Controller is in loopback. In Loopback mode, the CAN core
+ * transmits a recessive bitstream on to the XlnxZynqMPCAN Bus.
+ * Any message transmitted is looped back to the RX line and
+ * acknowledged. The XlnxZynqMPCAN core receives any message
+ * that it transmits.
+ */
+ if (fifo32_is_full(&s->rx_fifo)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOFLW, 1);
+ } else {
+ for (i = 0; i < CAN_FRAME_SIZE; i++) {
+ fifo32_push(&s->rx_fifo, data[i]);
+ }
+
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOK, 1);
+ }
+ } else {
+ /* Normal mode Tx. */
+ generate_frame(&frame, data);
+
+ trace_xlnx_can_tx_data(frame.can_id, frame.can_dlc,
+ frame.data[0], frame.data[1],
+ frame.data[2], frame.data[3],
+ frame.data[4], frame.data[5],
+ frame.data[6], frame.data[7]);
+ can_bus_client_send(&s->bus_client, &frame, 1);
+ }
+ }
+
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXOK, 1);
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, TXBFLL, 0);
+
+ if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SLEEP)) {
+ can_exit_sleep_mode(s);
+ }
+
+ can_update_irq(s);
+}
+
+static uint64_t can_srr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ ARRAY_FIELD_DP32(s->regs, SOFTWARE_RESET_REGISTER, CEN,
+ FIELD_EX32(val, SOFTWARE_RESET_REGISTER, CEN));
+
+ if (FIELD_EX32(val, SOFTWARE_RESET_REGISTER, SRST)) {
+ trace_xlnx_can_reset(val);
+
+ /* First, core will do software reset then will enter in config mode. */
+ can_config_reset(s);
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN) == 0) {
+ can_config_mode(s);
+ } else {
+ /*
+ * Leave config mode. Now XlnxZynqMPCAN core will enter normal,
+ * sleep, snoop or loopback mode depending upon LBACK, SLEEP, SNOOP
+ * register states.
+ */
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, CONFIG, 0);
+
+ ptimer_transaction_begin(s->can_timer);
+ ptimer_set_count(s->can_timer, 0);
+ ptimer_transaction_commit(s->can_timer);
+
+ /* XlnxZynqMPCAN is out of config mode. It will send pending data. */
+ transfer_fifo(s, &s->txhpb_fifo);
+ transfer_fifo(s, &s->tx_fifo);
+ }
+
+ update_status_register_mode_bits(s);
+
+ return s->regs[R_SOFTWARE_RESET_REGISTER];
+}
+
+static uint64_t can_msr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+ uint8_t multi_mode;
+
+ /*
+ * Multiple mode set check. This is done to make sure user doesn't set
+ * multiple modes.
+ */
+ multi_mode = FIELD_EX32(val, MODE_SELECT_REGISTER, LBACK) +
+ FIELD_EX32(val, MODE_SELECT_REGISTER, SLEEP) +
+ FIELD_EX32(val, MODE_SELECT_REGISTER, SNOOP);
+
+ if (multi_mode > 1) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to config"
+ " several modes simultaneously. One mode will be selected"
+ " according to their priority: LBACK > SLEEP > SNOOP.\n",
+ path);
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN) == 0) {
+ /* We are in configuration mode, any mode can be selected. */
+ s->regs[R_MODE_SELECT_REGISTER] = val;
+ } else {
+ bool sleep_mode_bit = FIELD_EX32(val, MODE_SELECT_REGISTER, SLEEP);
+
+ ARRAY_FIELD_DP32(s->regs, MODE_SELECT_REGISTER, SLEEP, sleep_mode_bit);
+
+ if (FIELD_EX32(val, MODE_SELECT_REGISTER, LBACK)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to set"
+ " LBACK mode without setting CEN bit as 0.\n",
+ path);
+ } else if (FIELD_EX32(val, MODE_SELECT_REGISTER, SNOOP)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to set"
+ " SNOOP mode without setting CEN bit as 0.\n",
+ path);
+ }
+
+ update_status_register_mode_bits(s);
+ }
+
+ return s->regs[R_MODE_SELECT_REGISTER];
+}
+
+static uint64_t can_brpr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ /* Only allow writes when in config mode. */
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN)) {
+ return s->regs[R_ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER];
+ }
+
+ return val;
+}
+
+static uint64_t can_btr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ /* Only allow writes when in config mode. */
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN)) {
+ return s->regs[R_ARBITRATION_PHASE_BIT_TIMING_REGISTER];
+ }
+
+ return val;
+}
+
+static uint64_t can_tcr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ if (FIELD_EX32(val, TIMESTAMP_REGISTER, CTS)) {
+ ptimer_transaction_begin(s->can_timer);
+ ptimer_set_count(s->can_timer, 0);
+ ptimer_transaction_commit(s->can_timer);
+ }
+
+ return 0;
+}
+
+static void update_rx_fifo(XlnxZynqMPCANState *s, const qemu_can_frame *frame)
+{
+ bool filter_pass = false;
+ uint16_t timestamp = 0;
+
+ /* If no filter is enabled. Message will be stored in FIFO. */
+ if (!((ARRAY_FIELD_EX32(s->regs, AFR, UAF1)) |
+ (ARRAY_FIELD_EX32(s->regs, AFR, UAF2)) |
+ (ARRAY_FIELD_EX32(s->regs, AFR, UAF3)) |
+ (ARRAY_FIELD_EX32(s->regs, AFR, UAF4)))) {
+ filter_pass = true;
+ }
+
+ /*
+ * Messages that pass any of the acceptance filters will be stored in
+ * the RX FIFO.
+ */
+ if (ARRAY_FIELD_EX32(s->regs, AFR, UAF1)) {
+ uint32_t id_masked = s->regs[R_AFMR1] & frame->can_id;
+ uint32_t filter_id_masked = s->regs[R_AFMR1] & s->regs[R_AFIR1];
+
+ if (filter_id_masked == id_masked) {
+ filter_pass = true;
+ }
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, AFR, UAF2)) {
+ uint32_t id_masked = s->regs[R_AFMR2] & frame->can_id;
+ uint32_t filter_id_masked = s->regs[R_AFMR2] & s->regs[R_AFIR2];
+
+ if (filter_id_masked == id_masked) {
+ filter_pass = true;
+ }
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, AFR, UAF3)) {
+ uint32_t id_masked = s->regs[R_AFMR3] & frame->can_id;
+ uint32_t filter_id_masked = s->regs[R_AFMR3] & s->regs[R_AFIR3];
+
+ if (filter_id_masked == id_masked) {
+ filter_pass = true;
+ }
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, AFR, UAF4)) {
+ uint32_t id_masked = s->regs[R_AFMR4] & frame->can_id;
+ uint32_t filter_id_masked = s->regs[R_AFMR4] & s->regs[R_AFIR4];
+
+ if (filter_id_masked == id_masked) {
+ filter_pass = true;
+ }
+ }
+
+ if (!filter_pass) {
+ trace_xlnx_can_rx_fifo_filter_reject(frame->can_id, frame->can_dlc);
+ return;
+ }
+
+ /* Store the message in fifo if it passed through any of the filters. */
+ if (filter_pass && frame->can_dlc <= MAX_DLC) {
+
+ if (fifo32_is_full(&s->rx_fifo)) {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOFLW, 1);
+ } else {
+ timestamp = CAN_TIMER_MAX - ptimer_get_count(s->can_timer);
+
+ fifo32_push(&s->rx_fifo, frame->can_id);
+
+ fifo32_push(&s->rx_fifo, deposit32(0, R_RXFIFO_DLC_DLC_SHIFT,
+ R_RXFIFO_DLC_DLC_LENGTH,
+ frame->can_dlc) |
+ deposit32(0, R_RXFIFO_DLC_RXT_SHIFT,
+ R_RXFIFO_DLC_RXT_LENGTH,
+ timestamp));
+
+ /* First 32 bit of the data. */
+ fifo32_push(&s->rx_fifo, deposit32(0, R_TXFIFO_DATA1_DB3_SHIFT,
+ R_TXFIFO_DATA1_DB3_LENGTH,
+ frame->data[0]) |
+ deposit32(0, R_TXFIFO_DATA1_DB2_SHIFT,
+ R_TXFIFO_DATA1_DB2_LENGTH,
+ frame->data[1]) |
+ deposit32(0, R_TXFIFO_DATA1_DB1_SHIFT,
+ R_TXFIFO_DATA1_DB1_LENGTH,
+ frame->data[2]) |
+ deposit32(0, R_TXFIFO_DATA1_DB0_SHIFT,
+ R_TXFIFO_DATA1_DB0_LENGTH,
+ frame->data[3]));
+ /* Last 32 bit of the data. */
+ fifo32_push(&s->rx_fifo, deposit32(0, R_TXFIFO_DATA2_DB7_SHIFT,
+ R_TXFIFO_DATA2_DB7_LENGTH,
+ frame->data[4]) |
+ deposit32(0, R_TXFIFO_DATA2_DB6_SHIFT,
+ R_TXFIFO_DATA2_DB6_LENGTH,
+ frame->data[5]) |
+ deposit32(0, R_TXFIFO_DATA2_DB5_SHIFT,
+ R_TXFIFO_DATA2_DB5_LENGTH,
+ frame->data[6]) |
+ deposit32(0, R_TXFIFO_DATA2_DB4_SHIFT,
+ R_TXFIFO_DATA2_DB4_LENGTH,
+ frame->data[7]));
+
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOK, 1);
+ trace_xlnx_can_rx_data(frame->can_id, frame->can_dlc,
+ frame->data[0], frame->data[1],
+ frame->data[2], frame->data[3],
+ frame->data[4], frame->data[5],
+ frame->data[6], frame->data[7]);
+ }
+
+ can_update_irq(s);
+ }
+}
+
+static uint64_t can_rxfifo_pre_read(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ if (!fifo32_is_empty(&s->rx_fifo)) {
+ val = fifo32_pop(&s->rx_fifo);
+ } else {
+ ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXUFLW, 1);
+ }
+
+ can_update_irq(s);
+ return val;
+}
+
+static void can_filter_enable_post_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, AFR, UAF1) &&
+ ARRAY_FIELD_EX32(s->regs, AFR, UAF2) &&
+ ARRAY_FIELD_EX32(s->regs, AFR, UAF3) &&
+ ARRAY_FIELD_EX32(s->regs, AFR, UAF4)) {
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, ACFBSY, 1);
+ } else {
+ ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, ACFBSY, 0);
+ }
+}
+
+static uint64_t can_filter_mask_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+ uint32_t reg_idx = (reg->access->addr) / 4;
+ uint32_t filter_number = (reg_idx - R_AFMR1) / 2;
+
+ /* modify an acceptance filter, the corresponding UAF bit should be '0'. */
+ if (!(s->regs[R_AFR] & (1 << filter_number))) {
+ s->regs[reg_idx] = val;
+
+ trace_xlnx_can_filter_mask_pre_write(filter_number, s->regs[reg_idx]);
+ } else {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Acceptance filter %d"
+ " mask is not set as corresponding UAF bit is not 0.\n",
+ path, filter_number + 1);
+ }
+
+ return s->regs[reg_idx];
+}
+
+static uint64_t can_filter_id_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+ uint32_t reg_idx = (reg->access->addr) / 4;
+ uint32_t filter_number = (reg_idx - R_AFIR1) / 2;
+
+ if (!(s->regs[R_AFR] & (1 << filter_number))) {
+ s->regs[reg_idx] = val;
+
+ trace_xlnx_can_filter_id_pre_write(filter_number, s->regs[reg_idx]);
+ } else {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Acceptance filter %d"
+ " id is not set as corresponding UAF bit is not 0.\n",
+ path, filter_number + 1);
+ }
+
+ return s->regs[reg_idx];
+}
+
+static void can_tx_post_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(reg->opaque);
+
+ bool is_txhpb = reg->access->addr > A_TXFIFO_DATA2;
+
+ bool initiate_transfer = (reg->access->addr == A_TXFIFO_DATA2) ||
+ (reg->access->addr == A_TXHPB_DATA2);
+
+ Fifo32 *f = is_txhpb ? &s->txhpb_fifo : &s->tx_fifo;
+
+ if (!fifo32_is_full(f)) {
+ fifo32_push(f, val);
+ } else {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: TX FIFO is full.\n", path);
+ }
+
+ /* Initiate the message send if TX register is written. */
+ if (initiate_transfer &&
+ ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN)) {
+ transfer_fifo(s, f);
+ }
+
+ can_update_irq(s);
+}
+
+static const RegisterAccessInfo can_regs_info[] = {
+ { .name = "SOFTWARE_RESET_REGISTER",
+ .addr = A_SOFTWARE_RESET_REGISTER,
+ .rsvd = 0xfffffffc,
+ .pre_write = can_srr_pre_write,
+ },{ .name = "MODE_SELECT_REGISTER",
+ .addr = A_MODE_SELECT_REGISTER,
+ .rsvd = 0xfffffff8,
+ .pre_write = can_msr_pre_write,
+ },{ .name = "ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER",
+ .addr = A_ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER,
+ .rsvd = 0xffffff00,
+ .pre_write = can_brpr_pre_write,
+ },{ .name = "ARBITRATION_PHASE_BIT_TIMING_REGISTER",
+ .addr = A_ARBITRATION_PHASE_BIT_TIMING_REGISTER,
+ .rsvd = 0xfffffe00,
+ .pre_write = can_btr_pre_write,
+ },{ .name = "ERROR_COUNTER_REGISTER",
+ .addr = A_ERROR_COUNTER_REGISTER,
+ .rsvd = 0xffff0000,
+ .ro = 0xffffffff,
+ },{ .name = "ERROR_STATUS_REGISTER",
+ .addr = A_ERROR_STATUS_REGISTER,
+ .rsvd = 0xffffffe0,
+ .w1c = 0x1f,
+ },{ .name = "STATUS_REGISTER", .addr = A_STATUS_REGISTER,
+ .reset = 0x1,
+ .rsvd = 0xffffe000,
+ .ro = 0x1fff,
+ },{ .name = "INTERRUPT_STATUS_REGISTER",
+ .addr = A_INTERRUPT_STATUS_REGISTER,
+ .reset = 0x6000,
+ .rsvd = 0xffff8000,
+ .ro = 0x7fff,
+ },{ .name = "INTERRUPT_ENABLE_REGISTER",
+ .addr = A_INTERRUPT_ENABLE_REGISTER,
+ .rsvd = 0xffff8000,
+ .post_write = can_ier_post_write,
+ },{ .name = "INTERRUPT_CLEAR_REGISTER",
+ .addr = A_INTERRUPT_CLEAR_REGISTER,
+ .rsvd = 0xffff8000,
+ .pre_write = can_icr_pre_write,
+ },{ .name = "TIMESTAMP_REGISTER",
+ .addr = A_TIMESTAMP_REGISTER,
+ .rsvd = 0xfffffffe,
+ .pre_write = can_tcr_pre_write,
+ },{ .name = "WIR", .addr = A_WIR,
+ .reset = 0x3f3f,
+ .rsvd = 0xffff0000,
+ },{ .name = "TXFIFO_ID", .addr = A_TXFIFO_ID,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXFIFO_DLC", .addr = A_TXFIFO_DLC,
+ .rsvd = 0xfffffff,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXFIFO_DATA1", .addr = A_TXFIFO_DATA1,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXFIFO_DATA2", .addr = A_TXFIFO_DATA2,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXHPB_ID", .addr = A_TXHPB_ID,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXHPB_DLC", .addr = A_TXHPB_DLC,
+ .rsvd = 0xfffffff,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXHPB_DATA1", .addr = A_TXHPB_DATA1,
+ .post_write = can_tx_post_write,
+ },{ .name = "TXHPB_DATA2", .addr = A_TXHPB_DATA2,
+ .post_write = can_tx_post_write,
+ },{ .name = "RXFIFO_ID", .addr = A_RXFIFO_ID,
+ .ro = 0xffffffff,
+ .post_read = can_rxfifo_pre_read,
+ },{ .name = "RXFIFO_DLC", .addr = A_RXFIFO_DLC,
+ .rsvd = 0xfff0000,
+ .post_read = can_rxfifo_pre_read,
+ },{ .name = "RXFIFO_DATA1", .addr = A_RXFIFO_DATA1,
+ .post_read = can_rxfifo_pre_read,
+ },{ .name = "RXFIFO_DATA2", .addr = A_RXFIFO_DATA2,
+ .post_read = can_rxfifo_pre_read,
+ },{ .name = "AFR", .addr = A_AFR,
+ .rsvd = 0xfffffff0,
+ .post_write = can_filter_enable_post_write,
+ },{ .name = "AFMR1", .addr = A_AFMR1,
+ .pre_write = can_filter_mask_pre_write,
+ },{ .name = "AFIR1", .addr = A_AFIR1,
+ .pre_write = can_filter_id_pre_write,
+ },{ .name = "AFMR2", .addr = A_AFMR2,
+ .pre_write = can_filter_mask_pre_write,
+ },{ .name = "AFIR2", .addr = A_AFIR2,
+ .pre_write = can_filter_id_pre_write,
+ },{ .name = "AFMR3", .addr = A_AFMR3,
+ .pre_write = can_filter_mask_pre_write,
+ },{ .name = "AFIR3", .addr = A_AFIR3,
+ .pre_write = can_filter_id_pre_write,
+ },{ .name = "AFMR4", .addr = A_AFMR4,
+ .pre_write = can_filter_mask_pre_write,
+ },{ .name = "AFIR4", .addr = A_AFIR4,
+ .pre_write = can_filter_id_pre_write,
+ }
+};
+
+static void xlnx_zynqmp_can_ptimer_cb(void *opaque)
+{
+ /* No action required on the timer rollover. */
+}
+
+static const MemoryRegionOps can_ops = {
+ .read = register_read_memory,
+ .write = register_write_memory,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void xlnx_zynqmp_can_reset_init(Object *obj, ResetType type)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(obj);
+ unsigned int i;
+
+ for (i = R_RXFIFO_ID; i < ARRAY_SIZE(s->reg_info); ++i) {
+ register_reset(&s->reg_info[i]);
+ }
+
+ ptimer_transaction_begin(s->can_timer);
+ ptimer_set_count(s->can_timer, 0);
+ ptimer_transaction_commit(s->can_timer);
+}
+
+static void xlnx_zynqmp_can_reset_hold(Object *obj)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(obj);
+ unsigned int i;
+
+ for (i = 0; i < R_RXFIFO_ID; ++i) {
+ register_reset(&s->reg_info[i]);
+ }
+
+ /*
+ * Reset FIFOs when CAN model is reset. This will clear the fifo writes
+ * done by post_write which gets called from register_reset function,
+ * post_write handle will not be able to trigger tx because CAN will be
+ * disabled when software_reset_register is cleared first.
+ */
+ fifo32_reset(&s->rx_fifo);
+ fifo32_reset(&s->tx_fifo);
+ fifo32_reset(&s->txhpb_fifo);
+}
+
+static bool xlnx_zynqmp_can_can_receive(CanBusClientState *client)
+{
+ XlnxZynqMPCANState *s = container_of(client, XlnxZynqMPCANState,
+ bus_client);
+
+ if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, SRST)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Controller is in reset state.\n",
+ path);
+ return false;
+ }
+
+ if ((ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN)) == 0) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Controller is disabled. Incoming"
+ " messages will be discarded.\n", path);
+ return false;
+ }
+
+ return true;
+}
+
+static ssize_t xlnx_zynqmp_can_receive(CanBusClientState *client,
+ const qemu_can_frame *buf, size_t buf_size) {
+ XlnxZynqMPCANState *s = container_of(client, XlnxZynqMPCANState,
+ bus_client);
+ const qemu_can_frame *frame = buf;
+
+ if (buf_size <= 0) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Error in the data received.\n",
+ path);
+ return 0;
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SNOOP)) {
+ /* Snoop Mode: Just keep the data. no response back. */
+ update_rx_fifo(s, frame);
+ } else if ((ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SLEEP))) {
+ /*
+ * XlnxZynqMPCAN is in sleep mode. Any data on bus will bring it to wake
+ * up state.
+ */
+ can_exit_sleep_mode(s);
+ update_rx_fifo(s, frame);
+ } else if ((ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SLEEP)) == 0) {
+ update_rx_fifo(s, frame);
+ } else {
+ /*
+ * XlnxZynqMPCAN will not participate in normal bus communication
+ * and will not receive any messages transmitted by other CAN nodes.
+ */
+ trace_xlnx_can_rx_discard(s->regs[R_STATUS_REGISTER]);
+ }
+
+ return 1;
+}
+
+static CanBusClientInfo can_xilinx_bus_client_info = {
+ .can_receive = xlnx_zynqmp_can_can_receive,
+ .receive = xlnx_zynqmp_can_receive,
+};
+
+static int xlnx_zynqmp_can_connect_to_bus(XlnxZynqMPCANState *s,
+ CanBusState *bus)
+{
+ s->bus_client.info = &can_xilinx_bus_client_info;
+
+ if (can_bus_insert_client(bus, &s->bus_client) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static void xlnx_zynqmp_can_realize(DeviceState *dev, Error **errp)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(dev);
+
+ if (s->canbus) {
+ if (xlnx_zynqmp_can_connect_to_bus(s, s->canbus) < 0) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
+ error_setg(errp, "%s: xlnx_zynqmp_can_connect_to_bus"
+ " failed.", path);
+ return;
+ }
+ }
+
+ /* Create RX FIFO, TXFIFO, TXHPB storage. */
+ fifo32_create(&s->rx_fifo, RXFIFO_SIZE);
+ fifo32_create(&s->tx_fifo, RXFIFO_SIZE);
+ fifo32_create(&s->txhpb_fifo, CAN_FRAME_SIZE);
+
+ /* Allocate a new timer. */
+ s->can_timer = ptimer_init(xlnx_zynqmp_can_ptimer_cb, s,
+ PTIMER_POLICY_DEFAULT);
+
+ ptimer_transaction_begin(s->can_timer);
+
+ ptimer_set_freq(s->can_timer, s->cfg.ext_clk_freq);
+ ptimer_set_limit(s->can_timer, CAN_TIMER_MAX, 1);
+ ptimer_run(s->can_timer, 0);
+ ptimer_transaction_commit(s->can_timer);
+}
+
+static void xlnx_zynqmp_can_init(Object *obj)
+{
+ XlnxZynqMPCANState *s = XLNX_ZYNQMP_CAN(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ RegisterInfoArray *reg_array;
+
+ memory_region_init(&s->iomem, obj, TYPE_XLNX_ZYNQMP_CAN,
+ XLNX_ZYNQMP_CAN_R_MAX * 4);
+ reg_array = register_init_block32(DEVICE(obj), can_regs_info,
+ ARRAY_SIZE(can_regs_info),
+ s->reg_info, s->regs,
+ &can_ops,
+ XLNX_ZYNQMP_CAN_ERR_DEBUG,
+ XLNX_ZYNQMP_CAN_R_MAX * 4);
+
+ memory_region_add_subregion(&s->iomem, 0x00, &reg_array->mem);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+}
+
+static const VMStateDescription vmstate_can = {
+ .name = TYPE_XLNX_ZYNQMP_CAN,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_FIFO32(rx_fifo, XlnxZynqMPCANState),
+ VMSTATE_FIFO32(tx_fifo, XlnxZynqMPCANState),
+ VMSTATE_FIFO32(txhpb_fifo, XlnxZynqMPCANState),
+ VMSTATE_UINT32_ARRAY(regs, XlnxZynqMPCANState, XLNX_ZYNQMP_CAN_R_MAX),
+ VMSTATE_PTIMER(can_timer, XlnxZynqMPCANState),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static Property xlnx_zynqmp_can_properties[] = {
+ DEFINE_PROP_UINT32("ext_clk_freq", XlnxZynqMPCANState, cfg.ext_clk_freq,
+ CAN_DEFAULT_CLOCK),
+ DEFINE_PROP_LINK("canbus", XlnxZynqMPCANState, canbus, TYPE_CAN_BUS,
+ CanBusState *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xlnx_zynqmp_can_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ rc->phases.enter = xlnx_zynqmp_can_reset_init;
+ rc->phases.hold = xlnx_zynqmp_can_reset_hold;
+ dc->realize = xlnx_zynqmp_can_realize;
+ device_class_set_props(dc, xlnx_zynqmp_can_properties);
+ dc->vmsd = &vmstate_can;
+}
+
+static const TypeInfo can_info = {
+ .name = TYPE_XLNX_ZYNQMP_CAN,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XlnxZynqMPCANState),
+ .class_init = xlnx_zynqmp_can_class_init,
+ .instance_init = xlnx_zynqmp_can_init,
+};
+
+static void can_register_types(void)
+{
+ type_register_static(&can_info);
+}
+
+type_init(can_register_types)